理解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 相关文章推荐
接收键盘指令的脚本
Jun 26 Javascript
jQuery 获取对象 基本选择与层级
May 31 Javascript
jquery下异步提交表单 异步跨域提交表单
Nov 17 Javascript
javascript五图轮播切换实用版
Aug 17 Javascript
对jQuery的事件绑定的一些思考(补充)
Apr 20 Javascript
基于jQuery实现图片的前进与后退功能
Apr 24 Javascript
分享33个jQuery与CSS3实现的绚丽鼠标悬停效果
Dec 15 Javascript
javascript使用正则表达式实现去掉空格之后的字符
Feb 15 Javascript
js实现仿京东2级菜单效果(带延时功能)
Aug 27 Javascript
jQuery UI库中dialog对话框功能使用全解析
Apr 23 Javascript
关于webuploader插件使用过程遇到的小问题
Nov 07 Javascript
JavaScript代码实现txt文件的上传预览功能
Mar 27 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
ThinkPHP之用户注册登录留言完整实例
2014/07/22 PHP
php递归创建目录的方法
2015/02/02 PHP
php注册登录系统简化版
2020/12/28 PHP
JavaScript 工具库 Cloudgamer JavaScript Library v0.1 发布
2009/10/29 Javascript
JQuery中Text方法用法实例分析
2015/05/18 Javascript
JavaScript实现级联菜单的方法
2015/06/29 Javascript
JS实现可点击展开与关闭的左侧广告代码
2015/09/02 Javascript
AngularJS向后端ASP.NET API控制器上传文件
2016/02/03 Javascript
jQuery通过写入cookie实现更换网页背景的方法
2016/04/15 Javascript
vue2.0父子组件间传递数据的方法
2018/08/16 Javascript
JS获取今天是本月第几周、本月共几周、本月有多少天、是今年的第几周、是今年的第几天的示例代码
2018/12/05 Javascript
vue项目中使用particles实现粒子背景效果及遇到的坑(按钮没有点击响应)
2020/02/11 Javascript
python基础教程之lambda表达式使用方法
2014/02/12 Python
Python中列表、字典、元组、集合数据结构整理
2014/11/20 Python
python正则表达式中的括号匹配问题
2014/12/14 Python
Python线程的两种编程方式
2015/04/14 Python
用Python登录Gmail并发送Gmail邮件的教程
2015/04/17 Python
python3 pandas 读取MySQL数据和插入的实例
2018/04/20 Python
python3中的md5加密实例
2018/05/29 Python
Python3实现的旋转矩阵图像算法示例
2019/04/03 Python
在python中用print()输出多个格式化参数的方法
2019/07/16 Python
python使用itchat模块给心爱的人每天发天气预报
2019/11/25 Python
使用PDF.JS插件在HTML中预览PDF文件的方法
2018/08/29 HTML / CSS
Nike英国官网:Nike.com (UK)
2017/02/13 全球购物
Europcar德国:全球汽车租赁领域的领导者
2018/08/15 全球购物
如何通过 CSS 写出火焰效果
2021/03/24 HTML / CSS
医学院校毕业生自荐信范文
2014/01/01 职场文书
初中毕业生的自我评价
2014/03/03 职场文书
银行优秀员工事迹材料
2014/05/29 职场文书
圣诞节活动策划方案
2014/06/09 职场文书
职工小家建设活动方案
2014/08/25 职场文书
2014入党积极分子破除“四风”思想汇报
2014/09/14 职场文书
自习课吵闹检讨书范文
2014/09/26 职场文书
个人租房协议书
2014/11/28 职场文书
python爬虫之爬取笔趣阁小说
2021/04/22 Python
解析MySQL binlog
2021/06/11 MySQL