JavaScript:如何在對象中嵌入私有成員

jopen 9年前發布 | 14K 次閱讀 JavaScript開發 JavaScript

最近,我開發一個項目 Angular Cloud Data Connector, 幫助Angular開發者使用云數據,特別是 Azure移動服務, 使用WEB標準,像索引數據庫(indexed DB)。我嘗試建立一種方式,使得JavaScript開發者能將私有成員嵌入到一個對象中。 

我解決這個問題的技術用到了我命名的閉包空間(closure space)。在這篇入門文章中,我要分享的是如何在你的項目中用它,及它對主流瀏覽器的性能和內存的影響。

在深入學習前,咱們先說下,你為什么需要用到私有成員(private members), 還有一種替代方式來模擬私有成員。

如果你想點評本文,盡情推(推ter)我: @deltakosh

1. 為何要用私有成員(Private Members)

當你用JavaScript 創建一個對象時,可以聲明值成員(value members)。 如果你打算控制對它們的讀/寫訪問操作,可以如下聲明:

var entity = {};
 
entity._property = "hello world";
Object.defineProperty(entity, "property", {
    get: function () { return this._property; },
    set: function (value) {
        this._property = value;
    },
    enumerable: true,
    configurable: true
});

這樣實現,你能完全控制讀和寫操作。問題在于_property 成員仍然可以直接訪問和修改。

這也就是為何我們需要更加穩定可靠的方式,聲明私有成員,它智能通過對象的方法來訪問。

2. 使用閉包空間(Closure Space)

解決方法是使用閉包空間。每當內部函數 (inner fanction) 訪問來自外部函數作用域的變量時,瀏覽器為你分配一段內存空間。有時很取巧,不過就我們的題目來講,這算是一個完美的解決方案。

我們在上個代碼版本中添加這個特性:
var createProperty = function (obj, prop, currentValue) 
{
    Object.defineProperty(obj, prop, 
    {
            get: function () { return currentValue; },
            set: function (value) {
            currentValue = value;
                    },
                    enumerable: true,
                    configurable: true    });
                    } 
var entity = {}; 
var myVar = "hello world";createProperty(entity, "property", myVar);

示例中,createProperty 函數有一個 currentValue 變量,存在 get 和 set 方法。此變量會保存到 get 和 set 函數的閉包空間中。現在,只有這兩個函數能看到和更新 currentValue 變量! 任務完成!

唯一需要警惕 caveat,警告,注意)的是源值 (myVar) 仍可訪問。下面給出另一個更健壯的版本(保護 myVar 變量):

var createProperty = function (obj, prop) {
    var currentValue = obj[prop];
    Object.defineProperty(obj, prop, {
        get: function () { return currentValue; },
        set: function (value) {
            currentValue = value;
        },
        enumerable: true,
        configurable: true
    });
}
 
var entity = {
    property: "hello world"
};
 
createProperty(entity, "property");

采用該函數, 即便源值都銷毀(destructed,注:意思是不能直接賦值)了。到此大功告成了!

3. 性能考慮Performance Considerations

現在咱們看看性能。

很明顯,比起一個簡單的變量,閉包空間,甚或(對象)屬性要慢的多,且更消耗資源。這就是本文更多關注普通方式和閉包空間機制差異的原因。

為證明閉包空間機制并不比標準方式更消耗資源, 我寫了下面代碼做個基準測試:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<style>
    html {
        font-family: "Helvetica Neue", Helvetica;
    }
</style>
<body>
    <div id="results">Computing...</div>
    <script>
        var results = document.getElementById("results");
        var sampleSize = 1000000;
        var opCounts = 1000000;
 
        var entities = [];
 
        setTimeout(function () {
            // Creating entities
            for (var index = 0; index < sampleSize; index++) {
                entities.push({
                    property: "hello world (" + index + ")"
                });
            }
 
            // Random reads
            var start = new Date().getTime();
            for (index = 0; index < opCounts; index++) {
                var position = Math.floor(Math.random() * entities.length);
                var temp = entities[position].property;
            }
            var end = new Date().getTime();
 
            results.innerHTML = "<strong>Results:</strong><br>Using member access: <strong>" + (end - start) + "</strong> ms";
        }, 0);
 
        setTimeout(function () {
            // Closure space =======================================
            var createProperty = function (obj, prop, currentValue) {
                Object.defineProperty(obj, prop, {
                    get: function () { return currentValue; },
                    set: function (value) {
                        currentValue = value;
                    },
                    enumerable: true,
                    configurable: true
                });
            }
            // Adding property and using closure space to save private value
            for (var index = 0; index < sampleSize; index++) {
                var entity = entities[index];
 
                var currentValue = entity.property;
                createProperty(entity, "property", currentValue);
            }
 
            // Random reads
            var start = new Date().getTime();
            for (index = 0; index < opCounts; index++) {
                var position = Math.floor(Math.random() * entities.length);
                var temp = entities[position].property;
            }
            var end = new Date().getTime();
 
            results.innerHTML += "<br>Using closure space: <strong>" + (end - start) + "</strong> ms";
        }, 0);
 
        setTimeout(function () {
            // Using local member =======================================
            // Adding property and using local member to save private value
            for (var index = 0; index < sampleSize; index++) {
                var entity = entities[index];
 
                entity._property = entity.property;
                Object.defineProperty(entity, "property", {
                    get: function () { return this._property; },
                    set: function (value) {
                        this._property = value;
                    },
                    enumerable: true,
                    configurable: true
                });
            }
 
            // Random reads
            var start = new Date().getTime();
            for (index = 0; index < opCounts; index++) {
                var position = Math.floor(Math.random() * entities.length);
                var temp = entities[position].property;
            }
            var end = new Date().getTime();
 
            results.innerHTML += "<br>Using local member: <strong>" + (end - start) + "</strong> ms";
        }, 0);
 
    </script>
