执行与加载
管理浏览器中的脚本 JavaScript 代码是个棘手的问题,因为代码执行过程会阻塞浏览器的其他进程,比如用户界面绘制,每次遇到<script>标签,页面都必须停下来等待代码下载 (如果是外链文件) 并执行,然后继续处理其他部分,尽管如此,还是有几种方法能减少javascript 对性能的影响。
闭合标签之前,将所有的<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 时尽可能这样做。
1. 最小化 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, 降低了性能消耗。
2. 如果需要多次访问某个 DOM 节点,请使用局部变量存储它的引用
var doc = document; // 存储document
doc.getElementById("div");
3. 小心处理 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++){
}
4. 如果可能的话,使用速度更快的 API,比如querySelectorAll() 和 firstElementChild
5. 要留意重绘和重排;批量修改样式时,’离线’操作 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 = window.scrollTop; // 这里直接缓存scrollTop
var set = setInterval(function(){
scroll--; // 这里不使用window.scrollTop--
window.scrollTop = scroll;
if(scroll < 0) {
clearInterval(set);
}
},100)
6. 动画中使用绝对定位。
对于用展开 / 折叠的效果,我们使用绝对定位,将其脱离文档流,会是重绘更少些。
7. 使用事件委托来减少事件处理器的数量。
如果你还不知道事件委托事什么,请访问:
http://blog.csdn.net/webxiaoma/article/details/53501616
算法和流程控制
这里我们讨论的是循环和判断,我们直接写推荐的书写方法
1. 循环体
循环包括 for循环、while循环、do-while循环、for-in循环。
其中for-in循环的性能明显比其他性能差些,另外 ES6 对于属性循环有出了for-of循环,比起for-in循环性能要好一些。
我们前面也说了,循环时,我们最好存储判断的长度,另外递减循环要比递加循环速度相当要快。
2. 判断
判断有:if 和 switch;
当我们做判断时,最好是吧最有可能出现的放到前面,另外if-else-if很多的话,推荐使用switch去判断,这样更有利于减少性能的消耗。有时候我们选择嵌套 if 也不使用很多if-else-if连用的方法如下:
// 推荐
if(){
if(){
if(){
}
}
}
// 尽量少用
if(){
} else if(){
} else if(){
} else if(){
} else if(){
} else if(){
}
对象和作用域
对象的注意点是:
1. 尽量不扩展内置原型 (如给 Object.prototype 添加自己的方法),除非你确定不会对你的团队造成影响
2. 访问的作用域和对象越深,越消耗性能,我们 尽量减少,不过这并不是硬性的要求,我们尽量减少那些不必要的深入访问。
其他注意点
避免隐士类型转换 (有时候判断需要考虑用全等号还是双等号)
var a = 0;
if(a === false){
//不执行
}
if(a == false){
//执行了
}
2. 尽量少的使用eval()
3. 项目发布时的代码压缩,js 文件整合,图片整合等等优化。
4. 整个团队的代码书写风格,要确定。
深圳 · 龙岗 · 大运软件小镇22栋201
电话:400 182 8580
邮箱:szhulian@qq.com