1.什麼是Iterator ? 遍歷器(Iterator)它是一種介面,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 介面,就可以完成遍歷操作。 Iterator 的作用有三個: 1.是為各種數據結構,提供一個統一的、簡便的訪問介面; 2.是使得數據結構的成員能夠按 ...
1.什麼是Iterator ?
遍歷器(Iterator)它是一種介面,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 介面,就可以完成遍歷操作。
Iterator 的作用有三個:
1.是為各種數據結構,提供一個統一的、簡便的訪問介面;
2.是使得數據結構的成員能夠按某種次序排列;
3.是 ES6 創造了一種新的遍歷命令for...of
迴圈,Iterator 介面主要供for...of
消費。
2.Iterator怎麼用 ?
for...of
語句在可迭代對象(包括 Array
,Map
,Set
,String
,TypedArray
,arguments 對象等等)上創建一個迭代迴圈,調用自定義迭代鉤子,併為每個不同屬性的值執行語句
/** * 語法 * @variable 每次迭代中屬性的值 * @iterable 被迭代枚舉其屬性的對象 */ for (variable of iterable) { //statements }
const array1 = ['a', 'b', 'c']; for (const element of array1) { console.log(element); } // a // b // c
接下來一起來實踐一下上面那些類型是不是真的可以用
// Array let arr = [10, 20, 30]; for (let value of arr) { value += 1; console.log(value); } // 11 // 21 // 31 // Map let map = new Map([["a", 1], ["b", 2], ["c", 3]]); for (let entry of map) { console.log(entry); } // ["a", 1] // ["b", 2] // ["c", 3] // Set let set = new Set([1, 1, 2, 2, 3, 3]); for (let value of set) { console.log(value); } // 1 // 2 // 3 // String let str = "boo"; for (let value of str) { console.log(value); } // "b" // "o" // "o" // TypedArray let typedArr = new Uint8Array([0x00, 0xff]); for (let value of typedArr) { console.log(value); } // 0 // 255 // arguments (function() { for (let argument of arguments) { console.log(argument); } })(1, 2, 3); // 1 // 2 // 3View Code
那其他的類型用for of會怎麼樣?
比如一個普通的object類型
產品:“那我就要把for of用在對象上”
我:“對象不能用for of,你看這不都報錯了嘛”
產品:“不聽不聽,你是不是不想做”
我:“好吧好吧,既然你想要用,那就滿足你”
首先要瞭解遍歷器 Iterator 的協議,傳送門:MDN 迭代協議,
簡而言之就是:
可迭代協議:
可迭代協議允許 JavaScript 對象定義或定製它們的迭代行為,例如,在一個 for..of
結構中,哪些值可以被遍歷到。一些內置類型同時是內置可迭代對象,並且有預設的迭代行為,比如 Array
或者 Map
,而其他內置類型則不是(比如 Object
))。
要成為可迭代對象, 一個對象必須實現 @@iterator
方法。這意味著對象(或者它原型鏈上的某個對象)必須有一個鍵為 @@iterator
的屬性,可通過常量 Symbol.iterator
訪問該屬性:
迭代器協議:
迭代器協議定義了產生一系列值(無論是有限個還是無限個)的標準方式。當值為有限個時,所有的值都被迭代完畢後,則會返回一個預設返回值。
只有實現了一個擁有以下語義(semantic)的 next()
方法,一個對象才能成為迭代器:
var obj = {
data: [1,2,3],
[Symbol.iterator]() {
var nextIndex = 0, self = this;
return {
next() {
var done = nextIndex >= self.data.length;
var value = done ? undefined : self.data[nextIndex++]
return { value: value, done: done }
}
}
}
}
for (const item of obj) { console.log(item) }
// 1
// 2
// 3
由此可見,普通對象不可直接使用for of,在[Symbol.iterator]屬性上部署遍歷器生成的方法後即可被for of使用(原型鏈上的對象具有該方法也可)
實際上,對象之所以沒有預設部署 Iterator 介面,是因為對象的哪個屬性先遍歷,哪個屬性後遍歷是不確定的,需要開發者手動指定。
那知道了上面遍歷器的協議,我們可以通過更簡單的方式判定上述六種類型是否實現了遍歷器介面了
Array.prototype.hasOwnProperty(Symbol.iterator); // true Set.prototype.hasOwnProperty(Symbol.iterator); // true Map.prototype.hasOwnProperty(Symbol.iterator); // true String.prototype.hasOwnProperty(Symbol.iterator); // true (function(){ console.log(arguments.hasOwnProperty(Symbol.iterator)) })(1,2,3) // true Uint8Array.prototype.__proto__.hasOwnProperty(Symbol.iterator) // true
3. 與for in的區別
可能看到for of 有人想到for in,那這兩個長這麼像,他兩有啥區別呢。咱們一起來看看
var arr1 = ['a', 'b', 'c', 'd']; // 輸出鍵名 for (let a in arr1) { console.log(a); // 0 1 2 3 }
// 輸出鍵值 for (let a of arr1) { console.log(a); // a b c d }
// 註意:還有個細節
// for...of
迴圈調用遍歷器介面,數組的遍歷器介面只返回具有數字索引的屬性。這一點跟for...in
迴圈也不一樣。
let arr2 = [3, 5, 7];
arr2.foo = 'hello';
for (let i in arr2) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr2) {
console.log(i); // "3", "5", "7"
}
4. 與其他迴圈的區別
var arr = Array(10).fill(1);
// for i 迴圈法,不夠簡潔
for (var i = 0; i < arr.length; i++) {
console.log(arr[i])
}
// forEach 迴圈,不能手動跳出迴圈
arr.forEach(function (value) {
console.log(value);
})
// for i 迴圈獲取的是鍵名,而不是鍵值,因此不大適用於遍曆數組
for (var i in arr) {
console.log(i)
}
// for of 可以跳出迴圈,語法簡潔
for (var i of arr) {
console.log(i)
}
經比較,遍歷對象用for in, 遍曆數組用for of
實際上,要實現對象的Iterator介面還有更簡潔的方法,就是使用generator
let obj = { * [Symbol.iterator]() { yield 'hello'; yield 'world'; } }; for (let x of obj) { console.log(x); } // hello // world
看到這裡有人又奇怪了,這個 * 號是個什麼鬼?
答案是 Generator 函數,Generator 函數是一個普通函數,但是有兩個特征。一是,function
關鍵字與函數名之間有一個星號;二是,函數體內部使用yield
表達式,定義不同的內部狀態(yield
在英語里的意思就是“產出”)。
2020-06-09 11:23:51