如何在 JavaScript 对象中嵌入私有成员 - Go语言中文社区

如何在 JavaScript 对象中嵌入私有成员


最近,我开发一个项目 Angular Cloud Data Connector, 帮助Angular开发者使用云数据,特别是 Azure移动服务, 使用WEB标准,像索引数据库(indexed DB)。我尝试建立一种方式,使得JavaScript开发者能将私有成员嵌入到一个对象中。

如何在 JavaScript 对象中嵌入私有成员

我解决这个问题的技术用到了我命名的闭包空间(closure space)。在这篇入门文章中,我要分享的是如何在你的项目中用它,及它对主流浏览器的性能和内存的影响。

在深入学习前,咱们先说下,你为什么需要用到私有成员(private members), 还有一种替代方式来模拟私有成员。

如果你想点评本文,尽情推(twitter)我: @deltakosh

1. 为何要用私有成员(Private Members)

当你用JavaScript 创建一个对象时,可以声明值成员(value members)。 如果你打算控制对它们的读/写访问操作,可以如下声明:

  1. var entity = {}; 
  2.  
  3. entity._property = "hello world"
  4. Object.defineProperty(entity, "property", { 
  5.     get: function () { return this._property; }, 
  6.     set: function (value) { 
  7.         this._property = value; 
  8.     }, 
  9.     enumerable: true
  10.     configurable: true 
  11. }); 

这样实现,你能完全控制读和写操作。问题在于_property 成员仍然可以直接访问和修改。

这也就是为何我们需要更加稳定可靠的方式,声明私有成员,它智能通过对象的方法来访问。

2. 使用闭包空间(Closure Space)

解决方法是使用闭包空间。每当内部函数 (inner fanction) 访问来自外部函数作用域的变量时,浏览器为你分配一段内存空间。有时很取巧,不过就我们的题目来讲,这算是一个***的解决方案。

我们在上个代码版本中添加这个特性:

  1. var createProperty = function (obj, prop, currentValue)  
  2.     Object.defineProperty(obj, prop,  
  3.     { 
  4.             get: function () { return currentValue; }, 
  5.             set: function (value) { 
  6.             currentValue = value; 
  7.                     }, 
  8.                     enumerable: true
  9.                     configurable: true    }); 
  10.                     }  
  11. var entity = {};  
  12. var myVar = "hello world";createProperty(entity, "property", myVar); 

示例中,createProperty 函数有一个 currentValue 变量,存在 get 和 set 方法。此变量会保存到 get 和 set 函数的闭包空间中。现在,只有这两个函数能看到和更新 currentValue 变量! 任务完成!

