编写高性能JavaScript

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执行的大型JavaScript应用所设计的。如果你是一个开发者,并且关心内存使用情况与页面性能,你应该了解用户浏览器中的JavaScript引擎是如何运作的。无论是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,这样做可以帮助你更好地优化你的应用程序。这并不是说应该专门为某一浏览器或引擎做优化,千万别这么做。但是,你应该问自己几个问题:•在我的代码里,是否可以使代码更高效一些•主流的JavaScript引擎都做了哪些优化•什么是引擎无法优化的,垃圾回收器(GC)是否能回收我所期望的东西加载快速的网站就像是一辆快速的跑车,需要用到特别定制的零件编写高性能代码时有一些常见的陷阱,在这篇文章中,我们将展示一些经过验证的、更好的编写代码方式。那么,JavaScript在V8里是如何工作的?如果你对JS引擎没有较深的了解,开发一个大型Web应用也没啥问题,就好比会开车的人也只是看过引擎盖而没有看过车盖内的引擎一样。鉴于Chrome是我的浏览器首选,所以谈一下它的JavaScript引擎。V8是由以下几个核心部分组成:六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越•一个基本的编译器,它会在代码执行前解析JavaScript代码并生成本地机器码,而不是执行字节码或简单地解释它。这些代码最开始并不是高度优化的。•V8将对象构建为对象模型。在JavaScript中对象表现为关联数组,但是在V8中对象被看作是隐藏的类,一个为了优化查询的内部类型系统。•运行时分析器监视正在运行的系统,并标识了“hot”的函数(例如花费很长时间运行的代码)。•优化编译器重新编译和优化那些被运行时分析器标识为“hot”的代码,并进行“内联”等优化(例如用被调用者的主体替换函数调用的位置)。•V8支持去优化,这意味着优化编译器如果发现对于代码优化的假设过于乐观,它会舍弃优化过的代码。•V8有个垃圾收集器,了解它是如何工作的和优化JavaScript一样重要。垃圾回收垃圾回收是内存管理的一种形式,其实就是一个收集器的概念,尝试回收不再被使用的对象所占用的内存。在JavaScript这种垃圾回收语言中,应用程序中仍在被引用的对象不会被清除。手动消除对象引用在大多数情况下是没有必要的。通过简单地把变量放在需要它们的地方(理想情况下,尽可能是局部作用域,即它们被使用的函数里而不是函数外层),一切将运作地很好。六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越垃圾回收器尝试回收内存在JavaScript中,是不可能强制进行垃圾回收的。你不应该这么做,因为垃圾收集过程是由运行时控制的,它知道什么是最好的清理时机。“消除引用”的误解网上有许多关于JavaScript内存回收的讨论都谈到delete这个关键字,虽然它可以被用来删除对象(map)中的属性(key),但有部分开发者认为它可以用来强制“消除引用”。建议尽可能避免使用delete,在下面的例子中deleteo.x的弊大于利,因为它改变了o的隐藏类,并使它成为一个慢对象。varo={x:1};deleteo.x;//trueo.x;//undefined你会很容易地在流行的JS库中找到引用删除——这是具有语言目的性的。这里需要注意的是避免在运行时修改”hot”对象的结构。JavaScript引擎可以检测出这种“hot”的对象,并尝试对其进行优化。如果对象在生命周期中其结构没有较大的改变,引擎将会更容易优化对象,而delete操作实际上会触发这种较大的结构改变,因此不利于引擎的优化。对于null是如何工作也是有误解的。将一个对象引用设置为null,并没有使对象变“空”,六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越只是将它的引用设置为空而已。使用o.x=null比使用delete会更好些,但可能也不是很必要。varo={x:1};o=null;o;//nullo.x//TypeError如果此引用是当前对象的最后引用,那么该对象将被作为垃圾回收。如果此引用不是当前对象的最后引用,则该对象是可访问的且不会被垃圾回收。另外需要注意的是,全局变量在页面的生命周期里是不被垃圾回收器清理的。无论页面打开多久,JavaScript运行时全局对象作用域中的变量会一直存在。varmyGlobalNamespace={};全局对象只会在刷新页面、导航到其他页面、关闭标签页或退出浏览器时才会被清理。函数作用域的变量将在超出作用域时被清理,即退出函数时,已经没有任何引用,这样的变量就被清理了。经验法则为了使垃圾回收器尽早收集尽可能多的对象,不要hold着不再使用的对象。这里有几件事需要记住:•正如前面提到的,在合适的范围内使用变量是手动消除引用的更好选择。即一个变量只在一个函数作用域中使用,就不要在全局作用域声明它。这意味着更干净省心的代码。•确保解绑那些不再需要的事件监听器,尤其是那些即将被销毁的DOM对象所绑定的事件监听器。•如果使用的数据缓存在本地,确保清理一下缓存或使用老化机制,以避免大量不被重用的数据被存储。函数接下来,我们谈谈函数。正如我们已经说过,垃圾收集的工作原理,是通过回收不再是访问的内存块(对象)。为了更好地说明这一点,这里有一些例子。functionfoo(){varbar=newLargeObject();六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越bar.someCall();}当foo返回时,bar指向的对象将会被垃圾收集器自动回收,因为它已没有任何存在的引用了。对比一下:functionfoo(){varbar=newLargeObject();bar.someCall();returnbar;}//somewhereelsevarb=foo();现在我们有一个引用指向bar对象,这样bar对象的生存周期就从foo的调用一直持续到调用者指定别的变量b(或b超出范围)。闭包(CLOSURES)当你看到一个函数,返回一个内部函数,该内部函数将获得范围外的访问权,即使在外部函数执行之后。这是一个基本的闭包——可以在特定的上下文中设置的变量的表达式。例如:functionsum(x){functionsumIt(y){returnx+y;};returnsumIt;}//UsagevarsumA=sum(4);六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越varsumB=sumA(3);console.log(sumB);//Returns7在sum调用上下文中生成的函数对象(sumIt)是无法被回收的,它被全局变量(sumA)所引用,并且可以通过sumA(n)调用。让我们来看看另外一个例子,这里我们可以访问变量largeStr吗?vara=function(){varlargeStr=newArray(1000000).join(‘x’);returnfunction(){returnlargeStr;};}();是的,我们可以通过a()访问largeStr,所以它没有被回收。下面这个呢?vara=function(){varsmallStr=‘x’;varlargeStr=newArray(1000000).join(‘x’);returnfunction(n){returnsmallStr;};}();我们不能再访问largeStr了,它已经是垃圾回收候选人了。【译者注:因为largeStr已不存在外部引用了】定时器最糟的内存泄漏地方之一是在循环中,或者在setTimeout()/setInterval()中,但这是相当常见的。思考下面的例子:varmyObj={callMeMaybe:function(){六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越varmyRef=this;varval=setTimeout(function(){console.log(‘Timeisrunningout!’);myRef.callMeMaybe();},1000);}};如果我们运行myObj.callMeMaybe();来启动定时器,可以看到控制台每秒打印出“Timeisrunningout!”。如果接着运行myObj=null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。同样值得牢记的是,setTimeout/setInterval调用(如函数)中的引用,将需要执行和完成,才可以被垃圾收集。当心性能陷阱永远不要优化代码,直到你真正需要。现在经常可以看到一些基准测试,显示N比M在V8中更为优化,但是在模块代码或应用中测试一下会发现,这些优化真正的效果比你期望的要小的多。六年老品牌值得信赖!!!我们的使命:加速中国职业化进程!我们的价值观:感恩、专业、尽责、合作、超越做的过多还不如什么都不做.比如我们想要创建这样一个模块:•需要一个本地的数据源包含数字ID•绘制包含这些数据的表格•添加事件处理程序,当用户点击的任何单元格时切换单元格的cssclass这个问题有几个不同的因素,虽然也很容易解决。我们如何存储数据,如何高效地绘制表格并且append到DOM中,如何更优地处理表格事件?面对这些问题最开始(天真)的做法是使用对象存储数据并放入数组中,使用jQuery遍历数据绘制表格并append到DOM中,最后使用事件绑定我们期望地点击行为。注意:这不是你应该做的varmoduleA=function(){return{data:dataArrayObject,init: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’,function(){$(this).toggleClass(‘active’);});}};}();这段代码简单有效地完成了任务。但在这种情况下,我们遍历的数据只是本应该简单地存放在数组中的数字型属性ID。有趣的是,直接使用DocumentFragment和本地DOM方法比使用jQuery(以这种方式)来生成表格是更优的选择,当然,事件代理比单独绑定每个td具有更高的性能。要注意虽然jQuery在内部使用DocumentFragment,但是在我们的例子中,代码在循环内调用append并且这些调用涉及到一些其他的小知识,因此在这里起到的优化作用不大。希望这不会是一个痛点,但请务必进行基准测试,以确保自己代码ok。对于我们的例子,上述的做法带来了(期望的)性能提升。事件代理对简单的绑定是一种改进,可选的DocumentFragment也起到了助推作用。varmoduleD=function(){return{data

1 / 27
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功