js中模擬實現(xiàn)私有屬性

今天在看《你不知道的js》這本書時,無意看到Object還有個方法叫做 getOwnPropertySymbols() ,用來獲取對象的Symbol屬性。記得之前看過一些文章說,可以使用Symbol來實現(xiàn)私有屬性,如果能直接使用 getOwnPropertySymbols() 方法獲取Symbol屬性,那還是私有屬性么?

今天復(fù)習(xí)整理一下關(guān)于js中創(chuàng)建私有屬性的一些問題。

由于js并不是Java那種類式面向?qū)ο?,因此即使es6添加了class支持,js根上還是基于原型的面向?qū)ο螅恢С质裁此接?,公有屬性的?/p>

要想實現(xiàn)私有屬性,基于現(xiàn)有的js,途徑只有一個: 閉包。

至于什么是閉包,網(wǎng)上一大把,有興趣也可以看看我之前整理的文章。

使用原始的閉包

const User = (function () {
    return class User{
        constructor(name) {
            this.getName = function () {
                return name;
            }
            this.setName = function (name) {
                this.getName = function () {
                    return name;
                }
            }
        }
    };
})();

這種方式的確可以實現(xiàn)私有的屬性,而且如果有子類繼承,也可以如下寫法:

const Boy = (function () {
    return class Boy extends User{
        constructor(name){
            super(name);
            this.getGender = function () {
                return 'boy';
            }
        }
    }
})();

但問題也有:

  • 代碼組織混亂。由于對構(gòu)造器函數(shù)形成一個閉包,因此所有的setter,getter函數(shù)都寫在了構(gòu)造器內(nèi)??梢詮纳厦娴腢ser的寫法中看出,setter函數(shù)中,還要再定義一遍getter,這中混亂,不是一般能忍受的。如果業(yè)務(wù)邏輯中還有其他的更多操作,那么混亂程度一下子就上來了。
  • 閉包的內(nèi)存開銷不容小覷。

針對上面的問題,我們可不可以將name單獨拿出來放到單獨一個地方存儲,優(yōu)化一下代碼的組織呢?比如下面這個:

const User = (function () {
    let privateName = null;
    return class User{
        constructor(name) {
            privateName = name;
        }
        getName(){
            return privateName;
        }
        setName(name) {
            privateName = name;
        }
    };
})();

上面這個例子,貌似是可以的,這樣代碼也清晰了,也能實現(xiàn)私有屬性。

但是,仔細(xì)分析一下,真的可以么?

如此的話,是不是所有的實例對象都共享一個 privateName 屬性?后面的實例會覆蓋前面實例的值??聪旅妫?/p>

const u1 = new User('coolcao');
const u2 = new User('lili');
console.log(u2.getName());
console.log(u1.getName());
// lili
// lili

針對上面的問題,我們使用一個 privateName 保存私有屬性,會被覆蓋,那么我們?nèi)绻褂靡粋€數(shù)組,保存多個,然后針對每個實例,生成一個唯一的存取標(biāo)識呢?

基于散列實現(xiàn)

const User = (function () {
    const privateData = {};
    let i = 0;
    return class User {
        constructor(name){
            this['_id'] = i ++;
            privateData[this['_id']] = {name: name};
        }
        getName() {
            return privateData[this['_id']].name;
        }
        setName(name) {
            privateData[this['_id']].name = name;
        }
    };
})();

我們使用 privateData 這個對象來保存所有實例的私有屬性,針對每個實例,使用一個id進(jìn)行標(biāo)識,每實例化一個實例,該id會自動加1。然后將該id作為鍵值,將私有屬性存入 privateData 對象。
嗯,這樣看上去比直接在構(gòu)造器中使用閉包要清晰多了,而且實例與實例之間也不沖突。

在es6之前,這可能是最合適的方案。雖然也會存在問題:

每個實例對象都會引用 privateData ,因此,還是由于閉包的問題,如果實例太多的話,內(nèi)存是個問題。

WeakMap實現(xiàn)

幸好 es6 來了,帶來了一個叫做 WeakMap 的東西,具體這東西是啥呢?可以看看阮老師的教程

