JavaScript基础知识
数据类型
有8种基本的数据类型(7中原始类型和一种引用类型)
1.Number类型
除了常规的数字,还包括所谓的特殊数值:infinity、-infinity 、NaN
2.BigInt类型
范围在(253-1)(即9007199254740991)或小于-(253-1)的整数
// 尾部的 “n” 表示这是一个 BigInt 类型 const bigInt = 1234567890123456789012345678901234567890n;
3.String类型
三种形式:1.双引号2.单引号3.反引号
反引号是功能扩展引号,它们允许我们通过变量和表达式包装在${}中,来将他们嵌入到字符串中。
let name = “John”; // 嵌入一个变量 alert( `Hello, ${name}!` ); // Hello, John! // 嵌入一个表达式 alert( `the result is ${1 + 2}` ); // the result is 3
4.Boolean类型(逻辑类型)
5.null值
特殊的null值不属于以上任何类型;js中的null值仅仅是一个代表无、空、值未知
6.undefined值
特殊值undefined和null一样自成类型
undefined的含义是未被赋值;如果一个变量已被声明,但未被赋值,那么它的值就是undefined
7.Object类型和symbol类型
其他所有的数据类型都被称为原始类型,因为它们的值只包含一个单独的内容(字符串、数字或其他类型)相反,object则用于储存数据集合和更复杂的实体
判断数据类型
typeof运算符
返回参数的类型。对typeof x的调用会以字符串的形式返回数据类型
typeof(x)和typeof x一样的效果
typeof undefined // “undefined” typeof 0 // “number” typeof 10n // “bigint” typeof true // “boolean” typeof “foo” // “string” typeof Symbol(“id”) // “symbol” typeof Math // “object” (1) typeof null // “object” (2) typeof alert // “function” (3)
注释:js语言中没有一个特别的‘function类型,函数隶属于object类型,但是typeof会对函数区别对待,并返回function
总结:JavaScript 中有八种基本的数据类型(译注:前七种为基本数据类型,也称为原始类型,而 object 为复杂数据类型)。
- number 用于任何类型的数字:整数或浮点数,在 ±(253-1) 范围内的整数。
- bigint 用于任意长度的整数。
- string 用于字符串:一个字符串可以包含 0 个或多个字符,所以没有单独的单字符类型。
- boolean 用于 true 和 false。
- null 用于未知的值 —— 只有一个 null 值的独立类型。
- undefined 用于未定义的值 —— 只有一个 undefined 值的独立类型。
- symbol 用于唯一的标识符。
- object 用于更复杂的数据结构。
我们可以通过 typeof 运算符查看存储在变量中的数据类型。
- 通常用作 typeof x,但 typeof(x) 也可行。
- 以字符串的形式返回类型名称,例如 “string”。
- typeof null 会返回 “object” —— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 object。
交互alert、prompt和comfirm
alert:他会显示一条信息,并等待用户按下ok;会将任何值都已字符串的形式展示
prompt:他会显示一个带有文本消息的模拟窗口,还有input框和确定/取消按钮
comfirm: 他会显示一个待有question以及确定和取消两个按钮的模拟窗口
类型转换
1.字符串转换
除了alert(value)这样的自动转换为字符串,还可以显示地调用String(value)来将value转换为字符串类型
let value = true; alert(typeof value); // boolean value = String(value); // 现在,值是一个字符串形式的 “true” alert(typeof value); // string
注释:字符串转换最为明显。会false转换为’false‘,null转换为“null”
2.数字型转换
在算数表达式中,会自动转换为number类型,也可以使用Number(value)显示地将这个value转换为number 类型
let str = “123”; alert(typeof str); // string let num = Number(str); // 变成 number 类型 123 alert(typeof num); // number
注释:如果转换一个string类型的,但是该字符串不是一个有效的数字,转换的结果会是NaN;
let age = Number(“an arbitrary string instead of a number”); alert(age); // NaN,转换失败
number类型转换规则
undefined ——— NaN
null ——— 0
true/false ——— 1and 0
string ——— 去除前后空格的空字符串为0,否则就会从剩余的字符串中读取数字,当类型转换出现error时返回NaN
3.布尔类型转换
发生在逻辑运算中或者可以显示调用Boolean(value)进行转换
直观上为’空‘的值(0、空字符串、null、undefined、NaN)将变为false
其他值转换为false
alert( Boolean(1) ); // true alert( Boolean(0) ); // false alert( Boolean(“hello”) ); // true alert( Boolean(“”) ); // false alert( Boolean(“0″) ); // true alert( Boolean(” “) ); // 空白,也是 true(任何非空字符串都是 true)
支持数学运算
加法 + 、减法 – 、乘法 * 、除法 / 、取余 % 、求幂 ** 。
加号的几种用法:1.简单的数字加法;2.连接两个字符串 3.将其转化为数字(加号应用于单个值,对数字没有任何作用,但是如果运算元不是数字,加号则将其转化为数字)它的效果和Number()相同
// 对数字无效 let x = 1; alert( +x ); // 1 let y = -2; alert( +y ); // -2 // 转化非数字 alert( +true ); // 1 alert( +”” ); // 0
自增自减
如果我们想要对变量进行自增操作,并且 需要立刻使用自增后的值,那么我们需要使用前置形式
let counter = 0; alert( ++counter ); // 1
如果我们想要将一个数加一,但是我们想使用其自增之前的值,那么我们需要使用后置形式
let counter = 0; alert( counter++ ); // 0
位运算符和逗号运算符
按位与(&)按位或(|)按位异或(^)按位非(~)左移(移(>>)无符号右移(>>>)
逗号运算符的优先级很低,比=还要低
值的比较
字符串比较:js会使用字典顺序进行判定
不同内类型间的比较:js会首先将其转化为数字再判定大小
null和undefined比较时:严格比较 null===undefined false 非严格比较: null==undefined true
undefined和null在相等型检查时,==不会进行任何类型的转换,它们有自己独立的比较规则,所以除了它们之间会等外,不会等于任何其他的值。
条件分支:if和?
if和?语句里面的false值:0,‘’,null,undefined,NaN
逻辑运算符
或(||)与(&&)非(!)空值合并运算符(??)
如果不是null/undefined,则??返回第一个参数,否则返回第二个参数
let user; alert(user ?? “匿名”); // 匿名(user 未定义) let user = “John”; alert(user ?? “匿名”); // John(user 已定义)
循环:while和for
跳出循环:break
继续下一次迭代:continue;停止本次的循环,继续下一次循环迭代
break和continue不能与三目运算符? 一起使用
break/continue标签
有时候需要一次从多层嵌套中跳出来
outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Value at coords (${i},${j})`, ”); // 如果是空字符串或被取消,则中断并跳出这两个循环。 if (!input) break outer; // (*) // 用得到的值做些事…… } } alert(‘Done!’);
Switch语句
执行严格相等
switch(x) { case ‘value1’: // if (x === ‘value1’) … [break] case ‘value2’: // if (x === ‘value2’) … [break] default: … [break] }
case分组
let a = 3; switch (a) { case 4: alert(‘Right!’); break; case 3: // (*) 下面这两个 case 被分在一组 case 5: alert(‘Wrong!’); alert(“Why don’t you take a math class?”); break; default: alert(‘The result is strange. Really.’); }
函数
在函数中声明的变量,只能在该函数内部可见,也可以函数外部变量;
如果在函数内部声明了同名变量,那么函数会遮蔽外部变量
只适用return但没有返回值也是可行的,但这会导致函数立即退出
function showMovie(age) { if ( !checkAge(age) ) { return; } alert( “Showing you the movie” ); // (*) // … }
在上述代码中,如果 checkAge(age) 返回 false,那么 showMovie 将不会运行到 alert。
空值的return或没有return的函数返回值为undefined;空值的return和returnundefined等效
function doNothing() { /* 没有代码 */ } alert( doNothing() === undefined ); // true function doNothing() { return; } alert( doNothing() === undefined ); // true
不要在return与返回值之间添加新行(不然会在return后面自动加上分号,则返回的就是undefined)
函数声明和函数表达式;函数声明没有分号,函数表达式有分号;
函数可以当成值来使用
函数声明只在它所在的代码块中可见,函数表达式在代码执行到它时才会被创建
function sayHi() { alert( “Hello” ); } let sayHi = function() { alert( “Hello” ); }; function sayHi() { alert( “Hello” ); } alert( sayHi ); // 显示函数代码
箭头函数
箭头函数具体有两种:不带花括号的,和待花括号的
Object(对象):基础知识
对象是用来存储键值和更复杂的实体。
可以通过使用带有可选属性列表的花括号来创建对象。一个属性就是一个键值对(key:value),其中键(key)是一个字符串(也叫属性名),值(value)可以是任何值
创建一个空的对象:
let user = new Object(); // “构造函数” 的语法 let user = {}; // “字面量” 的语法
可以使用点符号访问属性值:
// 读取文件的属性: alert( user.name ); // John alert( user.age ); // 30
也可以使用方括号,可用于任何字符串:
let user = {}; // 设置 user[“likes birds”] = true; // 读取 alert(user[“likes birds”]); // true // 删除 delete user[“likes birds”]; let key = “likes birds”; // 跟 user[“likes birds”] = true; 一样 user[key] = true; let user = { name: “John”, age: 30 }; let key = “name”; alert( user.key ) // undefined
计算属性
let fruit = prompt(“Which fruit to buy?”, “apple”); let bag = {}; // 从 fruit 变量中获取值 bag[fruit] = 5; let fruit = ‘apple’; let bag = { [fruit + ‘Computers’]: 5 // bag.appleComputers = 5 };
在实际开发中我们通常使用已存在的变量当做属性名:
function makeUser(name, age) { return { name: name, age: age, // ……其他的属性 }; } let user = makeUser(“John”, 30); alert(user.name); // John //使用属性值缩写的方法,使属性名变的更短;例如下面的,可以用name来代替name:name function makeUser(name, age) { return { name, // 与 name: name 相同 age, // 与 age: age 相同 // … }; }
对象的属性名可以是保留字,简而言之对象的属性命名没有限制
// 这些属性都没问题 let obj = { for: 1, let: 2, return: 3 }; alert( obj.for + obj.let + obj.return ); // 6
当数字0没用作对象的键值对时,会被转换为字符串‘0’
let obj = { 0: “test” // 等同于 “0”: “test” }; // 都会输出相同的属性(数字 0 被转为字符串 “0”) alert( obj[“0”] ); // test alert( obj[0] ); // test (相同的属性)
一个名为__proto__的属性,我们不能将它设置为非对象的值
属性存在性测试,in 操作符
JavaScript的对象有一个需要注意的特性,能够访问任何属性,计时属性不存在也不会报错。读取不存在的属性只会得到undefined,所以我们可以很容易的判断一个属性是否存在
let user = {}; alert( user.noSuchProperty === undefined ); // true 意思是没有这个属性 let user = { name: “John”, age: 30 }; alert( “age” in user ); // true,user.age 存在 alert( “blabla” in user ); // false,user.blabla 不存在。 let user = { age: 30 }; let key = “age”; alert( key in user ); // true,属性 “age” 存在
为何会有in运算符呢,大部分情况下与undefined进行比较就可以了,但是有一个例外的情况,这种比对方式会有问题,但是in运算符的判断结果仍是对的
let obj = { test: undefined }; alert( obj.test ); // 显示 undefined,所以属性不存在? alert( “test” in obj ); // true,属性存在!
这种情况很少发生,因为通常情况下不应该给对象赋值 undefined。我们通常会用 null 来表示未知的或者空的值。因此,in 运算符是代码中的特殊来宾。
for in 循环
为了遍历一个对象所有的键(key),可以使用一个特殊形式的循环:for in
let user = { name: “John”, age: 30, isAdmin: true }; for (let key in user) { // keys alert( key ); // name, age, isAdmin // 属性键的值 alert( user[key] ); // John, 30, true }
对象会被排序吗?整数属性会被进行排序,其他属性则按照创建的顺序显示
对象引用和复制
赋值了对象的变量存储的不是对象本身,而是该对象‘在内存中的地址’
仅两个对象为同一个对象时,两者才相等,而两个独立的对象就算内容相等也不相等
let a = {}; let b = a; // 复制引用 alert( a == b ); // true,都引用同一对象 alert( a === b ); // true let a = {}; let b = {}; // 两个独立的对象 alert( a == b ); // false
克隆和合并,object.assign
Object.assign(dest, [src1, src2, src3…])
第一个参数dest是指目标对象,后面的参数时源对象,该方法将所有源对象属性拷贝到目标对象dest中,调用结果返回dest
如果被拷贝的属性的属性名已经存在,那么它会被覆盖
深层克隆
使用object.assign克隆,如果其中一个属性是引用型对象,那么两个会共用一个属性对象
let user = { name: “John”, sizes: { height: 182, width: 50 } }; let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true,同一个对象 // user 和 clone 分享同一个 sizes user.sizes.width++; // 通过其中一个改变属性值 alert(clone.sizes.width); // 51,能从另外一个看到变更的结果
为了解决这个问题,可以使用递归来实现它,或者为了不重复造轮子,采用现有的实现例如lodash库的_.cloneDeep(obj)
之前使用const 时值是不可以更改的,但是使用const声明的对象是可以修改的
对象方法,this
在对象字面量中,有一种更短的(声明)方法的语法:
// 这些对象作用一样 user = { sayHi: function() { alert(“Hello”); } }; // 方法简写看起来更好,对吧? let user = { sayHi() { // 与 “sayHi: function(){…}” 一样 alert(“Hello”); } }; //可以使用方法中的this let user = { name: “John”, age: 30, sayHi() { // “this” 指的是“当前的对象” alert(this.name); } }; user.sayHi(); // John
在没有对象的情况下调用:this == undefined
箭头函数没有自己的this
let user = { firstName: “Ilya”, sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // Ilya
箭头函数的this是从外部的上下文中获取的
构造器和操作符 new
实现可重用的对象创建代码(任何函数都可以用作构造器,除了箭头函数)
构造函数:1.它们的命名以大写字母开头。2.它们只能由new操作符来执行
当一个函数被使用new操作符执行时,按照一下步骤进行操作:
1.一个新的空对象被创建并分配给了this
2.函数体执行。通常他会修改this,为其添加新的属性
3.返回this的值
function User(name) { // this = {};(隐式创建) // 添加属性到 this this.name = name; this.isAdmin = false; // return this;(隐式返回) } let user = new User(“Jack”); alert(user.name); // Jack alert(user.isAdmin); // false
构造器模式测试:new.target
在一个函数内部,我们可以使用new.target属性来检查它是否被使用new进行调用了。
function User() { alert(new.target); } // 不带 “new”: User(); // undefined // 带 “new”: new User(); // function User { … }
构造器的return
带有对象的return返回该对象,在所有其他情况下返回this
如果没有参数,可以省略new后的括号;
let user = new User; // <– 没有参数 // 等同于 let user = new User();
可选链?.
let user = null; alert( user?.address ); // undefined alert( user?.address.street ); // undefined
可选链不是一个运算符,而是一个特殊的语法结构,它还可以与函数和方括号一起使用
let userAdmin = { admin() { alert(“I am admin”); } }; let userGuest = {}; userAdmin.admin?.(); // I am admin userGuest.admin?.(); // 啥都没有(没有这样的方法) let key = “firstName”; let user1 = { firstName: “John” }; let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined
还可以和delete一起使用
delete user?.name; // 如果 user 存在,则删除 user.name
symbol类型
对象的属性键只能是字符串类型或者symbol类型。
symbol值表示唯一的标识符
let id1 = Symbol(“id”); let id2 = Symbol(“id”); alert(id1 == id2); // false
symbol不会被自动转换为字符串
所以想要显示一个symbol,我们需要在它上面调用.toString()。
let id = Symbol(“id”); alert(id.toString()); // Symbol(id),现在它有效了
或者获取symbol.description属性,只显示描述
let id = Symbol(“id”); alert(id.description); // id
let user = { // 属于另一个代码 name: “John” }; let id = Symbol(“id”); user[id] = 1; alert( user[id] ); // 我们可以使用 Symbol 作为键来访问数据
symbol在for in中会被跳过
let id = Symbol(“id”); let user = { name: “John”, age: 30, [id]: 123 }; for (let key in user) alert(key); // name, age (no symbols) // 使用 Symbol 任务直接访问 alert( “Direct: ” + user[id] );
Object.keys(user) 也会忽略它们,但是object.assign()则会同时复制字符串和symbol
for of 于for循环是一样的,用于遍历数组。而for in是遍历对象的。
全局symbol
// 从全局注册表中读取 let id = Symbol.for(“id”); // 如果该 Symbol 不存在,则创建它 // 再次读取(可能是在代码中的另一个位置) let idAgain = Symbol.for(“id”); // 相同的 Symbol alert( id === idAgain ); // true
symbol.keyfor
对于全局 Symbol,不仅有 Symbol.for(key) 按名字返回一个 Symbol,还有一个反向调用:Symbol.keyFor(sym),它的作用完全反过来:通过全局 Symbol 返回一个名字。
// 通过 name 获取 Symbol let sym = Symbol.for(“name”); let sym2 = Symbol.for(“id”); // 通过 Symbol 获取 name alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id
Symbol.keyFor 内部使用全局 Symbol 注册表来查找 Symbol 的键。所以它不适用于非全局 Symbol。如果 Symbol 不是全局的,它将无法找到它并返回 undefined。
也就是说,任何 Symbol 都具有 description 属性。
let globalSymbol = Symbol.for(“name”); let localSymbol = Symbol(“name”); alert( Symbol.keyFor(globalSymbol) ); // name,全局 Symbol alert( Symbol.keyFor(localSymbol) ); // undefined,非全局 alert( localSymbol.description ); // name
从技术上说,Symbol 不是 100% 隐藏的。有一个内建方法 Object.getOwnPropertySymbols(obj) 允许我们获取所有的 Symbol。还有一个名为 Reflect.ownKeys(obj) 的方法可以返回一个对象的 所有 键,包括 Symbol。所以它们并不是真正的隐藏。但是大多数库、内建方法和语法结构都没有使用这些方法。
数据类型
原始类型:
字符串方法str.toUpperCase()返回一个大写化处理的字符串
let str = “Hello”; alert( str.toUpperCase() ); // HELLO
数字也有自己的方法,toFixed(n)将数字舍入到给定的精度,总是返回一个字符串
let n = 1.23456; alert( n.toFixed(2) ); // 1.23
而原始类型null和undefined是例外的,它们没有对应的对象包装器,也就是没有提供任何方法,从某种意义上来讲,它们是最原始的。
数字类型
编写数字的更多方法:
let billion = 1000000000; let billion = 1_000_000_000; let billion = 1e9; // 10 亿,字面意思:数字 1 后面跟 9 个 0 alert( 7.3e9 ); // 73 亿(与 7300000000 和 7_300_000_000 相同) let mcs = 0.000001; let mcs = 1e-6; // 1 的左边有 6 个 0 //16进制 alert( 0xff ); // 255 alert( 0xFF ); // 255(一样,大小写没影响) //2进制和8进制 let a = 0b11111111; // 二进制形式的 255 let b = 0o377; // 八进制形式的 255 alert( a == b ); // true,两边是相同的数字,都是 255
toString(base)
方法num.toString(base)返回在给定base进制数字系统中num的字符串表示形式
base的范围可以从2到36.默认状况下是10
let num = 255; alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111
当为36时有一个有趣的问题,那就是
alert( 123456..toString(36) ); // 2n9c alert( 123456.toString(36) ); // 报错 let num = 123456 alert( num.toString(36) ); // 2n9c
舍入
Math.floor:下取整
Math.ceil:上取整
Math.round:四舍五入
Math.trunc:去掉小数点后面的值,直接取整数
不精确的计算
0.1+0.2 == 0.3 false
使用二进制数字系统无法精确的存储0.1和0.2,就像没有办法将三分之一存储为十进制小数一样
测试:isFinite和isNaN
Infinity(和-Infinity)是一个特殊的数值,比任何数值都大(小)
NaN代表一个error
它们属于number类型
isNaN(value)将其参数转换为数字,然后测试它是否为NaN:
alert( isNaN(NaN) ); // true alert( isNaN(“str”) ); // true alert( NaN === NaN ); // false
isFinite(value)将其参数转换为数字,如果是常规数字,则返回true ,否则返回false
alert( isFinite(“15”) ); // true alert( isFinite(“str”) ); // false,因为是一个特殊的值:NaN alert( isFinite(Infinity) ); // false,因为是一个特殊的值:Infinity
Object.is(value,value)
类似于===一样对值进行比较,但是它有两种边缘情况:
1.适用于NaN:Object.is(NaN,NaN) = true
2.值0和-0是不同的,Object.is(0,-0)= false
在其他情况下Object.is(a,b)与a === b相同
parseInt 和parseFloat
parseInt返回一个整数,parseFloat返回一个浮点数
alert( parseInt(‘100px’) ); // 100 alert( parseFloat(‘12.5em’) ); // 12.5 alert( parseInt(‘12.3’) ); // 12,只有整数部分被返回了 alert( parseFloat(‘12.3.4’) ); // 12.3,在第二个点出停止了读取
其他数学函数
Math.random():返回一个从0到1的随机数(不包括1)
Math.max(a,b,c….)/Math.min(a,b,c….):从任意数量的参数中返回最大/最小值
Math.pow(n,pow):返回n的给定的次幂
字符串
三种形式:单引号、双引号和反引号,其中反引号可以换行
特殊字符:
字符 |
描述 |
\n |
换行 |
\r |
在 Windows 文本文件中,两个字符\r\n 的组合代表一个换行。而在非 Windows 操作系上它就是\n。这是历史原因造成的,大多数的 Windows 软件也理解\n |
\’,\” |
引号 |
\\ |
反斜线 |
\t |
制表符 |
\b,\f,\v |
退格,换页,垂直标签 —— 为了兼容性,现在已经不使用了。 |
\xXX |
具有给定十六进制 UnicodeXX的 Unicode 字符,例如:’\x7A’和’z’相同。 |
\uXXXX |
以 UTF-16 编码的十六进制代码XXXX的 Unicode 字符,例如\u00A9—— 是版权符号© 的 Unicode。它必须正好是 4 个十六进制数字。 |
\u{X…XXXXXX}(1 到 6 个十六进制字符) |
具有给定 UTF-32 编码的 Unicode 符号。一些罕见的字符用两个 Unicode 符号编码,占用 4 个字节。这样我们就可以插入长代码了 |
字符串长度
str.length
访问字符
要获取在pos位置的一个字符,可以使用方括号[pos]或者调用str.chaAt(pos)方法,第一个字符从零位置开始。它们唯一的区别是,如果没有找到字符,[]返回undefined而charAt返回一个空的字符串
let str = `Hello`; // 第一个字符 alert( str[0] ); // H alert( str.charAt(0) ); // H // 最后一个字符 alert( str[str.length – 1] ); // o let str = `Hello`; alert( str[1000] ); // undefined alert( str.charAt(1000) ); // ”(空字符串)
也可以用for of 遍历字符
for (let char of “Hello”) { alert(char); // H,e,l,l,o(char 变为 “H”,然后是 “e”,然后是 “l” 等) }
字符串是不可变的
在JavaScript中,字符串不可更改。改变字符是不可能的。通常的解决方法时创建一个新的字符串
let str = ‘Hi’; str[0] = ‘h’; // error alert( str[0] ); // 无法运行
改变大小写
toLowerCase()和toUpperCase()方法可以改变大小写
alert( ‘Interface’.toUpperCase() ); // INTERFACE alert( ‘Interface’.toLowerCase() ); // interface alert( ‘Interface'[0].toLowerCase() ); // ‘i’
查找子字符串
在字符串中查找子字符串有很多种方法
str.indexof(substr,pos)
str.lastIndexof(substr,pos)
它从给定位置pos开始,在str中查找substr,如果没有找到,则返回-1,否则返回匹配成功的位置;
可选的第二个参数袁旭我们从一个给定的位置开始检索
let str = ‘Widget with id’; alert( str.indexOf(‘Widget’) ); // 0,因为 ‘Widget’ 一开始就被找到 alert( str.indexOf(‘widget’) ); // -1,没有找到,检索是大小写敏感的 alert( str.indexOf(“id”) ); // 1,”id” 在位置 1 处(……idget 和 id) let str = ‘Widget with id’; alert( str.indexOf(‘id’, 2) ) // 12
如果对所有存在位置都感兴趣,可以在一个循环中使用indexof。每一次新的调用都发生在上一匹配位置之后
let str = ‘As sly as a fox, as strong as an ox’; let target = ‘as’; // 这是我们要查找的目标 let pos = 0; while (true) { let foundPos = str.indexOf(target, pos); if (foundPos == -1) break; alert( `Found at ${foundPos}` ); pos = foundPos + 1; // 继续从下一个位置查找 } let str = “As sly as a fox, as strong as an ox”; let target = “as”; let pos = -1; while ((pos = str.indexOf(target, pos + 1)) != -1) { alert( pos ); }
按位NOT技巧(老代码不宜使用)
~ 将数字转换为32bit整数,删除小数部分,然后对其二进制表示中的所有位取反
includes、startsWith、endsWith
用来判断字符串中是否包含该字符串,也可以从规定的位置进行判断
alert( “Widget with id”.includes(“Widget”) ); // true alert( “Hello”.includes(“Bye”) ); // false alert( “Midget”.includes(“id”) ); // true alert( “Midget”.includes(“id”, 3) ); // false, 从位置 3 开始没有 “id” alert( “Widget”.startsWith(“Wid”) ); // true,”Widget” 以 “Wid” 开始 alert( “Widget”.endsWith(“get”) ); // true,”Widget” 以 “get” 结束
获取子字符串
JavaScript有三种获取字符串的方法:substr 、substring、 slice
str.slice(start,end)返回字符串,从start到end(但是不包括)部分
let str = “stringify”; alert( str.slice(0, 5) ); // ‘strin’,从 0 到 5 的子字符串(不包括 5) alert( str.slice(0, 1) ); // ‘s’,从 0 到 1,但不包括 1,所以只有在 0 处的字符
start/end也可以是负值,它们的意思是起始位置从字符串结尾计算,尾部从-1开始
let str = “stringify”; // 从右边的第四个位置开始,在右边的第一个位置结束 alert( str.slice(-4, -1) ); // ‘gif’
str.substring(start,end)返回字符串在start和end之间的部分
这与slice几乎相似,但是它允许start大于end,当start大于end时,会自动的转换到正常的排序
但是,不支持负数,一概被视为0
let str = “stringify”; // 这些对于 substring 是相同的 alert( str.substring(2, 6) ); // “ring” alert( str.substring(6, 2) ); // “ring” // ……但对 slice 是不同的: alert( str.slice(2, 6) ); // “ring”(一样) alert( str.slice(6, 2) ); // “”(空字符串)
str.substr(start,length)返回字符串从start开始的给定length部分
let str = “stringify”; alert( str.substr(2, 4) ); // ‘ring’,从位置 2 开始,获取 4 个字符
也可能是负数
let str = “stringify”; alert( str.substr(-4, 2) ); // ‘gi’,从第 4 位获取 2 个字符
比较字符串
正如我们之前学习的值的比较,字符串也有比较,它则按照字母顺序逐字比较
不过也有一些特殊的规则:
1.小写字母总是大于大写字母
alert( ‘a’ > ‘Z’ ); // true
2.带变音符号的字母存在乱序的情况
alert( ‘Österreich’ > ‘Zealand’ ); // true
如果我们对这些国家名进行排序,可能会导致奇怪的结果。
所有的字符串都使用UTF-16编码。
str.codePointAt(pos)返回在pos位置的字符代码
// 不同的字母有不同的代码 alert( “z”.codePointAt(0) ); // 122 alert( “Z”.codePointAt(0) ); // 90
str.fromCodePoint(code)通过数字code创建字符
alert( String.fromCodePoint(90) ); // Z
还可以用/u后跟十六进制代码,通过这些代码添加Unicode字符
// 在十六进制系统中 90 为 5a alert( ‘\u005a’ ); // Z
调用str.localeCompare(str2)会根据语言规则返回一个整数,这个整数能指示字符串str在排序顺序中排在字符串str2前面、后面、还是相同。
如果str排在str2前面,则返回负数;
如果str排在str2后面,则返回整数;
如果他们在相同位置,则返回0;
alert( ‘Österreich’.localeCompare(‘Zealand’) ); // -1
数组
创建一个空数组有两种语法:
let arr = new Array() let arr = []
数组元素从0开始编号
可以通过方括号中的数字获取元素,也可以替换元素,或者向数组新加一个元素
let fruits = [“Apple”, “Orange”, “Plum”]; alert( fruits[0] ); // Apple alert( fruits[1] ); // Orange alert( fruits[2] ); // Plum fruits[2] = ‘Pear’; // 现在变成了 [“Apple”, “Orange”, “Pear”] fruits[3] = ‘Lemon’; // 现在变成 [“Apple”, “Orange”, “Pear”, “Lemon”]
length属性的值是数组中元素的总个数
也可以用alert来显示整个数组
数组可以存储任何类型的元素
数组可以使用at获取最后一个元素(这是最新添加到js上的特性,旧式浏览器可能需要polyfills)
let fruits = [“Apple”, “Orange”, “Plum”]; // 与 fruits[fruits.length-1] 相同 alert( fruits.at(-1) ); // Plum
换句话说,arr.at(i)
如果i>=0,则与arr[i]完全相同,对于i
pop/push/shift/unshift方法
队列是最常见的使用数组的方法之一。(先进先出)
push在末端添加一个元素。
shift去除队列首段的一个元素,整个队列向前移,这样原先排第二的元素现在排在了第一
数组还有另外一个用例,就是数据结构栈(后进先出)
push在末端添加一个元素
pop在末端取出一个元素
js中数组既可以用作队列,也可以用作栈。允许这样的操作的数据结构被称为双端队列
pop/push/shift/unshift
let fruits = [“Apple”, “Orange”, “Pear”]; alert( fruits.pop() ); // 移除 “Pear” 然后 alert 显示出来 alert( fruits ); // Apple, Orange let fruits = [“Apple”, “Orange”]; fruits.push(“Pear”); alert( fruits ); // Apple, Orange, Pear let fruits = [“Apple”, “Orange”, “Pear”]; alert( fruits.shift() ); // 移除 Apple 然后 alert 显示出来 alert( fruits ); // Orange, Pear let fruits = [“Orange”, “Pear”]; fruits.unshift(‘Apple’); alert( fruits ); // Apple, Orange, Pear //push和unshift都可以一次性添加好几个元素 let fruits = [“Apple”]; fruits.push(“Orange”, “Peach”); fruits.unshift(“Pineapple”, “Lemon”); // [“Pineapple”, “Lemon”, “Apple”, “Orange”, “Peach”] alert( fruits );
在性能方面,push/pop 方法运行的比较快,而shift/unshift比较慢
循环
遍历数组最古老的方式就是for循环
let arr = [“Apple”, “Orange”, “Pear”]; for (let i = 0; i < arr.length; i++) { alert( arr[i] ); }
但对于数组来说还有另一种循环方式,for….of
let fruits = [“Apple”, “Orange”, “Plum”]; // 遍历数组元素 for (let fruit of fruits) { alert( fruit ); }
for ….of 不能获取当前元素的索引值,只是获取元素值,但大多数情况下是够用的。
从技术上来讲,数组也是对象,所以使用for….in也是可以的
let arr = [“Apple”, “Orange”, “Pear”]; for (let key in arr) { alert( arr[key] ); // Apple, Orange, Pear }
清空数组最简单的方式就是arr.length = 0
toString
数组有自己的toString方法的实现,会返回以逗号隔开的元素列表
let arr = [1, 2, 3]; alert( arr ); // 1,2,3 alert( String(arr) === ‘1,2,3’ ); // true alert( [] + 1 ); // “1” alert( [1] + 1 ); // “11” alert( [1,2] + 1 ); // “1,21”
不要使用 ==比较数组
数组方法
添加/移除数组元素
splite 分割字符串
splice如何从数组中删除元素
当使用delete删除元素时,虽然删除了,但是它的长度任然没有改变
这很正常,因为 delete obj.key 是通过 key 来移除对应的值。对于对象来说是可以的。单数数组的话是不可以的。
所以使用arr.splice来处理数组,它可以做到,添加删除和插入
arr.splice(start[, deleteCount, elem1, …, elemN])
语法如下:
let arr = [“I”, “study”, “JavaScript”]; arr.splice(1, 1); // 从索引 1 开始删除 1 个元素 alert( arr ); // [“I”, “JavaScript”] let arr = [“I”, “study”, “JavaScript”, “right”, “now”]; // 删除数组的前三项,并使用其他内容代替它们 arr.splice(0, 3, “Let’s”, “dance”); alert( arr ) // 现在 [“Let’s”, “dance”, “right”, “now”] let arr = [“I”, “study”, “JavaScript”]; // 从索引 2 开始 // 删除 0 个元素 // 然后插入 “complex” 和 “language” arr.splice(2, 0, “complex”, “language”); alert( arr ); // “I”, “study”, “complex”, “language”, “JavaScript”
允许负向索引
let arr = [1, 2, 5]; // 从索引 -1(尾端前一位) // 删除 0 个元素, // 然后插入 3 和 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5
slice会返回一个新数组,
arr.slice([start], [end])
let arr = [“t”, “e”, “s”, “t”]; alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素) alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)
我么也可以不带参数的调用它,arr.slice()会创建一个arr的副本。
concat创建一个新数组,但是不影响原数组。
arr.concat(arg1, arg2…) let arr = [1, 2]; // create an array from: arr and [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 // create an array from: arr and [3,4] and [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // create an array from: arr and [3,4], then add values 5 and 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
通常,它只复制数组中的元素,其他对象,即使它们看起来像数组一样,但仍然会被作为一个整体来添加
let arr = [1, 2]; let arrayLike = { 0: “something”, length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[object Object]
但是如果类似数组的对象具有symbol.isConcatSpreadable属性,那么它就会被当做一个数组来处理
let arr = [1, 2]; let arrayLike = { 0: “something”, length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[object Object]
遍历:forEach
arr.forEach方法允许为数组的每个元素都运行一个函数
arr.forEach(function(item, index, array) { // … do something with item });
在数组中搜索indexOf/lastIndexOf/includes
arr.indevOf和arr.includes方法语法相似,并且作用基本上也与字符串的方法相同。例如
arr.indexOf(item, from) —— 从索引 from 开始搜索 item,如果找到则返回索引,否则返回 -1。 arr.includes(item, from) —— 从索引 from 开始搜索 item,如果找到则返回 true(译注:如果没找到,则返回 false)。
let arr = [1, 0, false]; alert( arr.indexOf(0) ); // 1 alert( arr.indexOf(false) ); // 2 alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true
注意:indexOf和includes使用严格相等===进行比较,所以如果我们搜索的是false,他会准确找到false,而不是数字0
arr.lastIndexOf查找则是从由向左的方向
方法includes的一个次要但值得注意的特性是,它可以准确地处理NaN,这与indexOf不同。例如:
const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1(错,应该为 0) alert( arr.includes(NaN) );// true(正确)
find和findIndex/findLastIndex
想象一下,我们有一个对象数组,那我们如何找到具有特定条件的对象
let result = arr.find(function(item, index, array) { // 如果返回 true,则返回 item 并停止迭代 // 对于假值(falsy)的情况,则返回 undefined });
let users = [ {id: 1, name: “John”}, {id: 2, name: “Pete”}, {id: 3, name: “Mary”} ]; let user = users.find(item => item.id == 1); alert(user.name); // John
arr.findIndex方法与arr.find具有相同 的语法,但它返回找到的元素的索引,而不是元素本身,如果没有找到,则返回-1,;arr.findLastIndex方法类似于findIndex,但是从右向左搜索。
let users = [ {id: 1, name: “John”}, {id: 2, name: “Pete”}, {id: 3, name: “Mary”}, {id: 4, name: “John”} ]; // 寻找第一个 John 的索引 alert(users.findIndex(user => user.name == ‘John’)); // 0 // 寻找最后一个 John 的索引 alert(users.findLastIndex(user => user.name == ‘John’)); // 3
filter
find方法搜索的是使函数返回true的第一个元素,如果需要匹配的有很多,我们就可以使用aa.filter()
语法和find大致相同,但是filter返回的是所有匹配元素组成的数组
let results = arr.filter(function(item, index, array) { // 如果 true item 被 push 到 results,迭代继续 // 如果什么都没找到,则返回空数组 });
let users = [ {id: 1, name: “John”}, {id: 2, name: “Pete”}, {id: 3, name: “Mary”} ]; // 返回前两个用户的数组 let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2
map
arr.map是最有用和经常使用的方法之一
它对数组的每个元素都调用函数,并返回结果数组
let result = arr.map(function(item, index, array) { // 返回新值而不是当前元素 })
let lengths = [“Bilbo”, “Gandalf”, “Nazgul”].map(item => item.length); alert(lengths); // 5,7,6
sort
arr.sort方法对数组进行原位排序,更改元素的顺序。
它还返回排序后的数组,但是返回值通常被忽略。值得注意的是这些元素默认情况下呗按照字符串的方式进行排序
let arr = [ 1, 2, 15 ]; // 该方法重新排列 arr 的内容 arr.sort(); alert( arr ); // 1, 15, 2
function compare(a, b) { if (a > b) return 1; // 如果第一个值比第二个值大 if (a == b) return 0; // 如果两个值相等 if (a < b) return -1; // 如果第一个值比第二个值小 } function compareNumeric(a, b) { if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; } let arr = [ 1, 2, 15 ]; arr.sort(compareNumeric); arr.sort((a,b)=> a-b )// a-b 正数表示大于,负数表示小于,0表示等于 alert(arr); // 1, 2, 15
reverse
arr.reverse用于颠倒arr中元素的顺序
let arr = [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1
split和join
arr.split 通过给定的分隔符将字符串分割成一个数组
let names = ‘Bilbo, Gandalf, Nazgul’; let arr = names.split(‘, ‘); for (let name of arr) { alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字) } let str = “test”; alert( str.split(”) ); // t,e,s,t
split方法有一个可选的第二个数字参数,对数组长度进行限制
let arr = ‘Bilbo, Gandalf, Nazgul, Saruman’.split(‘, ‘, 2); alert(arr); // Bilbo, Gandalf
join和split相反,他会在它们之间创建一串字符串
let arr = [‘Bilbo’, ‘Gandalf’, ‘Nazgul’]; let str = arr.join(‘;’); // 使用分号 ; 将数组粘合成字符串 alert( str ); // Bilbo;Gandalf;Nazgul
reduce/reduceRight
实质上就是一个累加器,
let value = arr.reduce(function(accumulator, item, index, array) { // … }, [initial]); accumulator —— 是上一个函数调用的结果,第一次等于 initial(如果提供了 initial 的话)。 item —— 当前的数组元素。 index —— 当前索引。 arr —— 数组本身
let arr = [1, 2, 3, 4, 5]; let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15
也可以省略初始值,在没有初始值的情况下,会将数组的第一个元素作为初始值,并从第二个元素开始迭代;但是如果数组为空的话,是会报错的。
let arr = [1, 2, 3, 4, 5]; // 删除 reduce 的初始值(没有 0) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15
Array.isArray
alert(typeof {}); // object alert(typeof []); // object(相同) alert(Array.isArray({})); // false alert(Array.isArray([])); // true
Iterable object(可迭代对象)
对象是可迭代的,并且只能迭代一次,若第二次迭代,那么会在第一次的迭代下继续,而不是重新开始迭代
let range = { from: 1, to: 5, [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; for (let num of range) { alert(num); // 1, 然后是 2, 3, 4, 5 }
字符串也是可迭代的
数组和字符串是使用最广泛的内建可迭代对象,对于一个字符串,for of 遍历它的每个字符
for (let char of “test”) { // 触发 4 次,每个字符一次 alert( char ); // t, then e, then s, then t } 对于代理对,它也能正常工作(这里的代理对也就是指的是UTF-16的扩展字符) let str = ‘𝒳😂’; for (let char of str) { alert( char ); // 𝒳,然后是 😂 }
Map and Set(映射和集合)
Map
map是一个带键的数据项集合,就像一个Object一样,但是它们最大的差别是Map允许任何类型的键(key)
- new Map() —— 创建 map。
- map.set(key, value) —— 根据键存储值。
- map.get(key) —— 根据键来返回值,如果 map 中不存在对应的 key,则返回 undefined。
- map.has(key) —— 如果 key 存在则返回 true,否则返回 false。
- map.delete(key) —— 删除指定键的值。
- map.clear() —— 清空 map。
- map.size —— 返回当前元素个数。
let map = new Map(); map.set(‘1’, ‘str1’); // 字符串键 map.set(1, ‘num1’); // 数字键 map.set(true, ‘bool1’); // 布尔值键 // 还记得普通的 Object 吗? 它会将键转化为字符串 // Map 则会保留键的类型,所以下面这两个结果不同: alert( map.get(1) ); // ‘num1’ alert( map.get(‘1’) ); // ‘str1’ alert( map.size ); // 3
注意:map不建议使用map[key]的方法获取,使用set、get的方式进行。
map还可以使用对象作为键
let john = { name: “John” }; // 存储每个用户的来访次数 let visitsCountMap = new Map(); // john 是 Map 中的键 visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123
map是如何比较键的?使用严格===的方式,但区别是可以NaN===NaN的
map可以链式调用。
map.set(‘1’, ‘str1’) .set(1, ‘num1’) .set(true, ‘bool1’);
Map迭代
- map.keys() —— 遍历并返回一个包含所有键的可迭代对象,
- map.values() —— 遍历并返回一个包含所有值的可迭代对象,
- map.entries() —— 遍历并返回一个包含所有实体 [key, value] 的可迭代对象,for..of 在默认情况下使用的就是这个。
迭代的顺序和插入值的顺序相同,与普通的Object不同,Map保留了此顺序
let recipeMap = new Map([ [‘cucumber’, 500], [‘tomatoes’, 350], [‘onion’, 50] ]); // 遍历所有的键(vegetables) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } // 遍历所有的值(amounts) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } // 遍历所有的实体 [key, value] for (let entry of recipeMap) { // 与 recipeMap.entries() 相同 alert(entry); // cucumber,500 (and so on) }
此外Map有内建的forEach方法,与Array类似
// 对每个键值对 (key, value) 运行 forEach 函数 recipeMap.forEach( (value, key, map) => { alert(`${key}: ${value}`); // cucumber: 500 etc });
Object.entries:从对象创建Map
当创建一个Map后,我们可以传入一个带有键值对的数组(或其他科迭代对象)来进行初始化
// 键值对 [key, value] 数组 let map = new Map([ [‘1’, ‘str1’], [1, ‘num1’], [true, ‘bool1’] ]); alert( map.get(‘1’) ); // str1
如果我们想从一个已有的普通对象来创建一个Map,那么我们可以使用内建的方法Object.entries,该方法返回对象的键、值对数组,改数组格式完全按照Map所需的格式
let obj = { name: “John”, age: 30 }; let map = new Map(Object.entries(obj)); alert( map.get(‘name’) ); // John
Object.formEntries:从Map创建对象
let prices = Object.fromEntries([ [‘banana’, 1], [‘orange’, 2], [‘meat’, 4] ]); // 现在 prices = { banana: 1, orange: 2, meat: 4 } alert(prices.orange); // 2
Set
set是一个特殊的类型集合,值的集合,它的每一个值只能出现一次
- new Set(iterable) —— 创建一个 set,如果提供了一个 iterable 对象(通常是数组),将会从数组里面复制值到 set 中。
- set.add(value) —— 添加一个值,返回 set 本身
- set.delete(value) —— 删除值,如果 value 在这个方法调用的时候存在则返回 true ,否则返回 false。
- set.has(value) —— 如果 value 在 set 中,返回 true,否则返回 false。
- set.clear() —— 清空 set。
- set.size —— 返回元素个数。
它的主要特点是,重复使用同一个值调用set.add(value)并不会发生什么改变。这就是Set里面的每一个值只出现一次的原因。
let set = new Set(); let john = { name: “John” }; let pete = { name: “Pete” }; let mary = { name: “Mary” }; // visits,一些访客来访好几次 set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); // set 只保留不重复的值 alert( set.size ); // 3 for (let user of set) { alert(user.name); // John(然后 Pete 和 Mary) }
Set迭代
我们可以使用for of 或者forEach来遍历Set
let set = new Set([“oranges”, “apples”, “bananas”]); for (let value of set) alert(value); // 与 forEach 相同: set.forEach((value, valueAgain, set) => { alert(value); });
- set.keys() —— 遍历并返回一个包含所有值的可迭代对象,
- set.values() —— 与 set.keys() 作用相同,这是为了兼容 Map,
- set.entries() —— 遍历并返回一个包含所有的实体 [value, value] 的可迭代对象,它的存在也是为了兼容 Map。
WeakMap and WeakSet(弱映射和弱集合)
在WeakMap和WeakSet中使用一个对象作为键,并且没有其他对这个对象的引用,那么改对象将会被从内存(和map以及set)中自动清除
Object.keys、values、entries
- Object.keys(obj) —— 返回一个包含该对象所有的键的数组。
- Object.values(obj) —— 返回一个包含该对象所有的值的数组。
- Object.entries(obj) —— 返回一个包含该对象所有 [key, value] 键值对的数组。
let user = { name: “John”, age: 30 }; // 遍历所有的值 for (let value of Object.values(user)) { alert(value); // John, then 30 }
就像for in 循环一样,这些方法会忽略使用Symbol()作为键的属性
所以有一个单独的方法
Object.getOwnPropertySymbols,它会返回一个只包含 Symbol 类型的键的数组。另外,还有一种方法 Reflect.ownKeys(obj),它会返回 所有 键。
转换对象
对象缺少数组的许多方法:例如map和filter等
如果我们想要应用它们,那么我们可以使用Object.entries,然后使用Object.fromEntries
let prices = { banana: 1, orange: 2, meat: 4, }; let doublePrices = Object.fromEntries( // 将价格转换为数组,将每个键/值对映射为另一对 // 然后通过 fromEntries 再将结果转换为对象 Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) ); alert(doublePrices.meat); // 8
解构赋值
解构赋值是一种特殊的语法,它使我们可以将数组或对象拆分至一系列变量中。
数组解构
// 我们有一个存放了名字和姓氏的数组 let arr = [“John”, “Smith”] // 解构赋值 // 设置 firstName = arr[0] // 以及 surname = arr[1] let [firstName, surname] = arr; //或者下面这样的,两者是等价的 let firstName = arr[0]; let surname = arr[1]; alert(firstName); // John alert(surname); // Smith
注意:会忽略使用逗号的元素
// 不需要第二个元素 let [firstName, , title] = [“Julius”, “Caesar”, “Consul”, “of the Roman Republic”]; alert( title ); // Consul
实际上,我们可以将其与任何可迭代的对象一起使用,而不仅限于数组
等号右侧可以是任何可迭代的对象
let [a, b, c] = “abc”; // [“a”, “b”, “c”] let [one, two, three] = new Set([1, 2, 3]);
右侧的值上调用for of 并进行赋值的操作的语法糖
赋值给等号左侧的任何内容
let user = {}; [user.name, user.surname] = “John Smith”.split(‘ ‘); alert(user.name); // John alert(user.surname); // Smith
与entries()方法进行循环操作以及Map
let user = { name: “John”, age: 30 }; // 使用循环遍历键—值对 for (let [key, value] of Object.entries(user)) { alert(`${key}:${value}`); // name:John, then age:30 }
let user = new Map(); user.set(“name”, “John”); user.set(“age”, “30”); // Map 是以 [key, value] 对的形式进行迭代的,非常便于解构 for (let [key, value] of user) { alert(`${key}:${value}`); // name:John, then age:30 }
交换变量值的技巧
let guest = “Jane”; let admin = “Pete”; // 让我们来交换变量的值:使得 guest = Pete,admin = Jane [guest, admin] = [admin, guest]; alert(`${guest} ${admin}`); // Pete Jane(成功交换!)
通常,如果数组比左边的列表长,那么其余的数组项会被忽略,如果还想收集其余的数组项。我们可以使用三个点‘…’来再加一个参数以获取其余数组项。
let [name1, name2] = [“Julius”, “Caesar”, “Consul”, “of the Roman Republic”]; alert(name1); // Julius alert(name2); // Caesar // 其余数组项未被分配到任何地方 let [name1, name2, …rest] = [“Julius”, “Caesar”, “Consul”, “of the Roman Republic”]; // rest 是包含从第三项开始的其余数组项的数组 alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2
对象解构
let options = { title: “Menu”, width: 100, height: 200 }; let {title, width, height} = options; alert(title); // Menu alert(width); // 100 alert(height); // 200 let options = { title: “Menu” }; let {width = 100, height = 200, title} = options; let {width = prompt(“width?”), title = prompt(“title?”)} = options; let {width: w = 100, height: h = 200, title} = options; let {title, …rest} = options; alert(title); // Menu alert(width); // 100 alert(height); // 200
日期和时间
创建时间
// 0 表示 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); // 现在增加 24 小时,得到 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); // 现在减少24小时,得到31 Dec 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000);
如果只有一个参数,并且是字符串,那么它会被自动解析,该算法与Date.parse所使用的算法相同
let date = new Date(“2017-01-26”); alert(date); // 未指定具体时间,所以假定时间为格林尼治标准时间(GMT)的午夜零点 // 并根据运行代码时的用户的时区进行调整 // 因此,结果可能是 // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) // 或 // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)
new Date(year, month, date, hours, minutes, seconds, ms)
- year 应该是四位数。为了兼容性,也接受 2 位数,并将其视为 19xx,例如 98 与 1998 相同,但强烈建议始终使用 4 位数。
- month 计数从 0(一月)开始,到 11(十二月)结束。
- date 是当月的具体某一天,如果缺失,则为默认值 1。
- 如果 hours/minutes/seconds/ms 缺失,则均为默认值 0。
new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00 new Date(2011, 0, 1); // 同样,时分秒等均为默认值 0
访问日期组件
获取年份:getFullYear() — 四位数
获取月份:getMonth() —–从0到11
获取日期:getDate() —-从1到31
获取日期的时间戳:getTime()——-从1970-1-1开始到现在所经过的毫秒数
获取UTC和本地时区之间的时差:getTimezoneOffset() —— 以分钟为单位
周:getDay() ——从0(星期日)到6(星期六)
getHours(),getMinutes(),getSeconds(),getMilliseconds()
设置日期组件
- setFullYear(year, [month], [date])
- setMonth(month, [date])
- setDate(date)
- setHours(hour, [min], [sec], [ms])
- setMinutes(min, [sec], [ms])
- setSeconds(sec, [ms])
- setMilliseconds(ms)
- setTime(milliseconds)(使用自 1970-01-01 00:00:00 UTC+0 以来的毫秒数来设置整个日期)
可以发现有些方法时可以设置多个组件的。
let today = new Date(); today.setHours(0); alert(today); // 日期依然是今天,但是小时数被改为了 0 today.setHours(0, 0, 0, 0); alert(today); // 日期依然是今天,时间为 00:00:00。
自动校准
let date = new Date(2013, 0, 32); // 32 Jan 2013 ?!? alert(date); // ……是 1st Feb 2013! let date = new Date(2016, 1, 28); date.setDate(date.getDate() + 2); alert( date ); // 1 Mar 2016 let date = new Date(); date.setSeconds(date.getSeconds() + 70); alert( date ); // 显示正确的日期信息 let date = new Date(2016, 0, 2); // 2016 年 1 月 2 日 date.setDate(1); // 设置为当月的第一天 alert( date ); date.setDate(0); // 天数最小可以设置为 1,所以这里设置的是上一月的最后一天 alert( date ); // 31 Dec 2015
日期转化为数字,日期差值
当Date对象被转化为数字时,得到的是对应的时间戳,与使用date.getTime()的结果相同
let date = new Date(); alert(+date); // 以毫秒为单位的数值,与使用 date.getTime() 的结果相同
日期可以相减,相减的结果是以毫秒为单位时间差
let start = new Date(); // 开始测量时间 // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } let end = new Date(); // 结束测量时间 alert( `The loop took ${end – start} ms` );
Date.now()
如果我们仅仅想要测量时间戳,我么不需要Date对象。
有一个特殊的方法Date.now(),它会返回当前的时间戳
let start = Date.now(); // 从 1 Jan 1970 至今的时间戳 // do the job for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } let end = Date.now(); // 完成 alert( `The loop took ${end – start} ms` ); // 相减的是时间戳,而不是日期
对字符串调用Date.parse
Date.parse(str)方法可以从一个字符串中读取日期
字符串的格式应该为:YYYY-MM-DDTHH:mm:ss.sssZ,其中:
- YYYY-MM-DD —— 日期:年-月-日。
- 字符 “T” 是一个分隔符。
- HH:mm:ss.sss —— 时间:小时,分钟,秒,毫秒。
- 可选字符 ‘Z’ 为 +-hh:mm 格式的时区。单个字符 Z 代表 UTC+0 时区。
let ms = Date.parse(‘2012-01-26T13:51:50.417-07:00’); alert(ms); // 1327611110417 (时间戳) let date = new Date( Date.parse(‘2012-01-26T13:51:50.417-07:00’) ); alert(date);
JSON 方法,toJSON
javascript 提供了如下方法:
JSON.stringify将对象转换为JSON
JSON.parse将JSON转换为对象
let student = { name: ‘John’, age: 30, isAdmin: false, courses: [‘html’, ‘css’, ‘js’], spouse: null }; let json = JSON.stringify(student); alert(typeof json); // we’ve got a string! alert(json); /* JSON 编码的对象: { “name”: “John”, “age”: 30, “isAdmin”: false, “courses”: [“html”, “css”, “js”], “spouse”: null } */
JSON编码的随想个对象字面量有几个重要的区别
1.字符串使用双引号。JSON中没有单引号和反引号。
2.对象属性名称也是双引号。这是强制性的。
JSON支持一下数据类型:
- Objects { … }
- Arrays [ … ]
-
Primitives:
- strings,
- numbers,
- boolean values true/false,
- null。
// 数字在 JSON 还是数字 alert( JSON.stringify(1) ) // 1 // 字符串在 JSON 中还是字符串,只是被双引号扩起来 alert( JSON.stringify(‘test’) ) // “test” alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
也有一部分的类型被忽略:
- 函数属性(方法)。
- Symbol 类型的键和值。
- 存储 undefined 的属性。
let user = { sayHi() { // 被忽略 alert(“Hello”); }, [Symbol(“id”)]: 123, // 被忽略 something: undefined // 被忽略 }; alert( JSON.stringify(user) ); // {}(空对象)
重要的限制,不得有循环引用
let room = { number: 23 }; let meetup = { title: “Conference”, participants: [“john”, “ann”] }; meetup.place = room; // meetup 引用了 room room.occupiedBy = meetup; // room 引用了 meetup JSON.stringify(meetup); // Error: Converting circular structure to JSON
排除和转换: replacer
JSON.stringify的完整语法是:
let json = JSON.stringify(value[, replacer, space])
value:要编码的值;replacer:要编码的属性数组或映射函数;space:用于格式化的空格数量(对象内部缩进)
let room = { number: 23 }; let meetup = { title: “Conference”, participants: [{name: “John”}, {name: “Alice”}], place: room // meetup 引用了 room }; room.occupiedBy = meetup; // room 引用了 meetup alert( JSON.stringify(meetup, [‘title’, ‘participants’]) ); // {“title”:”Conference”,”participants”:[{},{}]}
自定义‘toJSON’
let room = { number: 23, toJSON() { return this.number; } }; let meetup = { title: “Conference”, room }; alert( JSON.stringify(room) ); // 23 alert( JSON.stringify(meetup) ); /* { “title”:”Conference”, “room”: 23 } */
JSON.parse
let value = JSON.parse(str, [reviver]);
str: 要解析的JSON字符串
reviver: 可选的函数,该函数将为每个(key,value)对调用,并可以对值进行转换
// 字符串化数组 let numbers = “[0, 1, 2, 3]”; numbers = JSON.parse(numbers); alert( numbers[1] ); // 1 let userData = ‘{ “name”: “John”, “age”: 35, “isAdmin”: false, “friends”: [0,1,2,3] }’; let user = JSON.parse(userData); alert( user.friends[1] ); // 1
此外JSON不支持注释,向JSON添加注释无效
使用reviver
let str = ‘{“title”:”Conference”,”date”:”2017-11-30T12:00:00.000Z”}’; let meetup = JSON.parse(str, function(key, value) { if (key == ‘date’) return new Date(value); return value; }); alert( meetup.date.getDate() ); // 现在正常运行了!
也是使用于嵌套对象的
let schedule = `{ “meetups”: [ {“title”:”Conference”,”date”:”2017-11-30T12:00:00.000Z”}, {“title”:”Birthday”,”date”:”2017-04-18T12:00:00.000Z”} ] }`; schedule = JSON.parse(schedule, function(key, value) { if (key == ‘date’) return new Date(value); return value; }); alert( schedule.meetups[1].date.getDate() ); // 正常运行了!
Rest参数与spread语法
语法:…变量名,这将会声明一个数组并指定其名称,其中存有剩余的参数。这三个点的语义就是: 收集剩余的参数并存进指定数组中
function sumAll(…args) { // 数组名为 args let sum = 0; for (let arg of args) sum += arg; return sum; } alert( sumAll(1) ); // 1 alert( sumAll(1, 2) ); // 3 alert( sumAll(1, 2, 3) ); // 6
注释:rest参数必须放到参数列表的末尾
function f(arg1, …rest, arg2) { // arg2 在 …rest 后面?! // error } function f(arg1, arg2, …rest) { }
spread语法,看起来和rest参数相似,也使用…,但是二者的用途完全相反,当在函数调用中使用…arr时,它会把可迭代对象arr展开到参数列表中。
let arr = [3, 5, 1]; let arr2 = [8, 9, 15]; let merged = [0, …arr, 2, …arr2]; alert(merged); // 0,3,5,1,2,8,9,15(0,然后是 arr,然后是 2,然后是 arr2)
Array.from :该方法会将一个可迭代对象(如字符串)转换为数组,
let str = “Hello”; // Array.from 将可迭代对象转换为数组 alert( Array.from(str) ); // H,e,l,l,o let str = “Hello”; alert( […str] ); // H,e,l,l,o
复制array/object
let arr = [1, 2, 3]; let arrCopy = […arr]; // 将数组 spread 到参数列表中 // 然后将结果放到一个新数组 // 两个数组中的内容相同吗? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true // 两个数组相等吗? alert(arr === arrCopy); // false(它们的引用是不同的) // 修改我们初始的数组不会修改副本: arr.push(4); alert(arr); // 1, 2, 3, 4 alert(arrCopy); // 1, 2, 3 let obj = { a: 1, b: 2, c: 3 }; let objCopy = { …obj }; // 将对象 spread 到参数列表中 // 然后将结果返回到一个新对象 // 两个对象中的内容相同吗? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true // 两个对象相等吗? alert(obj === objCopy); // false (not same reference) // 修改我们初始的对象不会修改副本: obj.d = 4; alert(JSON.stringify(obj)); // {“a”:1,”b”:2,”c”:3,”d”:4} alert(JSON.stringify(objCopy)); // {“a”:1,”b”:2,”c”:3}
这种方式比使用Object.assign([],arr)以及Object.assign({},arr)复制对象来说更加的简便
注释: 那么怎么区分…reset参数和spread语法呢
若…出现在函数参数列表的最后,那么他就是reset参数,它会把参数列表中剩余的参数收集到一个数组中。
若…出现在函数调用或类似的表达式中,那么它就是spread语法,它会把一个数组展开为列表。
对象创建属性以及获取属性
let user = {}; Object.defineProperty(user, “name”, { value: “John” }); let descriptor = Object.getOwnPropertyDescriptor(user, ‘name’); alert( JSON.stringify(descriptor, null, 2 ) ); /* { “value”: “John”, “writable”: false, “enumerable”: false, “configurable”: false } */