您好,欢迎访问三七文档
当前位置:首页 > 办公文档 > 会议纪要 > 编写快速、高效的JavaScript代码(中文)
编写快速、高效的JavaScript代码(中文)英文原文:WritingFast,Memory-EfficientJavaScript,编译:伯乐在线——戴嘉华许多Javascript引擎都是为了快速运行大型的JavaScript程序而特别设计的,例如Google的V8引擎(Chrome浏览器,Node均使用该引擎)。在你的开发过程中,如果你关心你程序的内存和性能的话,你应该了解并意识到,在你的代码背后,浏览器的JavaScript引擎中到底发生了什么事情。不论的V8,SpiderMonkey(Firefox),Carakan(Opera),Chakra(IE)或者其它类型的引擎。了解引擎背后的一些运行机制可以帮助你更好的优化你的应用程序。这并不是意味着你只为一种浏览器或者一种引擎进行程序的优化,而且,永远不要这样做。然而,你应该问自己下面这些问题:●我应该做点什么才能让我的代码更高效地运行。●流行的JavaScript引擎(通常)是怎么进行优化的。●有什么是引擎无法进行优化的,还有,垃圾回收器是不是按照我预想的那样,回收了我不需要的内存空间。在我们编写高效、快速的代码的时候,有许多常见的陷阱。在这篇文章当中,我们会去探索一些方法,让你的代码拥有更加良好的性能,我们也会为这些代码提供测试样例。JavaScript在V8引擎中是如何工作的?虽然在没有彻底了解JavaScript引擎的情况下,开发出大型的应用程序是有可能的,这就像车主开过车却没有看过引擎盖背后的东西一样。把我选择的Chrome浏览器作为例子,我将会谈谈它的JavaScript引擎的工作机制。V8引擎,是由几个核心的部分组成的。●一个基本的编译器(basecompiler),在你的代码运行之前,它会分析你的JavaScript代码并且生成本地的机器码,而不是通过字节码的方式来运行,也不是简单地解释它。这种机器码起初是没有被高度优化的。●V8通过对象模型(objectmodel)来表达你的对象。对象是在JavaScript中是以关联数组的方式呈现的,但是在V8引擎中,它们是通过隐藏类(hiddenclasses)的方式来表示的。这是一种可以优化查找的内部类型机制(internaltypesystem)。●一个运行期剖析器(runtimeprofiler),它会监视正在运行的系统,并且标识出“热点”函数(“hot”function),也就是那些最后会花费大量运行时间的代码。●一个优化编译器(optimizingcompiler),重新编译并优化运行期剖析器所标识“热点”代码,然后执行优化,例如,把代码进行内联化(inlining)(也就是在函数被调用的地方用函数主体去取代)。●V8引擎支持逆优化(deoptimization),意味着如果优化编译器发现在某些假定的情况下,把一些已经优化的代码进行了过度的优化,它就会把它门从生成的代码中抽离出来。●V8拥有垃圾回收器。理解它是如何运作的和理解如何优化你的JavaScript代码同等重要。垃圾回收垃圾回收是一种内存管理机制。垃圾回收器的概念是,它会尝试去重新分配已经不需要的对象所占据的内存空间。在如JavaScript拥有垃圾回收机制的语言中,如果你的程序中仍然存在指向一个对象的引用,那么该对象将不会被回收。在大多数的情况下,我们没有必要去手动得解除对象的引用(de-referencing)。只要简单地把变量放在它们应该的地方(在理想的情况下,变量应该尽量为局部变量,也就是说,在它们被使用的函数中声明它们,而不是在更外层的作用域),垃圾就能正确地被回收。在JavaScript中强制进行垃圾回收是一件不可能的事情,而且你也不会想这样做。因为垃圾回收的过程是由运行期所控制的,回收器通常知道垃圾回收的最佳时机在什么时候。关于解除引用的误解在网上不少关于JavaScript的内存分配问题的讨论中,关键字delete被频繁提出。虽然它本意是用来删除映射(map)中的键(keys),但是不少的开发者认为也可以使用它来强制解除引用。在可能的情况下,尽量避免使用delete。在下面的例子中,删除o.x在的代码背后会发生一些弊大于利的事情,因为它会改变o的隐藏类,并且把它转化成一般的对象,而这些一般对象会更慢。123varo={x:1};deleteo.x;//trueo.x;//undefined也就是说,在现在流行的JavaScript库中,你几乎肯定能找到delete删除引用的身影——它也确实存在这个语言目的。这里提出来的主旨是,让大家尽量避免在运行期改变热点对象(hotobjects)的结构。JavaScript引擎可以检测出这种的“热点”对象并尝试去优化它们,如果在对象的生命期中没有遇到重大的结构改变,引擎的检测和优化过程会来得更加容易,而使用delete则会触发对象结构上的这种改变。不少人对null的使用上也存在误解。将一个对象的引用设为null,并不是意味着“清空”该对象,而是将该引用指向null。用o.x=null比用delete要好,但这甚至可能不是必要的。1234varo={x:1};o=null;o;//nullo.x//TypeError如果被删除的引用是指向对象的最后一个引用,那么该对象就满足了垃圾回收的资格。如果该引用不是指向对象的最后一个引用,那么该对象仍然可以被获取,而不会被垃圾回收。另外要重点注意的是,要意识到,在你页面的生命期中,全局变量不会被垃圾回收器所清理。只要你的页面保持打开状态,JavaScript运行期中的全局对象就会常驻在内存当中。1varmyGlobalNamespace={};只有当你刷新页面,导航到不同的页面,关闭选项卡,或关闭你的浏览器,全局变量才会被清理。当函数作用域变量超出作用域范围,它就会被清理。当函数完全结束,并且再没有任何引用指向其中的变量,函数中的变量会被清理。经验法则为了给垃圾回收器尽早,尽量多地回收对象的机会,不要保留你不再需要的对像。这种情况大多会自动发生;这里有几件事是要谨记的:●就像之前所说的那样,一个比手动解除引用更好的选择是,在恰当的作用域中使用变量。也就是说,用可以自动从作用域中剔除的函数局部变量,去取代要手动清空的全局变量。这意味着你的代码会更加的整洁且要担忧的事情会更少。●确保要及时注销掉你不再需要的监听事件。特别是对那些必然要删除的DOM对象。●如果你正在使用本地数据缓存的话,确保要清除数据缓存或者使用老化机制(agingmechanism),以免保存了大量你不大可能复用的数据。函数接下来,让我们看看函数。正如我们所说的,垃圾回收是通过重新分配已经无法通过引用获得的内存块(对象)来工作的。为了更好地说明这一点,这里有一些例子。1234functionfoo(){varbar=newLargeObject();bar.someCall();}当foo函数结束的时候,bar指向的对象就会自动地被垃圾回器所获取,因为已经没有任何引用指向该对象了。对比以下代码:12345678functionfoo(){varbar=newLargeObject();bar.someCall();returnbar;}//somewhereelsevarb=foo();现在我们有了一个指向该对象的引用,这个引用会在该次调用中保留下来,直到调用者将b赋值给其他东西(或者b超出了作用域范围)。闭包现在我们来看看一个返回内部函数的函数,那个内部函数可以访问到更外层的作用域,即使外部函数已经执行完毕。这基本上就是一个闭包——一种可以使用设置在特殊上下文中的变量的表现。例如:1234567891011functionsum(x){functionsumIt(y){returnx+y;};returnsumIt;}//UsagevarsumA=sum(4);varsumB=sumA(3);console.log(sumB);//Returns7在sum运行上下文中创造的函数对象不会被垃圾回收,因为它被一个全局变量所指向,仍然非常容易被访问到。它可以通过sumA(n)来运行。让我们来看另外一个例子。这里,我们可以访问到largeStr吗?123456vara=function(){varlargeStr=newArray(1000000).join('x');returnfunction(){returnlargeStr;};}();答案是肯定的,我们可以通过a()来访问到它,所以它不会被回收。我们看看这个会怎么样:1234567vara=function(){varsmallStr='x';varlargeStr=newArray(1000000).join('x');returnfunction(n){returnsmallStr;};}();我们再也不能访问到它了,它会成为垃圾回收的候选对象。定时器最糟糕的状况之一是内存在循环中,或者在setTimeout()/setInterval()中泄露,但这相当的常见。考虑下面的例子:123456789varmyObj={callMeMaybe:function(){varmyRef=this;varval=setTimeout(function(){console.log('Timeisrunningout!');myRef.callMeMaybe();},1000);}};如果我们这样运行:1myObj.callMeMaybe();开始定时器,我们会看到每秒钟显示“Timeisrunningout!”然后如果我们运行下面代码:1myObj=null;定时器仍然运作。myObj不会被垃圾回收,因为传入setTimout的闭包函数仍然需要它来保证正常运作。反过来,闭包函数保留了指向myObj的引用,因为它通过myRef来获取了该对象。如果我们把该闭包函数传入其他任何的函数,同样的事情一样会发生,函数中仍然会存在指向对象的引用。同样值得牢牢记住的是,在setTimeout/setInterval的调用中的引用,例如函数引用,在运行完成之前是不会被垃圾回收的。注意性能陷阱很重要的一点是,除非你真正需要,否则没有必要优化你的代码,这个怎么强调都不为过。在大量的微基准测试中,你可以很轻易地发现,在V8引擎中N比M更加的优化,但是如果在真实的代码模型或者在真正的应用程序中进行测试,那些优化的实际影响可能比你期望的要小得多。假设现在我们想要建立的一个模块:●通过数字ID取出本地存储的数据资源。●用获得的数据生成表格内容。●为每个表格单元添加事件处理,每当用户点击表格单元,切换表格单元的class。即使这个问题解决起来很直观,但是有一些困难的因素。我们如何去存储这些数据,如何可以高效地生成一个表格并把它添加到DOM中去,如何优化地处理这个表格的事件处理?第一个(也是幼稚的)采取的方案可能是将每块可获取的数据存放在一个对象中,然后把所有对象集合到一个数组当中。有的人可能会用jQuery去循环访问数据然后把生成表格内容,然后把它添加到DOM中。最后,有的人可能会用使用事件绑定添加点击我们需要的点击事件。注意:这不是你应该做的事情:123456varmoduleA=function(){return{data:dataArrayObject,78910111213141516171819202122232425262728293init:function(){this.addTable();this.addEvents();},addTable:function(){for(vari=0;irows;i++){$tr=$('tr/tr');for(varj=0;jthis.data.length;j++){$tr.append('td'+this.data[j]['id']+'/td');}$tr.appendTo($tbody);}},addEvents:function(){$('tabletd').on('click',
本文标题:编写快速、高效的JavaScript代码(中文)
链接地址:https://www.777doc.com/doc-6150498 .html