《高性能javascript》读书笔记:第六章 快速响应的用户界面
浏览器限制了javascript任务的运行时间,调用栈大小限制和长时间运行脚本限制。
a,IE自第4版开始,设置默认限制为500万条语句。此限制放在windows注册表中,
叫HKEY_CURRENT_USER\Software\Microsoft\InternetExplorer\Styles\MaxScriptStatements b,FireFox的默认限制为10秒,该限制放在配置设置中(通过地址栏输入about:config),键名dom.max_script_run_time c,Safari的默认限制为5秒,该限制无法更改,但是可以通过Develop菜单选项Disable Runaway JavaScript Timer来禁用定时器 d,Chrome没有单独的长运行脚本限制,替代做法是依赖其通用崩溃检测系统来处理此类问题 e,Opera没有长运行脚本限制,它会继续执行javascript代码直到结束,鉴于Opera的架构,脚本运行结束时不会导致系统不稳定
单个js操作花费的总时间不应该超过100毫秒
IE会控制用户交互行为触发的js任务,因此它会识别连续两次的重复动作。例如,当有脚本运行时点击一个按钮四次,最终按钮的onclick事件处理器只被调用两次。
定时器 |
有一些复杂的js任务不能在100毫秒或更短时间内完成,这个时候就要让出UI线程的控制权停止执行js,使得UI可以更新,然后再继续执行js。此时需要用定时器。
创建定时器的两种方式 setTimeout() //创建一个只执行一次的定时器 setInterval() //创建一个周期性重复执行的定时器 这两种方法基本相同,最主要的区别是如果UI队列中已经存在由同一个setInterval()创建的任务,那么后续任务不会被添加到UI队列中。
示例
function greeting(){
alert("Hi");
}
setTimeout(greeting,250);//这段代码将在250毫秒后向UI队列插入一个任务,不一定是250毫秒后执行
定时器会重置所有相关的浏览器限制,并且调用栈也在定时器的代码中重置为0,所以这一特性使得定时器成为长时间运行JS代码理想的跨浏览器解决方案。
定时器通常不太精确,相差大约几毫秒,所以不可用于测量实际时间。
windows系统中定时器分辨率为15毫秒,所以设置定时器延时小于15毫秒将会导致IE锁定,所以延时最小值建议为25毫秒(实际时间有偏差而成15毫秒或30毫秒)以确保至少有15毫秒延时。
循环数组时如果数组太长或处理太费时,并且处理过程不需要同步,也不需要按顺序处理,则应该把循环的工作分解到一系列定时器中。
for(var i=0,len=items.length;i<len;i++){ process(item[i]); }
应该改成
var todo=items.concat();//克隆原数组
setTimeout(function(){
process(todo.shift());//取得数组的下个元素并进行处理,并把取得的这个元素从todo数组中删除
if(todo.length>0){ //检查是否还有更多的元素需要处理,如果有,那么就再启动一个定时器,否则调用callback()函数
setTimeout(arguments.callee,25); //因为下一个定时器需要运行相同的代码,所以第一个参数为arguments.callee.该值指向当前正在运行的匿名函数
}else{
callback(items);
}
},25);
该方法抽出为一可重用的函数
function processArray(items,process,callback){
var todo=items.concat();
setTimeout(function(){
process(todo.shift());
if(todo.length>0){
setTimeout(arguments.callee,25);
}else{
callback(items);
}
},25);
}
上面的通用方法使用示例
var items=[22,542,542,63,224];
function outputValue(value){
console.log(value);
}
processArray(items,outputValue,function(){
console.log("完成");
});
同上,可把参数相同的一系列函数调用分割到不同的定时器中执行,通用方法如下:
function multistep(steps,args,callback){
var tasks=steps.concat();
setTimeout(function(){
var task=tasks.shift();
task.apply(null,args||[]);
if(tasks.length>0){
setTimeout(arguments.callee,25);
}else{
callback();
}
},25);
}
使用示例
function saveDocument(id){ var tasks=[openDocument,writeText,closeDocument,updateUI]; multistep(tasks,[id],function(){ //此处的[id]为数组 alert("保存完成"); }); }
JS中可以通过Date对象来跟踪代码的运行时间,如下
var start= +new Date();//用+号可以将Date对象转化为数字,那么在后续的数学运算中就无须转换了
stop;
//在这里运行的能测出运行的时间
stop= +new Date();
alert(stop-start);//弹出提示显示运行了多少毫秒
Web Workers |
js没有办法在浏览器UI线程之外运行代码.Web Workers API引入了一个接口,能使代码运行且不占用浏览器UI线程的时间.每个新的Worker都在自己的线程中运行代码,不会影响浏览器UI,也不会影响其它Worker中运行的代码.
WebWorkers被Firefox3.5,Chrome3,Safari4原生支持.
Worker运行环境由如下部分组成:
一个navigator对象,只包括四个属性:appName,appVersion,userAgent,platform 一个location对象(与window.location相同,不过所有属性都是只读的) 一个self对象,指向全局worker对象 一个importScripts()方法,用来加载Worker所用到的外部js文件 所有的ECMAScript对象,诸如Object,Array,Date等 XMLHttpRequest构造器 setTimeout()和setInterval()方法 一个close()方法,它能立刻停止Worker运行
由于Web Worker有着不同的全局运行环境,因此无法从js代码中创建它.事实上,需要创建一个完全独立的js文件,其中包含需要在Worker中运行的代码.要创建Worker线程,必须传入这个js文件的URL:
var worker=new Worker("code.js");//此代码一旦执行,将为这个文件创建一个新的线程和一个新的Worker运行环境.该文件会被异步下载,直到文件下载并执行完成后才会启动此Worker
网页代码可以通过postMessage()方法给Worker传递数据,只有一个参数.Worker有一个用来接收信息的onmessage事件处理器.
var worker=new Worker("code.js");
worker.onmessage=function(event){
alert(event.data);
};
worker.postMessage("Nicholas");//只能传递字符串,数字,布尔值,null,undefined,Object的实例和Array的实例。Safari4和Chrome3只允许传递字符串
Worker可通过它自己的postMessage()方法把信息回传给页面
//code.js内部代码 self.onmessage=function(){ self.postMessage("hi"+event.data+"!"); };
消息系统是网页和Worker通信的唯一途径.
Worker通过importScripts()方法加载外部的js文件。该方法接收一个或多个js文件的url作为参数。importScripts()的调用 过程是阻塞式的,直到所有的文件加载并执行完成以后,脚本才会继续运行。由于Worker在UI线程之外运行,所以这种阻塞并不会影响 UI响应。
网页中应用的示例.示例流程即页面通过postMessage()向Wroker传递json字符串,Worker在它自己的onmessage事件处理器中接收此字符串然后解析,完成以后再通过postMessage传回页面.
var worker=new Worker("jsonparser.js");
//数据到位时,调用事件处理器
worker.onmessage=function(event){
var jsonData=event.data;//JSON结构被回传回来
evaluateData(jsonData);//使用JSON结构
};
worker.postMessage(jsonText);//传入大段要解析的json字符串
//Worker中负责解析json的代码如下:(jsonparse.js内部代码)
//当json数据存在时,该事件处理器会被调用
self.onmessage=function(event){
var jsonText=event.data;//json字符串由event.data传入
var jsonData=JSON.parse(jsonText);//解析,本示例假设此处特别费时(500毫秒)
selft.postMessage(jsonData);//回传结果
};
优先使用Worker的情况(浏览器支持)
1,编码/解码大字符串
2,复杂数学运算,包括图像或视频处理
3,大数组排序
4,任何超过100毫秒的处理过程都应当考虑Worker方案是不是比基于定时器的方案更合适.