目录
减少作用域链操作
避免全局查找
在函数作用域中如果多次使用全局变量,则应该用局部变量保存全局变量的引用,避免反复通过作用域查找全局变量。
function demo() {
var imgs = document.getElementByTagName("img"); //获取页面所有img标签
for(var i = 0; i <= imgs.length; i++) {
imgs[i].title = document.title + "image" + i;
}
//上面for循环中反复使用document,这里应该添加局部变量代替document,应使用下面优化代码
//for(var i = 0; i <= imgs.length; i++) {
// imgs[i].title = doc.title + "image" + i;
//}
}
函数尾调用优化
函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A
的内部调用函数B
,那么在A
的调用帧上方,还会形成一个B
的调用帧。等到B
运行结束,将结果返回到A
,B
的调用帧才会消失。如果函数B
内部还调用函数C
,那就还有一个C
的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
上面代码中,如果函数g
不是尾调用,函数f
就需要保存内部变量m
和n
的值、g
的调用位置等信息。但由于调用g
之后,函数f
就结束了,所以执行到最后一步,完全可以删除f(x)
的调用帧,只保留g(3)
的调用帧。
函数尾递归优化
原理同上
暂存变量
当多次使用一个对象中的属性或方法返回的变量时,我们应该通过定义新的变量存储,而不是反复去作用域链上取值。
优化循环
下面代码循环时会反复取imgs中的length属性,应通过定义新变量优化。
//var imgLength=img.length
//通过上面注释可优化
for(var i = 0; i <=imgs.length; i++) {
//执行代码
}
尽量使用原生方法
原生方法是用诸如c/c++之类的编译型语言写出来的,所以要比JavaScript的自己定义的新方法要快很多很多。
使用switch替代if-else
如果有一系列复杂的if-else语句,可以转化成单个switch语句则可以得到更快的代码。还可以通过case语句按照最可能的到最不可能的顺序进行组织,来进行进一步优化switch语句。
减少语句数
简化申明
var name = "Bill";
var age = 10;
var sex = "man";
//简化为一个语句申明变量
var name = "Bill",
age = 10,
sex = "man";
合并语句
var age = values[i];
i++;
//合并为一个语句
var age = values[i++];
使用数组和对象字面量
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
var person = new Object();
person.name = "Bill";
person.age = 10;
//优化后
var values = [123, 456, 789];
var person = {
name : "bill",
age : 10,
};
去除注释和空行
通常在webpack的打包文件会自动被去除,也可自行设置是否去除注释。
减少页面的重排(回流)和重绘
使用文档碎片减少DOM交互
例如下面代码,每执行一次for循环都会向DOM插入新的元素,一旦for循环次数很多,将严重影响代码性能,所以解决办法就是减少DOM交互,于是我们使用createDocumentFragment方法创建虚拟节点,把要插入DOM的元素先插入该虚拟节点,循环完之后再把虚拟节点插入DOM,虚拟节点是不会渲染出来的,只会渲染它的子节点。
var list = document.getElementById("myList"),
item,
i;
for (i = 0; i <= 10; i++) {
item.document.createElement("li");
list.appendChild(item);
item.appendChild(document.createTextNode(" Item" + i));
}
//优化后
var list = document.getElementById("myList");
//构建虚拟节点
fragment = document.createDocumentFragment(),
item,
i;
for (i = 0; i < 10; i++) {
item = document.createElement("li");
fragment.appendChild(item);
item.appendChild(document.createTextNode("Item" + i));
}
list.appendChild(fragment);
多次样式改变属性合并
浏览器会维护一个队列,将所有引起重排和重绘的操作都放在这个队列里,当操作达到一定的数量或者时间间隔时,浏览器会进行一个批处理。这样可以让多次的重排重绘,变成一次。
var el = document.getElementById("el");
el.style.color = "red";
el.style.height = "100px";
el.style.width = "20px";
//优化后
var el = document.getElementById("el");
el.style.cssText = 'color: red; height:100px; width:20px'
使用innerHTML
有两种在页面上创建DOM节点的方法:诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用,由于内部方式是编译好的而非解释执行的,所以执行快的多。
缓存多次使用的特殊属性
多次使用例如offsetTop,scrollTop,clientTop等属性(这些属性都是要实时返回给用户的几何属性或者布局属性)时,进行申明变量缓存。
避免使用document.write
document.write会导致页面重排。
减少drawImage
canvas的drawImage方法也会导致页面重排。
其它非代码方面
事件优化
使用事件委托
把事件绑定在祖先节点,由于有事件冒泡,当事件触发时根据event对象的target属性可以知道具体事件是在那个子元素发生的。从而执行不同的行为。这样就不必每个子节点都绑定事件。
TOUCH事件优化
使用touchstart、touchend代替click,因响应速度快。但应注意Touch响应过快,易引发误操作。
高频事件优化
Touchmove、Scroll 事件可导致多次渲染
a) 使用requestAnimationFrame监听帧变化,使得在正确的时间进行渲染
b) 增加响应变化的时间间隔,减少重绘次数
其它
多条字符串拼接
多条字符串拼接,存放数后使用array.join()方法进行拼接,效率会比+=高。
避免空的图片src
空的图片src仍然会使浏览器发送请求到服务器,这样完全是浪费时间,而且浪费服务器的资源。尤其是你的网站每天被很多人访问的时候,这种空请求造成的伤害不容忽略。例如下面代码:
var img = new Image();
img.src = "";