




已阅读5页,还剩8页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
提升JavaScript运行速度1. 循环脚本失控基本上有以下四个方面的原因:1. 在循环中执行了太多的操作。2. 臃肿的函数体3. 过多的递归4. 过多的DOM调用在这篇帖子中,我将会把重点放到第一条上:循环中的过多操作。循环的操作是同步进行的,所以执行一个循环所花费的时间完全取决于循环的次数。因此有两种情况会导致循环执行的时间过长,并直接导致锁定浏览器。一是循环体中包含了太多的操作,二是循环的次数过多。这两种情况都能直接导致锁定浏览器,并显示脚本失控的提示。解决这个问题的诀窍就是用下面这两个问题来评估每个循环:1. 这个循环必须要同步执行么?2. 循环里面的数据,必须要按顺序执行么?如果两个问题的答案都是否定的话,你就可以选择将循环里的操作进行分解。关键是要根据代码的具体环境确定上面两个问题的答案。一个典型的循环可能像下面这个样子:for(var i=0; i 0) setTimeout(arguments.callee, 100); , 100);chunk()函数的用途就是将一个数组分成小块处理(这也是名字的由来),我们可以传递三个参数。要处理的数组对象、处理函数以及一个可选的上下文变量,用于设置process()函数中对应的this对象。第一个timer用于处理操作之间的延时(这里设置为100毫秒,大家可以根据实际需要自行修改)。每次执行这个函数,都会将数组中的第一个对象取出,并传给process()函数进行操作,如果这时process()中还有未处理完的对象,另外一个timer就会启动,用于重复等待。上面提到的循环,可以通过下面的方法使用这个函数:chunk(items, process);需要注意的是,在这里数组采用了队列(queue)的形式,而且在循环的过程中,每次都会发生修改。如果你要修改数组的原始状态,这里介绍两种途径:一种是通过concat()函数,在传递之前,建立一个当前数组的副本:chunk(items.concat(), process);另外一种选择是直接修改chunk()函数,直接在函数内部进行修改:function chunk(array, process, context) var items = array.concat(); /clone the array setTimeout(function() var item = items.shift(); process.call(context, item); if (items.length 0) setTimeout(arguments.callee, 100); , 100);注意这种方法要比只保存一个索引安全的多,因为数组的内容在下次计时器生效之前可能会发生变化。这里提到的chunk()函数,只是优化循环性能的一个起点。你可以根据需要不断改进它,让它拥有更多的功能。比如说,在数组中所有对象都处理完成以后,可以增加一个函数回调。无论你是否会按照这种方式对函数进行修改,这只是一种JavaScript的代码开发模式,可以帮助优化数组的处理性能,还可以避免那个脚本失控的警告。4.递归递归是拖慢脚本运行速度的大敌之一。太多的递归会让浏览器变得越来越慢直到死掉或者莫名其妙的突然自动退出,所以我们一定要解决在JavaScript中出现的这一系列性能问题。在这个系列文章的第二篇中,我曾经简短的介绍了如何通过memoization技术来替代函数中太多的递归调用。memoization是一种可以缓存之前运算结果的技术,这样我们就不需要重新计算那些已经计算过的结果。对于通过递归来进行计算的函数,memoization简直是太有用了。我现在使用的memoizer是由 Crockford写的,主要应用在那些返回整数的递归运算中。当然并不是所有的递归函数都返回整数,所以我们需要一个更加通用的memoizer()函数来处理更多类型的递归函数。function memoizer(fundamental, cache) cache = cache | ; var shell = function(arg) if (! (arg in cache) cachearg = fundamental(shell, arg); return cachearg; ; return shell;这个版本的函数和Crockford写的版本有一点点不同。首先,参数的顺序被颠倒了,原有函数被设置为第一个参数,第二个参数是缓存对象,为可选参数,因为并不是所有的递归函数都包含初始信息。在函数内部,我将缓存对象的类型从数组转换为对象,这样这个版本就可以适应那些不是返回整数的递归函数。在shell函数里,我使用了in操作符来判断参数是否已经包含在缓存里。这种写法比测试类型不是undefined更加安全,因为undefined是一个有效的返回值。我们还是用之前提到的斐波纳契数列来做说明:var fibonacci = memoizer(function(recur, n) return recur(n - 1) + recur(n - 2);, 0: 0, 1: 1 );同样的,执行fibonacci(40)这个函数,只会对原有的函数调用40次,而不是夸张的331,160,280次。memoization对于那些有着严格定义的结果集的递归算法来说,简直是棒极了。然而,确实还有很多递归算法不适合使用memoization方法来进行优化。我在学校时的一位教授一直坚持认为,任何使用递归的情况,如果有需要,都可以使用迭代来代替。实际上,递归和迭代经常会被作为互相弥补的方法,尤其是在另外一种出问题的情况下。将递归算法转换为迭代算法的技术,也是和开发语言无关的。这对JavaScript来说是很重要的,因为很多东西在执行环境中是受到限制的(the importance in JavaScript is greater, though, because the resources of the execution environment are so restrictive.)。让我们回顾一个典型的递归算法,比如说归并排序,在JavaScript中实现这个算法需要下面的代码:function merge(left, right) var result = ; while (left.length 0 & right.length 0) if (left0 right0) result.push(left.shift(); else result.push(right.shift(); return result.concat(left).concat(right);/采用递归实现的归并排序算法function mergeSort(items) if (items.length = 1) return items; var middle = Math.floor(items.length / 2), left = items.slice(0, middle), right = items.slice(middle); return merge(mergeSort(left), mergeSort(right);调用mergeSort()函数处理一个数组,就可以返回经过排序的数组。注意每次调用mergeSort()函数,都会有两次递归调用。这个算法不可以使用memoization来进行优化,因为每个结果都只计算并使用一次,就算缓冲了结果也没有什么用。如果你使用mergeSort()函数来处理一个包含100个元素的数组,总共会有199次调用。1000个元素的数组将会执行1999次调用。在这种情况下,我们的解决方案是将递归算法转换为迭代算法,也就是说要引入一些循环(关于算法,可以参考这篇List Processing: Sort Again, Naturally):/ 采用迭代实现的归并排序算法function mergeSort(items) if (items.length = 1) return items; var work = ; for (var i = 0, len = items.length; i 1; lim = (lim + 1) / 2) for (var j = 0, k = 0; k = 0; i-) for (var j = i; j = 0; j-) if (itemsj pos; j-) if (arrayj arrayj - 1) value = dataj;dataj = dataj - 1;dataj - 1 = value;pos+;if (pos array.length) setTimeout(arguments.callee, 10); else onComplete();)();这个函数借助一个异步管理器来实现了冒泡算法,在每次遍历数组以前暂停一下。onComplete()函数会在数组排序完成后触发,提示用户数据已经准备好。bubbleSort()函数使用了和chunk()函数一样的基本技术(参考我的上一篇帖子),将行为包装在一个匿名函数中,将 arguments.callee传递给setTimeout()以达到重复操作的目的,直至排序完成。如果你要将嵌套的循环拆解成若干个小步骤,以达到解放浏览器的目的,这个函数提供了不错的指导意见。相似的问题还包括过多的递归。每个额外的递归调用都会占用更多的内存,从而减慢浏览器的运行。恼人的是,你可能在浏览器发出脚本失控警告之前,就耗尽了系统的内存,导致浏览器处于停止响应的状态。Crockford在博客上曾经对这个问题进行过深入的讨论。他当时使用的例子,就是用递归生成一个斐波那契数列。function fibonacci(n) return n 0) setTimeout(arguments.callee, 100); ,100);schedule函数有两个参数,一个是包含要执行函数的数组,另外一个是标明this所属的上下文对象。函数数组以队列方式实现,Timer事件每次触发的时候,都会将队列最前面的函数取出并执行,这个函数可以通过下面的方式执行一系列函数:schedule(doSomething, doSomethingElse, doOneMoreThing, window);很希望各个JavaScript的类库都增加类似这样的进程处理函数。YUI在3.0时就已经引入了Queue对象,可以通过timer连续调用一组函数。无论现有的技术可以帮助我们将复杂的进程拆分到什么程度,对于开发者来说,使用这种方法来理解并确定脚本失控的瓶颈是非常重要的。无论是太多的循环、递归还是其他的什么,你现在应该知道如果处理类似的情况。但要记住,这里提到的技术和函数只是起到抛砖引玉的作用,在实际的应用中,你应该对它们加以改进,这样才能发挥更大的作用。4.dom在过去的几周中,我为大家介绍了几种可以加快JavaScript脚本运行速度的技术。第一节介绍了如何优化循环。第二节的重点放在优化函数内部代码上,还介绍了队列(queuing)和记忆化(memoization)两种技术,来减轻函数的工作负担。第三节就如何将递归转换为迭代循环或者记忆化方式的话题,展开了讨论。第四节是这个系列的最后一篇,也就是本文,将重点阐述过多的DOM操作所带来的影响。我们都知道,DOM操作的效率是很低的,而且不是一般的慢,而且这也是引发性能问题的常见问题之一。为什么会慢呢?因为对DOM的修改为影响网页的用户界面,重绘页面是一项昂贵的操作。太多的DOM操作会导致一系列的重绘操作,为了确保执行结果的准确性,所有的修改操作是按顺序同步执行的。我们称这个过程叫做回流(reflow),同时这也是最昂贵的浏览器操作之一。回流操作主要会发生在几种情况下: * 当对DOM节点执行新增或者删除操作时。 * 动态设置一个样式时(比如element.style.width=10px)。 * 当获取一个必须经过计算的尺寸值时,比如访问offsetWidth、clientHeight或者其他需要经过计算的CSS值(在兼容DOM的浏览器中,可以通过getComputedStyle函数获取;在IE中,可以通过currentStyle属性获取)。解决问题的关键,就是限制通过DOM操作所引发回流的次数。大部分浏览器都不会在JavaScript的执行过程中更新DOM。相应的,这些浏览器将对对DOM的操作放进一个队列,并在JavaScript脚本执行完毕以后按顺序一次执行完毕。也就是说,在JavaScript执行的过程中,用户不能和浏览器进行互动,直到一个回流操作被执行。(失控脚本对话框会触发回流操作,因为他执行了一个中止JavaScript执行的操作,此时会对用户界面进行更新)如果要减少由于DOM修改带来的回流操作,有两个基本的方法。第一个就是在对当前DOM进行操作之前,尽可能多的做一些准备工作。一个经典的例子就是向document对象中添加很多DOM节点:for (var i=0; i items.length; i+) var item = document.createElement(li); item.appendChild(document.createTextNode(Option + i); list.appendChild(item);这段代码的效率是很低的,因为他在每次循环中都会修改当前DOM结构。为了提高性能,我们需要将这个次数降到最低,对于这个案例来说,最好的办法是建立一个文档碎片(document fragment),作为那些已创建元素元素的临时容器,最后一次将容器的内容直接添加到父节点中:var fragment = document.createDocumentFragment();for (var i=0; i items.length; i+) var item = document.createElement(li); item.appendChild(document.createTextNode(Option + i); fragment.appendChild(item);list.appendChild(fragment);经过调整的代码,只会修改一次当前DOM的结构,就在最后一行,而在这之前,我们用文档碎片来保存那些中间结果。因为文档碎片没有任何可见内容,所以这类修改不会触发回流操作。实际上,文档碎片也不能被添加到DOM中,我们需要将它作为参数传给appendChild函数,而实际上添加的不是文档碎片本身,而是它下面的所有子元素。避免不必要回流操作的另外一种方法,就是在对DOM操作之前,把要操作的元素,先从当前DOM结构中删除。对于删除一个元素,基本有两种方法:1. 通过removeChild()或者replaceChild()实现真正意义上的删除。2. 设置该元素的display样式为“none”。而一旦修改操作完成,上面这个过程就需要反转过来,将删除的元素重新添加到当前的DOM结构中,我们还是拿上面的例子来做说明:list.style.display = none;for (var i=0; i items.length; i+) var item = document.createElement(li); item.appendChild(document.createTextNode(Option + i); list.appendChild(item);list.style.display = ;将list的display样式设置为“none”后,就将这个元素从当前的DOM结构中删除了,因为这个节点不再可视。在将display属性设置回之前的默认值之前,向其下添加子元素是不会触发回流操作的。另外一个经常引起回流操作的情况是通过style属性对元素的外观进行修改。比如下面这个例子:element.style.backgroundColor = blue;element.style.color = red;element.style.fontSize = 12em;这段代码修改了三个样式,同时也就触发了三次回流操作。每次修改元素的style属性,都肯定会触发回流操作。如果你要同时修改一个元素的很多样式,最好的办法是将这些样式放到一个class下,然后直接修改元素的class,这可比单独修改元素的样式要强得多。比如下面这个例子:.newStyle background-color: blue; color: red; font-size: 12em;这样我们在JavaScript代码中,只需下面这行代码就可以修改样式:/*element.className = newStyle;*/修改元素的class属性,会一次将所有的样式应用在目标元素上,而且只会触发一次回流操作。这样做不止更加有效,而且还更容易维护。既然DOM几乎在所有情况下都很慢,就很有必要将获取的DOM数据缓存起来。这种方法,不仅对获取那些会触发回流操作的属性(比如offsetWidth等)尤为重要,就算对于一般情况,也同样适用。下面介绍一个效率低的夸张的例子:document.getElementById(myDiv).style.left = document.getElementById(myDiv).offsetLeft + document.getElementById(myDiv).offsetWidth + px;这里对getElementById()调用了三次,是一个很大的问题,访问DOM是很昂贵的,而这三个调用恰恰访问的是同一个元素,也许我们像下面这样写,会更好一些:var myDiv = document.getElementById(myDiv);myDiv.style.left = myDiv.offsetLeft + myDiv.offsetWidth + px;我们去掉了一些冗余操作,现在对DOM操作的次数已经被减小了。对于那些使用次数超过一次的DOM值,我们都应该缓冲起来,这样可以避免无谓的性能消耗。也许,拖慢属性访问速度的罪魁祸首就是HTMLCollection对象。这些对象是object类型的,只要DOM需要返回一组节点时就会使用这个对象,也就是说childNodes属性和getElementsByTagName()的返回值都属于这种情况。我们可能经常会将HTMLCollection当作数组来使用,但实际上他是一个根据DOM结构自动变化的实体对象。每次你访问一个HTMLCollecti
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年公路水运工程试验检测人员应试题及答案
- 安国市2025-2026学年七年级下学期语文月考测试试卷
- 阿拉善盟2025-2026学年七年级下学期语文期中测试试卷
- 安徽省阜阳市界首市2024-2025学年高一下学期第二次月考生物试卷及答案
- 2025 年小升初厦门市初一新生分班考试数学试卷(带答案解析)-(人教版)
- 2024-2025学年云南省楚雄州统编版四年级下册期末考试语文试卷
- 国际销售合同范本
- 私人房屋验收合同范本
- 书稿自费出版合同范本
- 承包水库农庄合同范本
- 中建制冷机组设备吊装工程专项施工方案冷水机组运输及吊装方案
- 读书分享读书交流会《人生海海》
- 人工智能在检验医学中的应用
- 基于保护创始人股东有限公司章程范本
- 郑州外国语中学初一新生分班(摸底)数学模拟考试(含答案)
- 人教版数学四年级上册教材课后习题参考答案(全)
- 人力资源员工旅游活动方案
- 外贸报价单英文模板excel报价单表格模板
- 夜间专项施工专项方案
- 糖尿病足病历讨论
- GB/T 20028-2005硫化橡胶或热塑性橡胶应用阿累尼乌斯图推算寿命和最高使用温度
评论
0/150
提交评论