ECMAScript 無論使用 Object Literal 或者 new
建立 Object,所有的 Property 都是 Public,也就是 ECMAScript 沒有 Field 概念,而 Encapsulation 算是 OOP 最基本原則之一,這也使得使用 ECMAScript 實踐 OOP 時有些許缺憾,本文使用 Closure 實踐 Encapsulation,並探討 Stage 3 的 Private Class Field 語法。
Version
ECMAScript 2020
Constructor Function
function Person(name, age) {
this.name = name
this.age = age
}
let person = new Person('Sam', 18)
person.name // ?
person.age // ?
若使用 ES5 的 constructor function 搭配 new
,則 name
與 age
都是 public property,沒有任何 encapsulation 可言。
Class with Constructor
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
}
let person = new Person('Sam', 18)
person.name // ?
person.age // ?
僅管用了 ES6 的 class 語法,new
之後的 name
與 age
依然沒有 encapsulation。
Value Object Pattern
function Person(name, age) {
_name = name
_age = age
return {
get name() { return _name },
get age() { return _age }
}
}
let person = Person('Sam', 18)
person._name // ?
person._age // ?
person.name // ?
person.age // ?
可透過 value object pattern 以 closure 實作 encapsulation,_name
與 _age
不能夠被直接存取,必須透過 name
與 age
getter,因此獲得 encapsulation。
注意
Person
不再使用new
,只是普通 function
Arrow Function + Closure
function Person(name, age) {
_name = name
_age = age
let obj = {}
Object.defineProperty(obj, 'name', {
get: () => _name
})
Object.defineProperty(obj, 'age', {
get: () => _age
})
return obj
}
let person = Person('Sam', 18)
person._name // ?
person._age // ?
person.name // ?
person.age // ?
Getter 由於語法限制,無法使用更精簡的 arrow function,若你還是堅持使用 arrow function,可改用 Object.defineProperty()
定義 name()
與 age()
,則 get
可搭配 arrow function。
function Person(name, age) {
_name = name
_age = age
return {
getName: () => _name,
getAge: () => _age
}
}
let person = Person('Sam', 18)
person._name // ?
person._age // ?
person.getName() // ?
person.getAge() // ?
若你不在乎 name
與 age
從 property 變成 getName()
與 getAge()
method,則可使用 arrow function。
IIFE + Closure
let person = ((name, age) => {
_name = name
_age = age
return {
getName: () => _name,
getAge: () => _age
}
})('Sam', 18)
person._name // ?
person._age // ?
person.getName() // ?
person.getAge() // ?
也可改用 IIFE 直接回傳 Object,連 Person()
也一併省略。
Private Class Fields
class Person {
#name = ''
#age = 0
constructor(name, age) {
this.#name = name
this.#age = age
}
get name() {
return this.#name
}
get age() {
return this.#age
}
}
let person = new Person('Sam', 18)
person.name // ?
person.age // ?
以上方式都是借助 closure 完成,不算是從 OOP 根本解決,ECMAScript 提出了新的 private class field 語法,目前還在 stage 3,即將成為標準。
第 2 行
#name = ''
#age = 0
constructor(name, age) {
this.#name = name
this.#age = age
}
在 variable 前加上 #
即成為 private field。
第 10 行
get name() {
return this.#name
}
get age() {
return this.#age
}
外界無法直接存取 #name
與 #age
,必須透過 name
與 age
getter 才能存取。
Conclusion
- Encapsulation 可藉由 value object pattern 與 closure 間接達成
- IIFE 可直接回傳 Object,連 function 都省略
- Private class field 目前在 Node 12 以上開始支援,也可加掛 Babel plugin 使用
Reference
Jordan Moore, Closures, Private Data, and Inheritance in JavaScript