高级之路篇三:高性能js

脚本

 

1、将所有<script>标签放在尽可能接近<body>标签底部的位置,尽量减少对整个页面下载的影响;

2、减少引用外部脚本文件的数量;

3、将脚本成组打包。页面的 <script>标签越少,页面的加载速度就越快,响应也更加迅速。不论外部脚本文件还是内联代码都是如此。

 

函数作用域

 

4、JavaScript 中,数据存储位置可以对代码整体性能产生重要影响。有四种数据访问类型:直接量,变量,数组项,对象成员。它们有不同的性能考虑。直接量和局部变量的访问速度要快于数组项和对象成员的访问速度。

5、某个函数中的作用域链的搜索也会消耗性能,在运行期上下文的作用域链中,一个标识符所处的位置越深,它的读写速度就越慢。所以,函数中局部变量的访问速度总是最快的,而全局变量通常是最慢的(优化的 JavaScript 引擎在某些情况下可以改变这种状况)。请记住,全局变量总是处于运行期上下文作用域链的最后一个位置,所以总是最远才能触及的。最好尽可能使用局部变量。一个好的经验法则是:用局部变量存储本地范围之外的变量值。

6、一个 try-catch 语句不应该作为JavaScript 错误的解决办法。如果你知道一个错误会经常发生,那说明应当修正代码本身的问题。

7、通常,一个函数的激活对象与运行期上下文一同销毁。当涉及闭包时,激活对象就无法销毁了,因为引用仍然存在于闭包的 [[Scope]]属性中。这意味着脚本中的闭包与非闭包函数相比,需要更多内存开销。最好是小心地使用闭包,内存和运行速度都值得被关注。但是,你可以通过本章早先讨论过的关于域外变量的处理建议,减轻对运行速度的影响:将常用的域外变量存入局部变量中,然后直接访问局部变量。

 

prototype原型

 

8、对象可以有两种类型的成员:实例成员(也称作 “own”成员)和原形成员。实例成员直接存在于实例自身,而原形成员则从对象原形继承。你可以使用 hasOwnProperty()函数确定一个对象是否具有特定名称的实例成员,(它的参数就是成员名称)。要确定对象是否具有某个名称的属性,你可以使用操作符 in

9、深入原形链越深,搜索的速度就会越慢。记住,搜索实例成员的过程比访问直接量或者局部变量负担更重,所以增加遍历原形链的开销正好放大了这种效果。

10、成员嵌套越深,访问速度越慢。 location.href 总是快于window.location.href ,而后者也要window.location.href.toString()更快。如果这些属性不是对象的实例属性,那么成员解析还要在每个点上搜索原形链,这将需要更长时间。

11、由于所有这些性能问题与对象成员有关,所以如果可能的话请避免使用它们。更确切地说,你应当小心地,只在必要情况下使用对象成员。例如,没有理由在一个函数中多次读取同一个对象成员的值。一般来说,如果在同一个函数中你要多次读取同一个对象属性,最好将它存入一个局部变量。以局部变量替代属性,避免多余的属性查找带来性能开销。在处理嵌套对象成员时这点特别重要,它们会对运行速度产生难以置信的影响。

 

DOM编程

 

12、一般经验法则是:轻轻地触DOM,并尽量保持在 ECMAScript 范围内完成操作,一次性加载到DOM。

13、更新页面时,使用虽不标准却被良好支持innerHTML 属性更好呢,还是使用纯 DOM 方法,如document.createElement () 更好呢?如果不考虑标准问题,它们的性能如何?答案是:性能差别不大,但是,在所有浏览器中, innerHTML 速度更快一些,除了最新的基于 WebKit 的浏览器(Chrome Safari)。

14、使用 DOM 方法更新页面内容的另一个途径是克隆已有 DOM 元素,而不是创建新的——即使用element.cloneNode()element 是一个已存在的节点)代替 document.createElement();在大多数浏览器上,克隆节点更有效率,但提高不太多。

15、当每次迭代过程访问集合的 length 属性时,它导致集合器更新,在所有浏览器上都会产生明显的性能损失。对一个相关的小集合进行遍历,只要将 length 缓存一下就足够好了。但是遍历数组比遍历集合快,如果先将集合元素拷贝到数组,访问它们的属性将更快。

function loopCacheLengthCollection() {

     var coll = document.getElementsByTagName_r('div'),

     len = coll.length;

     for (var count = 0; count < len; count++) {

         ...... 

     }

}

 

