编写高性能javascript注意笔记
前言
本文的一些注意的是我以前看书籍总结的,我们一般人写项目时,对我们的影响并不是很大,有时完全可以忽略,但是我们知道这些总不是什么坏处。
js这个大众语言我想,很多人多知道,它入门和简单,可是想要深入了解还是要有一定的水平、 的,同样的效果虽然都可以写出来,但还是性能和可维护性却有很大差别。下来我们就来总结一下书写高质量js代码的一些注意点。
代码维护是高成本的,如果我们在开发代码时,不注重代码的规范,可读性和可维护性,那么将来带给我们的将是更大的开支。如果说你是给公司干活,自己写完就不用管了,这样的思想很危险。不但对他人读取你代码时带来困扰,同时也是限制自己发展的一个障碍。代码终究是要给人去阅读的。我们整个团队在书写代码时应该劲量做到:
- 可读的
- 一致的
- 可预测的
- 看上去就像是同一个人写的
- 已记录
执行与加载
管理浏览器中的脚本 JavaScript 代码是个棘手的问题,因为代码执行过程会阻塞浏览器的其他进程,比如用户界面绘制,每次遇到<script>
标签,页面都必须停下来等待代码下载(如果是外链文件)并执行,然后继续处理其他部分,尽管如此,还是有几种方法能减少 javascript 对性能的影响。
</body>
闭合标签之前,将所有的<script>
标签放到页面底部,这确保在脚本执行前页面已加载完成了渲染。- 合并脚本,页面中的
<script>
标签越少,加载也就也快,响应也更迅速。无论外链文件还是内嵌脚本都是如此 - 有多种无阻塞下载JavaScript方法:
使用<script>
标签的defer属性(H5提供一个新属性async)
defer和async的相同点是采用并行下载,在下载过程中不会产生阻塞。区别在于执行时机,async是加载完成后自动执行,而defer需要等待页面完成后执行。
<script type="text/javascript" defer></script>
<script type="text/javascript" async></script>
使用动态创建 <script>
元素来下载并执行代码
使用动态创建<script>
元素不会阻塞页面的其他进程,这样我们可以将动态创建的<script>
放到页面的<head>
区域。
function(url){
var script = document.creatElement('script');
script.type = "text/javascript";
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}
我们什么时候知道脚本已经加载完成呢?Firefox,Opera,Chrome和safari3为我们提供了一个onload事件来监听,而IE 为我们提供了onreadystatechange事件并根据readyState属性来进行判断。
通过上面的方法,我们可以极大提高那些需要使用大量的JavaScript的web应用的实际性能。
变量处理
我们都知道,JavaScript通过函数管理作用域。在函数内部声明的变量只在这个函数内部,函数外面不可用。另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的。我们应该尽量的减少全局变量,这样避免全局变量过多而造成变量命名冲突,同时又占用内存。
声明变量是我们尽量用var去声明
function fun(){
a = 1; // 这样是不推荐的,因为a是隐式的全局变量
var b = 2; // 这样是推荐的
var c = d = 5 // 这样也是不推荐的,因为d是隐式的全局变量
}
减少全局变量我们还可以使用立即执行函数,下来我们也会有章节去详细的讲解
(function(){
var a = 3;
var b = 4;
})()
另外我们应该知道变量提升原则,这个在后面的章节中我们还会详细的讲解我们在写一些变量时,可以这样去写。
function(){
var a,
b,
c;
a = 1;
b = 2;
c = 3;
}
我们把常用的变量写到最上面,可以进行注释,这样更有利于维护我们的代码,同时防止变量在定义之前使用的逻辑错误。
DOM操作的注意点
脚本进行DOM的操作的代价是很昂贵的。它是富Web应用中最常见的性能瓶颈。浏览器中通常会把DOM 和 JavaScript(ECMAScript) 独立实现。我们每一次用js去操作DOM都会产生性能消耗,所以我们尽可能小的去处理DOM,这也是现在MVVM和MVC框架流行的一部分原因。我们在处理DOM时尽可能这样做。
- 最小化DOM访问次数,尽可能在JavaScript端处理
var ul = document.getElementById("ul");
var a = "";
for(var i=0;i<10;i++){
a += "<li>i</li>";
}
ul.innerHTML = a;
将要添加的标签存储在变量a中,一次性加入ul中,这样只访问一次dom,降低了性能消耗。
- 如果需要多次访问某个DOM节点,请使用局部变量存储它的引用
var doc = document; // 存储document
doc.getElementById("div");
- 小心处理HTML集合,因为它实时连系着底层文档,把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。
var divList = document.getElementsByTagName("div");
for(var i = 0,len = divList.length; i < len; i++){}
这里我们将divList的长度去存储在变量 len中,而不像下面这样每次循环都要读取一遍长度。
// 避免使用的例子
var divList = document.getElementsByTagName("div");
for(var i = 0; i < divList.length; i++){
}
- 如果可能的话,使用速度更快的API,比如querySelectorAll() 和 firstElementChild
- 要留意重绘和重排;批量修改样式时,'离线'操作DOM树,使用缓存、并减少访问布局信息的次数
浏览器下载完页面中的所有组件之后会解析并生成两个内部数据结构
DOM树: 表示页面结构
渲染树: 表示DOM节点如何显示
当DOM的变化影响了元素的几何属性时(宽高)—— 比如改变框宽度或给段落增加文字,导致行数增加,浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响,浏览器会是渲染树中受到影响的部分失效,并重新构造渲染树。这一过程称为 “重排(reflow)”。完成重排后,浏览器会重新绘制影响的部分到屏幕中,该过程称为 “重绘”。
那么我们该怎么减少重排和重绘
- 样式统一处理
我们在改变一个元素样式时,可以统一处理
//这种写法不推荐
var el = document.getElementById("div");
el.style.width = '100px';
el.style.height = '100px';
el.style.padding = '10px';
//这种写法推荐
var el = document.getElementById("div");
el.style.cssText = "width:100px;height:100px;padding:5px;";
下边的方法很明显只进行了一次重排,而上边的重排了三次。使用cssText时会覆盖前边的style样式,我们可以这样做
el.style.cssText += "width:100px;height:100px;padding:5px;";
另外我们还可以将改变的样式写在css样式表中,通过修改class来改变其样式。
var el = document.getElementById("div");
el.className = "active";
- 脱离文档流修改DOM
脱离文档修改DOM的步骤就是
使元素脱离文档流
对其应用多重改变
把元素带回文档中
这里我有三种方法:
(1) 第一种:隐藏元素,应用修改,重新显示
var el = document.getElementById("div");
el.style.display = "none";
el.style.width = '100px';
el.style.height = '100px';
el.style.padding = '10px';
el.style.display = "block";
(2) 第二种:使用文档片段在当前DOM之外构建一个子树,在把它拷贝回文档
var el = document.getElementById("ul");
var fragment = document.createDocumentFragment();
for(var i = 0; i < 50000; i++){
fragment.appendChild(document.createElement("li"));
}
el.appenChild(fragment);
推荐使用这种方式。
(3) 第三种:将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后在提货原始元素
var el = document.getElementById("div");
var cloneDiv = el.choleNode(true);
cloneDiv.appendChild = li;
el.parentNode.replaceChild(clone,el);
以上脱离文档流修改DOM的方法推荐第二种方法。
- 减少渲染变化的排队与刷新
由于每次重排都会产生计算消耗,大多数浏览器通过列队化修改并批量执行来优化重排过程。而我们不知不觉中就使用了一些强制刷新队列并要求计划任务立刻执行的方法:
1. offsetTop、offsetLeft、offsetWidth、offsetHeight
2. scrollTop、scrollLeft、scrollWidth、scrollHeight
3. clientTop、clientLeft、clientWidht、clientHeight
4. getComputedStyle() // currentStyle IE中
我们在使用上面方法时要注意,浏览器为了返回最新值,会刷新队列应用所用变更,我们应该减少他们的使用,如果使用他们我们最好缓存布局信息
例如我们将滚轮滚动到页面顶部:
var scroll = document.body.scrollTop; // 这里直接缓存scrollTop
var set = setInterval(function(){
scroll--; // 这里不使用window.scrollTop--
document.body.scrollTop = scroll;
if(scroll < 0) {
clearInterval(set);
}
},100)
- 动画中使用绝对定位。
对于用展开/折叠的效果,我们使用绝对定位,将其脱离文档流,会是重绘更少些。
- 使用事件委托来减少事件处理器的数量。
如果你还不知道事件委托事什么,请访问:
http://blog.csdn.net/webxiaoma/article/details/53501616
算法和流程控制
这里我们讨论的是循环和判断,我们直接写推荐的书写方法
- 循环体
循环包括 for循环、while循环、do-while循环、for-in循环。
其中for-in循环的性能明显比其他性能差些,另外ES6对于属性循环有出了for-of循环,比起for-in循环性能要好一些。
我们前面也说了,循环时,我们最好存储判断的长度,另外递减循环要比递加循环速度相当要快。
- 判断
判断有:if 和 switch;
当我们做判断时,最好是吧最有可能出现的放到前面,另外if-else-if很多的话,推荐使用switch去判断,这样更有利于减少性能的消耗。有时候我们选择嵌套if也不使用很多if-else-if连用的方法如下:
// 推荐
if(){
if(){
if(){
}
}
}
// 尽量少用
if(){
} else if(){
} else if(){
} else if(){
} else if(){
} else if(){
}
对象和作用域
对象的注意点是:
- 尽量不扩展内置原型(如给Object.prototype添加自己的方法),除非你确定不会对你的团队造成影响
- 访问的作用域和对象越深,越消耗性能,我们 尽量减少,不过这并不是硬性的要求,我们尽量减少那些不必要的深入访问。
其他注意点
- 避免隐士类型转换(有时候判断需要考虑用全等号还是双等号)。
var a = 0;
if(a === false){
//不执行
}
if(a == false){
//执行了
}
- 尽量少的使用eval()
- 项目发布时的代码压缩,js文件整合,图片整合等等优化。
- 整个团队的代码书写风格,要确定。