JavaScript 事件循环 — event loop
引言
相信所有学过 JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的异步概念 。在初期许多人会把异步理解成类似多线程的编程模式,其实他们中有着很大的差别,要完全理解异步,就需要了解 JS 的运行核心——事件循环(event loop)。在之前我对事件循环的认识也是一知半解的,直到我看了 Philip Roberts 的演讲 What the heck is the event loop anyway?,我才对事件循环有了一个全面的认识,所以我想写一篇介绍 JS 事件循环的文章,以供大家学习和参考。
一、为什么会有异步?
为什么 JS 当中会有异步?我们想象一下,如果我们同步的执行一下代码会发生什么:
1 $.get(url, function(data) {
2 //do something
3 });
在我们使用 ajax 进行通信的时候,我们都默认了它是异步的,但是如果我们设置其为同步执行,会发生什么?如果你自己写一个小的测试程序,将后台代码延迟5s你会发现浏览器会出现阻塞,直到 ajax 响应了之后才会正常运行。这便是异步模式要解决的首要问题,如何使浏览器非阻塞的运行任务。想象一下如果我们同步的执行 ajax 请求的话,我们的等待的时间是一个未知数,在网络通信中可能很快也可能很慢,也可能永远也不会响应,这也就会导致浏览器会阻塞在一个未知的任务上面,这也是我们不希望看到的。所以我们希望有一种方式能够异步的处理程序,我们并不需要关心一个 ajax 请求会在何时完成,甚至它可以永远不会响应,我们只需要知道在请求响应后该如何处理,并且在等待响应的这段时间内我们还可以做一些其他的工作。因此,便有了 JavaScript Event Loop。
二、什么是事件队列?
首先,我们先来看一段简单的代码:
1 console.log("script start");
2
3 setTimeout(function () {
4 console.log("setTimeout");
5 }, 1000);
6
7 console.log("script end");
你可以在这里查看结果:
我们可以看到,首先,程序输出 'script start' 和 'script end',在大约1s之后输出了 'setTimeout' 。该程序的 'script end' 并没有等待1s之后输出,而是立即输出。这是因为 setTimeout 是一个异步的函数。意思也就是说当我们设置一个延迟函数的时候,当前脚本并不会阻塞,它只是会在浏览器的事件表中进行记录,程序会继续向下执行。当延迟的时间结束之后,事件表会将回调函数添加至事件队列(task queue)中,事件队列拿到了任务过后便将任务压入执行栈(stack)当中,执行栈执行任务,输出 'setTimeout'。
事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。
现在,我们对上面的代码进行一点修改:
1 console.log("script start");
2
3 setTimeout(function () {
4 console.log("setTimeout");
5 }, 0);
6
7 console.log("script end");
将延迟时间设置为0,看看程序会以何种顺序输出?无论我们设置多少的延迟时间,'setTimeout' 总是会在 'script end' 之后输出。有些浏览器可能会有一个最小延迟时间,有的是 15ms,有的是 10ms,这个在很多书当中都有提到,这可能会给同学们造成一种错觉:由于程序运行速度很快,并且有最小延迟时间,所以 'setTimeout' 会在 'script end' 之后输出。现在让我们在稍微变一下,来消除你的错觉:
1 console.log("script start");
2
3 setTimeout(function () {
4 console.log("setTimeout");
5 }, 0);
6
7 //具体数字不定,这取决于你的硬件配置和浏览器
8 for(var i = 0; i < 999999999; i ++){
9 //do something
10 }
11
12 console.log("script end");
可以看出,无论后面我们做了多少延迟性的工作,'setTimeout' 总是会在 'script end' 之后输出。所以究竟发生了什么?这是因为 setTimeout 的回调函数只是会被添加至事件队列,而不是立即执行。由于当前的任务没有执行结束,所以 setTimeout 任务不会执行,直到输出了 'script end' 之后,当前任务执行完毕,执行栈为空,这时事件队列才会把 setTimeout 回调函数压入执行栈执行。
执行栈则像是函数的调用栈,是一个树状的
三、事件队列有何作用?
通过以上的 demo 相信同学们都会对事件队列和执行栈有了一个基本的认识,那么事件队列有何作用?最简单易懂的一点就是之前我们所提到的异步问题。由于 JS 是单线程的,同步执行任务会造成浏览器的阻塞,所以我们将 JS 分成一个又一个的任务,通过不停的循环来执行事件队列中的任务。这就使得当我们挂起某一个任务的时候可以去做一些其他的事情,而不需要等待这个任务执行完毕。所以事件循环的运行机制大致分为以下步骤:
- 检查事件队列是否为空,如果为空,则继续检查;如不为空,则执行 2;
- 取出事件队列的首部,压入执行栈;
- 执行任务;
- 检查执行栈,如果执行栈为空,则跳回第 1 步;如不为空,则继续检查;
然而目前为止我们讨论的仅仅是 JS 引擎如何执行 JS 代码,现在我们结合 Web APIs 来讨论事件循环在当中扮演的角色。
在开始我们讨论过 ajax 技术的异步性和同步性,通过事件循环机制,我们则不需要等待 ajax 响应之后再进行工作。我们则是设置一个回调函数,将 ajax 请求挂起,然后继续执行后面的代码,至于请求何时响应,对我们的程序不会有影响,甚至它可能永远也不响应,也不会使浏览器阻塞。而当响应成功了以后,浏览器的事件表则会将回调函数添加至事件队列中等待执行。事件监听器的回调函数也是一个任务,当我们注册了一个事件监听器时,浏览器事件表会进行登记,当我们触发事件时,事件表便将回调函数添加至事件队列当中。
我们知道 DOM 操作会触发浏览器对文档进行渲染,如修改排版规则,修改背景颜色等等,那么这类操作是如何在浏览器当中奏效的?至此我们已经知道了事件循环是如何执行的,事件循环器会不停的检查事件队列,如果不为空,则取出队首压入执行栈执行。当一个任务执行完毕之后,事件循环器又会继续不停的检查事件队列,不过在这间,浏览器会对页面进行渲染。这就保证了用户在浏览页面的时候不会出现页面阻塞的情况,这也使 JS 动画成为可能, jQuery 动画在底层均是使用 setTimeout 和 setInterval 来进行实现。想象一下如果我们同步的执行动画,那么我们不会看见任何渐变的效果,浏览器会在任务执行结束之后渲染窗口。反之我们使用异步的方法,浏览器会在每一个任务执行结束之后渲染窗口,这样我们就能看见动画的渐变效果了。
考虑如下两种遍历方式:
1 var arr = new Array(999);
2 arr.fill(1);
3 function asyncForEach(array, handler){
4 var t = setInterval(function () {
5 if(array.length === 0){
6 clearInterval(t);
7 }else {
8 handler(arr.shift());
9 }
10 }, 0);
11 }
12
13 //异步遍历
14 asyncForEach(arr, function (value) {
15 console.log(value);
16 });
17
18 //同步遍历
19 arr.forEach(function (value, index, arr) {
20 console.log(value);
21 });
经过测试,我们可以看出,采用同步遍历的方法,当数组长度上升到3位数的时候,便会出现阻塞,但是异步遍历却不会出现阻塞现象(除非数组长度非常大,那是因为计算机的内存空间不足)。这是因为同步遍历方法是一个单独的任务,这个任务会将所有的数组元素遍历一遍,然后才会开始下一个任务。而异步遍历的方法将每一次遍历拆分成一个单独的任务,一个任务只遍历一个数组元素,所以在每个任务之间,我们的浏览器可以进行渲染,所以我们不会看见阻塞的情况。下面这个 demo 演示了在异步遍历前后发生的事情:
总结
现在,相信你已经认识了 JavaScript 的真实面目了吧。 JavaScript 是一门单线程的语言,但是其事件循环的特性使得我们可以异步的执行程序。这些异步的程序也就是一个又一个独立的任务,这些任务包括了 setTimeout、setInterval、ajax、eventListener 等等。关于事件循环,我们需要记住以下几点:
- 事件队列严格按照时间先后顺序将任务压入执行栈执行;
- 当执行栈为空时,浏览器会一直不停的检查事件队列,如果不为空,则取出第一个任务;
- 在每一个任务结束之后,浏览器会对页面进行渲染;
转载至http://www.cnblogs.com/dong-xu/p/7000163.html
JavaScript 事件循环 — event loop的更多相关文章
- JavaScript事件循环(Event Loop)机制
JavaScript 是单线程单并发语言 什么是单线程 主程序只有一个线程,即同一时间片断内其只能执行单个任务. 为什么选择单线程? JavaScript的主要用途是与用户互动,以及操作DOM.这决定 ...
- 一文梳理JavaScript 事件循环(Event Loop)
事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑. 众所周知,JS是单线程的,即同一时间只能运行一个任务.一般情况下这不会引发问题,但是如果我们有一个 ...
- JS事件循环(Event Loop)机制
前言 众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言. 为何单线程? 因为如果在DOM操作中,有两个线程一个添加节点,一个删除节点,浏览器并不知道以哪个为准,所以只能选 ...
- 事件循环Event loop到底是什么
摘要:本文通过结合官方文档MDN和其他博客深入解析浏览器的事件循环机制,而NodeJS有另一套事件循环机制,不在本文讨论范围中.process.nextTick和setImmediate是NodeJS ...
- 简单了解一下事件循环(Event Loop)
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- 事件循环 event loop 究竟是什么
事件循环 event loop 究竟是什么 一些概念 浏览器运行时是多进程,从任务管理器或者活动监视器上可以验证. 打开新标签页和增加一个插件都会增加一个进程,如下图:  浏览器渲染进程是多线程,包 ...
- 浏览器与Node的事件循环(Event Loop)有何区别?
前言 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的. 一.线程与进程 1. 概念 我们经常说 JS 是单线程执行的,指的是一个进程里 ...
- JavaScript:彻底理解同步、异步和事件循环(Event Loop) (转)
原文出处:https://segmentfault.com/a/1190000004322358 一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS ...
- JavaScript:彻底理解同步、异步和事件循环(Event Loop)
一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个.不妨叫它主线程. 但是实际上还存在其他 ...
随机推荐
- Linux内核开发之异步通知与异步I/O(一)
“小王,听说过锦上添花吧..”我拍拍下王的头说. “还锦上添花你,为你上次提的几个东东,我是头上长包..”小王气愤地瞪着我. “啊,为啥这样呢,本来还特意拒绝了MM的月份,抽出时间打算给你说点高级的东 ...
- 后台管理-基于 Bootstrap 开发的网站后台管理面板
Bootstrap 开发框架真的很强大,今天推荐几个非常不错的基于 Bootstrap 开发的网站后台管理面板,全部都是响应式布局,细节做得都很不错.可以搜索keenthemes. Metronic ...
- centos 配置 samba 与windows共享文件夹
yum install samba /etc/samba/smb.conf directory mask = 0777 ← 指定新建目录的属性(以下4行) force directory mode = ...
- NS3网络仿真(6): 总线型网络
快乐虾 http://blog.csdn.net/lights_joy/ 欢迎转载.但请保留作者信息 在NS3提供的第一个演示样例first.py中,模拟了一个点对点的网络,接下来的一个演示样例代码模 ...
- hibernate中对象集合的保存
一.在java web设计中经常使用对象进行操作,在hibernate中对象集合的保存(一对多) 1需要进行如下步骤: 1) 设计数据表关系 2)引入jar包,需要注意引入数据库connector 3 ...
- Memory leak patterns in JavaScript
Handling circular references in JavaScript applications Plugging memory leaks in JavaScript is easy ...
- php调试函数
void debug_print_backtrace ([ int $options = 0 [, int $limit = 0 ]] ) array debug_backtrace ([ int $ ...
- 点滴积累【JS】---JS实现仿百度模糊搜索效果
效果: HTML代码: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="In ...
- Windows 10 开发人员预览版中的新增功能(转自 IT之家)
Windows 10 开发人员预览版中的新增功能 在Win10预览版中安装工具与SDK后,即可着手创建Windows通用应用或先浏览目前的环境与此前相比都发生了什么变化. 应用建模 文件资源管理器: ...
- 【转】Elasticsearch5.0 安装问题集锦
Elasticsearch5.0 安装问题集锦 elasticsearch 5.0 安装过程中遇到了一些问题,通过查找资料几乎都解决掉了,这里简单记录一下 ,供以后查阅参考,也希望可以帮助遇到同样问题 ...