JS性能优化

吐槽君 分类:javascript

1. 作用域相关

01 避免全局查找

优化经验:只要函数中有引用超过两次的全局对象,就应该把这个对象保存为一个局部变量。

原因:如果定义了一个全局的变量,那么在局部作用域查找这个全局变量时,会经历作用域链查找,因此全局变量与局部变量相比,是更费时间的。

代码分析:

第一个函数三次引用了全局document对象,如果页面图片非常多,那么for循环就会引用上百次等,每次都要遍历一次作用域链。

第二个函数优化了这一点。在局部保存了document对象的引用,因此只会查找一次作用域链,就优化了性能。

//优化前
function updateUI() {
  let imgs = document.getElementsByTagName("img");
  for (let i = 0, len = imgs.length; i < len; i++) {
    imgs[i].title = '${document.title} image ${i}';
  }

  let msg = document.getElementById("msg");
  msg.innerHTML = "Update complete.";
}

//优化后
function updateUI() {
  let doc = document;
  let imgs = doc.getElementsByTagName("img");
  for (let i = 0, len = imgs.length; i < len; i++) {
    imgs[i].title = '${doc.title} image ${i}';
  }

  let msg = doc.getElementById("msg");
  msg.innerHTML = "Update complete.";
}
 

02 避免使用with语句

优化经验:不要使用with语句。

原因:with语句会创建自己的作用域,因此会加长代码的作用域链,因此用with语句,比不用with要多查一步作用域链,所以with更慢。

with使用示例(不建议用):

function updateBody() {
	with(document.body){
		console.log(tagName);
	}
}
 

2. js方法相关

01 避免不必要的属性查找

优化经验:只要使用某个object深层属性超过一次,就应该将其保存在局部变量中

原因:在访问对象时,是要查找原型链的,算法复杂度是O(n),查找的属性越多,执行时间就越长。

代码分析:第一段代码,有六次属性查找,有几个点就有查找几次。因此优化的话,就应该避免多次使用一个object深层对象,应该保存到局部变量中。

第二段代码,优化完成后就只有四次属性查找,节约了33%

想一下,如果脚本特别大的情况,这种的提升就非常明显能改变性能了。

//优化前
let query = window.location.href.substring(window.location.href.indexOf("?"));

//优化后
let url = window.location.href;
let query = url.substring(url.indexOf("?"));
 

02 优化循环

优化经验和原因:

(1)简化优化条件。因为每次循环都会计算终止条件,所以它应该可以更快。

(2)简化循环体。确保循环中去除可以在循环外部计算的方法。

(3)使用后测试循环do-while是后测试循环,避免了对终止条件的初始评估。for、while循环是先测试循环。

优化分析:

第一个循环条件中,values.length被查询了i次,复杂度O(n)。

第二个循环条件中,values.length被查询了一次,复杂度O(1),因此性能提升了。

第三个属于后测试循环,values.length被查询了一次,复杂度O(1),并且 --i 和i>=0 合并成了一个。

//优化前
for (let i = 0; i < values.length; i++) {
  process(values[i]);
}
//优化后
for (let i = values.length - 1; i >= 0; i--) {
  process(values[i]);
}

//后测试循环
let i = values.length-1;
if (i > -1) {
  do {
    process(values[i]);
  }while(--i >= 0);
}
 

03 展开循环

优化经验:如果循环的次数是有限的,多次调用函数比循环调用函数会更快。

原因:这样可以节约创建循环、计算终止循环条件消耗,让代码更快。

其他:如果不能预知循环次数,那么可以用一种Duff's Device(达夫设备)的技术。达夫设备的基本思路是以8的倍数作为迭代次数从而将循环展开为一系列语句。

代码示例:

// 优化后
process(values[0]);
process(values[1]);
process(values[2]);
 

04 避免重复解释

优化经验:避免写一些字符串,里面带有js代码。

原因:如果代码包含在字符串里,js运行时必须启动解析器实例来解析这些字符串的代码,而实例化解析器很费时间。

代码示例:

// 对代码求值:不要
eval("console.log('Hello world!')");

// 创建新函数:不要
let sayHi = new Function("console.log('Hello world!')");

