點燈坊

失くすものさえない今が強くなるチャンスよ

深入探討 for...in 與 Class

Sam Xiao's Avatar 2020-01-03

Object.keys()for...in 的差異在於 Object.keys() 只能顯示目前 Object 的 Property,而 for...in 會連同 Prototype 的 Property 一併顯示。若 for...in 搭配 Constructor Function 或 Object.create() 時一切正常,但搭配 class 時,就無法顯示 Prototype 的 Property 了,為什麼會這樣呢 ?

Version

macOS Catalina 10.15.2
VS Code 1.41.1
Quokka 1.0.267
ECMAScript 2015

Constructor Function

function Book(title, price) {
  this.title = title
  this.price = price
}

Book.prototype.showDescription = function() {
  return `${this.title} : ${this.price}`
}
 
let book = new Book('FP in JavaScript', 100)

for(let key in book)
  console.log(key)

由 constructor function 建立 object,並將 method 定義在其 prototype,這是 ECMAScript OOP 標準寫法。

當對 object 使用 for...in loop,會顯示所有的 property,連 prototype 也會顯示。

forin000

Object.create()

let prototype = {
  showDescription: function() {
    return `${this.title} : ${this.price}`
  }
}
 
let book = Object.create(prototype)
book.title = 'FP in JavaScript'
book.price = 100

for(let key in book)
  console.log(key)

Prototype 除了事後在 constructor function 動態指定外,也可以事先建立 prototype object,然後傳入 Object.create()

一樣使用 for...in loop,也如預期列出 prototype 的 property。

forin001

Class

class Book {
  constructor(title, price) {
    this.title = title
    this.price = price
  }

  showDescription() {
    return `${this.title} / ${this.price}`
  }
}

let book = new Book('FP in JavaScript', 100)

for(let key in book)
  console.log(key)

ES6 導入 class 後,method 可以直接定義在 class 內,會自己幫我們定義在 prototype。

forin002

forin004

但使用 for...in loop,卻發現 prototype 的 showDescription() 無法顯示,退化成與 Object.keys() 功能一樣,為什麼會這樣呢 ?

Why ?

for...inObject.keys() 都僅能列出 enumerable property,所以很有可能 class 寫法造成 prototype 的 property 為 nonenumerable,使得 for...in 無法顯示。

class Book {
  constructor(title, price) {
    this.title = title
    this.price = price
  }

  showDescription() {
    return `${this.title} / ${this.price}`
  }
}

let book = new Book('FP in JavaScript', 100)
Object.getOwnPropertyDescriptor(book.__proto__, 'showDescription') // ?

Object.getOwnPropertyDescriptor() 會傳回每個 property 的屬性,藉此觀察是否為 nonenumerable。

forin003

也因為 enumerablefalse,所以 for...in loop 無法顯示。

Conclusion

  • for...in 為 ECMAScript 3 所定義,而 Object.keys() 為 ECMAScript 5.1 所加入,理論上兩者的差異就在於 prototype 部分,但 ES6 支援 class 後,又使得 for..inObject.keys() 功能趨於一至,個人是不太喜歡這種改變,這使得 for...in 與過去的觀念不同,算 breaking change,但既然 ES6 規格就是這樣定義,也只能自己注意這個微小的差異了