迭代器
迭代器(iterator):一个具有next方法的对象,且next方法也会返回一个对象,该返回的对象能够反映下一个数据以及指示是否迭代完成
迭代器创建函数(iterator creator):一个返回迭代器的函数
const arr = [1, 2, 3, 4, 5, 6];
function createIterator(arr) {
let i = 0;
return {
next() {
const res = {
value: arr[i],
done: i >= arr.length
}
i++;
return res;
}
}
}
const iterator = createIterator(arr);
let data = iterator.next();
while (!data.done) {
console.log(data.value);
data = iterator.next();
}
注意:迭代器不是只能迭代数组
可迭代协议和for of循环
ES6规定,如果一个对象具有知名符号属性Symbol.iterator(对象的原型链上有也算),并且该属性的值是一个迭代器创建函数,则该对象就是可迭代的(iterable)(即该对象就是可迭代对象)
数组就是一个可迭代对象:
const arr = [1, 2, 3, 4, 5];
const iterator = arr[Symbol.iterator]();
let data = iterator.next();
while (!data.done) {
console.log(data.value);
data = iterator.next();
}
很多类数组也是可迭代对象,比如:
const divs = document.querySelectorAll("div");
const iterator = divs[Symbol.iterator]();
let data = iterator.next();
while (!data.done) {
console.log(data.value);
data = iterator.next();
}
也可以自己创建可迭代对象
const obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator](){
const keys = Object.keys(this);
const that = this;
let i = 0;
return {
next(){
const propName = keys[i];
const propValue = that[propName];
const res = {
value: {
propName,
propValue
},
done: i >= keys.length
}
i++;
return res;
}
}
}
};
const iterator = obj[Symbol.iterator]();
let data = iterator.next();
while (!data.done) {
console.log(data.value);
data = iterator.next();
}
for of循环
for of循环专门用于遍历可迭代对象,格式如下:
for(const item of obj){
console.log(item);
}
其实for of循环就是下面写法的语法糖
const iterator = obj[Symbol.iterator]();
let data = iterator.next();
while (!data.done) {
const item = data.value;
console.log(item);
data = iterator.next();
}
注意:for of循环只能遍历可迭代对象,若提供的不是可迭代对象,则会报错
const obj = {
0: "a",
1: "b",
2: "c",
length: 3
};
for(const item of obj){ // TypeError: obj is not iterable
console.log(item);
}
展开运算符与可迭代对象
当展开运算符使用在对象身上,但展开后却不是成为对象的一部分,会自动使用迭代模式对对象进行展开
比如对象展开为数组
const obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator](){
const keys = Object.keys(this);
const that = this;
let i = 0;
return {
next(){
const propName = keys[i];
const propValue = that[propName];
const res = {
value: {
propName,
propValue
},
done: i >= keys.length
}
i++;
return res;
}
}
}
};
const arr = [...obj];
console.log(arr);
/**
[
{propName: 'a', propValue: 1},
{propName: 'b', propValue: 2},
{propName: 'c', propValue: 3}
]
*/
又或者对象展开成为函数的参数
const obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator](){
const keys = Object.keys(this);
const that = this;
let i = 0;
return {
next(){
const propName = keys[i];
const propValue = that[propName];
const res = {
value: {
propName,
propValue
},
done: i >= keys.length
}
i++;
return res;
}
}
}
};
function test(a, b, c){
console.log(a);
console.log(b);
console.log(c);
}
test(...obj);
/**
{propName: 'a', propValue: 1}
{propName: 'b', propValue: 2}
{propName: 'c', propValue: 3}
*/
注意:
-
如果上面两种情况,所展开的对象是不可迭代的,则只能对它们展开成为另一个对象
const obj1 = { a: 1, b: 2 }; const obj2 = { ...obj1 }; console.log(obj2); // { a: 1, b: 2 } const arr = [...obj1]; // TypeError: obj1 is not iterable -
如果将可迭代对象展开为另一个对象,可迭代对象的符号属性也会参与“展开”
const obj1 = { a: 1, b: 2, c: 3, [Symbol(1)]: 4 }; const obj2 = { ...obj1 }; console.log(obj2); /** obj2 = { a: 1, b: 2, c: 3, [Symbol(1)]: 4 } */ -
展开运算符只能使用在引用值身上,如果将展开运算符应用在原始值身上,则原始值将先会被包装成为引用值,然后变为对该引用值的展开
只要原始值被当做引用值使用,就JS就会为原始值临时生成一个引用值来代替原始值接受操作(包装类)
解构可迭代对象
-
解构运算符只能使用在引用值身上,如果将解构运算符应用在原始值身上,则原始值将先会被包装成为引用值,然后变为对该引用值的解构
const str = "123"; const [ a, b ] = str; console.log(a, b); // "1" "2" -
当使用数组解构方式解构可迭代的非数组对象时,会使用迭代模式对对象进行解构
const obj = { a: 1, b: 2, [Symbol.iterator](){ const keys = Object.keys(this); let index = 0; const that = this; return { next(){ const res = { value: undefined, done : index >= keys.length }; if(index < keys.length){ res.value = { key: keys[index], value: that[keys[index]] }; index++; } return res; } } } } console.log(obj[0]); // undefined const [ ele1, ele2 ] = obj; console.log(ele1, ele2); // { key: "a", value: 1 } { key: "b", value: 2 }
生成器
生成器是一个通过构造函数Generator创建的对象,但该构造函数只能在JS内部使用,我们无法使用该构造函数
生成器既是一个迭代器,又是一个可迭代对象
生成器的创建,必须依托于生成器函数(Generator Function)
注意:
- 生成器函数不是指Generator构造函数
- 调用生成器函数时,函数内部会自动调用构造函数Generator创建生成器,并将生成器返回
生成器函数
书写函数时,在function关键字后面或在函数名前面加上*,则该函数就是生成器函数
生成器函数返回的一定是生成器(就像async修饰的函数返回的一定是Promise一样)
function *method1(){}
const method2 = function*(){}
const obj = {
*method3(){}
}
生成器的函数内部就是给该函数返回的生成器提供迭代的数据的
生成器函数内部可以使用yield关键字,该关键字之后的数据,将会作为之后迭代所得到对象的value属性
function *method(){
yield 1;
}
const g = method();
console.log(g.next()); // { value: 1, done: false }
每执行到一个yield表达式,生成器函数的函数体就会”暂停执行“,直到下一次调用生成器的next方法
function *method(){
console.log("a");
yield 1;
console.log("b");
yield 2;
console.log("c");
}
const g = method();
console.log(g.next());
// "a"
// { value: 1, done: false }
console.log(g.next());
// "b"
// { value: 2, done: false }
console.log(g.next());
// "c"
// { value: undefined, done: true }
若生成器对应的生成器函数执行完,就意味着结束了,不会反过头来重新执行
function *method(){
yield 1;
}
const g = method();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: undefined, done: true }
console.log(g.next()); // { value: undefined, done: true }
console.log(g.next()); // { value: undefined, done: true }
... // { value: undefined, done: true }...
注意:
- 一个函数,它不允许同时被async和
*修饰 - 单单调用生成器函数,只会得到一个生成器结果,并不会实际执行函数的函数体,要想执行生成器函数的函数体,需要调用返回的生成器的next方法
- 生成器本身就是迭代器,因此生成器函数就是迭代器创建函数
细节
-
生成器函数的返回值,会出现在第一次done为true时的value属性中
function *method(){ yield 1; return 10; } const g = method(); console.log(g.next()); // { value: 1, done: false } console.log(g.next()); // { value: 10, done: true } console.log(g.next()); // { value: undefined, done: true } console.log(g.next()); // { value: undefined, done: true } -
调用生成器的next方法时,可以传递参数,传递的参数会成为上一次调用next时的yield表达式的返回值
function* method() { let info = yield 1; // 运行到yield表达式时就卡住了,此时赋值表达式还没有执行 console.log(info); info = yield 2; console.log(info); } const g = method(); g.next(); // 第一次调用生成器的next方法时传递参数无任何效果 g.next(10); // 控制台输出【10】,即yield 1表达式的返回值为10 g.next(20); // 控制台输出【20】,即yield 2表达式的返回值为10不传递实参,相当于对应的形参为undefined,因此默认情况下,yield表达式的返回值为undefined
function* method() { let info = yield 1; console.log(info); info = yield 2; console.log(info); } const g = method(); g.next(); // 第一次调用生成器的next方法时传递参数无任何效果 g.next(); // 控制台输出【undefined】 g.next(); // 控制台输出【undefined】 -
在生成器函数内部,可以调用其他生成器函数,但调用时要加上
yield *调用其他生成器函数时,只会得到其他生成器函数的next方法返回的done为false的对象
function *test1(){ yield "a"; yield "b"; return "c"; } function *test2(){ yield 1; yield *test1(); yield 2; return 3; } const g = test2(); console.log(g.next()); // { value: 1, done: false } console.log(g.next()); // { value: "a", done: false } console.log(g.next()); // { value: "b", done: false } console.log(g.next()); // { value: 2, done: false } console.log(g.next()); // { value: 3, done: true }
生成器的其他API
-
return方法
调用该方法,可以提前结束生成器函数
function* method(){ yield 1; yield 2; yield 3; return 4; } const g = method(); console.log(g.next()); // { value: 1, done: false } console.log(g.return()); // { value: undefined, done: true } console.log(g.next()); // { value: undefined, done: true } console.log(g.next()); // { value: undefined, done: true }注意:return()是直接让函数结束,并不是让函数执行return语句
-
throw方法
调用该方法,可以在生成器函数中产生一个错误
异步任务控制
Promise刚发布时,async和await还没有出现,所以当时人们使用生成器函数来模拟如今的async和await的功能
而async和await也是基于此才诞生出来的
function* task() {
const resp = yield fetch(...);
const body = yield resp.json();
console.log(body);
}
function run(generatorFunc) {
const generator = generatorFunc();
let result = generator.next();
handleResult();
function handleResult() {
if (result.done) {
return;
}
if (isPromise(result.value)) {
result.value.then((data) => {
result = generator.next(data);
handleResult();
});
} else {
result = generator.next(result.value);
handleResult();
}
}
}
run(task);
扩展
babel对async和await的转换:
原始内容:
function A(val){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(val);
}, 1000);
});
}
async function B(){
var b = await A(3);
var c = await A(4);
return b + c;
}
B().then((res)=>{
console.log(res);
});
转换后的结果(简化版):
function A(val){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(val);
}, 1000);
});
}
function B() {
function* _B() {
var b = yield A(3);
var c = yield A(4);
return b + c;
}
function asyncToGenerator(gen, resolve, reject, num) {
try {
var info = gen.next(num);
var val = info.value;
} catch (err) {
reject(err);
return;
}
if (info.done) {
resolve(val);
return;
} else {
Promise.resolve(val).then((data) => {
asyncToGenerator(gen, resolve, reject, data);
});
}
}
var gen = _B();
return new Promise((resolve, reject) => {
asyncToGenerator(gen, resolve, reject, undefined);
});
}
B().then((res) => {
console.log(res);
});