[Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列。使用下面代码,可以很容易使一个应用程序陷入泥潭。
while(true){}
而且它并不需要一个无限循环来写一个缓慢的程序。代码需要时间来运行,而低效的算法或数据结构可能导致运行长时间的计算。
效率不是js唯一关注的。基于事件的编程的确强加了一些特殊的约束。为了保持客户端应用程序的高度交互性和确保所有传入的请求在服务器应用程序中得到充分的服务,保持事件循环的每个轮次尽可能短是至关重要。否则,事件队列会滞销,其增长速度会超过分发处理事件处理程序的速度。在浏览器环境中,一些代价高昂的计算也会导致糟糕的用户体验,因为一个页面的用户界面无响应多数是由于在运行js代码。
那么,如果你的应用程序需要执行代价高昂的计算你该怎么办呢?没有一个完全正确的答案,但有一些通用的技术可用。也许最简单的方法是使用像Web客户端平台的Worker API这样的并发机制。这对于需要搜索大量可移动距离的人工智能游戏是一个很好的方法。游戏可能以生成大量的专门计算移动距离的worker开始。
var ai=new Worker('ai.js');
这将使用ai.js源文件作为worker的脚本,产生一个新的线程独立的事件队列的并发执行线程。该worker运行在一个完全隔离的状态--没有任何应用程序对象的直接访问。但是,应用程序与worker之间可以通过发送形式为字符串的messages来交互。所以,每当游戏需要程序计算移动时,它会发送一个消息给worker。
var userMove=/* ... */;
ai.postMessage(JSON.stringify({userMove:userMove}));
postMessage的参数被作为一个消息增加到worker的事件队列中。为了处理worker的响应,游戏会注册一个事件处理程序。
ai.onmessage=function(event){
executeMove(JSON.parse(event.data).computerMove);
};
与此同时,源文件ai.js指示worker监听消息并执行计算下一步移动所需的工作。
self.onmessage=function(event){
var userMove=JSON.parse(event.data).userMove;
var computerMove=computeNextMove(userMove);
var message=JSON.stringify({
computerMove:computerMove
});
selft.postMessage(message);
};
function computeNextMove(userMove){
//...
}
不是所有的js平台都提供类似Worker这样的API。而且有时传递消息的开销可能会过于昂贵。另一种方法是将算法分解为多个步骤,每个步骤组成一个可管理的工作块。第48条中搜索社交网络图的工作表算法。
Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member === other){
return true;
}
}
return false;
};
如果这段程序核心的while循环代价太过高昂,搜索工作很可能会以不可接受的时间运行而阻塞应用程序事件队列。即使我们可以使用Worker API,它也是昂贵或不方便实现的,因为它需要复制整个网络图的状态或在worker中存储网络图的状态,并总是使用消息传递来更新和查询网络。
幸运的是,这种算法被定义为一个步骤集的序列--while循环的迭代。可以通过增加一个回调参数将inNetwork转换为一个匿名函数,并像第64条讲述的,将while循环替换一个匿名的递归函数。
Member.prototype.inNetwork=function(other,callback){
var visited={};
var worklist=[this];
function next(){
if(worklist.length === 0){
callback(false);
return;
}
var member=worklist.pop();
if(member === other){
callback(true);
return;
}
setTimeout(next,0);
}
setTimeout(next,0);
};
这段代码的工作方式,为了替换while循环,这里写了一个局部的next函数,该函数执行循环中的单个迭代然后高度应用程序事件队列来异步运行下一次迭代。这使得在些期间已经发生的其他事件被处理后才继续下一次迭代。当搜索完成后,通过迭代的next来返回,从而有效地完成循环。
要调度迭代,我们使用多数js平台都可用的、通用的setTimeout API来注册next函数,使next函数经过一段最少时间(0毫秒)后运行。这具有几乎立刻将回调函数添加到事件队列上的作用。值得注意的是,虽然setTimeout有相对稳定的跨平台移植性,但通常还有更好的替代方案。例如,在浏览器环境中,最低的超时时间被压制为4毫秒,可以采用一种替代方案,使用postMessage立即压入一个事件。
如果应用程序事件队列的每个轮次中只执行算法的一个迭代。可以调整算法,自定义每个轮次中的迭代次数。这很容易实现,只须在next函数的主要部分的外围使用一个循环计数器。
Member.prototype.inNetwork=function(other,callback){
function next(){
for(var i=0;i<10;i++){
//...
}
setTimeout(next,0);
}
setTimeout(next,0);
};
提示
避免在主事件队列中执行代价高昂的算法
在支持Worker API的平台,该API可以用来在一个独立的事件队列中运行长计算程序
在Worker API不可用或代价昂贵的环境中,考虑将计算程序分解到事件循环的多个轮次中
附录一:Worker
Worker是可以在后台运行的任务,它能够被轻松创建,还能向它的创建者发送消息。只要调用worker()构造函数,指定一个需要运行在worker线程内的脚本,就能创建一个worker。
注意:worker能够产生新的worker,前提是这些worker托管于相同的源内来作为它们的父页面。
Worker线程能够在不干扰UI的情况下执行任务。另外,它能够使用XMLHttpRequest来执行I/O操作,只不过XMLHttpRequest上的responseXML与channel两个属性值始终返回null。
线程安全
Worker接口会生成真正的操作系统级别的线程,如果你不小心,那么并发会对你的代码产生影响。对于web worker来说,与其他线程的通信点会被很小心的控制,这意味着你很难引发并发问题。你没有办法去访问非线程安全的组件或者是DOM,此外还需要序列化对象来与线程交互特定的数据。
worker语法
构造函数
Worker(in DOMString scriptURL);
参数
scriptURL
worker将要执行的脚本的URL。它必须遵守同源策略。
返回值
一个新的Worker对象。
方法
void postMessage(Object message[,sequence<Transferable> transferList]);
参数
message
传输给woker的对象;它将包含于传递给onmessage处理函数的事件对象中的data字段内。可以传递任意值或是经过结构拷贝算法处理过的js对象,即可以包含循环引用。
transferList
一个可选的对象数组,用于转让它们的所有权。如果一个对象的所有权被转让,那么它在原来的上下文内将不可使用,而只能在转让到的worker内可用
void terminate();
立即终止worker。该方法不会给worker留下任何完成操作的机会;就是简单的立即停止。
属性
onmessage EeventListner
一个事件监听函数,每当拥有message属性的MessageEvent从worker中冒泡出来时就会执行该函数。事件的data属性存有消息内容。
onerror EeventListner
一个事件监听函数,每当类型为error的ErrorEvent从worker中冒泡出来时就会执行该函数。
错误信息对象
message 一个可读性良好的错误信息
filename 产生错误的脚本文件名
lineno 发生错误时所在的脚本文件号
[Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列的更多相关文章
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
- [Effective JavaScript 笔记]第53条:保持一致的约定
对于api使用者来说,你所使用的命名和函数签名是最能产生普遍影响的决策.这些约定很重要具有巨大的影响力.它建立了基本的词汇和使用它们的应用程序的惯用法.库的使用者必须学会阅读和使用这些.一致的约定可以 ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合
对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...
- [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染
之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...
随机推荐
- android-对话框
一.常用对话框 AlertDialog: 功能最丰富,实际应用最广的对话框(以下三种对话框都是该对话框的子类) ProgressDialog:进度对话框.这个对话框只是对进度条的包装 DatePick ...
- Android自定义UI模板
第一步:自定义xml属性 新建一个android项目,在values文件夹中新建一个atts.xml的文件,在这个xml文件中声明我们一会在使用自定义控件时候需要指明的属性.atts.xml < ...
- iBatis in 语句参数传入方法
刚刚开始在工作中用到iBatis 在用到in去查询或者删除 我本来是传递一个String的参数,但是总是报以下的错误
- 创建删除元素appendChild,removeChild,createElement,insertBefore
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...
- cookie&&session再理解笔记
就拿php来说,两个php页面之间不拿get,post传递变量的话,数据是不能共享的.访问完1.php页面该页面的变量就被销毁了.所以就拿学校食堂来说,拿现金买饭的话你交完钱后,他给你个票以便确认你, ...
- 演示对sys用户和普通用户进行审计的示例
1.确认数据库版本 1对SYS用户审计 1.1配置审计参数 1.2修改liunx日志配置文件 添加以下一列: 1.3 SYS 用户操作演示 2对普通用户审计 2.1配置审计参数 2.2演示对TEST用 ...
- linux同步系统时间
命令:ntpdate 路径:/usr/sbin/ntpdate 例子:ntpdate us.pool.ntp.org 查看日期时间命令:date 修改日期时间命令:date -s "2012 ...
- BizTalk开发系列(二十七) 异常管理中的数据编码
在BizTalk的异常管理解决方案中.大部分是通过订阅相关的升级属性来接收消息,并在自定义的流程或发送端口进行处理.但不管怎样,一般会定义统一的 错误消息Schema,这样不仅可以让我们通过异常信息快 ...
- python - socket - server
网络上关于socket的介绍文章数不胜数.自己记录下学习的点点滴滴.以供将来复习学习使用. socket中文的翻译是套接字,总感觉词不达意.简单的理解就是ip+port形成的一个管理单元.也是程序中应 ...
- How to bind data to a user control
http://support.microsoft.com/kb/327413 Create a user control by inheriting from the System.Windows. ...