唯一需要警惕 caveat,警告,注意)的是源值 (myVar) 仍可访问。下面给出另一个更健壮的版本(保护 myVar 变量):

  1. var createProperty = function (obj, prop) { 
  2.     var currentValue = obj[prop]; 
  3.     Object.defineProperty(obj, prop, { 
  4.         get: function () { return currentValue; }, 
  5.         set: function (value) { 
  6.             currentValue = value; 
  7.         }, 
  8.         enumerable: true
  9.         configurable: true 
  10.     }); 
  11.  
  12. var entity = { 
  13.     property: "hello world" 
  14. }; 
  15.  
  16. createProperty(entity, "property"); 

采用该函数, 即便源值都销毁(destructed,注:意思是不能直接赋值)了。到此大功告成了!

3. 性能考虑Performance Considerations

现在咱们看看性能。

很明显,比起一个简单的变量,闭包空间,甚或(对象)属性要慢的多,且更消耗资源。这就是本文更多关注普通方式和闭包空间机制差异的原因。

为证明闭包空间机制并不比标准方式更消耗资源, 我写了下面代码做个基准测试:

  1. <!DOCTYPE html> 
  2. <html xmlns="http://www.w3.org/1999/xhtml"
  3. <head> 
  4.     <title></title> 
  5. </head> 
  6. <style> 
  7.     html { 
  8.         font-family: "Helvetica Neue", Helvetica; 
  9.     } 
  10. </style> 
  11. <body> 
  12.     <div id="results">Computing...</div> 
  13.     <script> 
  14.         var results = document.getElementById("results"); 
  15.         var sampleSize = 1000000; 
  16.         var opCounts = 1000000; 
  17.  
  18.         var entities = []; 
  19.  
  20.         setTimeout(function () { 
  21.             // Creating entities 
  22.             for (var index = 0; index < sampleSize; index++) { 
  23.                 entities.push({ 
  24.                     property: "hello world (" + index + ")" 
  25.                 }); 
  26.             } 
  27.  
  28.             // Random reads 
  29.             var start = new Date().getTime(); 
  30.             for (index = 0; index < opCounts; index++) { 
  31.                 var position = Math.floor(Math.random() * entities.length); 
  32.                 var temp = entities[position].property; 
  33.             } 
  34.             var end = new Date().getTime(); 
  35.  
  36.             results.innerHTML = "<strong>Results:</strong><br>Using member access: <strong>" + (end - start) + "</strong> ms"
  37.         }, 0); 
  38.  
  39.         setTimeout(function () { 
  40.             // Closure space ======================================= 
  41.             var createProperty = function (obj, prop, currentValue) { 
  42.                 Object.defineProperty(obj, prop, { 
  43.                     get: function () { return currentValue; }, 
  44.                     set: function (value) { 
  45.                         currentValue = value; 
  46.                     }, 
  47.                     enumerable: true
  48.                     configurable: true 
  49.                 }); 
  50.             } 
  51.             // Adding property and using closure space to save private value 
  52.             for (var index = 0; index < sampleSize; index++) { 
  53.                 var entity = entities[index]; 
  54.  
  55.                 var currentValue = entity.property; 
  56.                 createProperty(entity, "property", currentValue); 
  57.             } 
  58.  
  59.             // Random reads 
  60.             var start = new Date().getTime(); 
  61.             for (index = 0; index < opCounts; index++) { 
  62.                 var position = Math.floor(Math.random() * entities.length); 
  63.                 var temp = entities[position].property; 
  64.             } 
  65.             var end = new Date().getTime(); 
  66.  
  67.             results.innerHTML += "<br>Using closure space: <strong>" + (end - start) + "</strong> ms"
  68.         }, 0); 
  69.  
  70.         setTimeout(function () { 
  71.             // Using local member ======================================= 
  72.             // Adding property and using local member to save private value 
  73.             for (var index = 0; index < sampleSize; index++) { 
  74.                 var entity = entities[index]; 
  75.  
  76.                 entity._property = entity.property; 
  77.                 Object.defineProperty(entity, "property", { 
  78.                     get: function () { return this._property; }, 
  79.                     set: function (value) { 
  80.                         this._property = value; 
  81.                     }, 
  82.                     enumerable: true
  83.                     configurable: true 
  84.                 }); 
  85.             } 
  86.  
  87.             // Random reads 
  88.             var start = new Date().getTime(); 
  89.             for (index = 0; index < opCounts; index++) { 
  90.                 var position = Math.floor(Math.random() * entities.length); 
  91.                 var temp = entities[position].property; 
  92.             } 
  93.             var end = new Date().getTime(); 
  94.  
  95.             results.innerHTML += "<br>Using local member: <strong>" + (end - start) + "</strong> ms"
  96.         }, 0); 
  97.  
  98.     </script> 
  99. </body> 
  100. </html> 

我创建了一百万个对象,都有属性成员。要完成下面三个测试:

  • 执行 1百万次随机访问属性。

  • 执行1百万次随机访问闭包空间实现版本。

  • 执行1百万次随机访问常规get/set实现版本。

测试结果参见下面表格和图表:

JavaScript:如何在对象中嵌入私有成员

JavaScript:如何在对象中嵌入私有成员

我们发现,闭包空间实现总是快于常规实现,根据浏览器的不同,还可以做进一步的性能优化。

Chrome 上的性能表现低于预期。或许存在 bug,因此,为确认(存在 bug),我联系了 Google 项目组,描述发生的症状。还有,如果你打算测试在 Microsoft Edge —微软新发布的浏览器,在windows10 中默认安装—中的性能表现,你可以点击下载 

然而,如果仔细研究,你会发现,使用闭包空间或属性比直接访问变量成员要10倍左右。 因此,使用要恰当且谨慎。

JavaScript:如何在对象中嵌入私有成员

4. 内存占用(Memory Footprint)

我们也得验证该技术不会消耗过多内存。为测试内存占用基准情况,我写了下面代码段:

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

  1. var sampleSize = 1000000; 
  2.  var entities = [];  
  3. // Creating entities 
  4. for (var index = 0; index < sampleSize; index++) { 
  5.     entities.push({ 
  6.             property: "hello world (" + index + ")" 
  7. });} 
  8.  
  9. 常规方式版本(Regular Way,get/set) 
  10.  
  11. var sampleSize = 1000000; 
  12.  
  13. var entities = []; 
  14.  
  15. // Adding property and using local member to save private value 
  16. for (var index = 0; index < sampleSize; index++) { 
  17.     var entity = {}; 
  18.  
  19.     entity._property = "hello world (" + index + ")"
  20.     Object.defineProperty(entity, "property", { 
  21.         get: function () { return this._property; }, 
  22.         set: function (value) { 
  23.             this._property = value; 
  24.         }, 
  25.         enumerable: true
  26.         configurable: true 
  27.     }); 
  28.  
  29.     entities.push(entity); 
  30.  
  31. 闭包空间版本(Closure Space Version) 
  32.  
  33. var sampleSize = 1000000; 
  34.  
  35. var entities = []; 
  36.  
  37. var createProperty = function (obj, prop, currentValue) { 
  38.     Object.defineProperty(obj, prop, { 
  39.         get: function () { return currentValue; }, 
  40.         set: function (value) { 
  41.             currentValue = value; 
  42.         }, 
  43.         enumerable: true
  44.         configurable: true 
  45.     }); 
  46.  
  47. // Adding property and using closure space to save private value 
  48. for (var index = 0; index < sampleSize; index++) { 
  49.     var entity = {}; 
  50.  
  51.     var currentValue = "hello world (" + index + ")"
  52.     createProperty(entity, "property", currentValue); 
  53.  
  54.     entities.push(entity); 

之后,我(在三个主流浏览器上)运行所有的三段代码,启动(浏览器)内嵌的内存性能分析器(本示例中使用 F12 工具条):

JavaScript:如何在对象中嵌入私有成员

我计算机上运行的结果如下图表:

JavaScript:如何在对象中嵌入私有成员

就闭包空间和常规方式,只有 Chrome上,闭包空间(内存占用)表现稍好,在 IE11 和 Firefox上占用内存反而增多,但是浏览器的比较结果e—对于现代浏览器,用户很可能不会在意这点差别。

更多 JavaScript 实践

或许你会吃惊,微软提供了一批有关开源 Javascript 主题的免费学习材料, 我们正在发起一个任务,关于创建更多 Microsoft Edge 来临 系列。 查看我的文章:

或者我们团队系列:

以及一些免费工具:Visual Studio 社区Azure 试用版跨浏览器测试工具用于 Mac, Linux, 或者 Windows。

结论(Conclusion)

如你所见,对于创建真正的私有数据来讲,闭包空间属性(机制)是一个很棒的做法。或许你得面对内存消耗小幅度增加(问题),但就我的看法,这却很合理 (这个代价可以换取相对于常规方法更高的性能增长)。

随带说一句, 如果你要自己动手试试,所以代码可以在 here下载。 推荐一篇不错的文章, “how-to” on Azure Mobile Services here

版权声明:本文来源51CTO,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:http://developer.51cto.com/art/201507/482993.htm
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-16 00:36:20
  • 阅读 ( 608 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