




已阅读5页,还剩6页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
实战分析 编写高效的JavaScript程序摘要:有人说想要快速的加载Web 网页就如同汽车一样,需要使用特殊工具。想知道JavaScript 如何在V8 中工作的吗?如何避免性能中出现的陷阱?当涉及到编写高效的内存和快速创建代码时总会出现一些常见的弊端,在这篇文章中我们将带领大家探索高效编写代码的测试验证方法。Addy Osmani 是谷歌公司Chrome 团队中的一名程序开发工程师。他是一位JavaScript爱好者,曾经编写过一本开放源码方面的书籍Learning JavaScript Design Patterns以及Developing Backbone Applications。为Modernizr 和jQuery 社区贡献了开源项目,目前正在从事Yeoman项目,旨在为开发者提供一系列健壮的工具、程序库和工作流,帮助他们快速构建出漂亮、引人注目的Web 应用。本文作者将带领大家探索高效编写代码的测试验证方法。文章内容如下:JavaScript 引擎包括Google V8(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-Referencing Misconceptions)在JavaScript 中回收内存在网上引发了许多争论,虽然它可以被用来删除对象(map)中的属性(key),但有部分开发者认为它可以用来强制“消除引用”。建议尽可能避免使用delete,在下面的例子中delete o.x 的弊大于利,因为它改变了o 的隐藏类,使它成为通用的慢对象。1. var o = x: 1 ;2.3. delete o.x; / true4.5. o.x; / undefined目的是为了在运行时避免修改活跃对象的结构,JavaScript 引擎可以删除类似“hot”对象,并试图对其进行优化。如果该对象的结果没有太大改变,超过生命周期,删除可能会导致其改变。对于null 是如何工作也是有误解的。将一个对象引用设置为null,并没有使对象变“空”,只是将它的引用设置为空而已。使用o.x= null 比使用delete 会更好些,但可能也不是很必要。1. var o = x: 1 ;2.3. o = null;4.5. o; / null6.7. o.x / TypeError8.如果这个引用是最后一个引用对象,那么该对象可进行垃圾回收;倘若不是,那么此方法不可行。注意,无论您的网页打开多久,全局变量不能被垃圾回收清理。1. 1var myGlobalNamespace = ;当你刷新新页面时,或导航到不同的页面,关闭标签页或是退出浏览器,才可进行全局清理;当作用域不存在这个函数作用域变量时,这个变量才会被清理,即该函数被退出或是没有被调用时,变量才能被清理。经验法则:为了给垃圾回收创造机会,尽可能早的收集对象,尽量不要隐藏不使用的对象。这一点主要是自动发生,这里有几点需要谨记:1. 正如之前我们提到的,手动引用在合适的范围内使用变量是个更好的选择,而不是将全局变量清空,只需使用不再需要的局部函数变量。也就是说我们不要为清洁代码而担心。2. 确保移除不再需要的事件侦听器,尤其是当DOM 对象将要被移除时。3. 如果你正在使用本地数据缓存,请务必清洁该缓存或使用老化机制来避免存储那些不再使用的大量数据。2.函数(Functions)正如我们前面提到的垃圾回收的工作原理是对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。下面的例子能够更好的说明这一点:1. function foo() 2.3. var bar = new LargeObject();4.5. bar.someCall();6.7. 8.当foo 返回时,bar 自动指向垃圾回收对象,这是因为没被调用,这里我们将做个对比:1. function foo() 2.3. var bar = new LargeObject();4.5. bar.someCall();6.7. return bar;8.9. 10.11. / somewhere else12.13. var b = foo();14.这里有个调用对象且被一直调用着直到这个调用交给b(或是超出b 范围)。3.闭包(Closures)当你看到一个函数返回到内部函数,该内部函数可以访问外部函数,即使外部函数正在被执行。这基本上是一个封闭的,可以在特定的范围内设置变量的表达式。比如:1. function sum (x) 2. function sumIt(y) 3. return x + y;4. ;5. return sumIt;6. 7.8. / Usage9. var sumA = sum(4);10. var sumB = sumA(3);11. console.log(sumB); / Returns 7在sum 调用上下文中生成的函数对象(sumIt)是无法被回收的,它被全局变量(sumA)所引用,并且可以通过sumA(n)调用。这里有个示例演示如何访问largeStr?1. var a = function () 2. var largeStr = new Array(1000000).join(x);3. return function () 4. return largeStr;5. ;6. ();我们可以通过a():1. var a = function () 2. var smallStr = x;3. var largeStr = new Array(1000000).join(x);4. return function (n) 5. return smallStr;6. ;7. ();此时,我们不能访问了,因为它是垃圾回收的候选者。4.计时器(Timers)最糟糕的莫过于在循环中泄露,或者在setTimeout()/setInterval()中,但这却是常见的问题之一。1. var myObj = 2. callMeMaybe: function () 3. var myRef = this;4. var val = setTimeout(function () 5. console.log(Time is running out!);6. myRef.callMeMaybe();7. , 1000);8. 9. ;如果我们运行:1. myObj.callMeMaybe();在计时器开始前,我们看到每一秒“时间已经不多了”,这时,我们将运行:1. myObj = null;三、当心性能陷阱除非你真正需要,否则永远不要优化代码。在V8 中你能轻易的看到一些细微的基准测试显示比如N 比M 更佳,但是在真实的模块代码中或是在实际的应用程序中测试,这些优化所带来的影响要比你想象中要小的多。创建一个模块,这里有三点:1. 采用本地的数据源包含ID 数值2. 绘制一个包含这些数据的表格3. 添加事件处理程序,当用户点击的任何单元格时切换单元格的css class如何存储数据?如何高效的绘制表格并追加到DOM?怎样处理表单上的事件?注意:下面的这段代码,千万不能做:1. var moduleA = 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 (var i = 0; i rows; i+) 14. $tr = $();15. for (var j = 0; j this.data.length; j+) 16. $tr.append( + this.datajid + );17. 18. $tr.appendTo($tbody);19. 20.21. ,22. addEvents: function () 23. $(table td).on(click, function () 24. $(this).toggleClass(active);25. );26. 27.28. ;29. ();很简单,但是却能把工作完成的很好。请注意,直接使用DocumentFragment 和本地DOM 方法生成表格比使用jQuery 更佳,事件委托通常比单独绑定每个td 更具备高性能。jQuery 一般在内部使用DocumentFragment,但是在这个例子中,通过内循环调用代码append() ,因此,无法在这个例子中进行优化,但愿这不是一个诟病,但请务必将代码进行基准测试。这里,我们通过opting for documentFragment 提高性能,事件代理对简单的绑定是一种改进,可选的DocumentFragment 也起到了助推作用。1. var moduleD = function () 2.3. return 4.5. data: dataArray,6.7. init: function () 8. this.addTable();9. this.addEvents();10. ,11. addTable: function () 12. var td, tr;13. var frag = document.createDocumentFragment();14. var frag2 = document.createDocumentFragment();15.16. for (var i = 0; i rows; i+) 17. tr = document.createElement(tr);18. for (var j = 0; j this.data.length; j+) 19. td = document.createElement(td);20. td.appendChild(document.createTextNode(this.dataj);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. var template = _.template($(#template).text();10. var html = 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. var modG = new moduleG();事实证明,选择模板和原型并没有给我们带来多大好处。四、V8 引擎优化技巧:特定的模式会导致V8 优化产生故障。很多函数无法得到优化,你可以在V8 平台使用-trace-opt file.js 搭配d8 实用程序。如果你关心速度,那么尽最大努力确保单态函数(functions monomorphic),确保变量(包括属性,数组和函数参数)只适应同样的隐藏类包含的对象。下面的代码演示了,我们不可这么做:1. function add(x, y) 2. return x+y;3. 4.5. add(1, 2);6. add(a,b);7. add(my_custom_object, undefined);未初始化时不要加载和执行删除操作,因为它们并没有输出差异,这样做反而会使程序变得更慢。不要编写大量函数,函数越多越难优化。1.Objects 使用技巧:适应构造函数来创建对象。这将确保所创建的所有对象具备相同的隐藏类并有帮助避免更改这些类。在程序或者复杂性上不要限制多种对象类型。(原因:长原型链中倾向于伤害,只有极少数的对象属性得到一个特殊的委托)对于活跃对象保持短原型链以及低字段计数。2.对象克隆(Object Cloning)对象克隆对于应用开发者来说是一种常见的现象。虽然在V8 中这是实现各种类型问题的基准,但是当你进行复制时,一定要当心。当复制较大的程序时通常很会慢,因此,尽量不要这么做。在JavaScript 循环中此举是非常糟糕的。这里有个最快的技巧方案,你不妨学习下:1. function clone(original) 2. this.foo = original.foo;3. this.bar = original.bar;4. 5. var copy = new clone(original);3.模块模式中的缓存功能在模块模式中使用缓存功能也许在性能方面会有所提升。请参阅下面的例子,通过jsPerf test测试。注,使用这种方法比依靠原型模式更佳。推荐:这是测试原型与模块模式性能代码1. / Prototypal pattern2. Klass1 = function () 3. Ktotype.foo = function () 4. log(foo);5. 6. Ktotype.bar = function () 7. log(bar);8. 9.10. / Module pattern11. Klass2 = function () 12. var foo = function () 13. log(foo);14. ,15. bar = function () 16. log(bar);17. ;18.19. return 20. foo: foo,21. bar: bar22. 23. 24.25. / Module pattern with cached functions26. var FooFunction = function () 27. log(foo);28. ;29. var BarFunction = function () 30. log(bar);31. ;32. Klass3 = function () 33. return 34. foo: FooFunction,35. bar: BarFunction36. 37. 38.39. / Iteration tests40.41. / Prototypal42. var i = 1000,43. objs = ;44. while (i-) 45. var o = new Klass1()46. objs.push(new Klass1();47. o.bar;48. o.foo;49. 50.51. / Module pattern52. var i = 1000,53. objs = ;54. while (i-) 55. var o = Klass2()56. objs.push(Klass2();57. o.bar;58. o.foo;59. 60. / Module pattern with cached functions61. var i = 1000,62. objs = ;63. while (i-) 64. var o = Klass3()65. objs.push(Klass3();66. o.bar;67. o.foo;68. 69. / See the test for full details4.数组使用技巧:一般情况下,我们不要删除数组元素。它是使数组过渡到较慢的内部表现形式。当密钥集变得稀疏时,V8 最终将切换到字典模式,这是变慢的原因之一。五、应用优化技巧:在Web 应用领域里,速度就是一切。没有用户希望在启动电子表格时需要等上几秒钟,或者花上几分钟时间来整理信息。这也是为什么在性能方面,需要格外注意的一点,有人甚至将编码阶段称为至关重要的一部分。理解和提升性能方面是非常有用的,但它也有一定的难度。这里推荐几个步骤来帮你解决:1. 测试:在应用程序中找到慢的节点 (45%)2. 理解:查找问题所在(45%)3. 修复:(10%)当然,还有许多工具或是技术方案帮助解决以上这些问题:1.基准测试。在JavaScript 上有许多方法可进行基准测试。2.剖析。Chrome 开发工具能够很好的支持JavaScript 分析器。你可以使用这些性能进行检测,哪些功能占用的时间比较长,然后对其进行优化。最重要的是,即使是很小的改变也能影响整体的表现。关于这款分析工具,这里有份详细的介绍。3.避免内存泄露-3 快照技术。谷歌开发团队通常会使用Chrome 开
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 侯车亭施工合同4篇
- 高中学校食品供货合同2篇
- 新解读《GB-T 31006-2014自动分拣过程包装物品条码规范》
- 年会设备租赁合同范本
- 新房全款代购合同范本
- 合伙开汽修合同范本
- 门窗护栏施工合同范本
- 休闲餐饮出租合同范本
- 果蔬分拣合同范本
- 邮政集团柜员合同范本
- 百师联盟2025-2026学年高三上学期开学摸底联考化学试卷
- (2025年标准)蔬菜订单收购协议书
- 茶壶课件教学课件
- 放射卫生知识培训内容描述课件
- 2025-2026学年人教版(2024)初中物理八年级上册教学计划及进度表
- 孟良崮战役课件
- 幼儿园物资采购应急预案(3篇)
- 卫生院医疗质量管理方案
- 2025年山东省济南中考数学试卷及标准答案
- 2025-2026学年人教版(2024)初中数学七年级上册教学计划及进度表
- 2025-2026学年冀教版(2024)小学数学三年级上册教学计划及进度表
评论
0/150
提交评论