16、访问集合元素时使用局部变量,自定义toArray()函数可认为是一个通用的集合转数组函数。

 

function toArray(coll) {

     for (var i = 0, a = [], len = coll.length ; i < len ; i++) {

          a[i] = coll[i];

     }

     return a;

}

 

17、IE 中, nextSibling 表现得比childNode 好。IE6 中, nextSibling 比对手快16 倍,而在IE7 中快乐 105 倍。鉴于这些结果,在老的 IE 中性能严苛的使用条件下,用 nextSibling 抓取DOM 是首选方法。在其他情况下,主要看个人和团队偏好。

18、在子节点操作中,IE67 8 只支持 children在所有浏览器中 children childNodes 更快,虽然差别不是太大,通常快 1.5 3倍。特别值得注意的是 IE,遍历children 明显快于遍历 childNodes——IE6 中快24 倍,在 IE7 中快124倍。

19、如果浏览器支持 document.querySelectorAll(),那么最好使用它。如果你使用JavaScript 库所提供的选择器API,确认一下该库是否确实使用了原生方法。如果不是,你大概需要将库升

级到新版本。支持选择器 APIInternet Explorer 8 Firefox 3.5 Safari 3.1Chrome 1 Opera 10另一个函数 querySelector()获益,这个便利的函数只返回符合查询条件的第一个节点。

20、重绘与重排版,DOM 改变影响到元素的几何属性(宽和高) ——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作 ——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。

不是所有的 DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。

重绘和重排版是负担很重的操作,可能导致网页应用的用户界面失去相应。所以,十分有必要尽可能减少这类事情的发生。除了上面说的改变几何宽高时会重排版外,还有添加或删除可见的 DOM 元素、元素位置改变、元素尺寸改变(因为边距,填充,边框宽度,宽度,高度等属性改变)、内容改变,例如,文本改变或图片被另一个不同尺寸的所替代、最初的页面渲染、浏览器窗口改变尺寸、

21、下面这些方法:

offsetTop, offsetLeft, offsetWidth, offsetHeight

scrollTop, scrollLeft, scrollWidth, scrollHeight

clientTop, clientLeft, clientWidth, clientHeight

getComputedStyle() ( currentStyle in IE)(在 IE 中此函数称为currentStyle

布局信息由这些属性和方法返回最新的数据,所以浏览器不得不运行渲染队列中待改变的项目并重新排版以返回正确的值。在改变风格的过程中,最好不要使用上面列出的那些属性。任何一个访问都将刷新渲染队列,即使你正在获取那些最近未发生改变的或者与最新的改变无关的布局信息。

22、重排版和重绘代价昂贵,所以,提高程序响应速度一个好策略是减少此类操作发生的机会。为减少发生次数,你应该将多个 DOM 和风格改变合并到一个批次中一次性执行。考虑这个例子:

var el = document.getElementById('mydiv');

el.style.borderLeft = '1px';

el.style.borderRight = '2px';

el.style.padding = '5px';

 

糟糕的例子中,它导致浏览器重排版了三次。大多数现代浏览器优化了这种情况只进行一次重排版,但是在老式浏览器中,或者同时有一个分离的同步进程(例如使用了一个定时器),效率将十分低下。一个达到同样效果而效率更高的方法是:将所有改变合并在一起执行,只修改DOM 一次。可通过使用cssText 属性实现:

 

var el = document.getElementById('mydiv');

el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

 

cssText 属性,覆盖已存在的风格信息。如果你打算保持当前的风格,你可以将它附加在cssText 字符串的后面。

 

el.style.cssText += "; border-left: 1px;";  //不推荐频繁的字符串拼接,借用数组实现,后续讲解。

 

另一个一次性改变风格的办法是修改 CSS 的类名称,而不是修改内联风格代码。这种方法适用于那些风格不依赖于运行逻辑,不需要计算的情况。

 

var el = document.getElementById('mydiv');

el.className = 'active';

 

23、当你需要对 DOM 元素进行多次修改时,你可以通过以下步骤减少重绘和重排版的次数:从文档流中摘除该元素(推荐方法:临时隐藏元素,进行修改,然后再显示它。第二种方法就是用文档片段,文档片断是一个轻量级的 document 对象,它被设计专用于更新、移动节点之类的任务。第三种解决方法首先创建要更新节点的副本(克隆:cloneNode),然后在副本上操作,最后用新节点覆盖老节点。)、对其应用多重改变、将元素带回文档中

24、动画流使用以下步骤可以避免对大部分页面进行重排版:使用绝对座标定位页面动画的元素,使它位于页面布局流之外;启动元素动画。当它扩大时,它临时覆盖部分页面。这是一个重绘过程,但只影响页面的一小部分,避免重排版并重绘一大块页面;当动画结束时,重新定位,从而只一次下移文档其他元素的位置

25、自从版本 7 之后,IE 可以在任何元素(严格模式)上使用 :hover 这个CSS 伪选择器。然而,如果大量的元素使用了 :hover 那么会降低反应速度。此问题在 IE8 中更显著。

26、连接每个句柄都是有代价的,简单而优雅的处理 DOM 事件的技术是事件托管。它基于这样一个事实:事件逐层冒泡总能被父元素捕获。采用事件托管技术之后,你只需要在一个包装元素上挂接一个句柄,用于处理子元素发生的所有事件。根据dom标准,每个事件都有三个阶段:捕获、到达目标、冒泡,虽然IE没有捕获阶段,但是实现事件托管技术只需要冒泡就足够了。默认冒泡的顶层是window。

 

算法和流控制

 

27、四种循环方式:for(){.....}属于直接循环,while(){......}属于前预测循环、do{....}while()属于后测试循环、for(var i in ...){.....}属于枚举循环。

28、在同样的循环迭代操作中, for-in 循环比其他类型的循环慢 7 倍之多。因此推荐的做法如下:除非你需要对数目不详的对象属性进行操作,否则避免使用 for-in 循环。

29、for-in 循环外,其他循环类型性能相当,难以确定哪种循环更快。选择循环类型应基于需求而不是性能。JavaScript 中,倒序循环可以略微提高循环性能,只要你消除因此而产生的额外操作。

 

///////第一种

for (var i=items.length; i--; ){

     process(items[i]);

}

 

///////第二种

 

var j = items.length;

while (j--){

     process(items[j]]);

}

 

