ES6的一些知识点
Babel 转码器
Babel ES6转码器
l\\配置文件 .babelrc
babel-core
babel-register
let const
let 代码快中有效 块级作用域
暂时性死区 先声明再调用
不存在变量提升
不允许重复声明 在同一作用域 在函数内不能重复声明参数
IIFE 立即执行函数
注意:ES5不允许块级作用域中声明函数。ES6可以。
全局不能访问块级作用域中的变量
变量解析赋值
模式匹配 let [a,b,c] = [1,2,3] || let [a,b,c] = [1,2,3,4] || let [a, [[b], b]] = [1,[[2,3],4]],let [,,z] = [1,2,3] 一定要可迭代对象
允许默认值 var [f = true] = [] // f = true
null
,不严格相等于undefined
,导致默认值不会生效注意:如果默认值是一个函数,那么是惰性取值
对象的解构赋值 var {f, s} = {f : 1, s :2} 注意:zhe是 :
1 var { foo: baz } = { foo: 'aaa', bar: 'bbb' }; // bax "aaa"对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。前者是模式不是变量
注意: let和const不能重复赋值,所以
1
2
3
4
5
6 let foo;
({foo} = {foo: 1}); // 成功
let baz;
({bar: baz} = {bar: 1}); // 成功上面代码中,
let
命令下面一行的圆括号是必须的,否则会报错。因为解析器会将起首的大括号,理解成一个代码块,而不是赋值语句。由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
1
2
3
4 var arr = [1, 2, 3];
var {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3字符串结构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
1
2
3
4
5
6 const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"类似数组的对象都有一个
length
属性,因此还可以对这个属性解构赋值。
1
2 let {length : len} = 'hello';
len // 5
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
1 | let {toString: s} = 123; |
上面代码中,数值和布尔值的包装对象都有toString
属性,因此变量s
都能取到值。
用处
(1)交换变量的值
1 | [x, y] = [y, x]; |
上面代码交换变量x
和y
的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
1 | // 返回一个数组 |
(3)函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
1 | // 参数是一组有次序的值 |
(4)提取JSON数据
解构赋值对提取JSON对象中的数据,尤其有用。
1 | var jsonData = { |
上面代码可以快速提取JSON数据的值。
(5)函数参数的默认值
1 | jQuery.ajax = function (url, { |
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';
这样的语句。
(6)遍历Map结构
任何部署了Iterator接口的对象,都可以用for...of
循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
1 | var map = new Map(); |
如果只想获取键名,或者只想获取键值,可以写成下面这样。
1 | // 获取键名 |
(7)输入模块的指定方法
加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。
1 | const { SourceMapConsumer, SourceNode } = require("source-map"); |
字符串的扩展
加强了对Unicode的支持
JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2
个字节。对于那些需要4
个字节储存的字符(Unicode码点大于0xFFFF
的字符),JavaScript会认为它们是两个字符。
1 | var s = "𠮷"; |
上面代码中,汉字“𠮷”(注意,这个字不是”吉祥“的”吉“)的码点是0x20BB7
,UTF-16编码为0xD842 0xDFB7
(十进制为55362 57271
),需要4
个字节储存。对于这种4
个字节的字符,JavaScript不能正确处理,字符串长度会误判为2
,而且charAt
方法无法读取整个字符,charCodeAt
方法只能分别返回前两个字节和后两个字节的值。
codePointAt
方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。
1 | function is32Bit(c) { |
String.fromCodePoint
ES5提供String.fromCharCode
方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符(Unicode编号大于0xFFFF
)。
1 | String.fromCharCode(0x20BB7) |
上面代码中,String.fromCharCode
不能识别大于0xFFFF
的码点,所以0x20BB7
就发生了溢出,最高位2
被舍弃了,最后返回码点U+0BB7
对应的字符,而不是码点U+20BB7
对应的字符。
ES6提供了String.fromCodePoint
方法,可以识别0xFFFF
的字符,弥补了String.fromCharCode
方法的不足。在作用上,正好与codePointAt
方法相反。
1 | String.fromCodePoint(0x20BB7) |
上面代码中,如果String.fromCodePoint
方法有多个参数,则它们会被合并成一个字符串返回。
注意,fromCodePoint
方法定义在String
对象上,而codePointAt
方法定义在字符串的实例对象上。
字符串的遍历器接口
for…of…
1 | for (let codePoint of 'foo') { |
除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF
的码点,传统的for
循环无法识别这样的码点。
1 | var text = String.fromCodePoint(0x20BB7); |
at
ES5对字符串对象提供charAt
方法,返回字符串给定位置的字符。该方法不能识别码点大于0xFFFF
的字符。
1 | 'abc'.charAt(0) // "a" |
上面代码中,charAt
方法返回的是UTF-16编码的第一个字节,实际上是无法显示的。
目前,有一个提案,提出字符串实例的at
方法,可以识别Unicode编号大于0xFFFF
的字符,返回正确的字符。
1 | 'abc'.at(0) // "a" |
这个方法可以通过垫片库实现
normalize
合成符号和重音字符
includes,startsWith,endsWith
repeat
padStart,padEnd 补全
模板字符串 `${}`
1 | const tmpl = addrs => ` |
如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。
1 | // 写法一 |
正则拓展
正则字符串 match search replace split
u修饰符 正确识别码点大于0xFFFF
的Unicode字符
y修饰符
y
修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
1 | var s = 'aaa_aa_a'; |
sticky 属性判断是否是y修饰符
flags属性 返回修饰符
/abc/ig.flags // ‘gi’
s修饰符:dotAll模式
让
.
匹配任意字符,如\n
,\t
等
数值的拓展
二进制和八进制的表示
二进制 0b 或者 0B 八进制 0o 或者 0O (转十进制用
Number
)
Number.isFinite(), Number.isNaN()
isFinite 判断是否有范围,isNaN 判断是否为NaN
Number.parseInt() Number.parseFloat()
Number.isInteger()
判断是否为整数,JavaScript内部,3===3.0
Number.EPSILON
新增极小值,判断浮点数是否精确
安全整数和Number.isSafeInteger()
JavaScript中表示的整数范围是-2^53~2^53,超过就无法精确表示了如,Math.pow(2,53) === Math.pow(2,53)+1 // true
Math.trunc()
Math.trunc
方法去除小数,和Number.parseInt的简单区别为 Number.parseInt(“1s”) // 1而 Math.trunc(“1s”) //NaN
Math.sign()
Math.sign()
方法判断一个数的正负
它会返回五种值。
- 参数为正数,返回+1;
- 参数为负数,返回-1;
- 参数为0,返回0;
- 参数为-0,返回-0;
- 其他值,返回NaN。
Math.cbrt()
Math.cbrt()
方法是计算一个的立方根,非数值型先用Number方法转化,否则是NaN
Math.clz32()
JavaScript的整数使用32位二进制形式表示,Math.clz32
方法返回一个数的32位无符号整数形式有多少个前导0。
1 | Math.clz32(0) // 32 |
Math.fround
返回一个数的单精度浮点数
Math.hypot
返回所有参数的平方和的平方根
1 | Math.hypot(3, 4); // 5 |
数组的拓展
Array.from()
Array.from
方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
1 | // NodeList对象 |
Array.from
方法则是还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length
属性。因此,任何有length
属性的对象,都可以通过Array.from
方法转为数组,而此时扩展运算符就无法转换。
1 | Array.from({ length: 3 }); |
Array.from
还可以接受第二个参数,作用类似于数组的map
方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
1 | function typesOf () { |
Array.of
Array.of
方法用于将一组值,转换为数组。不同于Array.from 的是,这个是一组值,而Array.from 是类似数组或者可迭代对象
1 | Array.of(3, 11, 8) // [3,11,8] |
这个方法的主要目的,是弥补数组构造函数Array()
的不足。因为参数个数的不同,会导致Array()
的行为有差异。
1 | Array() // [] |
数组实例的copyWithin()
1 | [1, 2, 3, 4, 5].copyWithin(0, 3) |
上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
下面是更多例子。
1 | // 将3号位复制到0号位 |
数组实例的find()和findIndex
1 | [1, 4, -5, 10].find((n) => n < 0) |
上面代码找出数组中第一个小于0的成员。
1 | [1, 5, 10, 15].find(function(value, index, arr) { |
上面代码中,find
方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
1 | [NaN].findIndex(y => Object.is(NaN, y)) |
弥补了indexOf
方法无法识别数组的NaN
成员 [NaN].indexOf(NaN) // -1
数组实例的fill()
fill
方法使用给定值,填充一个数组。
1 | ['a', 'b', 'c'].fill(7) |
上面代码表明,fill
方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
fill
方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
1 | ['a', 'b', 'c'].fill(7, 1, 2) |
数组实例的entries(), keys()和value()
ES6提供三个新的方法——entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象,可以用for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历。
1 | for (let index of ['a', 'b'].keys()) { |
数组的方法
1 | // forEach方法 |
数组空位的处理
ES5会跳过,ES6当做undefined 处理
函数的拓展
允许参数默认值
与解构赋值默认值结合使用
1 | function foo({x, y = 5}) { |
上述代码采用了对象解构赋值
下面是另一个对象的解构赋值默认值的例子。
1 | function fetch(url, { body = '', method = 'GET', headers = {} }) { |
上面代码中,如果函数fetch
的第二个参数是一个对象,就可以为它的三个属性设置默认值。
上面的写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
1 | function fetch(url, { method = 'GET' } = {}) { |
函数的length属性
指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
1 | (function (a) {}).length // 1 |
如果设置了默认值的参数不是尾参数,那么length
属性也不再计入后面的参数了。
1 | (function (a = 0, b, c) {}).length // 0 |
应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
1 | function throwIfMissing() { |
rest参数
用户获取函数多余参数,这样就不需要arguments对象 了,函数的length参数不包含rest参数
1 | function add(...values) { |
拓展运算符
拓展运算符
...
,将一个数组转化为用逗号隔开的参数排序
1 | console.log([1,2,3]) // [1,2,3] |
代替数组apply方法
1 | // ES5的写法 |
1 | // ES5 |
Map和Set结构,Generator函数
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
1 | let map = new Map([ |
Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
1 | var go = function*(){ |
函数name 属性
箭头函数
对象的拓展
允许直接写入变量和函数,作为对象的属性和方法。
1 | var foo = 'bar'; |
方法简写
1 | var o = { |
属性名表达式
Symbol
ES6 Symbol 就是为了防止属性名的冲突而ES6才增加上去的
1 | let s = Symbol(); |
如果 Symbol 的参数是一个对象,就会调用该对象的toString
方法,将其转为字符串,然后才生成一个 Symbol 值。
1 | const obj = { |
作为属性名的Symbol
防止不当心覆盖属性
1 | var mySymbol = Symbol(); |
注意以上的第二种方法,一定要用[]括起来,否则就是字符串”mySymbol” 键名
Symbol.for, Symbol.keyFor
1 | var s1 = Symbol.for('foo'); |
Symbol.for() 和 Symbol()的区别是: 前者不会每次调用都会生成新的Symbol,会先检查key是否存在,Symbol() 就会生成
上面代码中,由于Symbol()
写法没有登记机制,所以每次调用都会返回一个不同的值。
Symbol.keyFor
方法返回一个已登记的 Symbol 类型值的key
。
1 | var s1 = Symbol.for("foo"); |
Set和Map数据结构
ES6提供了新的 数据结构Set。它类似于数组和Python中的set差不多
1 | var s = new Set(); |
1 | // 例一 |
上述代码也展示一种去除数组重复成员的方法
1 | //去除数组的重复成员 |
注意:两个对象总会不相等的
1 | {} === {} // false |
Set 实例的属性和方法
Set结构的实例有以下属性。
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
add(value)
:添加某个值,返回Set结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
遍历操作
Set结构有四种遍历操作,默认的遍历方法就是values() 方法
- keys() 返回键名的遍历器
- values() 返回键值的遍历器
- entries() 返回键值对的遍历器
- forEach() 使用回调函数遍历每个成员
而且数组的map和filter方法也是可以用于set,也因此很容易实现并集和交集
1 | let a = new Set([1, 2, 3]); |
如果想在遍历操作中,同步改变原来的Set结构,目前没有直接的方法,但有两种变通方法。一种是利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构;另一种是利用Array.from
方法。
1 | // 方法一 |
map数据结构
Javascript的对象,本质上是键值对的集合,但是传统上只能使用字符串当做键,这让它在使用上带来了很大的讽刺。所以,为了解决这一个问题,ES6提供了Map数据结构
1 | var m = new Map(); |
作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
1 | var map = new Map([ |
注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心。
1 | var map = new Map(); |
上面代码的set
和get
方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get
方法无法读取该键,返回undefined
。
遍历方法
Map原生提供三个遍历器生成函数和一个遍历方法。
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回所有成员的遍历器。forEach()
:遍历Map的所有成员。
结合数组的map
方法、filter
方法,可以实现Map的遍历和过滤(Map本身没有map
和filter
方法)。
1 | let map0 = new Map() |
此外,Map还有一个forEach
方法,与数组的forEach
方法类似,也可以实现遍历。
1 | map.forEach(function(value, key, map) { |
forEach
方法还可以接受第二个参数,用来绑定this
。
1 | var reporter = { |
上面代码中,forEach
方法的回调函数的this
,就指向reporter
。
与其他数据结构的相互转换
1) Map 转化为数组,使用拓展运算符,Array.from
1 | let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); |
2) 数组转为Map,使用new Map() 就行
1 | new Map([[true, 7], [{foo: 3}, ['abc']]]) |
3) Map 转化为对象
所有Map键都是字符串才能转化为对象
1 | function strMapToObj(strMap) { |
4) 对象转化为Map
1 | function objToStrMap(obj) { |
5) Map转化为JSON
Map的键名要全是字符串 哦
1 | function strMapToJson(strMap) { |
6) JSON转化为Map
1 | function jsonToStrMap(jsonStr) { |
Proxy和Reflect
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
1 | var obj = new Proxy({}, { |
上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get
)和设置(set
)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj
,去读写它的属性,就会得到下面的结果。
1 | obj.count = 1 |
1 | var proxy = new Proxy(target, handler); |
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler
参数的写法。其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,即如果没有Proxy
的介入,操作原来要访问的就是这个对象,handler
参数也是一个对象,用来定制拦截行为。
如果handler
没有设置任何拦截,那就等同于直接通向原对象。
1 | var target = {}; |
上面代码中,handler
是一个空对象,没有任何拦截效果,访问handler
就等同于访问target
。
技巧
一个技巧是将Proxy对象,设置到object.proxy
属性,从而可以在object
对象上调用。
1 | var object = { proxy: new Proxy(target, handler) }; |
Proxy 实例也可以作为其他对象的原型对象。
1 | var proxy = new Proxy({}, { |
上面代码中,proxy
对象是obj
对象的原型,obj
对象本身并没有time
属性,所以根据原型链,会在proxy
对象上读取该属性,导致被拦截。
针对函数的proxy
同一个拦截器函数,可以设置拦截多个操作。
1 | var handler = { |
Proxy实例的方法
1. get方法
1 | var person = { |
实用例子:实现数组的负数索引
1 | function createArray(...elements) { |
2. set方法
set
方法用来拦截某个属性的赋值操作。
假定Person
对象有一个age
属性,该属性应该是一个不大于200的整数,那么可以使用Proxy
保证age
的属性值符合要求。
1 | let validator = { |
实用实例
有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get
和set
方法,就可以做到防止这些内部属性被外部读写。
1 | var handler = { |
3.apply 方法
apply
方法拦截函数的调用、call和apply操作。
1 | var handler = { |
下面是另外一个例子。
1 | var twice = { |
上面代码中,每当执行proxy
函数(直接调用或call
和apply
调用),就会被apply
方法拦截。
另外,直接调用Reflect.apply
方法,也会被拦截。
1 | Reflect.apply(proxy, null, [9, 10]) // 38 |
4. has 方法
has
方法用来拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in
运算符。
1 | var handler = { |
如果原对象的属性名的第一个字符是下划线,proxy.has
就会返回false
,从而不会被in
运算符发现。
has
拦截只对in
循环生效,对for...in
循环不生效,导致不符合要求的属性没有被排除在for...in
循环之外。
5.construct 方法
construct
方法是用来拦截new
命令的
1 | var p = new Proxy(function() {}, { |
construct
方法返回的必须是一个对象,否则会报错。
this问题
1 | const target = { |
上面代码中,一旦proxy
代理target.m
,后者内部的this
就是指向proxy
,而不是target
。
Iterator和for…of循环
Iterator 的概念
JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of
循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator
属性
数据结构默认的Iterator接口
一种数据结构只要部署了Iterator接口,我们就称这种数据结构是”可遍历的“(iterable)。
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator
属性,或者说,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”(iterable)
1 | const obj = { |
上面代码中,对象obj
是可遍历的(iterable),因为具有Symbol.iterator
属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next
方法。每次调用next
方法,都会返回一个代表当前成员的信息对象,具有value
和done
两个属性。
在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。
1 | let arr = ['a', 'b', 'c']; |
一个对象如果要有可被for...of
循环调用的Iterator接口,就必须在Symbol.iterator
的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
1 | class RangeIterator { |
上面代码是一个类部署Iterator接口的写法。Symbol.iterator
属性对应一个函数,执行后返回当前对象的遍历器对象。
下面是通过遍历器实现指针结构的例子。
1 | function Obj(value) { |
上面代码首先在构造函数的原型链上部署Symbol.iterator
方法,调用该方法会返回遍历器对象iterator
,调用该对象的next
方法,在返回一个值的同时,自动将内部指针移到下一个实例。
下面是另一个为对象添加Iterator接口的例子。
1 | let obj = { |
对于类似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是Symbol.iterator
方法直接引用数组的Iterator接口。
调用Iterator接口的场合
有一些场合会默认调用Iterator接口(即Symbol.iterator
方法),除了下文会介绍的for...of
循环,还有几个别的场合。
(1)解构赋值
对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator
方法。
1 | let set = new Set().add('a').add('b').add('c'); |
(2)扩展运算符
扩展运算符(…)也会调用默认的iterator接口。
1 | // 例一 |
上面代码的扩展运算符内部就调用Iterator接口。
实际上,这提供了一种简便机制,可以将任何部署了Iterator接口的数据结构,转为数组。也就是说,只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组。
1 | let arr = [...iterable]; |
(3)yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
1 | let generator = function* () { |
(4)其他场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
- for…of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])
) - Promise.all()
- Promise.race()
拓展
在原生的有遍历接口的数据结构上可以直接覆盖修改
1 | var str = new String("hi"); |
上面代码中,字符串str的Symbol.iterator
方法被修改了,所以扩展运算符(...
)返回的值变成了bye
,而字符串本身还是hi
。
Iterator接口和Generation函数
Symbol.iterator
方法的最简单实现,还是使用下一章要介绍的Generator函数。
1 | var myIterable = {}; |
数组原生具备iterator
接口(即默认部署了Symbol.iterator
属性),for...of
循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。
1 | const arr = ['red', 'green', 'blue']; |
上面代码中,空对象obj
部署了数组arr
的Symbol.iterator
属性,结果obj
的for...of
循环,产生了与arr
完全一样的结果。
Generator 函数
和Python中的生成器理解相同,执行Generator函数会返回一个遍历器对象,用到yield,下面就是一个标准的Generator函数。Generator函数就是遍历器生成函数
1 | function* helloWorldGenerator() { |
1 | hw.next() |
yield语句
由于Generator函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
语句就是暂停标志。
yield* 使用方法
1 | var arr = [1, [[2, 3], 4], [5, 6]]; |
next 函数参数
1 | function* f() { |
上面代码先定义了一个可以无限运行的Generator函数f
,如果next
方法没有参数,每次运行到yield
语句,变量reset
的值总是undefined
。当next
方法带一个参数true
时,当前的变量reset
就被重置为这个参数(即true
),因此i
会等于-1,下一轮循环就会从-1开始递增。
再看一个例子。这个看懂,Generator函数的next函数基本就过了
1 | function* foo(x) { |
注意,由于next
方法的参数表示上一个yield
语句的返回值,所以第一次使用next
方法时,不能带有参数。V8引擎直接忽略第一次使用next
方法时的参数,只有从第二次使用next
方法开始,参数才是有效的。从语义上讲,第一个next
方法用来启动遍历器对象,所以不用带有参数。
注意: Generator函数里面除了yield,还有return、throw。这里介绍return,return是跳出遍历器,return的值不在循环里。如下
1 | function *foo() { |
例子
下面是一个利用Generator函数和for...of
循环,实现斐波那契数列的例子。
1 | function* fibonacci() { |
对象添加遍历器
1 | function* objectEntries(obj) { |
yield* 语句
从语法角度看,如果yield
命令后面跟的是一个遍历器对象,需要在yield
命令后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*
语句。
如果在Generater函数内部,调用另一个Generator函数,默认情况下是没有效果的。
1 | function* foo() { |
上面代码中,foo
和bar
都是Generator函数,在bar
里面调用foo
,是不会有效果的。
这个就需要用到yield*
语句,用来在一个Generator函数里面执行另一个Generator函数。
1 | function* bar() { |
实际上,任何数据结构只要有Iterator接口,就可以被yield*
遍历。
1 | function* inner() { |
如果被代理的Generator函数有return
语句,那么就可以向代理它的Generator函数返回数据。
1 | function *foo() { |
好例子
下面是一个稍微复杂的例子,使用yield*
语句遍历完全二叉树。
1 | // 下面是二叉树的构造函数, |
ES6 Promise对象
异步编程的一种解决方案
所谓
Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。
1 | var promise = new Promise(function(resolve, reject) { |
Promise实例生成以后,可以用then
方法分别指定Resolved
状态和Reject
状态的回调函数。
1 | promise.then(function(value) { |
下面是一个用Promise对象实现的Ajax操作的例子。
1 | var getJSON = function(url) { |
上面代码中,getJSON
是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的HTTP请求,并且返回一个Promise对象。需要注意的是,在getJSON
内部,resolve
函数和reject
函数调用时,都带有参数。