# 6. 快速响应的用户界面
# 浏览器UI线程
- 用于执行js和更新用户界面的进程通常被称为‘浏览器UI线程’,UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲。一旦空闲,队列中下一个任务就被重新提取出来并运行。这些任务要么是运行js代码,要么是执行UI更新,包括重绘和重排。
# 浏览器限制
浏览器限制了js任务的运行时间。这种限制是有必要的,它确保某些恶意代码不能通过永不停止的密集操作锁住用户的浏览器或计算机。此限制分为两种:调用栈大小限制和长时间运行脚本限制。
如果[javascript]运行了整整几秒钟,那么可能是你做错了什么...。单个js操作花费的总时间不应该超过100毫秒。超过100毫秒意味着用户会感到自己与界面失去联系。
当脚本执行时,UI不随用户交互而更新。执行时间段内用户交互行为所引发的js任务被加入队列中,并在最初的js任务完成后依次执行。而这段时间内由用户交互行为引发的UI更新会被自动跳过,因为页面中的动态部分会被优先考虑。因此,在一个脚本运行期间点击一个按钮,将无法看到它被按下的样式,尽管它的onclick事件处理器会被执行。
IE会控制用户交互行为触发的js任务,因此它会识别连续两次的重复的动作。列如,当有脚本运行时点击一个按钮四次,最终按钮的onclick事件处理器只被调用两次。
# 使用定时器让出时间片段
- 让出控制权意味着停止执行js,使UI线程有机会更新,然后再继续执行js。
- 定时器与UI线程的交互方式有助于把运行耗时较长的脚本拆分为较短的片段。
- setTimeout和setInterval第二个参数表示任务何时被添加到UI队列,而不是一定会在这段时间后执行;这个任务会等待队列中其他所有任务执行完毕后才会执行。
- setInterval和setTimeout主要区别是,如果UI队列中已经存在由同一个setInterval创建的任务,那么后续任务不会被添加到UI队列中。
# 使用定时器处理数组
- 常见的一种造成长时间运行脚本的起因就是耗时过长的循环。如果已经尝试了循环优化技术还是没能减少足够的运行时间,那么下一步优化就是选用定时器。它的基本方法是把循环的工作分解到一系列定时器中。
var todo=items.concat();
setTimeout(function(){
process(todo.shift());
if(todo.length>0){
setTimeout(arguments.callee,25)//arguments.callee是当前函数
}else{
callback(items);
}
},25);
2
3
4
5
6
7
8
9
- 每个定时器的真实延时时间在很大程度上取决于具体情况。普遍来说,最好使用至少25毫秒,因为再小的延时对大多数UI更新来说不够用。
- 使用定时器处理数组的副作用是处理数组的总时长增加了。这是因为在每个条目处理完成后UI线程会空闲出来,并且在下一条目开始处理之前会有一段延时。尽管如此,为避免锁定浏览器给用户带来的糟糕体验,这种取舍是有必要的。
# 记录代码运行时间
- 如果你还记得js可以持续运行的最长时间为100毫秒,那么你可以优化先前的模式。我的建议是把这个数字减半,不要让任何js代码持续运行50毫秒以上,这样做只是确保代码永远不会影响用户体验。
- 加号(+)可以将Date对象转化为数字。
function timedProcessArray(items,process,callback){
var todo=items.concat();
setTimeout(function(){
var start=+new Date();
//通常来说批量处理比单个处理要快
do{
process(todo.shift())
}while(todo.length>0&&(+new Date()-start<50))
if(todo.length>0){
setTimeout(arguments.callee,25);
}else{
callback(items);
}
},25)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 定时器与性能
间隔在1秒或1秒以上的低频率的重复定时器几乎不会影响web应用的响应速度。这种情况下定时器延迟远远超过UI线程产生瓶颈的值,因此可安全的重复使用。当多个重复定时器使用较高的频率(100到200毫秒之间)时,应用就会明显变慢,响应也不及时。
在你的web应用中限制高频率重复定时器的数量。作为替代方案,应该创建一个独立的重复定时器,每次执行多个操作。
# Web Workers
js诞生以来,还没有办法在浏览器UI线程之外运行代码。web worker api改变了这种状况,它引入了一个接口,能使代码运行且不占用浏览器UI线程的时间。web worker已经被Firefox3.5、Chrome 3和Safari4原生支持。
每个新的worder都在自己的线程中运行代码。这意味着worker运行代码不仅不会影响浏览器UI,也不会影响其他worker运行的代码。
# worker运行环境
web workers从外部线程中修改DOM会导致用户界面出现错误。
worker运行环境由以下部分组成:
- 一个navigator对象
- 一个location对象(与window.location相同,不过所有属性是只读的)。
- 一个self对象,指向全局worker对象。
- 一个importScripts(),用来加载worker所用到的外部js文件。
- 所有的ECMAScript对象,诸如:Object,Array,Date等。
- XMLHttpRequest构造器。
- setTimeout和setInterval方法。
- 一个close方法,它能立刻停止worker运行。
web worker有着不同的全局运行环境,因此你无法从js代码中创建它,你需要创建一个完全独立的js文件,其中包含了需要在worker中运行的代码。
var worker = new Worker('code.js');
此代码一旦执行,将为这个文件创建一个新的线程和一个新的worder运行环境。该文件会被异步下载,直到文件下载并执行完成后才会启动此worker.
# 与worker通信
var worker=new Worker('code.js');
worker.onmessage=function(event){
alert(event.data);
}
worker.postMessage('xxx')
//code.js内部代码
self.onmessage=function(event){
self.postMessage('xxx');
}
2
3
4
5
6
7
8
9
10
# 加载外部文件
importScript()的调用过程是阻塞的,直到所有文件加载并执行完成之后,脚本才会继续运行。由于worker在UI线程之外运行,所以这种阻塞并不会影响UI响应。
← 5. 字符串和正则表达式 7. Ajax →