generator是一種特殊的iterator,generator可以替代iterator實現,使代碼更為簡潔
什麼是iterator
iterator叫做迭代器,是用來幫助某個數據結構進行遍歷的對象,這個對象需要符合迭代器協議(iterator protocol)。
迭代器協議要求實現next方法,next方法有如下要求
- 0或者1個函數入參
- 返回值需要包括兩個屬性,done 和 value。
當遍歷完成時, done 為 true,value 值為 undefined。
迭代器實現原理
- 創建一個指針對象,指向當前數據結構的起始位置
- 第一次調用對象的next方法,指針自動指向數據結構的第一個成員
- 接下來不斷調用next方法,指針一直往後移動,直到指向最後一個成員
- 每調用next方法返回一個包含value 和 done 屬性的對象
以下對象就實現了迭代器
const names = ["kiki", "alice", "macus"];
let index = 0;
const namesIterator = {
next: () => {
if (index == names.length) {
return { value: undefined, done: true };
} else {
return { value: names[index++], done: false };
}
},
};
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
當第四次調用next方法時,此時數據已經迭代完成,所以迭代器返回 done 為 true
可迭代對象
可迭代對象與迭代器對象不同。
- 迭代器對象需要符合迭代器協議(iterator protocol),並且返回next方法。
- 可迭代對象需要實現iterable protocol協議,即 @@iterator 方法,在代碼中通過 Symbol.iterator 實現,這樣當數據進行for...of遍歷時,就用調用@@iterator方法。
我們知道,對象這種數據類型是不可以通過for...of對其遍歷的
但如果我們對它實現了@@iterator方法之後,它就變成了可迭代對象
const obj = {
name: "alice",
age: 20,
hobby: "singing",
[Symbol.iterator]: function () {
let index = 0;
const keys = Object.keys(this);
return {
next: () => {
if (index == keys.length) {
return { value: undefined, done: true };
} else {
const key = keys[index];
index++;
return { value: this[key], done: false };
}
},
};
},
};
for (let item of obj) {
console.log(item);
}
const iterator = obj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
可迭代對象的實現中包括了迭代器對象。
原生可迭代對象
簡單來説,可以通過for...of遍歷的就是可迭代對象,原生可迭代對象包括:數組、字符串、arguments、set、map
const arr = ["kiki", "alice", "macus"];
for (let item of arr) {
console.log("數組", item);
}
const str = "hello";
for (let s of str) {
console.log("字符串", s);
}
function foo() {
for (let arg of arguments) {
console.log("arguments", arg);
}
}
foo(1, 2, 3, 4);
const mapEntries = [
["name", "alice"],
["age", "20"],
];
const map = new Map(mapEntries);
for (let m of map) {
console.log("map", m);
}
const set = new Set(arr);
for (let s of set) {
console.log("set", s);
}
以上都是原生可迭代對象
可迭代對象的用途
可迭代對象有以下用途
- for...of遍歷
- 展開語法
- 解構賦值
- 創建其他類型的對象,如array和set
- Promise.all也可以執行可迭代對象
對象可以使用展開語法和解構賦值,但它並不是可迭代對象,而是es9中單獨實現的屬性
const iteratorObj = {
names: ["kiki", "alice", "macus"],
[Symbol.iterator]: function () {
let index = 0;
return {
next: () => {
if (index == this.names.length) {
return { value: undefined, done: true };
} else {
return { value: this.names[index++], done: false };
}
},
};
},
};
for (let item of iteratorObj) {
console.log('for..of遍歷可迭代對象:',item);
}
const newArr = [...iteratorObj];
console.log('展開語法:',newArr);
const [name1, name2, name3] = iteratorObj;
console.log('解構賦值:',name1, name2, name3);
const set = new Set(iteratorObj);
const arr = Array.from(iteratorObj);
console.log('set:',set);
console.log('array:',arr)
Promise.all(iteratorObj).then((value) => {
console.log("promise.all:", value);
});
以上方法都是獲取next方法中的value值
自定義類的迭代
想要類變成可迭代對象,在類方法中添加 Symbol.iterator 方法並實現就可以了
class Student {
constructor(name, age, hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
push(hobby) {
this.hobbies.push(hobby);
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index === this.hobbies.length) {
return { done: true, value: undefined }
} else {
return { value: this.hobbies[index++], done: false };
}
},
};
}
}
const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
console.log(item)
}
此時可以通過for..of方法遍歷類中的hobbies屬性
什麼是generator
generator叫做生成器,可以用來控制函數什麼時候執行和暫停。
生成器函數也是函數,但是和普通函數之間存在如下區別
- 生成器函數之間需要添加一個*
- 執行需要使用一個變量來接收, 每使用一次 next()方法執行一段代碼
- 通過 yield 關鍵字暫停函數,yield既可以傳參, 又有返回值
- 返回值是一個生成器(generator),生成器是一種特殊的迭代器
以上代碼實現了生成器函數
function* foo(){
console.log('開始執行')
yield
console.log('world')
yield
console.log('結束執行')
}
const generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
調用next方法時,返回值與迭代器一致,為包含 value 和 done 的對象,此時value為undefine,因為yield後沒有加上返回值
yield傳參和返回值
通過next方法可以將參數傳遞到生成器函數中,通過yield可以返回數據
function* foo(value1){
console.log('開始執行')
const result1 = yield value1
const result2 = yield result1
const result3 = yield result2
console.log('結束執行')
}
const generator = foo('hello')
console.log(generator.next('世界'))
console.log(generator.next('merry'))
console.log(generator.next('christmas'))
console.log(generator.next('done'))
可以看到第一個next獲取的value值是通過生成器函數傳遞的,而不是第一個next方法執行時的參數,所以value值為"hello"而不是"世界"
generator的其他方法
- throw方法用於拋出異常(需要在生成器函數中捕獲)
- return方法用於中斷生成器函數的執行
function* createGenerator() {
console.log("開始執行");
try {
yield "hello";
console.log("hello");
} catch (error) {
yield error;
}
yield "world";
console.log("結束執行");
}
const generator = createGenerator();
console.log(generator.next());
console.log(generator.throw("throw"));
console.log(generator.return("return"));
使用return方法後,done變為true,value就變成了return函數中傳遞的值
生成器替代迭代器
生成器是一種特殊的迭代器,通過生成器可以在某些場景做一些替換,使代碼更為簡潔
// 迭代器實現next方法
function createArrayIterator(arr) {
let index = 0;
return {
next: function () {
if (index == arr.length) {
return { value: undefined, done: true };
} else {
return { value: arr[index++], done: false };
}
},
};
}
// generator遍歷暫停函數
function* createArrayGenerator(arr) {
for(let item of arr){
yield item
}
}
// yiled 語法糖
function* createArraYield(arr) {
yield* arr
}
const arr = ['alice', 'kiki', 'macus']
const iterator = createArrayIterator(arr)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
const generator = createArrayGenerator(arr)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
const yiledGen = createArraYield(arr)
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())
console.log(yiledGen.next())
以上三種方式所實現的功能是一致的
類中使用生成器
類中遍歷的方式由迭代器改為生成器
class Student {
constructor(name, age, hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
push(hobby) {
this.hobbies.push(hobby);
}
*[Symbol.iterator]() {
yield* this.hobbies
}
}
const student = new Student('kiki', '16', ['singing'])
student.push('swimming')
student.push('tennis')
for(let item of student){
console.log(item)
}
以上就是iterator和generator的用法和聯繫,關於js高級,還有很多需要開發者掌握的地方,可以看看我寫的其他博文,持續更新中~