快速响应的用户界面
ui线程
每个浏览器用于执行js代码和更新ui的进程就只有一个,这个被称为‘ui线程’,它基于一个简单的队列系统,也就是根据代码的先后循序将代码插入队列并执行。
一般来说,当用户点击一个按钮botton的时候,就会触发ui线程,他会创建两个任务并添加到队列中,第一个任务是更新ui按钮,它需要改变外观来表示它被点击了,然后再触发onclick事件,如果你对这个事件进行了调用其他函数,就会执行所调用的函数。
事实上,大多数浏览器会在js代码运行时停止把新的任务加入ui队列中去,也就是说当用户点击一个事件,这个事件所调用的函数运行时间过长,那么在运行的这段时间中用户再点击其他的事件是没有交互的,产生了一种假死的状态。
所以,一个js代码的运行它必须要快,以避免对用户体验造成不良影响。
浏览器限制
浏览器本身是有限制的,它会从js代码运行的行数和运行的时间进行限制,比如当运行超过500万条语句时浏览器会弹出警告,并停止脚本运行,甚至还有之前的栈大小限制,还有就是运行时间,当一个js文件开始运行时,浏览器会开始计时,如果这个脚本运行超过规定的时间,那么浏览器就会弹出提示,当前脚本运行时间过长,是否停止脚本运行。
但对于普通用户来说,他们可能并不知道这个脚本指的是什么,也就会处于是停止还是不停止运行的两难选择,为此,我们要从一开始就要避免出现这种问题。
多久才算久
100毫秒以内是最佳的时间
使用定时器让出时间片段
即便我们极力避免js的运行时间不超过100ms,但是难免还是会有大量的计算占用时间,所以我们可以通过定时器来一段一段的运行,每一段都有空出一些时间让其他代码运行。
定时器的两种方式:
- setTimeout()
- setInterval()
通过对定时器设置延迟时间来达到延迟调用的效果。
延迟的时间怎么计算的?
定时间被运行后开始计算,而不是他的上一级函数运行后,如:
function set() {
one();
setTimeout(function(){},250)
}
250ms的计算是从setTimeout运行后开始计算,而不是set()运行后。
如果你在一个事件中使用一个定时器,那么事件中运行的函数最好要快一些,如果事件函数运行超过的250ms,也就是你设置的延时时间,那么可能会在事件结束之前定时器就被运行了。
定时器的精度
事实上js的定时器并不是很精准,你设置延迟250毫秒并不意味着就一定是在250毫秒后运行,浏览器都在试图尽可能的精准,但往往会过快或者过慢几毫秒,因此,定时器不能用于测量实际的时间。
在windows系统中,定时器的步进值是15ms,也就是说,设置为10ms的延迟往往会是15ms后运行,或者低于15ms视为0,为此,最小的延迟值应该要是15或者30,起码要保证还有15ms的间隔。
注意,大部分浏览器在延迟小于10的时候,表现都不太一样。
使用定时间处理数组
常用的循环代码:
for(var i =0,len=item.length;i<len;i++) {
process(item[i]);
}
这是一个常用的循环代码,每次循环传入数组item的一个值,然后运行process()方法,假设这里的process运行时间过长,就会造成页面的假死,如果item数组庞大,也会如此。
为此我们就可以使用延迟调用来空出一部分时间给其他代码运行。
但是使用延迟调用要明确两个因素:
- 处理的过程是否必须同步
- 数据是否要按顺序处理
如果以上两点都不需要考虑,那么就可以使用该方法,因为延迟调用是异步的,然后有可能在不断延迟中,上一个还没运行完下一个就调用了。
function set(item) {
var arr = item.concat();
setTimeout(function(){
process(arr.shift());
if(arr.length > 0) {
setTimeout(arguments.callee,25)
}else {
callback();
}
},25)
}
然而事实上arguments.callee已经不推荐使用,因为每次递归都会重新创建arguments对象,这个代价是很昂贵的,我们可以使用非匿名函数。
function set(item) {
var arr = item.concat();
setTimeout(function fn(){
process(arr.shift());
if(arr.length > 0) {
setTimeout(fn,25)
}else {
callback();
}
},25)
}
这里插个题外话
一道面试题。接受参数n=5,不用for循环输出数组【1,2,3,4,5】
function set(num) {
var arr = [];
var i = 0;
return (function fn() {
arr.unshift(num--);
if(num > 0) {
fn();
}
return arr;
})()
}
我们还可以将定时器封装成一个可以多次调用的函数
function processArray(items,process,callback) {
var todo = itmes.concat();
setTimeoue(function fn(){
process(todo.shift());
},25)
if(todo.length > 0) {
setTimeoue(fn,25);
}else {
callback();
}
}
函数支持传三个参数,依次是需要传入的参数数组集合,调用的函数,回调的函数
分割任务
如果一个函数任务里面有几个子任务,且每个子任务都是相对独立的,但是一起的话运行时间过长,于是我们可以采用上面的那种办法
原方法:
function sava(id) {
open(id);
write(id);
close(id);
//上面都完成了
updataUI(id);
}
改为:
function sava(id) {
var tasks = [open,write,close];
setTimeout(function fn(){
var task = tasks.shift();
task(id);
if(tasks.length > 0) {
setTimeout(fn(),25);
}else {
updataUI(id);
}
},25)
}
记录代码的运行时间
如果我们需要每次运行的函数过多,那么无限的setTimeout反倒成为了累赘,因为是延迟调用,那么自然会造成反应的延迟,特别是数量过多的情况,为此我们可以想到之前的‘达夫设备’,一个setTimeout运行多个函数
那么怎么在极短的时间里运行多个函数呢?
一般来说,一个js代码的运行不超过100ms最佳,那么我们既然要优化,自然不能用最大值,所以我们可以采用50ms。
function timed(items,process,callback){
var todo = items.concat();
setTimeout(function fn() {
var start = +new Date();
do {
process(todo.shift());
}while(todo.length > 0 && (+new Date() - start > 50))
if(todo.length > 0 ) {
setTimeout(fn(),25);
}else {
callback();
}
},25)
}
当定时间内的函数运行时先创建一个时间,也就是当前时间,然后再do-while循环,第二次循环的时候判断todo还有没有值,并且第二次判断的时间减去刚开始的时间要不超过50ms才可以继续循环,如果不满足,重新延迟调用。
定时器的性能
定时器延迟的时间越短,且量越多,就会造成浏览器的ui线程堵塞,所以在延迟短的定时器中,尽量少的使用,但是超过1秒或以上的延迟其实不怎么会造成堵塞,所以在使用定时器的时候可以根据情况自行分配好延迟的时间。
workers
workers是新引入的一个api接口,它可以创建一个独立的线程来运行js,ie10及以上才支持,新版浏览器都支持,所以在不考虑到兼容性的问题是,workers是一个处理大量运算脚本的最佳选择。
workers创建时需要传入一个参数,这个参数就是需要运行的js文件路径,在workers中,有一个只读的navigator对象,还有一些js的对象,但是要注意,在workers中不要对ui进行操作,因为他是独立于ui线程的,所以操作可能会带来错误或者其他问题,workers本身用于独立计算那些,并且通过message事件和postMessage()相互通信。
也就是说js首先会创建一个workers环境,new Workers(‘xxx.js’);
然后创建一个onmessage事件,通过event对象的data属性获取到从workers传来的参数,页面也可以通过postMessage()向workers传参。
在workers环境中也创建一个onmessage事件用于监听页面传来的参数,也是event对象的data获取数据。
向页面传参也是postMessage()方法。
var worker = new Workers('xxx.js');
worker.onmessage = function(event) {
collback(event.data);
}
worker.postMessage('参数');
//workers
self.onmessage = function(event) {
process(event.data);
}
worker.postMessage('计算好参数传给页面');
在workers中也可以再载入其他js文件,载入的后js文件在workers中运行,使用importScripts()方法
//workers中
imporScripts('xxx.js','sss.js');
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
全部评论 3
YIR
Google Chrome Windows 10木灵鱼儿
FireFox Windows 10YIR
Google Chrome Windows 10