ECMAScript 6 入门
Times to digest knowledge deeply and systematically. Begin with this open-source book first.
1.) Destructuring
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
解构赋值允许指定默认值。
可作用于数组和object。对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
1 | let [a, b, c] = [1, 2, 3]; |
2.) 字符串的新增方法
String.fromCodePoint()
String.raw()
实例方法:codePointAt()
实例方法:normalize()
实例方法:includes(), startsWith(), endsWith()
实例方法:repeat()
实例方法:padStart(),padEnd()
实例方法:trimStart(),trimEnd()
实例方法:matchAll()
padStart()
的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。1
2
3'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"- 另一个用途是提示字符串格式。
1
2'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
3.) regex
This is crazy…put it aside first
4.) Number related
Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
Kyle说,JS倾向将数据类型转换为number再进行比较,比较高效。
ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。
1 | // ES5的写法 |
ES2016 新增了一个指数运算符(**)。
1 | 2 ** 2 // 4 |
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
1 | // 相当于 2 ** (3 ** 2) |
上面代码中,首先计算的是第二个指数运算符,而不是第一个。
指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
1 | let a = 1.5; |
ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
1 | const a = 2172141653n; |
BigInt 与普通整数是两种值,它们之间并不相等。
1 | 42n === 42 // false |
typeof运算符对于 BigInt 类型的数据返回bigint。
1 | typeof 123n // 'bigint' |
5.) function
argument can have default value
参数变量是默认声明的,所以不能用let或const再次声明。
1 | function foo(x = 5) { |
1 | // 写法一 |
上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
1 | // 函数没有参数的情况 |
context 作用域,需要多考虑。
rest参数 […arguments]
arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。
strict mode
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
arrow function
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
1 | // 报错 |
箭头函数可以与变量解构结合使用。
1 | const full = ({ first, last }) => first + ' ' + last; |
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。
1 | var handler = { |
上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。
Tail Call 尾调用(函数式编程的重要概念)
尾调用不一定出现在函数尾部,只要是最后一步操作即可。
1 | function f(x) { |
上面代码中,函数m和n都属于尾调用,因为它们都是函数f的最后一步操作。
尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。ES6 亦是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署“尾调用优化”。这就是说,ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。
函数式编程有一个概念,叫做柯里化(currying),意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。
总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
catch 命令的参数省略
JavaScript 语言的try…catch结构,以前明确要求catch命令后面必须跟参数,接受try代码块抛出的错误对象。
1 | try { |
上面代码中,catch命令后面带有参数err。
很多时候,catch代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。ES2019 做出了改变,允许catch语句省略参数。
1 | try { |
array
spread operator
1.) clone array
扩展运算符提供了复制数组的简便写法。
1 | const a1 = [1, 2]; |
上面的两种写法,a2都是a1的克隆。
2.) concat array
1 | onst arr1 = ['a', 'b']; |
不过,这两种方法都是浅拷贝,使用的时候需要注意。
1 | const a1 = [{ foo: 1 }]; |
上面代码中,a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
3.) work with destructor
与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
1 | // ES5 |
4.) turn a string into an array
扩展运算符还可以将字符串转为真正的数组。
1 | [...'hello'] |
5.) Symbol.iterator
实现了 Iterator 接口的对象
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
1 | let nodeList = document.querySelectorAll('div'); |
上面代码中,querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 。
对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。use Array.from instead, it can turn arrayLike into real array.
Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
取出一组 DOM 节点的文本内容。
1 | let spans = document.querySelectorAll('span.name'); |
Array.of()
Array.of方法用于将一组值,转换为数组。
1 | Array.of(3, 11, 8) // [3,11,8] |
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
1 | Array() // [] |
上面代码中,Array方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,Array()才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。<Kyle's tutorial mentioned it as well.>
Array.of基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。它的行为非常统一。
find() findIndex()
这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。
entries() keys() values()
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
includes()
Map 和 Set 数据结构有一个has方法,需要注意与includes区分。
Map 结构的has方法,是用来查找键名的,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。
Set 结构的has方法,是用来查找值的,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。
flat(),flatMap()
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。该方法返回一个新数组,对原数据没有影响。