一、Iterator(遍歷器)的概念
- Iterator 是一種接口惹苗,為各種不同的數(shù)據結構提供統(tǒng)一的訪問機制殿较。
- 任何數(shù)據結構只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據結構的所有成員)桩蓉。
- 表示“集合”的數(shù)據結構:數(shù)組(Array)淋纲、對象(Object)、Map 和 Set院究。開發(fā)者還可以組合使用它們帚戳,定義自己的數(shù)據結構玷或,比如數(shù)組的成員是 Map,Map 的成員是對象片任。
Iterator 的作用:
- 為各種數(shù)據結構提供一個統(tǒng)一的、簡便的訪問接口蔬胯;
- 使得數(shù)據結構的成員能夠按某種次序排序对供;
- ES6 創(chuàng)造了一種新的遍歷命令
for...of
循環(huán),Iterator 接口主要供for...of
消費氛濒。
Iterator 的遍歷過程:
- 創(chuàng)建一個指針對象产场,指向當前數(shù)據結構的起始位置。遍歷器對象本質上舞竿,就是一個指針對象京景。
- 第一次調用指針對象的next方法,可以將指針指向數(shù)據結構的第一個成員骗奖。
- 第二次調用指針對象的next方法确徙,指針就指向數(shù)據結構的第二個成員。
- 不斷調用指針對象的next方法执桌,直到它指向數(shù)據結構的結束位置鄙皇。
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
var it = makeIterator(['a', 'b']);
console.log(it.next()) // { value: "a", done: false }
console.log(it.next()) // { value: "b", done: false }
console.log(it.next()) // { value: undefined, done: true }
說明:定義了一個 makeIterator 遍歷器生成函數(shù),它返回一個遍歷器對象仰挣。對數(shù)組執(zhí)行這個函數(shù)伴逸,返回數(shù)組的遍歷器對象 it。
- 其中膘壶,
done:false
和value:undefined
可以省略错蝴,上面的 makeIterator 方法可以改寫如下:
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true};
}
};
}
注:Iterator 只是把接口規(guī)格加到數(shù)據結構之上,遍歷器與它所遍歷的那個數(shù)據結構颓芭,實際上是分開的顷锰;完全可以寫出沒有對應數(shù)據結構的遍歷器對象,或者說用遍歷器對象模擬出數(shù)據結構畜伐。
var it = sqrMaker();
console.log(it.next().value) // 0
console.log(it.next().value) // 1
console.log(it.next().value) // 4
console.log(it.next().value) // 9
// ...
function sqrMaker() {
var index = 0;
return {
next: function() {
return {value: index++ ** 2, done: false};
}
};
}
二馍惹、默認 Iterator 接口
- ES6 規(guī)定,默認的 Iterator 接口部署在數(shù)據結構的Symbol.iterator屬性玛界,或者說万矾,一個數(shù)據結構只要具有Symbol.iterator屬性,就可以認為是“可遍歷的”(iterable)慎框。
- Symbol.iterator屬性本身是一個函數(shù)良狈,就是當前數(shù)據結構默認的遍歷器生成函數(shù)。執(zhí)行這個函數(shù)笨枯,就會返回一個遍歷器薪丁。至于屬性名Symbol.iterator遇西,它是一個表達式,返回Symbol對象的iterator屬性严嗜,這是一個預定義好的粱檀、類型為 Symbol 的特殊值。
- 有些數(shù)據結構原生具備 Iterator 接口(比如數(shù)組)漫玄,即不用任何處理茄蚯,就可以被 for...of 循環(huán)遍歷,因為這些數(shù)據結構原生部署了 Symbol.iterator 屬性睦优。
- 原生具備 Iterator 接口的數(shù)據結構如下:
1渗常、Array
2、Map
3汗盘、Set
4皱碘、String
5、TypedArray
6隐孽、函數(shù)的 arguments 對象
7癌椿、NodeList 對象 - 舉例,數(shù)組的 Symbol.iterator 屬性:
var arr = ['a','b','c','d']; var arrayIt = arr[Symbol.iterator](); console.log(arrayIt.next()); //{value: "a", done: false} console.log(arrayIt.next()); //{value: "b", done: false} console.log(arrayIt.next()); //{value: "c", done: false} console.log(arrayIt.next()); //{value: "d", done: false} console.log(arrayIt.next()); //{value: undefined, done: true}
-
說明:
1缓醋、變量 array 是一個數(shù)組如失,原生就具有遍歷器接口,部署在 array 的 Symbol.iterator 屬性上面送粱。所以褪贵,調用這個屬性,就得到遍歷器對象抗俄。
2脆丁、對于原生部署 Iterator 接口的數(shù)據結構,不用自己寫遍歷器生成函數(shù)动雹,for...of循環(huán)會自動遍歷它們槽卫。除此之外,其他數(shù)據結構(主要是對象)的 Iterator 接口胰蝠,都需要自己在Symbol.iterator屬性上面部署歼培,這樣才會被for...of循環(huán)遍歷。
- 原生具備 Iterator 接口的數(shù)據結構如下:
一個對象如果要具備可被for...of循環(huán)調用的 Iterator 接口茸塞,就必須在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具有該方法也可):
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
-
說明:上面代碼是一個類部署 Iterator 接口的寫法躲庄。Symbol.iterator
屬性對應一個函數(shù),執(zhí)行后返回當前對象的遍歷器對象钾虐。
為對象添加 Iterator 接口的例子:
var objt = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
var o = objt[Symbol.iterator]()
console.log(o.next()) // {value: "hello", done: false}
console.log(o.next()) // {value: "world", done: false}
console.log(o.next()) // {value: undefined, done: true}
類數(shù)組對象:
- 存在數(shù)值鍵名和 length 屬性噪窘;
- 部署 Iterator 接口,有一個簡便方法效扫,就是Symbol.iterator方法直接引用數(shù)組的 Iterator 接口倔监。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]; [...document.querySelectorAll('div')] // 可以執(zhí)行了
- 說明:NodeList 對象是類似數(shù)組的對象直砂,本來就具有遍歷接口,可以直接遍歷浩习。上面代碼中静暂,我們將它的遍歷接口改成數(shù)組的Symbol.iterator屬性,可以看到沒有任何影響瘦锹。
類似數(shù)組的對象調用數(shù)組的 Symbol.iterator 方法的例子:
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
- 普通對象部署數(shù)組的Symbol.iterator方法籍嘹,并無效果:
let iterable2 = { a: 'a', b: 'b', c: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; for (let item of iterable2) { console.log(item); // undefined, undefined, undefined }
- 如果 Symbol.iterator 方法對應的不是遍歷器生成函數(shù)(即會返回一個遍歷器對象),解釋引擎將會報錯:
var obj2 = {}; obj2[Symbol.iterator] = () => 1; [...obj2] // 編輯器 ----> TypeError: obj2[Symbol.iterator] is not a function // 瀏覽器 ----> TypeError: Result of the Symbol.iterator method is not an object
- 有了遍歷器接口弯院,數(shù)據結構就可以用 for...of 循環(huán)遍歷,也可以使用 while 循環(huán)遍歷:
var $iterator = ['x', 'y', 'z'][Symbol.iterator](); var $result = $iterator.next(); while (!$result.done) { var x = $result.value; console.log('yjw ------- ', x); $result = $iterator.next(); }
三泪掀、調用 Iterator 接口的場景
解構賦值:
- 對數(shù)組和 Set 結構進行解構賦值時仔掸,會默認調用 Symbol.iterator 方法:
var set = new Set().add('a').add('b').add('c'); var [x,y] = set; console.log('x = ', x, ' ; y = ', y); // x = a ; y = b var [first, ...rest] = set; console.log('first = ', first, ' ; rest = ', rest); // first = a ; rest = [ 'b', 'c' ]
var arr = ['b', 'd', 'e']; Array.prototype[Symbol.iterator] = function(){ const self = this; let index = 0; return { next() { if (index < self.length) { console.log('yjw ----------> ', index) return { value: self[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } var [n1,n2] = arr // yjw ----------> 0 // yjw ----------> 1
擴展運算符:
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
補充:只要某個數(shù)據結構部署了 Iterator 接口蝗碎,就可以對它使用擴展運算符,將其轉為數(shù)組。
yield *
- yield* 后面跟的是一個可遍歷的結構项戴,它會調用該結構的遍歷器接口
var generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); console.log(iterator.next()) // { value: 1, done: false } console.log(iterator.next()) // { value: 2, done: false } console.log(iterator.next()) // { value: 3, done: false } console.log(iterator.next()) // { value: 4, done: false } console.log(iterator.next()) // { value: 5, done: false } console.log(iterator.next()) // { value: undefined, done: true }
- 其它場合:
- 由于數(shù)組的遍歷會調用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場合欠橘,其實都調用了遍歷器接口:
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
四涛目、字符串的 Iterator 接口
- 字符串是一個類似數(shù)組的對象,原生具有 Iterator 接口
var someString = "hi"; console.log(typeof someString[Symbol.iterator]) // "function" var iterator = someString[Symbol.iterator](); console.log(iterator.next()) // { value: "h", done: false } console.log(iterator.next()) // { value: "i", done: false } console.log(iterator.next()) // { value: undefined, done: true }
- 覆蓋字符串原生的 Symbol.iterator 方法靠抑,修改遍歷器行為
var str = new String("hi"); console.log([...str]) // ["h", "i"] str[Symbol.iterator] = function() { return { next: function() { if (this._first) { this._first = false; console.log('============= yjw ============') return { value: "bye", done: false }; } else { return { done: true }; } }, _first: true }; }; console.log([...str]) // ["bye"] console.log(str) // "hi"
五量九、Iterator 接口與 Generator 函數(shù)
- Symbol.iterator方法的最簡單實現(xiàn):
var myIterable2 = { [Symbol.iterator]: function* () { yield 1; yield 2; yield 3; } }; console.log([...myIterable2]) // [1, 2, 3] // 或者采用下面的簡潔寫法 var obj2 = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; } }; for (let x of obj2) { console.log(x); } // "hello" // "world"
遍歷器對象構成條件:
- next:必須要部署該方法;
- return:可選的部署方法颂碧;
- throw:主要配合 generator(下一章) 函數(shù)使用荠列,一般的遍歷對象用不到這個方法。
六载城、for...of 循環(huán)
一個數(shù)據結構只要部署了 Symbol.iterator 屬性肌似,就被視為具有 iterator 接口,就可以用 for...of 循環(huán)遍歷它的成員诉瓦;
-
使用范圍:數(shù)組川队、Set 和 Map 結構、某些類似數(shù)組的對象(比如arguments對象睬澡、DOM NodeList 對象)固额、后文的 Generator 對象,以及字符串猴贰。
var testArr = ['x', 'y', 'z']; for (let v of testArr){ console.log(v) // x y z } var obj3 = {}; obj3[Symbol.iterator] = testArr[Symbol.iterator].bind(testArr); for(let v of obj3) { console.log(v); // x y z }
可以替代 forEach 方法对雪;
-
for...in 缺點:
- 只能獲取鍵名,不能獲取鍵值米绕;
- 數(shù)組的鍵名是數(shù)字瑟捣,但是這里的鍵名是字符串馋艺;
- 可以返回非數(shù)值鍵名;
- 優(yōu)點:可以遍歷普通對象迈套。
- 結論:for...in 主要是為遍歷對象設計的捐祠,不適用于遍歷數(shù)組。
var testArr = ['x', 'y', 'z']; testArr.foo = 'abc' for (let v of testArr){ console.log('of ---> ', v) } for (let v in testArr){ console.log('in ---> ', v) }
-
能正確識別 32 為 UTF-16 字符:
for (let x of 'a\uD83D\uDC0A') { console.log(x); } // 'a' // '\uD83D\uDC0A'
-
有些類數(shù)組的對象沒有 Iterator 接口桑李,可以使用 Array.from 方法將其轉為數(shù)組:
var arrayLike = { 0: 'a', 1: 'b', length: 2}; // 報錯 for (let x of arrayLike) { console.log(x); } // 正確 for (let x of Array.from(arrayLike)) { console.log(x); } // a b
-
不能遍歷普通的對象踱蛀,必須部署了 Iterator 接口后才能使用。其它的解決方法:
// 方案一: var someObject = {a: 'aa', b: 'bb'}; for (var key of Object.keys(someObject)) { console.log(key + ': ' + someObject[key]); } // a: aa // b: bb // 方案二: function* entries(obj) { for (let key of Object.keys(obj)) { yield [key, obj[key]]; } } var obj = {name: 'yijiang', age: '18'}; for (let [key, value] of entries(obj)) { console.log(key, '->', value); } // name -> yijiang // age -> 18
和其它遍歷語法的比較:
- 原始的 for 循環(huán):麻煩贵白;
var array = [1,2,3,4,5,6,7,8,9] for(let i=0; i < array.length; i++){ console.log(array[i]); if(i>5) break; } // 1 2 3 4 5 6 7
- forEach 方法:無法中途跳出
var array = [1,2,3,4,5,6,7,8,9] array.forEach(v => { console.log(v) if(v > 5) return; })