這天,我想要在 JavaScript 的 Class 中加入一個私有欄位(Private Field),用來封裝建構式傳入的參數,以提供給唯讀屬性使用,爬文研究之後發現,雖然 JavaScript 有定義私有欄位的語法,但它是實驗性質的功能,不一定每個瀏覽器都有支援,至少目前為止 Firefox 就完全不支援,所以我得拐個彎了。
閉包(Closure)
第一招就是用閉包,把建構式傳入的參數「包」進方法之中,但是傳統的 function + 閉包的方式無法直接使用 getter 來宣告唯讀屬性。
function MyClass(firstName, lastName) {
var _firstName = firstName;
var _lastName = lastName;
this.fullName = function () {
return _firstName.trim() + ", " + _lastName.trim();
}
}
var myInstance = new MyClass("Johnny", "Chuang");
console.log(myInstance.fullName()); // Johnny, Chuang
其中 _firstName 及 _lastName 是可以省略的
function MyClass(firstName, lastName) {
this.fullName = function () {
return firstName.trim() + ", " + lastName.trim();
}
}
var myInstance = new MyClass("Johnny", "Chuang");
console.log(myInstance.fullName()); // Johnny, Chuang
WeakMap
第二招是使用 WeakMap 這個資料結構,WeakMap 是一種 Key/Value 的資料結構,但是它只接受用 object 當 Key。
var wm = new WeakMap();
wm.set("abc", 1); // Uncaught TypeError: Invalid value used as weak map key
wm.set(100, 2); // Uncaught TypeError: Invalid value used as weak map key
var obj = {};
wm.set(obj, 111); // OK
而且 WeakMap 還有一個特性,如果拿來當 Key 的 object 被 GC 回收了,其對應的 Value 也會被 GC 回收,挺適合拿來實作私有欄位。
var MyClass = (function () {
var _privateFields = new WeakMap();
class MyClass {
constructor(firstName, lastName) {
_privateFields.set(this, { firstName: firstName, lastName: lastName });
}
get fullName() {
return _privateFields.get(this).firstName.trim() + ", " + _privateFields.get(this).lastName.trim();
}
}
return MyClass;
})();
var myInstance = new MyClass("Johnny", "Chuang");
console.log(myInstance.fullName); // Johnny, Chuang
Symbol
第三招是用 Symbol,Symbol 是 ES6 加入的一個基本資料型態,可以拿它來當物件的屬性名稱,Symbol 值是獨一無二的,保證不重複,而且無法使用 Literal 的方式去存取它,用它來實作的私有欄位,看得到但改不到。
var MyClass = (function () {
var _firstName = Symbol();
var _lastName = Symbol();
class MyClass {
constructor(firstName, lastName) {
this[_firstName] = firstName;
this[_lastName] = lastName;
}
get fullName() {
return this[_firstName].trim() + ", " + this[_lastName].trim();
}
}
return MyClass;
})();
var myInstance = new MyClass("Johnny", "Chuang");
console.log(myInstance.fullName); // Johnny, Chuang
以上三種在 JavaScript 拐個彎建立私有欄位的方式,提供給各位服友參考,如果還有其他方式,還請各位朋友不吝分享。