关于前端性能优化的讨论一直都很多,包罗的知识也很多,可以说性能优化只有更好,没有最好。前面我写了一篇关于 css 优化的总结文章,今天再从 javascript 方面聊一聊。
1. 从资源加载方面来说,浏览器的加载顺序是按源码从上到下加载解析的,遇到 link,script 等资源都会阻塞页面渲染,所以我们会把 script 放在 </body> 前面,我们还可以结合构建工具(webpack,gulp…)压缩 js 文件,抽离公共 js、去掉空格、注释,尽可能地让 js 文件变小,防止脚本阻塞页面渲染。
2. 在写代码的时候我们还要注意以下问题。
(1)减少作用域链上的查找次数。我们知道,js 代码在执行的时候,如果需要访问一个变量或者一个函数的时候,它需要从当前执行环境的作用域链一级一级地向上查找,直到全局作用域。如果我们需要经常访问全局环境的变量对象的时候,我们每次都必须在当前作用域链上一级一级的遍历,这显然是比较耗时的。
function getTitle() {
var h1 = document.getElementByTagName(“h1″);
for(var i = 0, len = h1.length; i < len; i++) {
h1[i].innerHTML = document.title + ” 测试 ” + i;
}
}
上面这样写就非常耗时,我们可以优化一下:
function getTitle() {
var doc = document;
var h1 = doc.getElementByTagName(“h1″);
for(var i = 0, len = h1.length; i < len; i++) {
h1[i].innerHTML = doc.title + ” 测试 ” + i;
}
}
通过创建一个指向 document 的局部变量,就可以通过限制一次全局查找来改进这个函数的性能。
(2)闭包导致的内存泄露。闭包可以保证函数内的变量安全,可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会被自动清除。使用场合:设计私有的方法和变量。使用不当就会造成内存占用过高。我们需要手动销毁内存中的变量。
(3)尽量少用全局变量,尽量使用局部变量。全局变量如果不手动销毁,会一直存在,造成全局变量污染,可能很产生一些意想不到的错误,而局部变量运行完成后,就被会被回收;
(4)使用 classname 代替大量的内联样式修改。很多时候我们会在用户操作的时候,页面元素样式会进行相应的变化,这时候我们就可以把要变化的样式写成一个 class, 操作 class 变化,就能实现大量样式的变化。
(5)批量元素绑定事件,可以使用事件委托。事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。比如我们有 100 个 li,每个 li 都要绑定 click 点击事件,就可以用事件委托。举一个例子:我们需要给所有的 button 绑定 click 事件
<div id=”box”>
<input type=”button” id=”add” value=” 添加 ” />
<input type=”button” id=”remove” value=” 删除 ” />
<input type=”button” id=”move” value=” 移动 ” />
<input type=”button” id=”select” value=” 选择 ” />
</div>
我们有可能会这样写
window.onload = function(){
var Add = document.getElementById(“add”);
var Remove = document.getElementById(“remove”);
var Move = document.getElementById(“move”);
var Select = document.getElementById(“select”);
Add.onclick = function(){
alert(‘ 添加 ’);
};
Remove.onclick = function(){
alert(‘ 删除 ’);
};
Move.onclick = function(){
alert(‘ 移动 ’);
};
Select.onclick = function(){
alert(‘ 选择 ’);
}
}
用事件委托就可以这样写:
window.onload = function(){
var oBox = document.getElementById(“box”);
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == ‘input’){
switch(target.id){
case ‘add’ :
alert(‘ 添加 ’);
break;
case ‘remove’ :
alert(‘ 删除 ’);
break;
case ‘move’ :
alert(‘ 移动 ’);
break;
case ‘select’ :
alert(‘ 选择 ’);
break;
}
}
}
}
而且使用事件委托,还有一个好处就是,当你添加一个新的 button,一样的会绑定上 click 事件,这就是委托事件的好处。上面这样的写法是原生 js 的写法,jquery 可以这样写:
$(“#box”).on(“click”,”input”,function(event){
var targetId = $(this).attr(‘id’);
switch(targetId){
case ‘add’ :
alert(‘ 添加 ’);
break;
case ‘remove’ :
alert(‘ 删除 ’);
break;
case ‘move’ :
alert(‘ 移动 ’);
break;
case ‘select’ :
alert(‘ 选择 ’);
break;
}
})
这样写就简便得多。
(6)避免不必要的 DOM 操作,尽量使用 ID 选择器:ID 选择器是最快的,避免一层层地去查找节点。
(7)类型转换:把数字转换成字符串使用 number + “”。虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:
(“” +) > String() > .toString() > new String()
(8)对字符串进行循环操作,譬如替换、查找,应使用正则表达式。因为本身 JavaScript 的循环速度就比较慢,而正则表达式的操作是用 C 写成的语言的 API,性能很好。
(9)对象查询使用 [“”] 查询要比.items()更快。这和前面的减少对象查找的思路是一样的,调用.items()增加了一次查询和函数的调用。
(10)浮点数转换成整型使用 Math.floor()或者 Math.round()。parseInt()是用于将字符串转换成数字,Math 是内部对象,所以 Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。
关于 js 性能优化来说,涉及到很多方面,特别是现在又出现很多的前端框架,优化方法又各有不同。上面说的这些只是很浅显的东西,在开发中多注意一下就可以了,尽量精简自己的代码。性能优化还需要继续深入研究。