///////第三种

 

var k = items.length-1;

do {

     process(items[k]);

} while (k--);

 

ECMA第四版新增了一个方法forEach(),三个参数:值、索引、数组本身,速度方面滞后,慎用!

 

items.forEach(function(value, index, array){

     process(value);

});

 

30、使用 if-else 或者switch 的流行理论是基于测试条件的数量:条件数量较大,倾向于使用 switch 而不是if-else。这通常归结到代码的易读性。这种观点认为,如果条件较少时, if-else 容易阅读,而条件较多时 switch更容易阅读。

31、优化 if-else 的目标总是最小化找到正确分支之前所判断条件体的数量。最简单的优化方法是将最常见的条件体放在首位。另外一种减少条件判断数量的方法是将 if-else 组织成一系列嵌套的if-else 表达式。使用一个单独的一长串的 if-else 通常导致运行缓慢,因为每个条件体都要被计算。

32、当有大量离散值需要测试时, if-else switch 都比使用查表法要慢得多。在 JavaScript 中查表法可使用数组或者普通对象实现,查表法访问数据比 if-else 或者switch 快,特别当条件体的数目很大时。当使用查表法时,必须完全消除所有条件判断。操作转换成一个数组项查询或者一个对象成员查询。使用查表法的一个主要优点是:由于没有条件判断,当候选值数量增加时,很少,甚至没有增加额外的性能开销。

33、递归函数的问题是,一个错误定义,或者缺少终结条件可导致长时间运行,冻结用户界面。此外,递归函数还会遇到浏览器调用栈大小的限制。IE例外,IE只与系统内存有关,其它浏览器由浏览器来决定。当你使用了太多的递归,超过最大调用栈尺寸时,浏览器会出错并弹出以下信息:

Internet Explorer: “Stack overflow at line x”

Chrome 是唯一不显示调用栈溢出错误的浏览器。

34、任何可以用递归实现的算法都可以用迭代实现,循环比反复调用一个函数的开销要低。

35、减少工作量就是最好的性能优化技术。代码所做的事情越少,它的运行速度就越快。根据这些原则,避免重复工作也很有意义。多次执行相同的任务也在浪费时间。制表,通过缓存先前计算结果为后续计算所重复使用,避免了重复工作。这使得制表成为递归算法中有用的技术。

 