</body>
</html>

我創建了一百萬個對象,都有屬性成員。要完成下面三個測試:

  • 執行 1百萬次隨機訪問屬性。

    </li>

  • 執行1百萬次隨機訪問閉包空間實現版本。

    </li>

  • 執行1百萬次隨機訪問常規get/set實現版本。

    </li> </ul>

    測試結果參見下面表格和圖表:

    JavaScript:如何在對象中嵌入私有成員JavaScript:如何在對象中嵌入私有成員

    我們發現,閉包空間實現總是快于常規實現,根據瀏覽器的不同,還可以做進一步的性能優化。

    Chrome 上的性能表現低于預期。或許存在 bug,因此,為確認(存在 bug),我聯系了 Google 項目組,描述發生的癥狀。還有,如果你打算測試在 Microsoft Edge —微軟新發布的瀏覽器,在windows10 中默認安裝—中的性能表現,你可以點擊下載 

    然而,如果仔細研究,你會發現,使用閉包空間或屬性比直接訪問變量成員要10倍左右。 因此,使用要恰當且謹慎。

    JavaScript:如何在對象中嵌入私有成員

    4. 內存占用(Memory Footprint)

    我們也得驗證該技術不會消耗過多內存。為測試內存占用基準情況,我寫了下面代碼段:

    直接屬性引用版本(Reference Code)

    var sampleSize = 1000000;
     var entities = []; 
    // Creating entities
    for (var index = 0; index < sampleSize; index++) {
        entities.push({
                property: "hello world (" + index + ")"
    });}

    常規方式版本(Regular Way,get/set)

    var sampleSize = 1000000;
     
    var entities = [];
     
    // Adding property and using local member to save private value
    for (var index = 0; index < sampleSize; index++) {
        var entity = {};
     
        entity._property = "hello world (" + index + ")";
        Object.defineProperty(entity, "property", {
            get: function () { return this._property; },
            set: function (value) {
                this._property = value;
            },
            enumerable: true,
            configurable: true
        });
     
        entities.push(entity);
    }

    閉包空間版本(Closure Space Version)

    var sampleSize = 1000000;
     
    var entities = [];
     
    var createProperty = function (obj, prop, currentValue) {
        Object.defineProperty(obj, prop, {
            get: function () { return currentValue; },
            set: function (value) {
                currentValue = value;
            },
            enumerable: true,
            configurable: true
        });
    }
     
    // Adding property and using closure space to save private value
    for (var index = 0; index < sampleSize; index++) {
        var entity = {};
     
        var currentValue = "hello world (" + index + ")";
        createProperty(entity, "property", currentValue);
     
        entities.push(entity);
    }

    之后,我(在三個主流瀏覽器上)運行所有的三段代碼,啟動(瀏覽器)內嵌的內存性能分析器(本示例中使用 F12 工具條):

    JavaScript:如何在對象中嵌入私有成員

    我計算機上運行的結果如下圖表:

    JavaScript:如何在對象中嵌入私有成員

    就閉包空間和常規方式,只有 Chrome上,閉包空間(內存占用)表現稍好,在 IE11 和 Firefox上占用內存反而增多,但是瀏覽器的比較結果e—對于現代瀏覽器,用戶很可能不會在意這點差別。


    更多 JavaSc 實踐

    或許你會吃驚,微軟提供了一批有關開源 Javascript 主題的免費學習材料, 我們正在發起一個任務,關于創建更多 Microsoft Edge 來臨 系列。 查看我的文章:

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!