高性能JavaScript
author: @TiffanysBearjavascript
从《高性能JavaScript》一书中的整理笔记:css
一、将常用的对象成员、数组项、和域外变量存入局部变量html
缘由:数据存储位置对大地代码总体性能会产生重要的影响,直接变量和局部变量的访问速度快于数组和对象成员。由于局部变量位于做用域链的第一个对象中,全局变量位于做用域链的最后一环。变量在做用域链的位置越深,访问的时间就越长。java
var doc = document;
var db = doc.body;
var odiv = doc.getElementById('div1');
复制代码
二、避免使用with表达式,由于他改变了运行期上下文的做用域链。node
三、同理with,也要注意使用try-catch,由于catch也会改变运行期上下文的做用域链。git
四、嵌套成员变量会形成重大的性能影响,尽可能少用。github
五、DOM操做量化问题:数组
// 在循坏中更新页面,问题所在:每次循环都对DOM元素访问了两次
// 一次是读取document.getElementById('here').innerHTML的内容
// 一次是修改它。
function changeDOM() {
for (var i=0; i < 15000; i++) {
document.getElementById('here').innerHTML += 'a';
}
}
// 改变方法,使用局部变量存好改变量,在循环结束时一并修改
function changeDOM() {
var content ='';
for (var i=0; i < 15000; i++) {
content += 'a';
}
document.getElementById('here').innerHTML += content;
}
// 关于js字符串拼接的性能优化问题
// js的处理机制是:新建一个临时字符串,将新字符串赋值为 content + 'a'
// 而后返回这个新字符串并同时销毁原始字符串
// 致使字符串的链接效率较低的重要缘由不只在于对于新的临时变量的不断建立
// 还有js的垃圾回收机制下不断在对象建立期间回收,致使的效率低下
// 提升效率的办法是用数组的join函数:
function changeDOM() {
var content =[];
for (var i=0; i < 15000; i++) {
content.push('a');
}
document.getElementById('here').innerHTML += content.join('');
}
// 可是同时也要注意,后来的大部分浏览器都对“+”的链接字符串作了优化
// 因为SpiderMonkey等引擎对字符串的“+”运算作了优化,结果使用Array.join的效率反而不如直接用"+"!
// 所以建议是:在IE7如下,使用join,在新浏览器下,除了变量缓存外,不须要作别的优化
      
