理解JS事件循环


Posted in Javascript onJanuary 07, 2016

伴随着JavaScript这种web浏览器脚本语言的普及,对它的事件驱动交互模型,以及它与Ruby、Python和Java中常见的请求-响应模型的区别有一个基本了解,对您是有益的。在这篇文章中,我将解释一些JavaScript并发模型的核心概念,包括其事件循环和消息队列,希望能够提升你对一种语言的理解,这种语言你可能已经在使用但也许并不完全理解。

这篇文章是写给谁的?

这篇文章是针对在客户端或服务器端使用或计划使用JavaScript的web开发人员的。如果你已经精通事件循环,那么这篇文章的大部分对你来说会很熟悉。对于那些还不是很精通的人,我希望能给你提供一个基本的了解,这样可以更好地帮助你阅读和编写日常代码。

非阻塞I / O

在JavaScript中,几乎所有的I/O都是非阻塞的。这包括HTTP请求,数据库操作和磁盘读写,单线程执行要求在运行期执行一个操作时,提供一个回调函数,然后继续做其它的事情。当操作已经完成时,消息和已提供的回调函数一起插入到队列。在将来的某个时候,消息从队列移除,回调函数触发。

虽然这种交互模型可能对已经习惯使用用户界面的开发人员很熟悉,比如“mousedown,”和“click”事件在某一时刻被触发。这与通常在服务器端应用程序进行的同步式请求-响应模型是不同的。

让我们来比较一下两小块代码,发出HTTP请求到www.google.com和输出响应到控制台。首先看看Ruby,配合使用Faraday(一个Ruby 的HTTP 客户端开发库):

response = Faraday.get 'http://www.google.com'
puts response
puts 'Done!'

执行路径很容易跟踪:

1、执行get方法,执行的线程等待,直到收到响应
2、从谷歌收到响应并返回给调用者,它存储在一个变量中
3、变量的值(在本例中,就是我们的响应)输出到控制台
4、值“Done!“输出到控制台
让我们使用Node.js和Request库在JavaScript做同样的事情:

request('http://www.google.com', function(error, response, body) {
 console.log(body);
});
 
console.log('Done!');

表面上看略有不同,实际行为截然不同:

1、执行请求函数,传递一个匿名函数作为回调,当响应在将来某个时候可用时执行回调。
2、“Done!“立即输出到控制台
3、在将来的某个时候,响应返回和回调执行时,输出它的内容到控制台
事件循环

将调用者和响应解耦,使得JavaScript在运行期等待异步操作完成和回调触发时可以做其他事情。但是这些回调在内存中是如何组织的,按什么顺序执行?什么导致他们被调用?

JavaScript运行时包含一个消息队列,它存储了需要处理的消息的列表和相关的回调函数。这些消息是以队列的形式来响应回调函数所涉及的外部事件(如鼠标单击或收到HTTP请求的响应)的。例如,如果用户单击一个按钮,但没有提供回调函数,那么也没有消息会被加入队列。

在一次循环,队列提取下一条消息(每次提取称为一次“tick”),当事件发生,该消息的回调执行。

理解JS事件循环

回调函数的调用在调用栈作为初始化frame(片段),由于JavaScript是单线程的,未来的消息提取和处理因为等待栈的所有调用返回而被停止。后续(同步)函数调用会添加新的调用frame到栈(例如,函数init调用函数changeColor)。

function init() {
 var link = document.getElementById("foo");
 
 link.addEventListener("click", function changeColor() {
  this.style.color = "burlywood";
 });
}
 
init();

在这个例子中,当用户单击“foo”元素时,一条消息(及其回调函数changeColor)会被插入到队列,并触发“onclick“事件。当消息离开队列时,其回调函数changeColor被调用。当changeColor返回(或者是抛出一个错误),事件循环仍在继续。只要函数changeColor存在,并指定为“foo”元素的onclick方法的回调,那么在该元素上单击会导致更多的消息(和相关的回调changeColor)插入队列。

队列附加消息

