[料理佳餚] 拐個彎的 JavaScript 的私有欄位(Private Field)

這天,我想要在 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 拐個彎建立私有欄位的方式,提供給各位服友參考,如果還有其他方式,還請各位朋友不吝分享。

參考資料

C# 指南 ASP.NET 教學 ASP.NET MVC 指引
Azure SQL Database 教學 SQL Server 教學 Xamarin.Forms 教學