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。
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 即可。
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 = {}
寫法更精簡
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,因此節省記憶體。
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 新增了 class
與 constructor
,讓我們可以類似傳統 OOP 方式使用 class
,但其本質仍是 constructor function,是道地的 syntatic sugar。
與傳統 OOP 不同的是:ES6 的 class 並不用宣告 public property (也不能宣告 property,因為不合語法),而是直接在
constructor()
內使用this
定義 property
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
},
其中 writable
、enumerable
與 configurable
預設為 false
可省略,之所以一一指定為 true
,是為了與 object literal 的 plain object 對應等效。
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。
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()
。
Conclusion
- Object literal、empty object 或 new Object 方式,雖然可以建立 method,但只能建立在 object 上,較浪費記憶體
- Constructor function、class 或
Object.create()
,則能夠將 method 建立在 object 的 prototype 上,較節省記憶體 Object.defineProperty()
則屬於事後全動態建立 object,靈活性最高