如何编写高效的JavaScript程序.docx
- 文档编号:10767030
- 上传时间:2023-05-27
- 格式:DOCX
- 页数:19
- 大小:23.55KB
如何编写高效的JavaScript程序.docx
《如何编写高效的JavaScript程序.docx》由会员分享,可在线阅读,更多相关《如何编写高效的JavaScript程序.docx(19页珍藏版)》请在冰点文库上搜索。
如何编写高效的JavaScript程序
[实战分析]编写高效的JavaScript程序
摘要:
有人说想要快速的加载Web网页就如同汽车一样,需要使用特殊工具。
想知道
JavaScript如何在V8中工作的吗?
如何避免性能中出现的陷阱?
当涉及到编写高效的内存
和快速创建代码时总会出现一些常见的弊端,在这篇文章中我们将带领大家探索高效编写代
码的测试验证方法。
AddyOsmani是谷歌公司Chrome团队中的一名程序开发工程师。
他是一位JavaScript
爱好者,曾经编写过一本开放源码方面的书籍《LearningJavaScriptDesignPatterns》
以及《DevelopingBackboneApplications》。
为Modernizr和jQuery社区贡献了开源
项目,目前正在从事‘Yeoman’项目,旨在为开发者提供一系列健壮的工具、程序库和工作
流,帮助他们快速构建出漂亮、引人注目的Web应用。
本文作者将带领大家探索高效编写
代码的测试验证方法。
文章内容如下:
JavaScript引擎包括GoogleV8(Chrome,Node)都是专为快速执行大型JavaScript程序
而设计的。
在开发过程中,如果你在乎内存使用率和性能情况,那么你应该会关心在用户的
浏览器中JavaScript引擎背后是怎么样的。
无论是V8、SpiderMonkey(Firefox)、Carakan
(Opera)、Chakra(IE)还是其他,有了它们可以帮助你更好的优化应用程序。
我们应该时不时地询问自己:
·我还能做些什么使代码更加有效?
·主流的JavaScript引擎做了哪些优化?
·什么是引擎无法优化的,我能期待利用垃圾回收进行清洁吗?
快速的加载Web网页就如同汽车一样,需要使用特殊工具。
当涉及到编写高效的内存和快速创建代码时总会出现一些常见的弊端,在这篇文章中我们将
探索高效编写代码的测试验证方法。
一、JavaScript如何在V8中工作?
如果你对JS引擎没有较深的了解,开发一个大型Web应用也没啥问题,就好比会开车的
人也只是看过引擎盖而没有看过车盖内的引擎一样(这里将Web网页比如成汽车)。
Chrome
浏览器是我的优先选择,这里我将谈下V8的核心组件:
·一个基本的编译器,在代码执行前分析JavaScript、生成本地机器代码而非执行字
节代码或是简单的解释,该段代码之初不是高度优化的。
·V8用对象模型“表述”对象。
在JavaScript中,对象是一个关联数组,但是V8中,
对象被“表述”为隐藏类,这种隐藏类是V8的内部类型,用于优化后的查找。
·运行时分析器监视正在运行的系统并优化“hot”(活跃)函数。
(比如,终结运行已久
的代码)
·通过运行时分析器把优化编译器重新编译和被运行时分析器标识为“hot”的代码,这
是一种有效的编译优化技术,(例如用被调用者的主体替换函数调用的位置)。
·V8支持去优化,也就是说当你发现一些假设的优化代码太过乐观,优化编译器可以
退出已生成的代码。
·垃圾回收,了解它是如何工作的,如同优化JavaScript一样同等重要。
二、垃圾回收
垃圾回收是内存管理的一种形式,它试图通过将不再使用的对象修复从而释放内存占用率。
垃圾回收语言(比如JavaScript)是指在JavaScript这种垃圾回收语言中,应用程序中仍
在被引用的对象不会被清除。
手动消除对象引用在大多数情况下是没有必要的。
通过简单地
把变量放在需要它们的地方(理想情况下,尽可能是局部作用域,即它们被使用的函数里而
不是函数外层),一切将运作地很好。
垃圾回收清除内存
在JavaScript中强制执行垃圾回收是不取的,当然,你也不会想这么做,因为垃圾回收进
程被运行时控制着,它知道什么时候才是适合清理代码的最好时机。
1.“消除引用”的误解(De-ReferencingMisconceptions)
在JavaScript中回收内存在网上引发了许多争论,虽然它可以被用来删除对象(map)中
的属性(key),但有部分开发者认为它可以用来强制“消除引用”。
建议尽可能避免使用
delete,在下面的例子中deleteo.x的弊大于利,因为它改变了o的隐藏类,使它成为通用
的慢对象。
1.varo={x:
1};
2.
3.deleteo.x;//true
4.
5.o.x;//undefined
目的是为了在运行时避免修改活跃对象的结构,JavaScript引擎可以删除类似“hot”对象,并
试图对其进行优化。
如果该对象的结果没有太大改变,超过生命周期,删除可能会导致其改
变。
对于null是如何工作也是有误解的。
将一个对象引用设置为null,并没有使对象变“空”,只
是将它的引用设置为空而已。
使用o.x=null比使用delete会更好些,但可能也不是很必要。
1.varo={x:
1};
2.
3.o=null;
4.
5.o;//null
6.
7.o.x//TypeError
8.
如果这个引用是最后一个引用对象,那么该对象可进行垃圾回收;倘若不是,那么此方法不
可行。
注意,无论您的网页打开多久,全局变量不能被垃圾回收清理。
1.1varmyGlobalNamespace={};
当你刷新新页面时,或导航到不同的页面,关闭标签页或是退出浏览器,才可进行全局清
理;当作用域不存在这个函数作用域变量时,这个变量才会被清理,即该函数被退出或是
没有被调用时,变量才能被清理。
经验法则:
为了给垃圾回收创造机会,尽可能早的收集对象,尽量不要隐藏不使用的对象。
这一点主要
是自动发生,这里有几点需要谨记:
1.正如之前我们提到的,手动引用在合适的范围内使用变量是个更好的选择,而不是
将全局变量清空,只需使用不再需要的局部函数变量。
也就是说我们不要为清洁代码
而担心。
2.确保移除不再需要的事件侦听器,尤其是当DOM对象将要被移除时。
3.如果你正在使用本地数据缓存,请务必清洁该缓存或使用老化机制来避免存储那些
不再使用的大量数据。
2.函数(Functions)
正如我们前面提到的垃圾回收的工作原理是对内存堆中已经死亡的或者长时间没有使用的
对象进行清除和回收。
下面的例子能够更好的说明这一点:
1.functionfoo(){
2.
3.varbar=newLargeObject();
4.
5.bar.someCall();
6.
7.}
8.
当foo返回时,bar自动指向垃圾回收对象,这是因为没被调用,这里我们将做个对比:
1.functionfoo(){
2.
3.varbar=newLargeObject();
4.
5.bar.someCall();
6.
7.returnbar;
8.
9.}
10.
11.//somewhereelse
12.
13.varb=foo();
14.
这里有个调用对象且被一直调用着直到这个调用交给b(或是超出b范围)。
3.闭包(Closures)
当你看到一个函数返回到内部函数,该内部函数可以访问外部函数,即使外部函数正在被执
行。
这基本上是一个封闭的,可以在特定的范围内设置变量的表达式。
比如:
1.functionsum(x){
2.functionsumIt(y){
3.returnx+y;
4.};
5.returnsumIt;
6.}
7.
8.//Usage
9.varsumA=sum(4);
10.varsumB=sumA(3);
11.console.log(sumB);//Returns7
在sum调用上下文中生成的函数对象(sumIt)是无法被回收的,它被全局变量(sumA)
所引用,并且可以通过sumA(n)调用。
这里有个示例演示如何访问largeStr?
1.vara=function(){
2.varlargeStr=newArray(1000000).join('x');
3.returnfunction(){
4.returnlargeStr;
5.};
6.}();
我们可以通过a():
1.vara=function(){
2.varsmallStr='x';
3.varlargeStr=newArray(1000000).join('x');
4.returnfunction(n){
5.returnsmallStr;
6.};
7.}();
此时,我们不能访问了,因为它是垃圾回收的候选者。
4.计时器(Timers)
最糟糕的莫过于在循环中泄露,或者在setTimeout()/setInterval()中,但这却是常见的问题
之一。
1.varmyObj={
2.callMeMaybe:
function(){
3.varmyRef=this;
4.varval=setTimeout(function(){
5.console.log('Timeisrunningout!
');
6.myRef.callMeMaybe();
7.},1000);
8.}
9.};
如果我们运行:
1.myObj.callMeMaybe();
在计时器开始前,我们看到每一秒“时间已经不多了”,这时,我们将运行:
1.myObj=null;
三、当心性能陷阱
除非你真正需要,否则永远不要优化代码。
在V8中你能轻易的看到一些细微的基准测试显
示比如N比M更佳,但是在真实的模块代码中或是在实际的应用程序中测试,这些优化所
带来的影响要比你想象中要小的多。
创建一个模块,这里有三点:
1.采用本地的数据源包含ID数值
2.绘制一个包含这些数据的表格
3.添加事件处理程序,当用户点击的任何单元格时切换单元格的cssclass
如何存储数据?
如何高效的绘制表格并追加到DOM?
怎样处理表单上的事件?
注意:
下面的这段代码,千万不能做:
1.varmoduleA=function(){
2.return{
3.
4.data:
dataArrayObject,
5.
6.init:
function(){
7.this.addTable();
8.this.addEvents();
9.},
10.
11.addTable:
function(){
12.
13.for(vari=0;i 14.$tr=$('');
15.for(varj=0;j 16.$tr.append(''+this.data[j]['id']+' '
);
17.}
18.$tr.appendTo($tbody);
19.}
20.
21.},
22.addEvents:
function(){
23.$('tabletd').on('click',function(){
24.$(this).toggleClass('active');
25.});
26.}
27.
28.};
29.}();
很简单,但是却能把工作完成的很好。
请注意,直接使用DocumentFragment和本地DOM方法生成表格比使用jQuery更佳,事
件委托通常比单独绑定每个td更具备高性能。
jQuery一般在内部使用DocumentFragment,
但是在这个例子中,通过内循环调用代码append(),因此,无法在这个例子中进行优化,
但愿这不是一个诟病,但请务必将代码进行基准测试。
这里,我们通过optingfordocumentFragment提高性能,事件代理对简单的绑定是一种改
进,可选的DocumentFragment也起到了助推作用。
1.varmoduleD=function(){
2.
3.return{
4.
5.data:
dataArray,
6.
7.init:
function(){
8.this.addTable();
9.this.addEvents();
10.},
11.addTable:
function(){
12.vartd,tr;
13.varfrag=document.createDocumentFragment();
14.varfrag2=document.createDocumentFragment();
15.
16.for(vari=0;i 17.tr=document.createElement('tr'); 18.for(varj=0;j 19.td=document.createElement('td'); 20.td.appendChild(document.createTextNode(this.data [j])); 21. 22.frag2.appendChild(td); 23.} 24.tr.appendChild(frag2); 25.frag.appendChild(tr); 26.} 27.tbody.appendChild(frag); 28.}, 29.addEvents: function(){ 30.$('table').on('click','td',function(){ 31.$(this).toggleClass('active'); 32.}); 33.} 34. 35.}; 36. 37.}(); 我们不妨看看其他提供性能的方法,也许你曾读过使用原型模式或是使用JavaScript模板 框架进行高度优化。 但是使用这些仅针对可读的代码。 此外,还有预编译。 我们一起来实践 下: 1.moduleG=function(){}; 2. 3.moduleG.prototype.data=dataArray; 4.moduleG.prototype.init=function(){ 5.this.addTable(); 6.this.addEvents(); 7.}; 8.moduleG.prototype.addTable=function(){ 9.vartemplate=_.template($('#template').text()); 10.varhtml=template({'data': this.data}); 11.$tbody.append(html); 12.}; 13.moduleG.prototype.addEvents=function(){ 14.$('table').on('click','td',function(){ 15.$(this).toggleClass('active'); 16.}); 17.}; 18. 19.varmodG=newmoduleG(); 事实证明,选择模板和原型并没有给我们带来多大好处。 四、V8引擎优化技巧: 特定的模式会导致V8优化产生故障。 很多函数无法得到优化,你可以在V8平台使用 --trace-optfile.js搭配d8实用程序。 如果你关心速度,那么尽最大努力确保单态函数(functionsmonomorphic),确保变量(包括 属性,数组和函数参数)只适应同样的隐藏类包含的对象。 下面的代码演示了,我们不可这么做: 1.functionadd(x,y){ 2.returnx+y; 3.} 4. 5.add(1,2); 6.add('a','b'); 7.add(my_custom_object,undefined); 未初始化时不要加载和执行删除操作,因为它们并没有输出差异,这样做反而会使程序变 得更慢。 不要编写大量函数,函数越多越难优化。 1.Objects使用技巧: 适应构造函数来创建对象。 这将确保所创建的所有对象具备相同的隐藏类并有帮助避免更改 这些类。 在程序或者复杂性上不要限制多种对象类型。 (原因: 长原型链中倾向于伤害,只有极少数 的对象属性得到一个特殊的委托)对于活跃对象保持短原型链以及低字段计数。 2.对象克隆(ObjectCloning) 对象克隆对于应用开发者来说是一种常见的现象。 虽然在V8中这是实现各种类型问题的基 准,但是当你进行复制时,一定要当心。 当复制较大的程序时通常很会慢,因此,尽量不要 这么做。 在JavaScript循环中此举是非常糟糕的。 这里有个最快的技巧方案,你不妨学习 下: 1.functionclone(original){ 2.this.foo=original.foo; 3.this.bar=original.bar; 4.} 5.varcopy=newclone(original); 3.模块模式中的缓存功能 在模块模式中使用缓存功能也许在性能方面会有所提升。 请参阅下面的例子,通过jsPerftest 测试。 注,使用这种方法比依靠原型模式更佳。 推荐: 这是测试原型与模块模式性能代码 1.//Prototypalpattern 2.Klass1=function(){} 3.Klass1.prototype.foo=function(){ 4.log('foo'); 5.} 6.Klass1.prototype.bar=function(){ 7.log('bar'); 8.} 9. 10.//Modulepattern 11.Klass2=function(){ 12.varfoo=function(){ 13.log('foo'); 14.}, 15.bar=function(){ 16.log('bar'); 17.}; 18. 19.return{ 20.foo: foo, 21.bar: bar 22.} 23.} 24. 25.//Modulepatternwithcachedfunctions 26.varFooFunction=function(){ 27.log('foo'); 28.}; 29.varBarFunction=function(){ 30.log('bar'); 31.}; 32.Klass3=function(){ 33.return{ 34.foo: FooFunction, 35.bar: BarFunction 36.} 37.} 38. 39.//Iterationtests 40. 41.//Prototypal 42.vari=1000, 43.objs=[]; 44.while(i--){ 45.varo=newKlass1() 46.objs.push(newKlass1()); 47.o.bar; 48.o.foo; 49.} 50. 51.//Modulepattern 52.vari=1000, 53.objs=[]; 54.while(i--){ 55.varo=Klass2() 56.objs.push(Klass2()); 57.o.bar; 58.o.foo; 59.} 60.//Modulepatternwithcachedfunctions 61.vari=1000, 62.objs=[]; 63.while(i--){ 64.varo=Klass3() 65.objs.push(Klass3()); 66.o.bar; 67.o.foo; 68.} 69.//Seethetestforfulldetails 4.数组使用技巧: 一般情况下,我们不要删除数组元素。 它是使数组过渡到较慢的内部表现形式。 当密钥集变 得稀疏时,V8最终将切换到字典模式,这是变慢的原因之一。 五、应用优化技巧: 在Web应用领域里,速度就是一切。 没有用户希望在启动电子表格时需要等上几秒钟,或 者花上几分钟时间来整理信息。 这也是为什么在性能方面,需要格外注意的一点,有人甚至 将编码阶段称为至关重要的一部分。 理解和提升性能方面是非常有用的,但它也有一定的难度。 这里推荐几个步骤来帮你解决: 1.测试: 在应用程序中找到慢的节点(~45%) 2.理解: 查找问题所在(~45%) 3.修复: (~10%) 当然,还有许多工具或是技术方案帮助解决以上这些问题: 1.基准测试。 在JavaScript上有许多方法可进行基准测试。 2.剖析。 Chrome开发工具能够很好的支持JavaScript分析器。 你可以使用这些性能进行检 测,哪些功能占用的时间比较长,然后对其进行优化。 最重要的是,即使是很小的改变也能 影响整体的表现。 关于这款分析工具,这里有份详细的介绍。 3.避免内存泄露-3快照技术。 谷歌开发团队通常会使用Chrome开发工具包括Gmail来帮 助他们发现和修复内存泄露;此外,3snapshot也是不错的选择。 该技术允许在程序中记 录一些行为、强制垃圾回收、查询,如果DOM节点无法返回预期的基线上,3snapshot 帮助分析确定是否存在内存泄露。 4.单页面程序上的内存管理。 当你在编写单页程序时(比如,AngularJS,Backbone, Ember)),内存管理非常重要,他们从未得到刷新。 这就意味着内存泄露很明显。 在移动 单页程序上存在着巨大的陷阱,因为内存有限,长期运行的程序比如email客户端或者社交 网络应用。 因此,它肩负着巨大的责任。 Derick发表了这篇《memorypitfalls》教您如何使用Backbone.js以及如何进行修复。 Felix Geisendörfer的这篇在Node中调试内存泄露也值得一读。 5.最小化回流。 回流是指在浏览器中用户阻止此操作,所以它是有助于理解如何提高回流 时间,你可以使用DocumentFragment一个轻量级的文档对象来处理。 6.Javascript
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 如何 编写 高效 JavaScript 程序