复制代码
六、克隆已有的DOM元素,即element.cloneNode(),比起新建节点来讲,即element.createElement(),会快一点,可是性能提升不是很大。浏览器
七、遍历数组明显快于一样大小和内容的HTML集合缓存
八、 for循环时,HTML某元素集合的长度不建议直接做为循环终止条件,最好将集合的长度赋给一个变量,而后使用变量做为循环终止条件;
缘由:当每次迭代过程访问集合的length时,它致使集合器更新,在全部的浏览器上都会产生明显的性能损失。
九、须要考虑实际状况的优化,根据7,能够将集合中的元素经过for循坏赋值到数组中,访问数组的数组快于集合。可是要注意对于复制的开销是否值得。
function toArray(collection) {
var arr = [];
var clen = collection.length;
for (var i= 0; i < clen; i++) {
arr[i] = collection[i];
}
}
复制代码
十、获取DOM节点,使用nextSibling方式与childNodes方式,在不一样的浏览器中,这两种方法的时间基本相等。可是在IE中,nextSibling比childNodes好,IE6下,nextSibling比对手快16倍,在IE7下,快105倍。所以,在老的IE中性能严苛的使用条件下,用nextSibling较好。
十一、querySelectorAll()能够联合查询,即querySelectorAll(‘div .warning,div .notice’),在各大浏览器中支持也挺好的,还能够过滤不少非元素节点;
这个网站是:canIuse,能够检查HTML、CSS元素在各大浏览器的兼容状况,一个颇有用的网站!
十二、重绘和重排版;
重绘:不须要改变元素的长度和宽度,不影响DOM的几何属性;
重排版:影响了几何属性,须要从新计算元素的几何属性,并且其余元素的几何属性有可能也会受影响。浏览器会在重排版过程当中,从新绘制屏幕上受影响的部分。
获取布局信息的操做将致使刷新队列的动做,如使用:offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
、scrollTop
、scrollLeft
、scrollWidth
、clientTop
、clientLeft
、clientHeight
、geteComputedStyle()
(在IE中此函数成为currentStyle);浏览器此时不得不进行渲染队列中带改变的项目,并从新排版以返回正确值。
解决办法:
经过延迟访问布局信息避免重排版。
总体修改cssText的css代码,而不是分开访问,修改cssText的属性
// 访问了4次DOM,第二次开始重排列并强迫渲染队列执行
var el = document.getElementById('div1');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 改进:改变合并,经过cssText实现
var el = document.getElementById('div1');
el.cssText += 'border-left = 1px; border-right = 2px; padding = 5px;';
复制代码
改变css类名来实现样式改变
当对DOM元素进行屡次修改时,能够经过如下的步骤减小重绘和重排版的次数:
(注意:此过程引起两次重排版,第一次引起一次,第三次引起一次。若是没有此步骤的话,每次对第二步的改变都有可能带来重排版。)
从文档流中摘除该元素,摘除该元素的方法有: a、对其应用多重改变 b、将元素带回文档中 c、使其隐藏,进行修改后在显示 d、使用文档片断建立子树,在将他拷贝进文档
var doc = document;
// 建立文档子树
var frag = doc.createDocumentFragment();
// 自定义函数,将修改内容data赋给文档片断frag,具体过程忽略
appendDataToElement(frag,data);
// 注意:添加时实际添加的是文档片断的子节点群,而不是frag本身,只会引起一次重排版
doc.getElementById('div1').appendChild(frag);
复制代码
建立一个节点的副本,在副本上进行修改,再让复制节点覆盖原先节点
// 建立一个节点的副本,在副本上进行修改,再让复制节点覆盖原先节点
var oldNode = document.getElementById('old');
var clone = old.cloneNode();
appendDataToElement(clone, data);
oldNode.replaceChild(clone, oldNode);
复制代码
ps:推荐第二种,由于其涉及最少数量的操做和重排列。
1四、减小对布局信息的查询次数,查询时将他赋值给局部变量参与计算。
例子,在元素网右下方不断平移时,在timeout中能够写:
var current = myElement.offsetLeft;
current++;
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current > 500) {
// stop animation
}
// 拒绝下面的写法,每次移动都会查询一次偏移量,致使浏览器刷新渲染队列,很是耗时
myElement.style.left = myElement.offsetLeft + 'px';
myElement.style.top = myElement.offsetLeft + 'px';
if (myElement.offsetLeft > 500) {
// stop animation
}
复制代码
1五、大量的元素使用:hover以后,页面性能将下降,特别是IE8中。所以强烈建议,在数据量很大的表格中,减小鼠标在表上移动效果,减小高亮行的显示,使用高亮是个慢速过程CPU使用率会提升到80%-90%,尽可能避免使用这种效果。
1六、事件托管
讲到事件托管,首先咱们来看一看冒泡机制:
DOM2级事件规定事件包括三个阶段: ① 事件捕获阶段 ② 处于目标阶段 ③ 事件冒泡阶段
图片引用来源:www.w3.org/TR/DOM-Leve…
以下图的实验结果能够知道,当咱们点击了inner以后,捕获和冒泡结果如上图的规律相同;
所以,由于每个元素有一个或多个事件句柄与之相连时,可能会影响性能,毕竟链接每个句柄都是有代价的,因此咱们采用事件托管技术,在一个包装元素上挂接一个句柄,用于处理子元素发生的全部事件。
下面咱们以以下的dom结构为例:
假若有一个ul,下面有不少个li:
<div>
<ul id="ulList">
<li id="item1"></li>
<li id="item2"></li>
<li id="item3"></li>
<li id="item4"></li>
<li id="item5"></li>
</ul>
</div>
复制代码
当某个Li被点击的时候须要触发相应的处理事件。咱们一般的写法,是为每一个Li都添加onClick的事件监听。
function addListenersLi(liNode) {
liNode.onclick = function clickHandler(){}
}
window.onload = function(){
var ulNode = document.getElementById("ulList");
var liNodes = ulNode.getElementByTagName("li");
for(var i=0, l = liNodes.length; i < l; i++){
addListeners4Li(liNodes[i]);
}
}
复制代码
若是li足够多,或者对于li的操做特别频繁,为每个li绑定一个点击事件将会特别影响性能,由于在此期间,你须要访问和修改更多的DOM节点,事件的绑定过程发生在onload事件中,绑定自己也很是耗时;同时,浏览器须要保存每一个句柄的记录,很占用内存。重点是有些绑定了还不必定会用着,并非100%的按钮或连接都会被点到的哟!
所以,采用事件托管更为高效,当事件被抛到更上层的父节点的时候,咱们经过检查事件的目标对象(target)来判断并获取事件源Li。下面的代码能够完成咱们想要的效果:
var oul = document.getElementById('ulList');
oul.addEventListener('click',function(e){
var e = e || window.event;
var target = e.target || e.srcElement;
console.log(target.nodeName);
if (target.nodeName == 'LI') {
// 事件真正的处理程序
alert(target.id);
e.preventDefault();
e.stopPropagation();
}
else {
console.log(target.nodeName);
}
});
复制代码