最近,我開發一個專案 Angular Cloud Data Connector, 幫助Angular開發者使用雲端數據,特別是 Azure行動服務, 使用WEB標準,像索引資料庫(indexed DB)。我嘗試建立一種方式,讓JavaScript開發者能將私有成員嵌入到一個物件中。
我解決這個問題的技術用到了我命名的閉包空間(closure space)。在這篇入門文章中,我要分享的是如何在你的專案中使用它,以及它對主流瀏覽器的效能和記憶體的影響。
在深入學習前,咱們先說下,你為什麼需要用到私有成員(private members), 還有一種替代方式來模擬私有成員。
如果你想點評本文,盡情推(twitter)我: @deltakosh。
當你用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 成員仍然可以直接存取和修改。
這也就是為何我們需要更穩定可靠的方式,宣告私有成員,它智慧透過物件的方法來存取。
解決方法是使用閉包空間。每當內部函數 (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,註:意思是不能直接賦值)了。到此大功告成了!
#現在咱們看看性能。
很明顯,比起一個簡單的變量,閉包空間,甚或(物件)屬性要慢的多,且更消耗資源。這就是本文更關注普通方式和閉包空間機制差異的原因。
為證明閉包空間機制並不比標準方式更消耗資源, 我寫了下面程式碼做個基準測試:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <style> html { font-family: "Helvetica Neue", Helvetica; } </style> <body> <p id="results">Computing...</p> <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百萬次隨機存取屬性。
執行1百萬次隨機存取閉包空間實作版本。
執行1百萬次隨機存取常規get/set實作版本。
測試結果請參考下面表格和圖表:
#我們發現,閉包空間實作總是快於常規實現,根據瀏覽器的不同,還可以做進一步的效能最佳化。
Chrome 上的效能表現低於預期。或許有 bug,因此,為確認(存在 bug),我聯繫了 Google 專案組,描述發生的症狀。還有,如果你打算測試在 Microsoft Edge —微軟新發布的瀏覽器,在windows10 中預設安裝—中的效能表現,你可以點擊下載 。
然而,如果仔細研究,你會發現,使用閉包空間或屬性比直接存取變數成員大約10倍。 因此,使用要恰當且謹慎。
我們也得驗證該技術不會消耗過多記憶體。為測試記憶體佔用基準情況,我寫了下面程式碼片段:
var sampleSize = 1000000; var entities = []; // Creating entities for (var index = 0; index < sampleSize; index++) { entities.push({ property: "hello world (" + index + ")" });}
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); }
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 工具列):
我電腦上執行的結果如下圖表:
就閉包空間和常規方式,只有Chrome上,閉包空間(內存佔用)表現稍好,在IE11 和Firefox上佔用內存反而增多,但是瀏覽器的比較結果e—對於現代瀏覽器,用戶很可能不會在意這點差異。
可能你會吃驚,微軟提供了一批有關開源 Javascript 主題的免費學習材料, 我們正在發起一個任務,關於創建更多 Microsoft Edge 來臨 系列。 檢視我的文章:
基於HTML5 和Babylon.JS 開發WebGL 3D 基礎
建立單頁應用,基於ASP.NET 和AngularJS
HTML 進階影像技術
或我們團隊系列:
HTML/JavaScript 效能優化使用技巧(系列有7部分,從響應式設計到休閒遊戲的效能優化)
#現代Web 平台快速起步( HTML, CSS, and JS基礎)
開發通用的Windows Apps,使用HTML 和JavaScript 快速起步(使用你自己的JS建立app)
以及一些免費工具:Visual Studio 社區, Azure 試用版和跨瀏覽器測試工具用於Mac, Linux, 或Windows。
如你所見,對於創建真正的私有資料來講,閉包空間屬性(機制)是一個很棒的做法。或許你得面對記憶體消耗小幅度增加(問題),但就我的看法,這卻很合理 (這個代價可以換取相對於常規方法更高的效能成長)。
隨帶說一句, 如果你要自己動手試試,所以程式碼可以在 here下載。 推薦一篇不錯的文章, 「how-to」 on Azure Mobile Services here。
#以上是私有成員在JavaScript物件中如何嵌入的範例程式碼(圖)的詳細內容。更多資訊請關注PHP中文網其他相關文章!