如果一个函数在代码中按异步调用(比如setTimeout),提供的回调将最终作为一个不同的消息队列的一部分被执行,它将发生在事件循环的某个未来的动作上。例如:

function f() {
 console.log("foo");
 setTimeout(g, 0);
 console.log("baz");
 h();
}
 
function g() {
 console.log("bar");
}
 
function h() {
 console.log("blix");
}
 
f();

由于setTimeout的非阻塞特性,它的回调将在至少0毫秒后触发,而不是作为消息的一部分被处理。在这个示例中,setTimeout被调用, 传入了一个回调函数g且延时0毫秒后执行。当我们指定时间到达(当前情况是,几乎立即执行),一个单独的消息将被加入队列(g作为回调函数)。控制台打印的结果会是像这样:“foo”,“baz”,“blix”,然后是事件循环的下一个动作:“bar”。如果在同一个调用片段中,两个调用都设置为setTimeout -传递给第二个参数的值也相同-则它们的回调将按照调用顺序插入队列。

Web Workers

使用Web Workers允许您能够将一项费时的操作在一个单独的线程中执行,从而可以释放主线程去做别的事情。worker(工作线程)包括一个独立的消息队列,事件循 环,内存空间独立于实例化它的原始线程。worker和主线程之间的通信通过消息传递,看起来很像我们往常常见的传统事件代码示例。

理解JS事件循环

首先,我们的worker:

// our worker, which does some CPU-intensive operation
var reportResult = function(e) {
 pi = SomeLib.computePiToSpecifiedDecimals(e.data);
 postMessage(pi);
};
 
onmessage = reportResult;

然后,主要的代码块在我们的HTML中以script-标签存在:

// our main code, in a <script>-tag in our HTML page
var piWorker = new Worker("pi_calculator.js");
var logResult = function(e) {
 console.log("PI: " + e.data);
};
 
piWorker.addEventListener("message", logResult, false);
piWorker.postMessage(100000);

在这个例子中,主线程创建一个worker,同时注册logResult回调函数到其“消息”事件。在worker里,reportResult函数注册到自己的“消息”事件中。当worker线程接收到主线程的消息,worker入队一条消息同时带上reportResult回调函数。消息出队时,一条新消息发送回主线程,新消息入队主线程队列(带上logResult回调函数)。这样,开发人员可以将cpu密集型操作委托给一个单独的线程,使主线程解放出来继续处理消息和事件。

关于闭包的

JavaScript对闭包的支持,允许你这样注册回调函数,当回调函数执行时,保持了对他们被创建的环境的访问(即使回调的执行时创建了一个全新的调用栈)。理解我们的回调作为一个不同的消息的一部分被执行,而不是创建它的那个会很有意思。看看下面的例子:

function changeHeaderDeferred() {
 var header = document.getElementById("header");
 
 setTimeout(function changeHeader() {
  header.style.color = "red";
 
  return false;
 }, 100);
 
 return false;
}
 
changeHeaderDeferred();

在这个例子中,changeHeaderDeferred函数被执行时包含了变量header。函数 setTimeout被调用,导致消息(带上changeHeader回调)被添加到消息队列,在大约100毫秒后执行。然后 changeHeaderDeferred函数返回false,结束第一个消息的处理,但header变量仍然可以通过闭包被引用,而不是被垃圾回收。当 第二个消息被处理(changeHeader函数),它保持了对在外部函数作用域中声明的header变量的访问。一旦第二个消息 (changeHeader函数)执行结束,header变量可以被垃圾回收。

提醒

JavaScript 事件驱动的交互模型不同于许多程序员习惯的请求-响应模型,但如你所见,它并不复杂。使用简单的消息队列和事件循环,JavaScript使得开发人员在构建他们的系统时使用大量asynchronously-fired(异步-触发)回调函数,让运行时环境能在等待外部事件触发的同时处理并发操作。然而,这不过是并发的一种方法。