簡單說來,WeakMap鍵名所引用的對象都是弱引用,即垃圾回收機制不將該引用考慮在內(nèi)。因此,只要所引用的對象的其他引用都被清除,垃圾回收機制就會釋放該對象所占用的內(nèi)存。也就是說,一旦不再需要,WeakMap 里面的鍵名對象和所對應(yīng)的鍵值對會自動消失,不用手動刪除引用。

好了,那么上面的這個強引用關(guān)系,我們可以使用 WeakMap 弱引用來代替:

const User = (function () {
    const privateData = new WeakMap();
    class User {
        constructor(name) {
            privateData.set(this,{name:name});
        }
        getName() {
            return privateData.get(this).name;
        }
        setName(name) {
            privateData.get(this).name = name;
        }
    }
})();

如此的代碼,干凈清爽了許多,而且由于WeakMap是弱引用,如果沒有其他引用和該鍵引用同一個對象,這個對象將會被當(dāng)作垃圾回收掉。解決了內(nèi)存泄露的問題。

好了,js模擬閉包,就這幾個方式了,從這幾個例子來看,都使用了自執(zhí)行函數(shù)(IIFE),因此都會形成閉包。這也是我最開始說的,要想在js實現(xiàn)私有屬性,只能使用閉包。

Symbol的問題

話說回來我當(dāng)初的疑問,ES6的Symbol實現(xiàn)的私有屬性有啥問題呢?

看下面例子:

const User = (function () {
    const NAME = Symbol('User#Name');
    return class User{
        constructor(name){
            this[NAME] = name;
        }
        getName(){
            return this[NAME];
        }
        setName(name) {
            this[NAME] = name;
        }
    }
})();

NAME是個Symbol,從外部并不能拿到NAME確切的值,好像是有點私有屬性的意思。但是 有一個 Object.getOwnPropertySymbols() 方法可以拿到對象所有的Symbol屬性,雖然我不知道具體存了個啥,但是能拿到這個標(biāo)識,就可以修改屬性值了:

const smbs = Object.getOwnPropertySymbols(user);
for(let s of smbs) {
    user[s] = 'good';
}
console.log(user.getName());
// good

因此,嚴(yán)格意義上說,Symbol其實并不能實現(xiàn)私有屬性。

不過倒是可以將上面第二種基于散列的方式改為Symbol的方式:

const User = (function () {
    const privateData = {};
    return class User {
        constructor(name){
            this._name = Symbol('name');
            privateData[this._name] = {name: name};
        }
        getName() {
            return privateData[this._name].name;
        }
        setName(name) {
            privateData[this._name].name = name;
        }
    };
})();

這樣對外暴露的只是 _name 的這個符號,外部還是無法直接訪問每個實例的 privateData 中的值。但這個其實和第二種是一樣的,閉包引起的問題還是無法解決。

展望未來

js的私有屬性,目前處于 stage2 階段,目前還未最終確定,不過我們可以先看一下模樣:

class Point {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
  }

  equals(point) {
    return this.#x === point.#x && this.#y === point.#y;
  }
}

語法如上,使用 # 定義私有屬性,在類的內(nèi)部可以直接 使用 this.#x 的形式引用。
目前使用#進(jìn)行定義和訪問私有屬性,未來會不會使用 public,private等關(guān)鍵字不得而知。

目前使用#,可能的原因是,js沒有靜態(tài)類型系統(tǒng)。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文為阮一峰大神的《ECMAScript 6 入門》的個人版提純! babel babel負(fù)責(zé)將JS高級語法轉(zhuǎn)義,...
    Devildi已被占用閱讀 2,129評論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,635評論 1 32
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 3,125評論 2 9
  • 第3章 基本概念 3.1 語法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,504評論 0 21
  • 第一章:塊級作用域綁定 塊級聲明 1.var聲明及變量提升機制:在函數(shù)作用域或者全局作用域中通過關(guān)鍵字var聲明的...
    BeADre_wang閱讀 989評論 0 0

友情鏈接更多精彩內(nèi)容