本文目录
[iterator]
1. iterator的定义
2. Iterator 的作用
3. iterator默认结构
4. 调用iterator的场合
5. 字符串的iterator
6. 遍历器对象的 return(),throw()
7. for...of 循环
8. iterator的实现
[generator]
1. 简介
2. 运行规则
3. Generator.prototype.throw()
4. Generator.prototype.return()
5. next()、throw()、return() 的共同点
6. yield* 表达式
7. 作为对象属性的 Generator 函数
8. Generator 函数的this
9. Generator 函数的异步应用
1. iterator的定义
[iterator]
[定义]
为各种不同的数据结构提供统一的访问机制的一种接口
Iterator 的作用:
- 是为各种数据结构,提供一个统一的、简便的遍历访问接口;
- 使得数据结构的成员能够按某种次序排列;
- ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
iterator默认结构
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的[Symbol.iterator]属性。
看例子:
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
其中value表示遍历到的值,done表示遍历是否结束。
原生具备 Iterator 接口的数据结构有7种:
- Array
- Set
- Map
- TypedArray(Int8Array(), Uint8Array()等等 )
- 函数的 arguments 对象
- NodeList
- String
调用iterator的场合(用数组作为参数的都是)
- 解构赋值
- 扩展运算符
- Array.from
- for...of
- new Map,new WeakMap, new Set,new WeakSet
- yield*
- promise.all()
- promise.race()
字符串的iterator
let str = 'wcx';
let it = str[Symbol.iterator]();
console.log(it.next().value)//w
console.log(it.next().value)//c
console.log(it.next().value)//x
遍历器对象的 return(),throw()
- 自定义的遍历器中next方法是必须的,return和throw是可选的,如果用for...of访问可遍历的对象,那么想中途中断可以通过break或者throw,同时会进入return方法。
- Genterator规格中规定return必须返回一个对象。
- throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
let obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function () {
return {
next: function () {
let arr = Object.keys(obj);
let value = obj[arr[obj.count]];
obj.count++;
return {
value: value,
done: obj.count === arr.length + 1 ? true : false
}
},
return() {
console.log('enter return')
return {
value: 123,
done: false
}
}
}
}
}
//设置个不可枚举的计数器用于遍历计数
Object.defineProperty(obj, 'count', {
value: 0,
enumerable: false,
writable: true,
configurable: true
})
//break 和 throw都会进入enter
for(let i of obj) {
if(i === 3) {
// break
throw Error(i)
}else {
console.log(i)
}
}
//改良版
let obj1 = {
a: 1,
b: 2,
c: 3,
}
Object.defineProperty(obj1, Symbol.iterator, {
value: function() {
let index = 0;
let arr = Object.keys(obj);
return {
next: function() {
let rlt = arr[index];
index ++;
return {
value: rlt,
done: index > arr.length
}
}
}
}
})
for(let i of obj1) {
console.log(i)
}
iterator的实现
iterator的实现除了上面那种方法歪还可以通过Generator实现
let obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function *() {
yield obj.a
yield obj.b
yield obj.c
}
}
for(let i of obj) {
console.log(i)
}
[generator]
简介
- 定义:
Generator 函数是 ES6 提供的一种异步编程解决方案。 - 三种理解角度:
- 封装了多个内部状态的状态机
- 能生成遍历器对象的函数(将一个生成器赋值给一个对象的[Symbol.iterator]等价于给这对象部署了一个iterator接口)
- 由关键字function和* 组成的函数
运行规则
- yield 后面紧跟着的表达式的值做为返回的遍历器对象的value,如果遇到return,那么return后面跟着的值作为遍历器对象的value,如果都没有,则value的值为undefined
- 如果没有中途中断,那么next比yield多一个的时候才会运行结束。
- next(x)的参数x作为上一个yield表达式的值,第一个next不赋值。
- 每次调用next方法会是函数运行停留在对应的yield之前(可以停在表达式中间!),再次调用next方法会使函数运行到下一个yield之前。
- yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
- yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
下面的例子从简单到复杂来说明下generator的运行规则:
规则1~3:
let a,b,c;
function *foo() {
a = yield 1;
b = yield 2;
c = yield 3;
return 123;
}
let it = foo();
//yield后面的值每次赋值给对应的value,第一个yield对第一个next,依次类推
console.log(it.next())//{valueL: 1, done: false}//第一个next不会进行赋值
console.log(it.next(10))//{valueL: 2, done: false}
console.log(it.next(20))//{valueL: 3, done: false}
console.log(it.next(30))//{valueL: 123, done: true}
console.log(a)//10
console.log(b)//20
console.log(c)//30
function *bar() {
console.log(123)
}
let itx = bar();
console.log(it.next())//{valueL: undefined, done: true}
规则 4:
let a = 1;
function *foo() {
a = a + (yield 2)
}
let it = foo();
it.next();//运行到这里时候,函数的状态是 a = a + ....,此时a为初始值1
console.log(a);//1
it.next(4);//运行到这里的时候,yield 2的值是4也就是next里的参数赋值的,这个时候a = 1 + 4 = 5
console.log(a)//5
规则5:
function foo() {
yield 123
}
let it = foo();
it.next()//报错
规则6:
let str;
function *foo() {
// str = 'hello' + yield//错误
str = 'hello' + (yield)//正确
}
let it = foo();
it.next();
console.log(str)//undefined
it.next(123)
console.log(str)//hello123
下面的例子出自《你不知道的JS》,是2个生成器交替运行的例子
let a = 1;
let b = 2;
function *foo() {
a++;
yield;
b = b * a;
a = (yield b) + 3;
}
function *bar() {
b--;
yield;
a = (yield 8) + b;
b = a * (yield 2);
}
function step(gen) {
let it = gen();
let last;
return function() {//yiled给我传什么我都给我上一个next传过去
last = it.next(last).value;
}
}
let s1 = step(foo);
let s2 = step(bar);
//操作//a的值//b的值//it.next(last).value中last的值
s2() // 1 1 undefined//line1
s2() // 1 1 8//line2
s1() // 2 1 undefined//line3
s2() // 9 1 2//line4
s1() // 9 9 9//line5
s1() // 12 9 undefiend//line6
s2() // 12 24 undefiend//line7
console.log(a, b)//line8
//s2(),这个时候b进行了b--操作,b变为了1,a还是默认值1,传入的last是初始化的值还未赋值,所以是undefined, 赋值后的last由于第一个yield后面未跟任何值所以也是undefined;所以结果是1 1 undfined
//s2(), 这个时候运行到a = (yield 8) + b 中的yield 8时暂停了,yield 8还要等待下次的next的参数, a 维持原来的值1,而此时 last通过yield 8 变为了8, b是1。所以结果是1 1 8
//s1(), 这个时候由于进行了a++ 操作,a 变为了2,b还是原来的值为1,last为未赋值的状态undefined,所以结果是 2 1 undefined
//s2(),这个时候 yield 8 的值是line2中的last也就是 8,b 是1,因此a = 8 + 1= 9, last由于 yield 2 传的值变为2,所以结果是9 1 2
//s1().这个时候 b = b * a = 9 * 1 = 9; a 保持原值为9,last的值是b也就是9,所以结果是9 9 9
//s1(), 这个时候 a = (yield b) + 3 也就是line5中的last值 + 3所以是9 + 3 = 12, 由于没有新的yield和return了所以last的值变为undefined
//s2(), 这个时候 b = (yield 2) * a 其中yield 2的值是line4中的值是2, a 是 12, 所以结果是24
//最终输出结果 12 24
//如果函数bar的最后一行变为这样呢
//b = a * (yield 2);
//解析:这个时候line4 中yield暂停的时候a还是 9,此时所有上下文中的一切都被‘冻结了’,直到下次next调用,所以b = 9 * 2 = 18
Generator.prototype.throw()
Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
错误捕获遵循如下几点:
- 由于throw相当于调用了next方法,如果throw的时候函数体已经运行到最后了,如果generator函数外部有try...catch,那么多出的错误会被外部的try...catch捕获。
- throw方法可以接受一个参数,该参数会被catch语句接收。
- 遍历器的throw和全局的throw不一样,后者只能被generator函数体外的try...cathch捕获
- 如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。
- throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
- Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。
例子:
let it = foo();
it.next()
try{
it.throw(new Error('err1'))//内部捕获
it.throw(new Error('err2'))//外部捕获
}catch(e) {
console.log('外部捕获:', e)
}
function *bar() {
yield console.log('第一次next')
yield console.log('第二次next')
}
let itx = bar();
itx.next()
itx.throw('err')//没有设置捕获直接中断运行
function *gen() {
yield;
throw('我想给外部捕获')
}
let itg = gen();
try {
itg.next()
itg.next()
}catch(e) {
console.log('外部捕获:', e)
}
Generator.prototype.return()
function *foo() {
try {
yield console.log('第一次next')
yield console.log('第二次next')
}catch(e) {
console.log('内部捕获:', e)
}
}
let it = foo();
it.next();
it.return(console.log('return'))
//第一次next
//return
next()、throw()、return() 的共同点(这个好记)
三者都相当于替换yield表达式
next(1):
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
throw('出错了')
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
return(2):
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
yield*表达式
yield*后面跟一个可遍历的对象,那么相当于for...of了以便后面的对象
function *foo() {
console.log("inside *foo(): ", yield "B")
console.log("inside *foo(): ", yield "C")
return 'D'
}
function *bar() {
console.log("inside *bar(): ", yield "A")
console.log("inside *bar(): ", yield *foo())
console.log("inside *bar(): ", yield "E")
return 'F'
}
//相当于
// function *bar() {
// console.log("inside *bar(): ", yield "A")
// console.log("inside *foo(): ", yield "B")
// console.log("inside *foo(): ", yield "C")
// console.log("inside *bar(): ", 'D')
// console.log("inside *bar(): ", yield "E")
// return 'F'
// }
let it = bar();
console.log('outside:', it.next().value)
//outside: A
console.log('outside:', it.next(1).value)
//inside *bar(): 1
//outside: B
console.log('outside:', it.next(2).value)
//inside *foo(): 2
//outside: C
console.log('outside:', it.next(3).value)
//inside *foo(): 3
//inside *bar(): D
//outside: E
console.log('outside:', it.next(4).value)
//inside *bar(): 4
//outside: F
作为对象属性的 Generator 函数
let obj = {
* myGeneratorMethod() {
yield 1
}
};
//相当于
// let obj = {
// myGeneratorMethod: function *() {
// yield 1
// }
// }
Generator 函数的this
由于Generator 函数总是返回遍历器对象 ,这里的this不适用默认绑定规则。想取得你想要的的结果,可以通过硬绑定或其他方法。
Generator 函数的异步应用
不管是基于Promise的+ Generatror还是基于Thunk + Generator,都是一个控制权的交替来实现的,我们从下面一个最小的例子实现,同步执行异步操作来作为启发:
let a = 1;
function *foo() {
yield console.log('操作开始了');
console.log('a1', a)
setTimeout(() => {
a = 666;
it.next();
},1000)
yield
console.log('a2', a)
}
let it = foo();
it.next();
it.next();
//a1 1
//a2 666 如果换成普通的函数,a 打印出来的值永远是1
网友评论