昨天的L-System中Javascript的實作中,expander中使用了Iterator,今天就來介紹Javascript中和Iterator相關的主題。
symbol資料型態是ES6新添加「基本資料型態」,主要是為了避免替物件增添屬性時撞名,也可以做為Javascript內建運算的協議用,每個symbol變數都是獨一無二,我們主要使用Javascript內建運算的協定(protocol),所以就不多列程式碼。
使用Symbol('鍵值')取得變數,參數的鍵值只是參考提醒用,即便鍵值一樣,得到的仍是不同的Symbol變數。
建立新的Symbol資料時,請不要使用new關鍵字。
使用Symbol.for('鍵值')取得變數,會在全域Symbol註冊,使用Symbol.For和Symbol.keyFor會在全域Symbol註冊中搜尋是否有相同鍵值的Symbol變數,如果存在,就會共享這個變數,否則會產生一個新的Symbol變數並註冊。
Well-Known Symbols是Symbol的靜態屬性,也就是Javascript內建的Symbol,主要作為Javascript內建運算的協定(protocol)使用,例如在物件中實作Symbol.iterator,便是一個iterable的物件,這也是我們介紹Symbol資料型態的原因。
存取Symbol屬性只能使用中括號[symbol]存取,不能使用.運算子存取;字串屬性同時可使用[]和.運算子存取。
console.log(person[idSymbol])
const idSymbol = Symbol('id')
const person = {
[idSymbol]: 1,
name: 'Sean',
age: 23,
}
console.log(Object.getOwnPropertySymbols(person)) // [Symbol(id)]
console.log(Object.getOwnPropertyNames(person)) // ['name', 'age']
console.log(Object.keys(person)) // ['name', 'age']
console.log(Object.values(person)) // ['Sean', '23']
要成為Iterator,物件內必須要實作next()函式,next()函式必須傳回一個物件,物件內要包含value和done屬性,done是boolean型別,如果物件內沒有value,value的值會被設為undefined,若缺了done,其值會被設為false;iterator可選擇實作另兩個方法return()和throw()也預期要傳回一個物件,return若傳回含有value的物件,則done的值會被設為true,iterator會停止再執行。
我們以費氏數列實作一個Iterator
const Fibo = {
a_1: 1,
a_2: 1,
next() {
const stop = 100
let result = this.a_1 + this.a_2
while (result < stop) {
this.a_1 = this.a_2
this.a_2 = result
return {
done: false,
value: result
}
result = this.a_1 + this.a_2
}
return { done: true }
},
}
console.log(Fibo.next()) // {done: false, value: 2}
console.log(Fibo.next()) // {done: false, value: 3}
console.log(Fibo.next()) // {done: false, value: 5}
console.log(Fibo.next()) // {done: false, value: 8}
console.log(Fibo.next()) // {done: false, value: 13}
console.log(Fibo.next()) // {done: false, value: 21}
console.log(Fibo.next()) // {done: false, value: 34}
console.log(Fibo.next()) // {done: false, value: 55}
console.log(Fibo.next()) // {done: false, value: 89}
console.log(Fibo.next()) // {done: true}
如果一個物件要成為可迭代的物件,這個物件必須實作Symbol.iterator方法,而且這個方法必須回傳一個Iterator。我們很容易就可以讓一個Iterator變成Iterable(可迭代的),只要Symbol.iterator方法中回傳this(也就是這個Iterator)便能滿足協定(protocol),我們將上面的物件稍作修改,並讓它成為函式建構子。
function fibonacci(start, second, stop) {
let a_1 = start
let a_2 = second
return {
next() {
let result = a_1 + a_2
a_1 = a_2
a_2 = result
while (result < stop) {
return {
done: false,
value: result
}
}
return { done: true }
},
[Symbol.iterator]() { return this }
}
}
let f1 = fibonacci(1, 1, 1000)
成為可迭代物件(Iterable)有什麼好處呢?你可以使用for...of語法和賦值解構運算子[...]
for (let f of f1) {
console.log(f)
}
// 或
const f1Arr = [...f1]
console.log(f1Arr)
注意,只可以迭代一次。
如果覺得迭代器(Iterator)和可迭代(Iterable)物件實作複雜,Javascript支援了產生器函式,可以執行相同的功能,而語法則更容易理解。使用產生器函式時,function關鍵字的後面要加*
字符,而回傳給呼叫者的時候要用yield關鍵字,而不是使用return。上面的函式,可以改寫成產生器函式的方式如下:
function* getFibo(first, second, stop) {
let a_1 = first, a_2 = second
try {
for (let result = a_1 + a_2; result < stop; result) {
yield result
a_1 = a_2
a_2 = result
result = a_1 + a_2
}
} catch (e) {
console.log(e.message)
}
}
let f2 = getFibo(1, 1, 100)
console.log(f2.next()) // {done: false, value: 2}
console.log(f2.next()) // {done: false, value: 3}
console.log(f2.next()) // {done: false, value: 5}
console.log([...f2]) // [8, 13, 21, 34, 55, 89]
執行的結果和可迭代物件完全一樣,由此可知,f2本身就是一個迭代器,而每次執行f2.next()時,又會回傳一個迭代器,直到產生器函式執行結束。產生器函式傳回的迭代器當然也可以使用for...of語法和賦值解構運算子[...]。
產生器函式所傳回的產生器next()方法,除了接受值之外,也可以傳值進入產生器函式,產生器函式內只要用寫入
dataAccepted = yield dataSent,data便可接受next()括號內的值。這裏有個比較要注意的地方,因為第一次呼叫next()時,產生器函式執行到這行時,只會執行傳值給呼叫者的工作,然後把控制交還給呼叫者,所以產生器函式這邊無法接受呼叫者傳送的值,因此有傳值和沒傳值的結果是一樣,等到下一次呼叫next()時,則從接受呼叫者傳值開始,一直執行到傳值給呼叫值。如果沒有dataSent,執行的流程一樣,只是每次傳送給呼叫者時,並沒有值被傳送,所以呼叫者接受的物件中,value的屬性是underfined。
今天將Symbol、迭代器()、可迭代物件()和產生器函式()作了簡單的介紹,希望對學習Javascript能有幫助,連同今天的內容,Javascript寫了四篇,剛好是計畫內的上限,應該是沒有機會再介紹Javascript的內容了。Javascript和一般的物件導向語言不太相同,如果要徹底了解,可找專為Javascript發文的系列,這邊就只到這邊了。
Well-Known Symbols是Symbol的靜態屬性,也就是Javascript內建的Symbol,主要作為Javascript內建運算的協定(protocol)使用,例如在物件中實作Symbol.interator
Symbol.iterator
謝謝,打錯字了。