文章內容分兩部分:javascript
上半部分開始...java
迭代器模式:提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。git
簡單理解(白話理解):統一 「集合」 型數據結構的遍歷接口,實現可循環遍歷獲取集合中各數據項(不關心數據項中的數據結構)。es6
生活小栗子:清單 TodoList。每日清單有學習類、生活類、工做類、運動類等項目,清單列表只管羅列,無論類別。github
// 統一遍歷接口實現
var each = function(arr, callBack) {
for (let i = 0, len = arr.length; i < len; i++) {
// 將值,索引返回給回調函數callBack處理
if (callBack(i, arr[i]) === false) {
break; // 停止迭代器,跳出循環
}
}
}
// 外部調用
each([1, 2, 3, 4, 5], function(index, value) {
if (value > 3) {
return false; // 返回false停止each
}
console.log([index, value]);
})
// 輸出:[0, 1] [1, 2] [2, 3]
複製代碼
「迭代器模式的核心,就是實現統一遍歷接口。」設計模式
內部迭代器: 內部定義迭代規則,控制整個迭代過程,外部只需一次初始調用數組
// jQuery 的 $.each(跟上文each函數實現原理相似)
$.each(['Angular', 'React', 'Vue'], function(index, value) {
console.log([index, value]);
});
// 輸出:[0, Angular] [1, React] [2, Vue]
複製代碼
優勢:調用方式簡單,外部僅需一次調用 缺點:迭代規則預先設置,欠缺靈活性。沒法實現複雜遍歷需求(如: 同時迭代比對兩個數組)瀏覽器
外部迭代器: 外部顯示(手動)地控制迭代下一個數據項數據結構
藉助 ES6 新增的 Generator
函數中的 yield*
表達式來實現外部迭代器。函數
// ES6 的 yield 實現外部迭代器
function* generatorEach(arr) {
for (let [index, value] of arr.entries()) {
yield console.log([index, value]);
}
}
let each = generatorEach(['Angular', 'React', 'Vue']);
each.next();
each.next();
each.next();
// 輸出:[0, 'Angular'] [1, 'React'] [2, 'Vue']
複製代碼
優勢:靈活性更佳,適用面廣,能應對更加複雜的迭代需求 缺點:需顯示調用迭代進行(手動控制迭代過程),外部調用方式較複雜
不一樣數據結構類型的 「數據集合」,須要對外提供統一的遍歷接口,而又不暴露或修改內部結構時,可應用迭代器模式實現。
下半部分開始...
「迭代器等同於遍歷器。在某些文章中,可能會出現遍歷器的字眼,其實二者的意思一致。」
JavaScript 中 原有表示 「集合」 的數據結構主要是 「數組(Array)」 和 「對象(Object)」,ES6又新增了 Map
和 Set
,共四種數據集合,瀏覽器端還有 NodeList
類數組結構。爲 「集合」 型數據尋求統一的遍歷接口,正是 ES6 的 Iterator 誕生的背景。
ES6 中迭代器 Iterator 做爲一個接口,做用就是爲各類不一樣數據結構提供統一的訪問機制。任何數據結構只要部署了 Iterator 接口,就能夠完成遍歷操做。
Iterator 做用:
for...of
實現循環遍歷Iterator只是一種接口,與遍歷的數據結構是分開的。 重溫迭代器模式特色:我只要統一遍歷數據項的接口,不關心其數據結構。
ES6 默認的 Iterator 接口部署在數據結構的 Symbol.iterator
屬性上,該屬性自己是一個函數,表明當前數據結構默認的遍歷器生成函數。執行該函數 [Symbol.iterator]()
,會返回一個遍歷器對象。只要數據結構擁有 Symbol.iterator
屬性,那麼它就是 「可遍歷的」 。
遍歷器對象的特徵:
next
屬性方法;next()
,會返回一個包含 value
和 done
屬性的對象
value
: 當前數據結構成員的值done
: 布爾值,表示遍歷是否結束原生具有 Iterator 接口的數據結構:
let arr = ['a', 'b', 'c'];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 'a', done: false }
iterator.next(); // { value: 'b', done: false }
iterator.next(); // { value: 'c', done: false }
iterator.next(); // { value: undefined, done: false }
複製代碼
原生部署 Iterator 接口的數據結構,無需手動執行遍歷器生成函數,可以使用 for...of
自動循環遍歷。
for...of
運行原理:
[Symobo.iterator]()
方法,拿到遍歷器對象;next()
方法,獲得 {value: ..., done: ... }
對象// for...of 自動遍歷擁有 Iterator 接口的數據結構
let arr = ['a', 'b', 'c'];
for (let item of arr) {
console.log(item);
}
// 輸出:a b c
複製代碼
類數組對象:存在數值鍵名和
length
屬性的對象
類數組對象部署 Iterator 方法:
// 方法一:
NodeList.prototype[Symbol.iterator] = Array.prototype[Sybmol.iterator];
// 方法二:
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
// for...of 遍歷類數組對象
let arrLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of arrLike) {
console.log(item);
}
// 輸出:a b c
複製代碼
對象(Object)沒有默認 Iterator 接口,由於對象屬性遍歷順序不肯定,需開發者手動指定。
注意:
Symbol.iterator
方法,並沒有效果;Symbol.iterator
方法對應的部署遍歷器生成函數(即返回一個遍歷器對象),解釋引擎會報錯。var obj = {};
obj[Symbol.iterator] = () => 1;
[...obj]; // TypeError: [] is not a function
複製代碼
for...of
遍歷普通對象的解決方法:
Objet.keys
將對象鍵名生成一個數組,而後遍歷該數組;let person = {
name: 'Ken',
sex: 'Male'
}
// Object.keys
for (let key of Object.keys(person)) {
console.log(`${key}: ${person[key]}`);
}
// Generator 包裝對象
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(person)) {
console.log(`${key}: ${value}`);
}
// 輸出:
// name: Ken
// sex: Male
複製代碼
yield*
for...of
Array.from()
Map()/Set()/WeakMap()/WeakSet()
Promise.all()/Promise.race()
for 循環 :需定義索引變量,指定循環終結條件。
for (let i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]);
}
複製代碼
forEach: 沒法中途跳出循環,break/return
。
forEach(arr, function(item, index) {
console.log(item, index);
})
複製代碼
for...in:
let triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
this.color = 'red';
}
ColoredTriangle.prototype = triangle;
let obj = new ColoredTriangle();
for (let prop in obj) {
// 需手動判斷是否屬於自身屬性,而不是原型鏈屬性
if (obj.hasOwnProperty(prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
// 輸出:obj.color = red
複製代碼
for...of 較其它三者優勢:
for...in
同樣簡潔,但沒有 for...in
的缺點;forEach
, 可以使用 break/return/continue
退出循環;缺點:遍歷普通對象時,不能直接使用。
參考文章
本文首發Github,期待Star! github.com/ZengLingYon…
做者:以樂之名 本文原創,有不當的地方歡迎指出。轉載請指明出處。