function memfactorial(n){

     if (!memfactorial.cache){

          memfactorial.cache = {

               "0": 1,

               "1": 1

          };

     }

     if (!memfactorial.cache.hasOwnProperty(n)){

          memfactorial.cache[n] = n * memfactorial (n-1);

     }

     return memfactorial.cache[n];

}

 

这个使用制表技术的阶乘函数的关键是建立一个缓存对象。此对象位于函数内部,并预置了两个最简单的阶乘: 0 1 。在计算阶乘之前,首先检查缓存中是否已经存在相应的计算结果。没有对应的缓冲值说明这是第一次进行此数值的计算,计算完成之后结果被存入缓存之中,以备今后使用。

 

字符串和正则表达式

 

36、向字符串末尾不断地添加内容,来创建一个字符串(例如,创建一个 HTML 表或者一个XML 文档),但此类处理在一些浏览器上表现糟糕而遭人痛恨。很频繁的拼接字符串建议结合数组的push()、join()方法来实现或更好!

当连接数量巨大或尺寸巨大的字符串时,数组联合是 IE7 和它的早期版本上唯一具有合理性能的方法。

如果你不关心 IE7 和它的早期版本,数组联合是连接字符串最慢的方法之一。使用简单的 ++= 取而代之,可避免(产生)不必要的中间字符串。

37、尽量避免一个正则表达式做太多的工作。复杂的搜索问题需要条件逻辑,拆分为两个或多个正则表达式更容易解决,通常也更高效,每个正则表达式只在最后的匹配结果中执行查找。

38、正则以什么字符结尾的,建议用charAt函数在特定位置上读取字符,字符串函数slice,substr,substring可用于在特定位置上提取并检查字符串的值。所有这些字符串操作函数速度都很快,当您搜索那些不依赖正则表达式复杂特性的文本字符串时,它们有助于您避免正则表达式带来的性能开销。

 

响应接口

 

39、响应时间尽量控制在100ms内,用户会觉得自己就是成立里面的一个对象,超过100ms用户就会觉得已经和程序断开。但是我会消减一半,界定为50ms。

40、JavaScript 定时器延时往往不准确,快慢大约几毫秒。正因为这个原因,定时器不可用于测量实际时间。Windows 系统上定时器分辨率为 15 毫秒,所以最小值建议为 25 毫秒(实际时间是15 30)以确保至少15 毫秒延迟。

41、通常将一个任务分解成一系列子任务。

 

Aja异步JavaScript和XML

 

42、在现代高性能 JavaScript 中使用的三种技术是XHR(XMLHttpRequest)、动态脚本标签插入(同事还可以解决跨越问题的黑客技术)、多部分的 XHR。使用Cometiframe(作为数据传输技术)往往是极限情况,不在这里讨论。

 

var url = '/data.php';

var params = [

     'id=934875',

     'limit=20'

];

var req = new XMLHttpRequest();

req.onreadystatechange = function() {

     if (req.readyState === 4) { //4表示整个响应报文已经接收完可用于操作。等于3呢,表示此时正在与服务器交互,报文还在传输中,即所谓的“流”,这一步是提高数据请求性能的关键所在,但是在IE6/7中是没有这个状态值的!!!

          var responseHeaders = req.getAllResponseHeaders(); // Get the response headers.

          var data = req.responseText; // Get the data.

          // Process the data here...

     }

}

req.open('GET', url + '?' + params.join('&'), true);

req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // Set a request header.

req.send(null); // Send the request.

 

43、如果请求不改变服务器状态只是取回数据(又称作幂等动作)则使用 GETGET 请求被缓冲起来,如果你多次提取相同的数据可提高性能。只有当 URL 和参数的长度超过了2'048 个字符时才使用 POST 提取数据。因为Internet Explorer 限制 URL的长度,过长将导致请求(参数)被截断。

44、最新的技术,多部分 XHRMXHR )允许你只用一个 HTTP 请求就可以从服务器端获取多个资源。它通过将资源(可以是 CSS 文件,HTML 片段,JavaScript 代码,或 base64 编码的图片)打包成一个由特定分隔符界定的大字符串,从服务器端发送到客户端。 JavaScript 代码处理此长字符串,根据它的媒体类型和其他 信息头 解析出每个资源。

45、在考虑数据传输技术时,你必须考虑这些因素:功能集,兼容性,性能,和方向(发给服务器或者从服务器接收)。在考虑数据格式时,唯一需要比较的尺度的就是速度。

46、精简对象属性名,split()是最快的字符串操作之一。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章