最近在刷面試題,這次學習下 迭代器,生成器與非同步迭代器(頭大)。
話不多說開始擼:
原文地址:我的部落格
Iterator 迭代器
iterator
是一個特殊的物件,它有用一個 next
方法,next
方法返回一個物件。
這個物件包含兩個屬性:
value: any
,表示成員的值done: boolean
,表示迭代器是否結束
iterator.next() // 返回 {value: '', done: false}
迭代器內部會儲存一個指標,指向迭代器的成員位置,每呼叫一次 next
方法,指標就會移動到下一個成員
直到指標指向迭代器最後一個成員後面的位置
這時,done
的值為 true
,value
的值一般為 undefined
,需要根據 iterator
的實際實現來決定。
簡單實現一個迭代器類
成員屬性介紹:
private point: number = 0
指標 初始為0private params: any[] | Object
傳入可迭代資料private keys: any[]
可迭代資料的 keyprivate length: number
可迭代資料的 key 的長度
class MyIterator {
private point: number = 0;
private params: any[] | Object;
private keys: any[];
private length: number;
constructor(params: any[] | Object) {
this.params = params;
this.keys = Object.keys(params);
this.length = this.keys.length;
}
public next(): { done: boolean; value: any; } {
const done = this.point >= this.length;
const value = done? undefined : this.params[this.keys[this.point]];
if (!done) this.point++;
return {
done,
value,
}
}
}
const iterator = new MyIterator([1,2,3]);
console.log(1, iterator.next()); // 1 { done: false, value: 1 }
console.log(2, iterator.next()); // 2 { done: false, value: 2 }
console.log(3, iterator.next()); // 3 { done: false, value: 3 }
console.log(4, iterator.next()); // 4 { done: true, value: undefined }
console.log(5, iterator.next()); // 5 { done: true, value: undefined }
複製程式碼
iterator介面
首先介紹下 for...in
與 for...of
區別
- 推薦在迴圈物件屬性的時候,使用
for...in
(會遍歷到原型鏈),在遍歷陣列的時候的時候使用for...of
(Object.getOwnPropertyNames()
只會拿到自身可列舉非可列舉的屬性) for...in
迴圈出的是key
,for...of
迴圈出的是value
for...of
是ES6新引入的特性for...of
不能迴圈沒有[Symbol.iterator]
屬性的物件,需要通過和Object.keys()
搭配使用
擁有了 iterator
介面的資料結構,也就是具有 Symbol.iterator
方法的資料結構,就可以被 for...of
遍歷。
Symbol.iterator
方法類似於上面實現的 MyIterator
。
- 陣列天生部署了迭代器介面
const array = [1, 2, 3];
typeof array[Symbol.iterator] // 'function'
for (const val of array) {
console.log(val);
}
// 1
// 2
// 3
複製程式碼
- 物件沒有迭代器介面
const obj = {a: 'a1', b: 'b1', c: 'c1'};
typeof obj[Symbol.iterator] // 'undefined'
for (const val of obj) {
console.log(val);
}
// VM974:4 Uncaught TypeError: obj is not iterable
obj[Symbol.iterator] = function() {
const keys = Object.keys(this);
const len = keys.length;
let pointer = 0;
return {
next() {
const done = pointer >= len;
const value = !done ? self[this[pointer++]] : undefined;
return {
value,
done
};
}
}
}
for (const val of obj) {
console.log(val);
}
// 'a'
// 'b'
// 'c'
複製程式碼
- 當迭代器遍的狀態已經完成時候,不會再更改狀態
Generator 生成器
Generator
是一個特殊的函式,函式體內部使用 yield
表示式,定義不同的內部狀態。
當執行 Generator
函式時,不會直接執行函式體,而是會返回一個 迭代器物件(iterator)。
Generator
函式內部可以使用yield
表示式,定義內部狀態function
關鍵字與函式名之間有一個*
function* generator() {
yield 1;
yield 2;
return 3;
}
const myIterator = generator();
// 當呼叫iterator的next方法時,函式體開始執行,
console.log(myIterator.next()); // {value: 1, done: false}
console.log(myIterator.next()); // {value: 2, done: false}
console.log(myIterator.next()); // {value: 3, done: true}
for (const val of myIterator) {
console.log(val); // 不會觸發
}
複製程式碼
當 for...of
之後,迭代器的狀態會關閉。
實現箇中序遍歷(網上看到的,很屌)
function* traverseTree(node) {
if (node == null) return;
yield* traverseTree(node.left);
yield node.value;
yield* traverseTree(node.right);
}
複製程式碼
gennerator巢狀
生成器函式中使用生成器函式 需要使用 *
當我們想在 generator b
中巢狀 generator a
時,怎麼巢狀呢?
yield *a(); ==> yield 1
,實際上就是把 yield 1
放在這個位置
所以在生成器函式中使用生成器函式 需要使用 *
function* a(){
yield 1;
}
function* b(){
yield* a();
yield 2;
}
let it = b();
console.log(it.next()); // { value: 1, done: false }
複製程式碼
實現非同步迭代
// ajax 是一個返回 Promise 的函式
function ajaxName() {
return Promise.resolve('測試');
}
function ajaxSex() {
return Promise.resolve('男');
}
function ajaxSchool() {
return Promise.resolve('遼寧大學');
}
function * fetchInfo () {
const name = yield ajaxName();
const sex = yield ajaxSex();
const school = yield ajaxSchool();
}
const info = fetchInfo();
// 加入的控制程式碼
info.next().value.then(name => {
console.log(name); // 測試
return info.next().value;
}).then(sex => {
console.log(sex); // 男
return info.next().value;
}).then(school => {
console.log(school); // 遼寧大學
});
複製程式碼
async/await 非同步迭代器
async/await
其實相當於簡化了Generator
更改下上面的非同步迭代器:
// 相同
async function fetchInfo () {
const name = await ajaxName();
console.log(name); // 測試
const sex = await ajaxSex();
console.log(sex); // 男
const school = await ajaxSchool();
console.log(school); // 遼寧大學
}
複製程式碼
-
await
只能用在async
關鍵詞的函式中 -
async
函式返回一個Promise
-
async/await
相當於封裝了Promise
await 到底在等啥?
還是這個例子:
async function fetchInfo () {
const name = await ajaxName();
console.log(name); // 測試
const sex = await ajaxSex();
console.log(sex); // 男
const school = await ajaxSchool();
console.log(school); // 遼寧大學
}
複製程式碼
await
表示式會暫停當前async function
的執行,等待Promise
處理完成。- 若
Promise
正常處理(fulfilled),其**建構函式第一個引數resolve
函式的引數(resolve(value)
)**作為await
表示式的值,繼續執行async function
。 - 若
Promise
處理異常(rejected),await
表示式會把Promise
的異常原因丟擲。 - 另外,如果
await
操作符後的表示式的值不是一個Promise
,則返回該值本身。a = await 10
a就是10
上面的非同步函式相當於:
function fetchInfo() {
let name, sex, school;
return ajaxName().then(name => {
name = name;
console.log(name); // 測試
return ajaxSex();
}).then(sex => {
sex = sex;
console.log(sex); // 男
return ajaxSchool();
}).then(school => {
school = school;
console.log(school); // 遼寧大學
});
}
複製程式碼
async/await 併發
我們的程式碼在執行到 await
的時候會等待結果返回才執行下一行,這樣如果我們有很多需要非同步執行的操作就會變成一個序列的流程,可能會導致非常慢。
比如如下程式碼,我們需要遍歷獲取 redis
中儲存的100個使用者的資訊:
const ids = [1,2,3,4];
const users=[];
for (let i=0;i<ids.length;i++) {
users.push(await db.get(ids));
}
複製程式碼
由於每次資料庫讀取操作都要消耗時間,這個介面將會變得非常慢。
如果我們把它變成一個並行的操作,將會極大提升效率
const ids = [1,2,3,4];
const users=[];
const p = ids.map(async (id) => await db.get(id)); // any[]
const users = await Promise.all(p); // any[]
複製程式碼
總結
Iterator
是一個可迭代介面,任何實現了此介面的資料結構都可以被for...of
迴圈遍歷Generator
是一個可以暫停和繼續執行的函式體,可以完全實現Iterator
的功能,並且由於可以儲存上下文,非常適合實現簡單的狀態機。另外通過一些流程控制程式碼的配合,可以比較容易進行非同步操作。Async/Await
就是Generator
進行非同步操作並封裝了Promise
的語法糖。返回值為Promise
的函式。