响应接口
长运行脚本(500万以上)最好的办法就是避免他们。
接口最好在100毫秒响应用户输入。
- 用定时器让出时间片
当一些JavaScript任务不能再100ms之内完成的时候,最好的办法就是:停止JavaScript运行,给UI线程机会进行更新,然后再继续运行JavaScript。
var btn=document.querySelector('#btn');
btn.onclick=function () {
onefunc();
setTimeout(function () {
btn.style.color="red";
console.log("setTimeout");
},50)
twofunc();
}
function onefunc() {
console.log("onefunc");
}
function twofunc() {
console.log("twofunc");
}
此代码将在50毫秒之后,向UI队列插入一个JavaScript任务运行` btn.style.color=”red”; console.log(“setTimeout”);`。在那个点之前,所有其他的UI更新和JavaScript任务都在运行。注意:第二个参数指出什么时候应当将任务添加到UI队列之中,并不是说那时代码将被执行。这个任务必须等到队列中其他的任务都执行之后才能被执行。 结果`onefunc; twofunc;setTimeout;`
注意如果调用setTimeout
的函数又调用了其他任务,耗时超过定时器延时,定时器代码将立即被执行,他与主调函数之间没有可察觉的延迟。
var btn=document.querySelector('#btn');
btn.onclick=function () {
onefunc();
setTimeout(function () {
btn.style.color="red";
},5)
twofunc();
}
function onefunc() {
console.log("onefunc");
}
function twofunc() {
var str="0"
for(var i=0;i<100000;i++){
str+=i;
}
console.log(str);
}
如上面的实例代码,twofunc运行超过了5ms,此时先打印`onefunc`;然后btn的字体变色,最后才打印str的字符串;这里有一个问题;如果没有注释掉`console.log(“setTimeout”);`这时候顺序会发生变化; 先打印`onefunc`;然后btn的字体变色,接着打印str的字符串;最后才打印`”setTimeout”;`
在任何一种情况下,创建一个定时器造成UI线程暂停,如同他从一个任务切换到下一个任务。因此,定时器代码复位所有相关的浏览器限制,,包括长运行脚本的时间。此外,调用栈也在定时器代码中复位为0。
这一特性使得定时器成为长运行JavaScript代码理想的跨浏览器解决方案。
**是否使用定时器取代循环的两个决定性因素**
- 此过程必须是同步处理吗?
数据必须按顺序处理吗?
如果这两个回答都是“否”,那么代码将适用于定时器分解工作。
一段基本的异步代码模式如下:
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);
}
processArray(array,function (e) {
console.log(e);
},function () {
console.log("done");
});
基本思想:创建一个原始数组的克隆,将他作为处理对象。每一次调用setTimeout()
创建一个定时器处理队列中的第一项。调用todo.shift()
返回他的第一项然后将他从数组中删除。此值作为参数传给process
,然后检查是否还有更多的项需要处理。如果todo
队列还有内容,那么就再启动一个定时器。因为下个定时器需要运行相同的代码,所以第一个参数传入arguments.callee
。此值指向当前正在运行的匿名函数。如果不再有内容需要处理,将调用callback
函数。
分解任务
我们通常将一个任务分解成一系列子任务。如果一个函数运行时间太长,那么查看它是否可以分解成一系列能够短时间完成的较小的函数。可将一行代码简单地看作一个原子任务,多行代码组合在一起构成一个独立任务。某些函数可基于函数调用进行拆分。例如
function saveDocument(id){
writeText(id);
closeDocument(id);
updateUI(id);
}
如果函数运行事件太长,可以将他拆分一系列更小的步骤,把独立的方法放在定时器中调用。你可以将每个函数都放入一个数组中,然后使用setTimeout(arguments.callee, 25);
function saveDocument(id){
var tasks = [openDocument, writeText, closeDocument, updateUI];
var task = tasks.shift();
task(id);
if (tasks.length > 0){
setTimeout(arguments.callee, 25);
}
}
上面的的方法就是:将每个方法放入任务数组,然后在每个定时器中调用一个方法,可以封装为
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);
}
multistep()函数接收三个参数:用于执行的函数数组,为每个函数提供参数的参数数组,当处理结束时 调用的回调函数。函数用法如下
function saveDocument(id) {
var tasks = [openDocument, writeText, closeDocument, updateUI];
multistep(tasks, [id], function () {
alert("Save completed!");
});
}
注意传给 multistep()的第二个参数必须是数组,它创建时只包含一个 id。正如数组处理那样,使用此函 数的前提条件是:任务可以异步处理而不影响用户体验或导致依赖代码出错。
限时运行代码
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);
}
此函数中添加了一个 do-while 循环,它在每个数组项处理之后检测时间。定时器函数运行时数组中存放 了至少一个项,所以后测试循环比前测试更合理。在 Firefox 3 中,如果 process()是一个空函数,处理一个 1’000 个项的数组需要 38 - 34 毫秒;原始的 processArray()函数处理同一个数组需要超过 25’000 毫秒。这就 是定时任务的作用,避免将任务分解成过于碎小的片断。
定时器与性能
过度使用定时器会对性能产生负面影响。在代码使用定时器序列,同一时间只有一个定时器存在,只有当这个定时器结束时才创建一个新的定时器。以这种方式使用定时器不会带来性能问题。
当多个重复的定时器被同时创建会产生性能问题。因为只有一个 UI 线程,所有定时器竞争运行时间。
要在你的网页应用中限制高频率重复定时器的数量。同时,建一个单独的重复定时器,每次执行多个操作。
Web Workers 网页工人线程
var worker = new Worker("jsonparser.js");
worker.onmessage = function (event) {
var jsonData = event.data;
evaluateData(jsonData);
};
worker.postMessage(jsonText);
self.onmessage = function (event) {
var jsonText = event.data;
var jsonData = JSON.parse(jsonText);
self.postMessage(jsonData);
};
页面使用 postMessage()将一个 JSON 字符串传给工人线程。工人线程在它的 onmessage 事件句柄中收到 这个字符串也就是 event.data,然后开始解析它。完成时所产生的 JSON 对象通过工人线程的 postMessage() 方法传回页面。然后此对象便成为页面 onmessage 事件句柄的 event.data。请记住,此工程只能在 Firefox 3.5 和更高版本中运行,而 Safari 4 和 Chrome 3 中,页面和工人线程之间只允许传递字符串。
summary
JavaScript和用户界面更新在同一个进程内运行,同一时刻只有其中一个可以运行。这意味着当JavaScript 代码正在运行时,用户界面不能响应输入,反之亦然。有效地管理 UI 线程就是要确保 JavaScript 不能运行 太长时间,以免影响用户体验。后,请牢记如下几点:
- JavaScript 运行时间不应该超过 100 毫秒。过长的运行时间导致 UI 更新出现可察觉的延迟,从而对整体 用户体验产生负面影响。
- JavaScript 运行期间,浏览器响应用户交互的行为存在差异。无论如何,JavaScript 长时间运行将导致用 户体验混乱和脱节。
- 定时器可用于安排代码推迟执行,它使得你可以将长运行脚本分解成一系列较小的任务。
- 网页工人线程是新式浏览器才支持的特性,它允许你在 UI 线程之外运行 JavaScript 代码而避免锁定 UI。
- 网页应用程序越复杂,积极主动地管理 UI 线程就越显得重要。没有什么 JavaScript 代码可以重要到允 许影响用户体验的程度。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)