目录
迭代器(Iterator
)
定义
迭代器是 JavaScript
中一种用于遍历数据集合的机制,提供了一种标准的接口,使得可以按需逐个访问集合中的元素,而不需要知道集合的内部结构
要求
在JavaScript
中,迭代器是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol
),迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式
在JavaScript
中这个标准就是一个特定的next
方法,next
方法有如下的要求:
-
next
是一个无参数或者一个参数的函数,返回一个拥有以下两个属性的对象: -
done(boolean)
:- 如果迭代器可以产生序列中的下一个值,则为
false
(这等价于没有指定done
这个属性) - 如果迭代器已将序列迭代完毕,则为
true
,这种情况下value
是可选的,有value
则为迭代结束之后的默认返回值
- 如果迭代器可以产生序列中的下一个值,则为
-
value
:- 迭代器返回的任何
JavaScript
值,done
为true
时可省略
// 简单的迭代器 const iterator = { next() { return { done: false, value: "abc", }; }, }; console.log(iterator.next()); // {done: false, value: 'abc'} // 练习数组的迭代器 后面会用迭代器替换 const names = ["abc", "def", "ghi"]; let index = 0; const namesIterator = { next() { if (index < names.length) { return { done: false, value: names[index++], // i++:先返回当前值再自增,++i:先自增,再返回新值 }; } else { return { done: true, }; } }, }; console.log(namesIterator.next()); // {done: false, value: 'abc'} console.log(namesIterator.next()); // {done: false, value: 'def'} console.log(namesIterator.next()); // {done: false, value: 'ghi'} console.log(namesIterator.next()); // {done: true} // 函数封装 后面会用迭代器替换 function createArrIterator(arr) { let index = 0; // 返回迭代器对象 return { next() { if (index < arr.length) { return { done: false, vlaue: arr[index++], }; } else { return { done: true }; } }, }; } const fnNamesIterator = createArrIterator(names); console.log(fnNamesIterator.next()); // {done: false, value: 'abc'} console.log(fnNamesIterator.next()); // {done: false, value: 'def'} console.log(fnNamesIterator.next()); // {done: false, value: 'ghi'} console.log(fnNamesIterator.next()); // {done: true}
- 迭代器返回的任何
可迭代对象
什么是可迭代对象?它和迭代器是不同的概念:
-
当一个对象实现了
iterable protocol
协议时,它就是一个可迭代对象 -
这个对象的要求是必须实现
@@iterator
方法,在代码中我们使用Symbol.iterator
访问该属性
我们转成可迭代对象有什么好处?
-
当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作
-
比如
for...of
操作时,其实就会调用它的@@iterator
方法
// 普通对象为不可迭代对象
// const obj = {
// name: "obj1",
// age: 18,
// actions: ["run", "move", "jump"],
// };
// for (const item of obj) { // 报错:Uncaught TypeError: obj is not iterable
// console.log(item);
// }
// 可迭代对象
const obj1 = {
name: "obj1",
age: 18,
actions: ["run", "move", "jump"],
[Symbol.iterator]: function () {
let index = 0;
// 返回生成器
return {
next() {
if (index < obj1.actions.length) {
return {
done: false,
value: obj1.actions[index++],
};
} else {
return { done: true };
}
return;
},
};
},
};
// 必须写这一行,这样就不会每次创建新的迭代器,创建新的迭代器index永远是0
const obj1Iterator = obj1[Symbol.iterator]();
console.log(obj1Iterator.next()); // {done: false, value: 'run'}
console.log(obj1Iterator.next()); // {done: false, value: 'move'}
console.log(obj1Iterator.next()); // {done: false, value: 'jump'}
console.log(obj1Iterator.next()); // {done: true}
for (const item of obj1) {
// 调用obj1里面实现的[Symbol.iterator]方法
console.log(item); // 打印三次,分别是run move jump
}
// 可迭代对象优化,使用this和箭头函数
const obj2 = {
name: "obj2",
age: 18,
actions: ["run", "move", "jump"],
[Symbol.iterator]: function () {
// 执行时obj2[Symbol.iterator](),this时obj2
let index = 0;
// 返回生成器
return {
next: () => {
if (index < this.actions.length) {
return {
done: false,
value: this.actions[index++], // 执行时obj2Iterator.next(),使用了箭头函数这里的this取上层作用域this
};
} else {
return { done: true };
}
},
};
},
};
const obj2Iterator = obj2[Symbol.iterator]();
console.log(obj2Iterator.next()); // {done: false, value: 'run'}
console.log(obj2Iterator.next()); // {done: false, value: 'move'}
console.log(obj2Iterator.next()); // {done: false, value: 'jump'}
console.log(obj2Iterator.next()); // {done: true}
for (const item of obj2) {
console.log(item); // 打印三次,分别是run move jump
}
// 迭代对象属性
const obj3 = {
name: "obj3",
age: 18,
actions: ["run", "move", "jump"],
[Symbol.iterator]: function () {
let index = 0;
// const arr = Object.keys(this); // 迭代obj3的key, 打印三次,分别是name age actions
// const arr = Object.values(this); // 迭代obj3的value,打印三次,分别是"obj3" 18 ["run", "move", "jump"]
const arr = Object.entries(this); // 迭代obj3的item
return {
next: () => {
if (index < arr.length) {
return {
done: false,
value: arr[index++],
};
} else {
return { done: true };
}
},
};
},
};
for (const item of obj3) {
// 调用obj3里面实现的[Symbol.iterator]方法
console.log(item); // 打印三次,分别是['name', 'obj3'] ['age', 18] ['actions', Array(3)]
}
可迭代对象应用
那么可迭代对象可以被用在哪里呢?
JavaScript
中语法:for ...of
、展开语法、yield*
、解构赋值- 创建一些对象时:
new Map([Iterable])
、new WeakMap([iterable])
、new Set([iterable])
、new WeakSet([iterable])
- 一些方法的调用:
Promise.all(iterable)
、Promise.race(iterable)
、Array.from(iterable)
原生迭代器对象
事实上平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象:
String
、Array
、Map
、Set
、arguments
对象、NodeList
集合
const actions = ["move", "jump"];
console.log(actions[Symbol.iterator]); // 是个函数
const iterable = actions[Symbol.iterator]();
console.log(iterable.next()); // {value: 'move', done: false}
console.log(iterable.next()); // {value: 'jump', done: false}
console.log(iterable.next()); // {value: undefined, done: true}
自定义类的迭代
我们可以通过class
定义一个自己的类,这个类可以创建很多的对象,如果也希望自己的类创建出来的对象是可迭代的,需要在定义类的时候就可以添加上 @@iterator
方法
// 自定义类的迭代
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
[Symbol.iterator]() {
let index = 0;
const arr = Object.keys(this);
return {
next() {
if (index < arr.length) {
return {
done: false,
value: arr[index++],
};
} else {
return {
done: true,
};
}
},
};
}
}
const p1 = new Person("p1", 18);
for (const item of p1) {
console.log(item); // 执行两遍 打印:name age
}
迭代器的中断
迭代器在某些情况下会在未完全迭代完的情况下被中断
-
比如遍历的过程中通过
break、return、throw
中断了循环操作const obj1 = { name: "obj1", age: 18, [Symbol.iterator]: function () { let index = 0; const arr = Object.entries(this); return { next: () => { if (index < arr.length) { return { done: false, value: arr[index++], }; } else { return { done: true }; } }, }; }, }; for (const item of obj) { console.log(item); // 打印一次:['name', 'obj'] break; // 循环中断 }
-
比如在解构的时候,没有解构所有的值
- 在解构时,迭代的终止并不是由解构操作本身引发的
- 解构赋值只是自动调用对象的迭代器取出一个新的值,直到解构出所需的元素为止,它并不会主动终止迭代器
- 终止迭代是因为当解构赋值达到所需的值时,解构过程完成,不再继续请求后续的迭代值
const obj = { [Symbol.iterator]: function () { let index = 0; const values = [10, 20, 30, 40]; return { next() { if (index < values.length) { return { done: false, value: values[index++] }; } else { return { done: true }; } }, }; }, }; // 只解构出前两个值,next会执行两次 const [x, y] = obj; console.log(x); // 10 console.log(y); // 20
生成器(Generator
)
定义
生成器是ES6
中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
要求
平时会编写很多函数,这些函数终止的条件通常是return
或者发生了异常
生成器函数也是一个函数,但是和普通的函数有一些区别:
-
首先,生成器函数需要在
function
的后面加一个符号:*
-
其次,生成器函数可以通过
yield
关键字来控制函数的执行流程 -
最后,生成器函数的返回值是一个
Generator
(生成器)
生成器事实上是一种特殊的迭代器
function* generatorFn() {
console.log(1111);
console.log(2222);
yield;
console.log(3333);
console.log(4444);
yield;
console.log(5555);
console.log(6666);
}
执行
- 调用生成器函数不会立即执行它的代码,而是返回一个迭代器对象
- 通过调用迭代器的
next()
方法来逐步执行生成器函数的代码,yield
语句会暂停执行并返回值 - 当所有的
yield
语句都被执行后,生成器函数执行完毕,返回的done
属性为true
- 不希望
next
返回的是一个undefined
,这时可以通过yield
来返回结果
function* generatorFn() {
console.log(1111); // 执行第一个next()是打印
console.log(2222); // 执行第一个next()是打印
yield 1122;
console.log(3333); // 执行第二个next()是打印
console.log(4444); // 执行第二个next()是打印
yield;
console.log(5555); // 执行第三个next()是打印
console.log(6666); // 执行第三个next()是打印
}
const generator = generatorFn();
console.log(generator); // generatorFn
console.log(generator.next()); // {value: 1122, done: false}
console.log(generator.next()); // {value: undefined, done: false}
console.log(generator.next()); // value: undefined, done: true}
console.log(generator.next()); // {value: undefined, done: true}
传参
在调用next
函数的时候可以给它传递参数,这个参数会作为上一个yield
语句的返回值
- 当第一次调用
next()
时,由于生成器还没有开始执行传入的参数会被忽略,因此在生成器函数做参数接收 - 从第二次
next()
开始,传入的参数会作为yield
表达式的返回值使用
function* generatorFn(num1) {
console.log(num1); // 第一个参数通常不在next中传,而是函数接收
console.log(1111); // 执行第一个next()是打印
console.log(2222); // 执行第一个next()是打印
const name = yield 1122;
console.log(name); // next2
console.log(3333); // 执行第二个next()是打印
console.log(4444); // 执行第二个next()是打印
yield;
console.log(5555); // 执行第三个next()是打印
console.log(6666); // 执行第三个next()是打印
}
const generator = generatorFn(12);
console.log(generator); // generatorFn
console.log(generator.next()); // {value: 1122, done: false}
console.log(generator.next("next2")); // {value: undefined, done: false}
console.log(generator.next()); // value: undefined, done: true}
console.log(generator.next()); // {value: undefined, done: true}
提前结束
生成器可以在执行过程中通过 return
语句提前结束,使用 return
可以停止生成器的迭代,并且可以将一个值作为生成器的结束值返回,这个返回值可以通过 throw
来捕获
function* generatorFn() {
console.log(1111); // 执行第一个next()是打印
console.log(2222); // 执行第一个next()是打印
yield 1122;
console.log(3333); // 执行第二个next()是打印
console.log(4444); // 执行第二个next()是打印
yield;
console.log(5555); // 执行第三个next()是打印
console.log(6666); // 执行第三个next()是打印
}
const generator = generatorFn(12);
console.log(generator); // generatorFn
console.log(generator.next()); // {value: 1122, done: false}
console.log(generator.return("next2")); // {value: undefined, done: true}
console.log(generator.next()); // value: undefined, done: true}
console.log(generator.next()); // {value: undefined, done: true}
抛出异常
在 JavaScript
中,生成器函数可以使用 throw
语句抛出异常,并在生成器内部捕获这些异常。抛出异常可以使你在生成器执行过程中处理错误情况,类似于传统的 try...catch
机制
function* myGenerator() {
try {
yield "Step 1";
yield "Step 2";
} catch (error) {
console.log(error.message); // Custom error
yield "Step 3 after error"; // 在 catch 块中继续 yield
} finally {
console.log("Finally block executed.");
}
}
const gen = myGenerator();
console.log(gen.next()); // { value: 'Step 1', done: false }
console.log(gen.next()); // { value: 'Step 2', done: false }
console.log(gen.throw(new Error("Custom error"))); // {value: 'Step 3 after error', done: false}
// Finally block executed.
console.log(gen.next()); // { value: 'Step 3 after error', done: false }
console.log(gen.next()); // { value: undefined, done: true }
替换迭代器
使用 yield*
来处理一个可迭代对象,相当于是yield
的语法糖,会依次迭代这个可迭代对象,每次迭代其中的一个值
// 生成器替代迭代器
const names = ["abc", "def", "ghi"];
function* createArrIterator(arr) {
// for (let i = 0; i < arr.length; i++) {
// yield arr[i];
// }
// 使用语法糖
yield* arr;
}
const fnNamesIterator = createArrIterator(names);
console.log(fnNamesIterator.next()); // {done: false, value: 'abc'}
console.log(fnNamesIterator.next()); // {done: false, value: 'def'}
console.log(fnNamesIterator.next()); // {done: false, value: 'ghi'}
console.log(fnNamesIterator.next()); // {value: undefined, done: true}
生成器实现自定义类迭代
使用 *[Symbol.iterator]
在类中写迭代器
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
*[Symbol.iterator]() {
yield* Object.keys(this);
}
}
const p1 = new Person("p1", 18);
for (const item of p1) {
console.log(item); // 执行两遍 打印:name age
}
console.log(p1[Symbol.iterator]); // 生成器函数
生成器和其它异步处理方案
/* 模拟请求 */
// 方式一
function requestData1(url, callback) {
setTimeout(() => {
console.log(url);
callback && callback(url);
}, 2000);
}
// 方式二
function requestData2(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(url);
resolve(url);
}, 2000);
});
}
/* 模拟调用场景
第一次调用拿到数据
第二次的请求url依赖于第一次的结果
第三次的请求url依赖于第二次的结果
依次类推
*/
// 方式一:普通方法
function getData1() {
requestData1("https://hello", (res1) => {
requestData1(`${res1}/china`, (res2) => {
requestData1(`${res2}/global`, (res3) => {
requestData1(`${res3}/world`, (res4) => {
// 依次递推就是回调地狱
});
});
});
});
}
// 方式二:Promise链式调用
function getData2() {
requestData2("https://hello")
.then((res1) => {
requestData2(`${res1}/china`).then((res2) => {
requestData2(`${res2}/global`).then((res3) => {
requestData2(`${res3}/world`).then((res4) => {
// 依次递推依旧回调地狱
});
});
});
})
.catch((err) => {});
}
// 方式三:Promise优化
function getData3() {
requestData2("https://hello")
.then((res1) => {
return requestData2(`${res1}/china`); // return Promise,状态由这个Promise决定,可以再调用then
})
.then((res2) => {
return requestData2(`${res2}/global`);
})
.then((res3) => {
return requestData2(`${res3}/world`);
})
.then((res4) => {
// 依次类推解决了回调地狱
})
.catch((err) => {});
}
// 方式四:生成器Promise方案
function* getData4() {
const res1 = yield requestData2("https://hello");
const res2 = yield requestData2(`${res1}/china`); // res1是next传来的参数也就是第一次Promise请求完得到的值
const res3 = yield requestData2(`${res2}/global`);
const res4 = yield requestData2(`${res3}/world`);
}
// const gen = getData4();
// console.log(gen.next()); // {value: Promise, done: false}
// gen.next().value.then((res1) => {
// gen.next(res1).value.then((res2) => {
// gen.next(res2).value.then((res3) => {
// gen.next(res3).value.then((res4) => {
// // 依次类推也有回调地狱问题,下面优化
// });
// });
// });
// });
// autoExecute(getData4);
// 方式五:生成器优化,自动执行生成器
function autoExecute(genFn) {
const gen = genFn();
function exec(res) {
const result = gen.next(res);
if (result.done) return result.value;
result.value.then((res) => {
exec(res);
});
}
exec();
}
// 方式六:使用async/await和Promise
async function getData6() {
const res1 = await requestData2("https://hello");
const res2 = await requestData2(`${res1}/china`);
const res3 = await requestData2(`${res2}/global`);
const res4 = await requestData2(`${res3}/world`);
}
getData1();
getData2();
getData3();
getData6();