標題會這樣下是因為光是講迭代這個大標題一定會提到可迭代協議、迭代器、迭代器協議等等,所以仿造一個舞台劇的劇名感XD
當初會想研究迭代是因為我發現 array有 iterable protocol所以可以被迭代,擁有 iterable protocol就等於有了[Symbol.iterator]
,但 iterator protocol也是因為有了[Symbol.iterator]
,誒誒那不是等於array就是iterator嗎?可是卻無法直接使用next()
方法?
研究的時候覺得天啊真是個大坑啊~~但就是遇到了就遇到了,愛上了就愛上了
就要研究個清楚啊!
為什麼會選擇這個「迭代」?
迭代、疊代有兩種寫法,一直在猶豫要用哪種寫法,後來參考這篇文章:請問到底是在「疊代」或是在「迭代」?
"「疊代」:累進取代,不斷重複進行後者加上前者、替換掉前者的動作;"
"「迭代」:替換取代,表示幾個固定的物件彼此輪替取代的動作;"
我們說的迭代應該比較符合「迭代」吧!
迭代有好幾種方法,讓我們來慢慢介紹:
在ES6之前,所有的object都可以用for...in
來迭代。
如果是陣列就會取到 index 值,如果是物件就會取到 key 值。
陣列如果擁有 index值也同樣可以拿到 value:
let array = [2,3,4,5,6,7]
for(const index in array) {
console.log(array[index]);
}
//2
//3
//4
//5
//6
//7
物件擁有了 key值,也可以拿到 value:
let obj = { name:"Jade", age: 28, city: "taipei" }
for(let key in obj) {
console.log(obj[key])
}
// Jade
// 28
// taipei
然而ES6出了一個新的方法叫做for...of
,用在陣列身上的話會直接取到 value,不用再用 index的方式去找。
然而這個方法卻不能用在物件的身上。
why?
讓我們直接來試試看物件:
const obj = { name:"Jade", age: 28, city: "taipei" }
for(let value of obj) {
console.log(value)
}
// TypeError: obj is not iterable
發現真的無法用在物件身上,但我們得到了一個新的詞 iterable
所以物件不是iterable,但陣列是iterable 是因為陣列有iterable protocol可迭代協議
。
Array.prototype
有個很奇妙的東西長這樣:Array.prototype[@@iterator]()
,這一段其實是等於可以被這樣使用:
array[Symbol.iterator]()
意思是陣列擁有Symbol.iterator
屬性的方法可以去使用。而且擁有了這個屬性,就等於有可迭代協議(iterable protocol)
了。
來試試看有加array[Symbol.iterator]()
的效果
var array = [1,2,3,4];
var array1 = array[Symbol.iterator]();
for (let value of array1) {
console.log(value);
}
//1
//2
//3
//4
跟直接使用的效果一樣:
var array = [1,2,3,4];
for (let value of array) {
console.log(value);
}
//1
//2
//3
//4
再回到上一篇文章,為什麼只有陣列有,物件沒有,我想跟儲存方式有很大的關係:
for...of
去拜訪每一個 index所對應的 value。物件沒辦法直接被遍歷 value 是因為陣列跟物件儲存的方式就不一樣,所以沒辦法用for...of
去迭代 value。
不過object還是可以透過我們替他加上 [Symbol.iterator]()
方法(就是讓他變成迭代器),讓我們去替他定義要從哪裡開始遍歷 value。
現在我們知道了iteration, iterable protocol,現在要來介紹 iterator。
維基百科:迭代器(iterator),是確使使用者可在容器物件(container,例如連結串列或陣列)上遍訪的物件。
呈上一個,只有陣列有[Symbol.iterator]()
方法,所以就直接以陣列來講了。
在閱讀陣列方法的時候,發現這些方法回傳值會是 「Array Iterator object」
array.prototype.keys()
array.prototype.entries()
array.prototype.values()
這次用array.prototype.entries()
來試試看會發生什麼事:
const arr = ['j', 'a', 'd', 'e'];
const ArrayIterator = arr.entries();
console.log(ArrayIterator);
// Object [Array Iterator] {}
居然變數ArrayIterator
已經變成一個迭代器物件了,所以不能像是object.entries()
直接幫你列出來,就要使用iterator內建的 next()
方法,把值印出來。
而使用next()
方法就是產生了一個 iterator protocol(迭代器協議)。
他要使用next()
方法才能回傳物件包含value、done的屬性:{value: current value , done: true/ false }
正確使用array.entries()
的方式
const arr = ['j', 'a', 'd', 'e'];
const iterator = arr.entries();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
//{ value: [ 0, 'j' ], done: false }
//{ value: [ 1, 'a' ], done: false }
//{ value: [ 2, 'd' ], done: false }
//{ value: [ 3, 'e' ], done: false }
//{ value: undefined, done: true } 表示遍歷結束
這裡是當初我最不懂的地方,後來在MDN iterable protocol看到這段:
...Whenever an object needs to be iterated (such as at the beginning of a for...of loop), its @@iterator method is called with no arguments, and the returned iterator is used to obtain the values to be iterated.
(點下中文翻譯)
...每當物件需要被迭代時(比如在一個開始的 for..of 迴圈中),物件的 @@iterator 方法會被以不傳入引數的方式呼叫,並會使用其回傳的**迭代器(iterator)**來獲得被迭代出來的值。
於是我困惑的結局就是:
雖然陣列有iterable protocol(可迭代協議),不等於他可以直接使用next()
,只是在迭代的當下會自動轉換成要用next()
才能回傳的的value來回傳,所以我們就只會看到 value而已。
所以現在做了const iterator = arr.entries()
就是把他真的變成了迭代器,就必須按照迭代器的next()
方法才能拿到value。
const arr = ['j', 'a', 'd', 'e'];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
//
{ value: 'j', done: false }
{ value: 'a', done: false }
{ value: 'd', done: false }
{ value: 'e', done: false }
{ value: undefined, done: true }
這個就是迭代器出來的樣子。
當陣列沒有直接使用[Symbol.iterator]()
,還是可以回傳使用[Symbol.iterator]()
才能有的value!但如果我想使用next()
並想知道什麼時候done,就要真的把陣列變成迭代器才做得到了。
for...of
迭代。[Symbol.iterator]()
屬性,所以只要使用for...of
要迭代的話就會要他去做本來要用next()
才能回傳的value。[Symbol.iterator]()
屬性的話就可以使用next()
來找到下一個值,現在就等於有了iterator protocol。如果還沒有很懂的話可以去看MDN的Iteration protocols
什麼?跟我說 Iterator 還可以講到 Generator?
先讓我喘口氣我再考慮看看...
打完元氣大傷,謝謝大家~明天見!
參考資料:
[筆記] 談談 JavaScript 中 for ... in 這個 function
Iterator 和 for...of 循环
Iteration protocols
JavaScript iterators and generators: A complete guide
JavaScript ES6 Iterables/Iterators 迭代器
迭代器(Iterator)
What does @@ ("at at") mean in ES6 JavaScript?