以上就是本文的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
ExtJS 2.0实用简明教程 之Ext类库简介
Apr 29 Javascript
jquery 合并内容相同的单元格(示例代码)
Dec 13 Javascript
vue自定义指令实现v-tap插件
Nov 03 Javascript
jQuery基于ajax实现页面加载后检查用户登录状态的方法
Feb 10 Javascript
get  post jsonp三种数据交互形式实例详解
Aug 25 Javascript
vue将对象新增的属性添加到检测序列的方法
Feb 24 Javascript
对vue里函数的调用顺序介绍
Mar 17 Javascript
深入浅析JS中的严格模式
Jun 04 Javascript
vue 使用自定义指令实现表单校验的方法
Aug 28 Javascript
vue-awesome-swiper 基于vue实现h5滑动翻页效果【推荐】
Nov 08 Javascript
详解vue组件中使用路由方法
Feb 12 Javascript
vue2.0项目集成Cesium的实现方法
Jul 30 Javascript
angularjs创建弹出框实现拖动效果
Aug 25 #Javascript
JavaScript中定义类的方式详解
Jan 07 #Javascript
javascript类型系统 Window对象学习笔记
Jan 07 #Javascript
直接拿来用的页面跳转进度条JS实现
Jan 06 #Javascript
基于jQuery实现鼠标点击导航菜单水波动画效果附源码下载
Jan 06 #Javascript
JavaScript动态创建div等元素实例讲解
Jan 06 #Javascript
谈谈JavaScript类型系统之Math
Jan 06 #Javascript
You might like
php写的简易聊天室代码
2011/06/04 PHP
PHP学习散记_编码(json_encode 中文不显示)
2011/11/10 PHP
PHP中对缓冲区的控制实现代码
2013/09/29 PHP
汇总PHPmailer群发Gmail的常见问题
2016/02/24 PHP
jQuery+php简单实现全选删除的方法
2016/11/28 PHP
PHP设计模式之工厂模式详解
2017/10/24 PHP
JS的IE和Firefox兼容性集锦
2006/12/11 Javascript
javascript Zifa FormValid 0.1表单验证 代码打包下载
2007/06/08 Javascript
JavaScript中链式调用之研习
2011/04/07 Javascript
基于jquery实现漂亮的动态信息提示效果
2011/08/02 Javascript
JS Replace()的高级使用方法介绍
2013/06/29 Javascript
Js放到HTML文件中的哪个位置有什么区别
2013/08/21 Javascript
JavaScript indexOf方法入门实例(计算指定字符在字符串中首次出现的位置)
2014/10/17 Javascript
多功能jQuery树插件zTree实现权限列表简单实例
2016/07/12 Javascript
js遍历json的key和value的实例
2017/01/22 Javascript
javascript简写常用的12个技巧(可以大大减少你的js代码量)
2020/03/28 Javascript
AngularJS 表单验证手机号的实例(非必填)
2017/11/12 Javascript
jQuery实现弹窗下底部页面禁止滑动效果
2017/12/19 jQuery
手写Node静态资源服务器的实现方法
2018/03/20 Javascript
Vue移动端实现图片上传及超过1M压缩上传
2019/12/23 Javascript
tf.truncated_normal与tf.random_normal的详细用法
2018/03/05 Python
Python实现的文轩网爬虫完整示例
2019/05/16 Python
mac使用python识别图形验证码功能
2020/01/10 Python
浅谈django channels 路由误导
2020/05/28 Python
python语音识别指南终极版(有这一篇足矣)
2020/09/09 Python
python数据抓取3种方法总结
2021/02/07 Python
购买澳大利亚最好的服装和内衣在线:BONDS
2016/10/14 全球购物
建筑设计学生的自我评价
2014/01/16 职场文书
元旦红领巾广播稿
2014/02/19 职场文书
《燕子专列》教学反思
2014/02/21 职场文书
病媒生物防治方案
2014/05/13 职场文书
新闻编辑专业自荐信
2014/07/02 职场文书
护士节活动总结
2014/08/29 职场文书
小学教师岗位职责
2015/04/02 职场文书
高中政治教学反思
2016/02/23 职场文书
女性励志书籍推荐
2019/08/19 职场文书