ECMASCRIPT和JavaScript的关系
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
要讲清楚这个问题,需要回顾历史。1996年11月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是1.0版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。日常场合,这两个词是可以互换的。
let 命令
let命令类似于var,但是只在块级作用域内生效,不能进行变量提升。
1 2 3 4 5 6 7
| { let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
|
const 命令
const类似于var,定义常量,一旦声明,常量的值不能改变。常量必须赋值,如果不赋值就会报错。
1 2
| const foo; VM1586:1 Uncaught SyntaxError: Missing initializer in const declaration
|
const 命令也不能进行变量提升
顶层对象属性
1 2 3 4 5 6 7
| var a = 1; // 如果在Node的REPL环境,可以写成global.a // 或者采用通用方法,写成this.a window.a // 1 let b = 1; window.b // undefined
|
上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。
数组的解构赋值
1 2 3 4 5
| let [a, b, c] = [1, 2, 3]; let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3
|
变量的解构赋值
1 2 3 4 5 6
| let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb" let { baz } = { foo: "aaa", bar: "bbb" }; baz // undefined
|
字符串的解构赋值
1 2 3 4 5 6 7 8
| const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o" let {length : len} = 'hello'; len // 5
|
数值和布尔值的解构赋值
1 2 3 4 5
| let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true
|
函数参数的解构赋值
1 2 3 4 5
| function add([x, y]){ return x + y; } add([1, 2]); // 3
|
变量赋值的用途
交换变量值
1 2 3 4
| let x = 1; let y = 2; [x, y] = [y, x];
|
变量赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
|
字符串的扩展
includes(), startsWith(),endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
1 2 3 4 5
| var s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
|
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。
1 2 3
| 'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // ""
|
数值的扩展
Number.isFinite(),Number.isNaN()
1 2 3 4 5 6 7
| Number.isFinite(15) Number.isFinite(0.8); // true Number.isFinite(NaN); // Number.isNaN(NaN) // true Number.isNaN(15) // false Number.isNaN('15') // false Number.isNaN(true) // false
|
Number.parseInt(), Number.parseFloat()
1 2 3 4 5 6 7
| // ES5的写法 parseInt('12.34') // 12 parseFloat('123.45#') // 123.45 // ES6的写法 Number.parseInt('12.34') // 12 Number.parseFloat('123.45#') // 123.45
|
Number.isInteger()
1 2 3 4 5
| Number.isInteger(25) // true Number.isInteger(25.0) // true Number.isInteger(25.1) // false Number.isInteger("15") // false Number.isInteger(true) // false
|
Math对象的扩展
Math.trunc()
Math.trunc方法用于去除一个数的小数部分,返回整数部分
1 2 3 4 5 6
| Math.trunc() Math.trunc(4.1) // 4 Math.trunc(4.9) // 4 Math.trunc(-4.1) // -4 Math.trunc(-4.9) // -4 Math.trunc(-0.1234) // -0
|
Math.sign(4)
Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
参数为正数,返回+1;
参数为负数,返回-1;
参数为0,返回0;
参数为-0,返回-0;
其他值,返回NaN。
Array.from 方法用于将两类对象转换成真正的数组
1 2 3 4 5 6 7 8 9 10 11 12
| let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
|
实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
1 2 3 4 5 6 7 8 9 10 11
| // NodeList对象 let ps = document.querySelectorAll('p'); Array.from(ps).forEach(function (p) { console.log(p); }); // arguments对象 function foo() { var args = Array.from(arguments); // ... }
|
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
1 2 3 4 5 6
| Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
|
Array.of()
Array.of方法用于将一组值,转换为数组。
1 2 3
| Array.of(3, 11, 8) // [3,11,8] Array.of(3) // [3] Array.of(3).length // 1
|
数组实例find()和findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
1 2
| [1, 4, -5, 10].find((n) => n < 0) // -5
|
上面代码找出数组中第一个小于0的成员。
1 2 3
| [1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10
|
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
1 2 3
| [1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2
|
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
另外,这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。
1 2 3 4 5
| [NaN].indexOf(NaN) // -1 [NaN].findIndex(y => Object.is(NaN, y)) // 0
|
数组的fill()
fill方法使用给定值,填充一个数组。
1 2 3 4 5
| ['a', 'b', 'c'].fill(7) // [7, 7, 7] new Array(3).fill(7) // [7, 7, 7]
|
数组实例的entries(),keys()和values()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
|
数组的includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016 引入了该方法。
1 2 3
| [1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true
|
数组的空位
forEach(), filter(), every() 和some()都会跳过空位。
map()会跳过空位,但会保留这个值
join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
// forEach方法
[,’a’].forEach((x,i) => console.log(i)); // 1
// filter方法
[‘a’,,’b’].filter(x => true) // [‘a’,’b’]
// every方法
[,’a’].every(x => x===’a’) // true
// some方法
[,’a’].some(x => x !== ‘a’) // false
// map方法
[,’a’].map(x => 1) // [,1]
// join方法
[,’a’,undefined,null].join(‘#’) // “#a##”
// toString方法
[,’a’,undefined,null].toString() // “,a,,”
函数的扩展
基本用法
1 2 3 4 5 6 7
| function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
|
与解构赋值相结合
参数默认值可以与解构赋值的默认值,结合起来使用。
1 2 3 4 5 6 7 8
| function foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined, 5 foo({x: 1}) // 1, 5 foo({x: 1, y: 2}) // 1, 2 foo() // TypeError: Cannot read property 'x' of undefined
|
函数的length
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
1 2 3
| (function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2
|
rest参数
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
1 2 3 4 5 6 7 8 9 10 11
| function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
|
函数的length属性,不包括 rest 参数。
1 2 3
| (function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
|
扩展运算符的应用
合并数组
扩展运算符提供了数组合并的新写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // ES5 [1, 2].concat(more) // ES6 [1, 2, ...more] var arr1 = ['a', 'b']; var arr2 = ['c']; var arr3 = ['d', 'e']; // ES5的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
|
(2)与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
1 2 3 4
| // ES5 a = list[0], rest = list.slice(1) // ES6 [a, ...rest] = list
|
Map和Set结构,Generator函数
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
1 2 3 4 5 6 7
| let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3]
|
name 属性
函数的name属性,返回该函数的函数名。
1 2
| function foo() {} foo.name // "foo"
|
箭头函数
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
属性的简洁用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function f(x, y) { return {x, y}; } // 等同于 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2} let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };
|
方法名的name
函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
1 2 3 4 5 6 7
| const person = { sayName() { console.log('hello!'); }, }; person.sayName.name // "sayName"
|
Object.is()
ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
1 2 3 4
| Object.is('foo', 'foo') // true Object.is({}, {}) // false
|
##Object.assign()
基本用法
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
1 2 3 4 5 6 7
| var target = { a: 1 }; var source1 = { b: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
|
属性的遍历
ES6 一共有5种方法可以遍历对象的属性。
(1)for…in
for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举。
以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。
首先遍历所有属性名为数值的属性,按照数字排序。
其次遍历所有属性名为字符串的属性,按照生成时间排序。
最后遍历所有属性名为 Symbol 值的属性,按照生成时间排序。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// [‘2’, ‘10’, ‘b’, ‘a’, Symbol()]
上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2和10,其次是字符串属性b和a,最后是 Symbol 属性。
Object.keys(),Object.values(),Object.entries()
Object.keys()
ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
1 2 3
| var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"]
|
Object.entries
Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
1 2 3
| var obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
|
set
Array.from方法可以将 Set 结构转为数组。
1 2
| const items = new Set([1, 2, 3, 4, 5]); const array = Array.from(items);
|
map
1 2 3 4 5 6 7 8 9
| const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
|
size()属性
size属性返回 Map 结构的成员总数。
1 2 3 4 5
| const map = new Map(); map.set('foo', true); map.set('bar', false); map.size // 2
|
set(key, value)
set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
1 2 3 4 5
| const m = new Map(); m.set('edition', 6) // 键是字符串 m.set(262, 'standard') // 键是数值 m.set(undefined, 'nah') // 键是 undefined
|
get(key)
has(key)
delete(key) 返回true
clear()
便利方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历 Map 的所有成员。