1、把耗时长的复杂运算交给其他线程:web worker
当用户和浏览器交互时,操作系统接收到和计算机连接的各种设备,如键盘或鼠标的输入,在判断哪个应用应该接收这些输入之后,将他们打包成单独事件并放置到该应用的事件队列中。浏览器按照队列顺序完成其队列中单独事件的处理,它按照先进先出的顺序把他们从队列中取出,然后决定如何处理这个事件。
浏览器的处理过程是单线程的,因此它每次只能处理这些任务中的一个,并且任意一个任务都能阻止其他任务的执行。因此当有耗时长的复杂运算需要处理时,为了不让页面处于卡死状态,我们可以使用HTML5的web worker,具体使用方法见js之HTML5 API的第四点,有详细介绍
另外还可以通过setTimeout将耗时长的任务分解成多个代码段执行,每次setTimeout的空闲时段浏览器可以处理其他事件,页面不至于卡死
var state;
function expensiveOperation(){
var startTime = new Date().getMilliseconds;
while(new Date().getMilliseconds-startTime<100){
//TODO,它用如下方法执行开销很大的运算:
//它在迭代的语句块中执行100s内完成的工作,然后修改state的值,如果运算已经完成,则state修改为true
}
if(!state){
//退出10ms后再次执行expensiveOperation
//这10ms的时间浏览器可以用来处理其他事件,如页面渲染、其他交互事件等
setTimeout(expensiveOperation(),10);
}
}
2、减少内存使用
js的绝大部分运行环境都实现了垃圾回收,然后自动内存管理是有开销的,他们会冻结整个运行环境,在这个过程中,他们查找那些不在使用或能够回收的未使用内存的对象。垃圾回收可查看js之作用域和内存问题的第3点
减少内存可以用以下三种方式:
- 少定义全局变量:当某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的变量和函数定义也随之被销毁。但是全局变量会一直存在内存中,所以如果一个全局变量一旦不再有用,最好通过将其值设为null,来释放其引用,这个做法叫解除引用
- 使用delete删除不再需要的js对象,它不能删除全局变量。
- 从网页的DOM树上移除不再是必须的节点。
3、拆分初始化负载
web程序很大一部分代码不会在启动时被使用,所以我们可以把页面的js分成两部分:一部分是渲染初始页面必须的,剩下的做为另一部分,采用按需加载,或延时加载。
4、无阻塞加载脚本
script标签的阻塞行为会对页面性能产生负面影响,大多数浏览器在下载或执行脚本的同时不会下载其他内容,有时候这种阻塞是有必要的,如果一个js文件不依赖于页面中其他内容,可以单独加载,我们希望以不阻塞其他内容下载的方式来加载js
浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或js的名字空间,他们会对后续内容造成影响,一个js文件可能依赖另一个js文件,他们存在依赖关系。
要实现无阻塞加载脚本可以使用以下方法:
- Script DOM Element
使用js动态的创建script DOM元素并设置其src
var scriptElem = document.createElement('script');
scriptElem.src = 'js的路径';
document.head.appendChild(scriptElem);
下载过程中使用这种方式创建脚本不会阻塞其他组件。
- 使用Defer或Async
script标签拥有defer和async属性,他们都可以实现脚本的无阻塞加载,但它们有一定的区别:
defer:浏览器会异步下载该文件,不会影响后续DOM的渲染,如果有多个script标签设置了defer,则会按照引入的顺序执行所有的script,defer脚本会在文档渲染完毕后,在DOMContentLoaded事件调用前执行。
async:浏览器会异步下载该脚本,当下载完成时直接执行,而不会按照引入顺序来执行
5、编写高效的JS
- 管理作用域
当执行js时,js引擎会创建一个执行上下文(也称为作用域),执行上下文设定了代码执行时所处的环境,js引擎会在页面加载后创建一个全局的执行上下文,然后每执行一个函数时都会创建一个对应的执行上下文,最后建立一个执行上下文的堆栈,当前起作用的执行上下文在堆栈的最顶部。
每个执行上下文都有一个与之关联的作用域链,当函数被创建时(不是执行),js引擎会把创建时执行上下文的作用域链赋值给函数的内部属性[[Scope]](内部属性不能通过js来存取,所以无法直接访问此属性),然后,当函数被执行时,js引擎会创建一个活动对象,并在初始化时给this、arguments、命名参数和该函数的所有局部变量赋值。活动对象会出现在执行上下文作用域链的顶端,紧接其后的是函数的[[Scope]]属性中的对象。
在执行代码时,js引擎通过搜索执行上下文的作用域链来解析诸如变量和函数名这样的标识符,解析标识符的过程从作用域链的顶部开始,按照自上而下的顺序进行。读取变量值的总耗时随着查找作用域链的逐层深入而不断增长,所以标识符存取越深存取速度越慢,所以应该尽量的使用局部变量,一个好的经验是任何非局部变量在函数中的使用超过1次时,都应该将其存储为局部变量。
- 高效的数据存取
js中有4种地方可以存取数据:
字面量值、变量、数组元素、对象属性
从字面量和变量中读取值的开销很小,但从数组或对象中读取数据,需要通过索引或属性值,开销会更大。所以我们应该将需要频繁存取的值存储到局部变量中,如果一开始它在数组或对象中,只要使用超过1次,最好就将其赋值给一个局部变量。
- 快速条件判断
当判断一两个条件时,if语句通常比switch快
超过两个以上switch语句往往更快,这是因为大多数情况下,switch语句中执行单个条件所需时间比在if语句中短。
超过10个时,如果条件值能用数字或字符串这样的离散值来表示,并且对应的结果是单一值,而不是一系列的操作,则可以使用数组或对象查询。使用数组查询少量的结果是不合适的,因为数组查询往往比少量的条件判断语句慢,当查询范围很大时,数组查询才会变得非常有用,因为它们不必检测上下边界,只需简单地把对应的值填入数组的索引区域中就可以马上进行数组查询了。
下面例子的条件判断可以通过数组来查询(如果判断条件是字符串,可以通过对象来查询):
var flag = 10;
var str = ''
switch (flag) {
case 0:
str = 'result0';
case 1:
str = 'result1';
case 2:
str = 'result2';
case 3:
str = 'result3';
case 4:
str = 'result4';
case 1:
str = 'result5';
case 5:
str = 'result6';
case 6:
str = 'result7';
case 7:
str = 'result8';
case 8:
str = 'result9';
case 9:
str = 'result10';
case 10:
str = 'result11';
case 11:
str = 'result12';
case 12:
str = 'result13';
default:
break;
}
var flag = 10;
var str = '';
var strArr = ['result0','result1','result2','result3','result4','result5','result6','result7','result8','result9','result10','result11','result12'];
str = strArr[flag];
6、简化CSS选择符
浏览器的渲染组件负责页面的渲染工作,主要有以下几个步骤:
构建DOM树→结合样式表对象构建Render树→布局render→绘制render。
render树的构建是通过遍历DOM树的节点,每个节点都会去查找自己匹配的规则,每次查找匹配的规则都需要遍历整个规则表,这个过程有很大的工作量,所以编写良好的css可以提高渲染速度。
我们的阅读习惯都是从左到右,但是CSS选择符却是从右到左进行匹配的,因此缩小最右边的选择符的范围是非常有必要的,编写高效的选择符可以遵从以下规则:
- 避免使用通配规则
除了传统意义上的通配选择符之外,相邻兄弟选择符、子选择符、后代选择符合属性选择符都算是通配规则,尽量使用ID、类
- 不要限定ID选择符
在页面中一个ID只能对应一个元素,所以没必要添加额外的限定符,如div#one
- 不要限定类选择符
不要用具体的标签限定类选择符,而是根据实际情况对类名进行扩展,如li.item。
- 让规则越具体越好
不要试图编写像ol li a这样的长选择符,最好是创建一个像 .list这样的具体规则,如果是这样的不具体的长选择符,css从右到左开始匹配,页面中所有的a标签都会匹配中此规则,然后继续判断它的祖先级元素是否有li,然后继续往上判断是否有ol,因为第一次的命中范围太大了,继续往左匹配的时候可能会发现此dom节点并不适用这条规则。匹配过程如果沿着一条开始看似正确,后来却被证明是无用的路径,则必须去尝试另一条路径。
- 避免使用后代选择符
通常处理后代选择符的开销是最高的,而使用子选择符也可以得到想要的结果
回流时间
当用户和网页交互时,浏览器应用样式和布局元素所花费的时间,叫做回流时间,当使用 js修改DOM元素样式的某些属性会触发回流。
回流并不会涉及页面上的所有元素,仅仅只会对那些受回流影响的元素重新布局,如果是对body或者有很多后代的其他元素修改样式,那么回流的开销一定很大
回流需要重新应用CSS规则,这意味着浏览器必须再次匹配所有的CSS选择符。