// 设置超时函数:不要
setTimeout("console.log('Hello world!')", 500);
 

05 其他性能优化注意事项

1.原生方法很快。原生方法指的是使用C++或C编写的,不是js写的方法,比如Math这种。

2.switch语句很快,如果代码中有复杂的if-else语句,将其转换成switch,switch语句中把最可能的放前面,不太可能的放后面。

3.位操作很快,在执行数学运算操作时,位操作比任何布尔值或数值计算更快。对于复杂计算来讲,可以较高提升效率。

3.语句最少优化

优化经验:声明多个变量时,都可以使用一个let语句声明。

原因:一次声明比多次声明变量,性能肯定要快。

01 多变量声明

//优化前
//四条语句浪费
let count = 5;
let color = "blue";
let values = [1,2,3];
let now = new Date();
//优化后
// 一条语句更好
let count = 5,
  color = "blue",
values = [1,2,3],
now = new Date();
 

02 插入迭代性值

优化经验:遇到递增或者递减的值,尽可能组合语句。

原因:两次执行语句可以 合并成一个语句

//优化前
let name = values[i];
i++;
//优化后
let name = values[i++]
 

03 使用数组和对象字面量

优化经验:创建一个数组或对象的时候,减少语句

//优化前
// 创建和初始化数组用了四条语句:浪费
let values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;

// 创建和初始化对象用了四条语句:浪费
let person = new Object();
person.name = "Nicholas";
person.age = 29;
person.sayName = function() {
  console.log(this.name);
};

//优化后
// 一条语句创建并初始化数组
let values = [123, 456, 789];

// 一条语句创建并初始化对象
let person = {
  name: "Nicholas",
  age: 29,
  sayName() {
    console.log(this.name);
  }
};
 

4.优化Dom

首先,操作dom为什么会慢?

原因1:dom中包含了数千条信息,每次访问dom,浏览器会重新计算数千项指标,才能执行更新。

原因2:dom属于v8引擎,js属于js引擎,js每次操作dom就意味着v8引擎和js引擎就进行一次通信,频繁的更新dom就是频繁的进行引擎间的通信,成本很高,性能代价很大。

01 使用createDocumentFragement

使用文档片段构建dom结构, 在对dom修改的时候,只修改创建的文档片段,最终一次性添加到dom中.

这样修改后,完成这样的操作只需要触发一次实时更新。

let list = document.getElementById("myList"),
  fragment = document.createDocumentFragment(),
  item;

for (let i = 0; i < 10; i++) {
  item = document.createElement("li");
  fragment.appendChild(item);
  item.appendChild(document.createTextNode("Item " + i));
}

list.appendChild(fragment);
 

02 使用innerHTML

优化经验:在页面中,创建DOM节点的方式有两种,使用createElement,和使用innerHTML。

原因:对于少量更新,两者区别不大,对于大量DOM更新,使用innerHTML要快很多。

调用一次innerHTML,后台其实也是在创建原生DOM,这种方式要比js中的创建要快,因为这是原生,js中属于解释型代码。

let list = document.getElementById("myList"),
  html = "";

for (let i = 0; i < 10; i++) {
  html += '<li>Item ${i}</li>';
}

list.innerHTML = html;
 

03 使用事件委托

就是把事件注册到父级,而不是注册到每个单独的子元素,减少触发事件监听。

04 注意HTMlCollection

最常见的就是去访问一个数组的length,比如for循环中,每次循环执行,都去判断数组的长度,接着就会去查询文档,这个查询是很耗时的,来看优化后的例子。这里我们吧length赋给了len,因此只查询了一次。

优化思路:目标就是减少查询次数,把这些耗时的东西存储到临时变量中。

let images = document.getElementsByTagName("img");

for (let i = 0, len = images.length; i < len; i++) {
  // 处理
}
 

总结

以上就是关于js的一些性能优化中需要注意的事项,在ES6中还有那么多的语法,优化的方案还有很多,但最关键的还是优化的思想,后续开发中重视起来对每一行代码的优化,是成为优秀的开发者重要的一环。

回复

我来回复
  • 暂无回复内容