iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0

昨天的L-System中Javascript的實作中,expander中使用了Iterator,今天就來介紹Javascript中和Iterator相關的主題。

Symbol資料型態

symbol資料型態是ES6新添加「基本資料型態」,主要是為了避免替物件增添屬性時撞名,也可以做為Javascript內建運算的協議用,每個symbol變數都是獨一無二,我們主要使用Javascript內建運算的協定(protocol),所以就不多列程式碼。

不共享Symbol變數

使用Symbol('鍵值')取得變數,參數的鍵值只是參考提醒用,即便鍵值一樣,得到的仍是不同的Symbol變數。

建立新的Symbol資料時,請不要使用new關鍵字。

共享Symbol變數

使用Symbol.for('鍵值')取得變數,會在全域Symbol註冊,使用Symbol.For和Symbol.keyFor會在全域Symbol註冊中搜尋是否有相同鍵值的Symbol變數,如果存在,就會共享這個變數,否則會產生一個新的Symbol變數並註冊。

Well-Known Symbol

Well-Known Symbols是Symbol的靜態屬性,也就是Javascript內建的Symbol,主要作為Javascript內建運算的協定(protocol)使用,例如在物件中實作Symbol.iterator,便是一個iterable的物件,這也是我們介紹Symbol資料型態的原因。

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(迭代器)

要成為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}

Iterable(可迭代)

如果一個物件要成為可迭代的物件,這個物件必須實作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)

注意,只可以迭代一次。

Generator function(產生器函式)

如果覺得迭代器(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發文的系列,這邊就只到這邊了。


上一篇
L系統(Lindenmayer Systems)
下一篇
JSXGraph3D元素
系列文
30天數學老師作互動式教學網頁30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
良葛格
iT邦新手 2 級 ‧ 2022-10-03 08:04:55

Well-Known Symbols是Symbol的靜態屬性,也就是Javascript內建的Symbol,主要作為Javascript內建運算的協定(protocol)使用,例如在物件中實作Symbol.interator

Symbol.iterator

olddunk iT邦新手 4 級 ‧ 2022-10-03 08:19:30 檢舉

謝謝,打錯字了。

我要留言

立即登入留言