點燈坊

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

ECMAScript 之建立 Object

Sam Xiao's Avatar 2020-01-06

ECMAScript 由 Object 與 Function 構成,傳統 OOP 的 Object 都必須由 Class 而來,但 ECMAScript 的 Object 卻有多種建立方式。

Version

macOS Catalina 10.15.2
VS Code 1.41.1
Quokka 1.0.267
ECMAScript 2015

Create Object

有別於傳統 OOP 一定要先建立 class,才能使用 new 建立 object,ECMAScript 提供了多種建立 object 方式:

  • Object Literal
  • Empty Object
  • New Object
  • Constructor Function
  • Class
  • Object.create()
  • Object.defineProperty()

Object Literal

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

book.showDescription() // ?

以 key-value 方式直接在 {} 之間建立 object,其中 showDescription() 相當於傳統 OOP 的 method,但由於 ECMAScript 本質是 object 與 function,所以相當於將 anonymous function 指定給 showDescription property。

以 object literal 所建立的 object 也稱為 plain object。

object000

Empty Object

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

book.showDescription() // ?

ECMAScript 有一個傳統 OOP 沒有的特色,就是可以動態新增 property,既然如此,我們可以一開始只宣告一個 empty object,事後再動態新增 property 即可。

object001

New Object

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

book.showDescription() // ?

由於 Object 為 ECMAScript 內建型別之一,因此你也可以使用 new Object 建立 object。

不過實務上幾乎不會這樣寫,因為 let person = {} 寫法更精簡

object002

Constructor Function

let Book = function(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)
book.showDescription() // ?

以上 3 種方式適合需要以 declarative 方式建立 object 時使用,也就是 data 自己可以決定,但實務上 data 幾乎由 user 決定,也就是要以 parameter 傳入。

另外這 3 種方式,每次建立 object 時,都會重新建立 function,然後指定到 property,事實上我們發現,每個 object 只有 data 會不一樣,但 function 都是一樣的,因此不斷地建立 相同 funtion 很浪費記憶體。

比較好的方式是 function 只建立一次,然後各 object 共用相同的 function。

由於 ECMAScript 本來就是以 function 為主的語言,因此使用 function 建立 object 也就不意外了。

其中 this 所建立的 property 都是 public,最後會自動 return this

為了避免 function 重複建立,在 Book()prototype property 新增 showDescription property 為 fuction,也就是儘管建立多個 object,但 showDescription() 都指向同一個 function,因此節省記憶體。

object003

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)
book.showDescription() // ?

ES6 新增了 classconstructor,讓我們可以類似傳統 OOP 方式使用 class,但其本質仍是 constructor function,是道地的 syntatic sugar。

與傳統 OOP 不同的是:ES6 的 class 並不用宣告 public property (也不能宣告 property,因為不合語法),而是直接在 constructor() 內使用 this 定義 property

object004

Object.create()

let prototype = {
  showDescription: function() {
    return `${this.title} / ${this.price}`
  }
}

let book = Object.create(prototype, {
  title: {
    value: 'FP in JavaScript',
    writable: true,
    enumerable: true,
    configurable: true
  },
  price: {
    value: 100,
    writable: true,
    enumerable: true,
    configurable: true
  }
})

book.showDescription() // ?

Object.create() 也可以建立 object,它提供了兩個 argument:

  • 第一個 argument:指定新 object 的 prototype
  • 第二個 argument:指定新 object 的 property descriptor,可省略

第 8 行

title: {
  value: 'FP in JavaScript',
  writable: true,
  enumerable: true,
  configurable: true
},

其中 writableenumerableconfigurable 預設為 false 可省略,之所以一一指定為 true,是為了與 object literal 的 plain object 對應等效。

object005

Object.defineProperty()

let book = {}

Object.defineProperty(book, 'title', {
  value: 'FP in JavaScript',
  writable: true,
  enumerable: true,
  configurable: true
})

Object.defineProperty(book, 'price', {
  value: 100,
  writable: true,
  enumerable: true,
  configurable: true
})

Object.setPrototypeOf(book, {
  showDescription: function() {
    return `${this.title} / ${this.price}`
  }
})

book.showDescription() // ?

也可先建立 empty object 即可,事後再靠 Object.defineProperty()Object.setPrototype() 建立 property 與 method。

10 行

Object.defineProperty(book, 'price', {
  value: 100,
  writable: true,
  enumerable: true,
  configurable: true
})

Object.defineProperty() 第二個 argument 傳入的也是 property descriptor。

17 行

Object.setPrototypeOf(book, {
  showDescription: function() {
    return `${this.title} / ${this.price}`
  }
})

使用 Object.setPrototypeOf() 設定 book 的 prototype,並將 showDescription() 建立在 prototype object。

object006

let book = {}

Object.defineProperty(book, 'title', {
  value: 'FP in JavaScript',
  writable: true,
  enumerable: true,
  configurable: true
})

Object.defineProperty(book, 'price', {
  value: 100,
  writable: true,
  enumerable: true,
  configurable: true
})

book.__proto__ = {
  showDescription: function() {
    return `${this.title} / ${this.price}`
  }
}

book.showDescription() // ?

17 行

book.__proto__ = {
  showDescription: function() {
    return `${this.title} / ${this.price}`
  }
}

也可改用 ES6 的 __proto__ 取代 Object.setPrototypeOf()

object007

Conclusion

  • Object literal、empty object 或 new Object 方式,雖然可以建立 method,但只能建立在 object 上,較浪費記憶體
  • Constructor function、class 或 Object.create(),則能夠將 method 建立在 object 的 prototype 上,較節省記憶體
  • Object.defineProperty() 則屬於事後全動態建立 object,靈活性最高