浏览器和Node中的事件循环机制

2019-05-18 admin

一、前言

前几天听公司一个公司三年的前端说“今天又学到了一个知识点-微任务、宏任务”,我问他这是什么东西,由于在吃饭他浅浅的说了下,当时没太理解就私下学习整理一番,由于谈微任务、宏任务必谈到事件循环,于是就有了这篇博客。

在谈到事件循环机制之前我们需要知道一些基础知识就是:

  • js是单线程的
  • js一开始是作为脚本语言运行在客户端

其实js是单线程在它作为脚本语言操作dom的时候就决定了。那么此时就有一个性能问题,那么js在浏览器端是如何处理这个问题的呢?同时,js在后台Node中又是如何解决的呢?这就是本篇需要介绍的事件循环机制,这里我将分别以浏览器和Node两个方面来分析。

二、浏览器端

在讲解事件循环之前先谈谈js中同步代码、异步代码的执行流程。

2.1、js同步代码执行过程

js引擎在执行通过代码的过程中,会安装顺序依次存储到一个地方去,这个地方就叫做执行栈,当我们调用一个方法的时候,js会生成一个和这个方法相对应的上下文(context)。这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,这个作用域中定义的变量以及这个作用域的this对象。

function a() {
    console.log("method a execute...");
}
function b() {
    a();
}
function c() {
    b();
}
c();

以上面例子分析:js在执行的时候会有一个全局上下文,我们这里就称为GContext,下面分析步骤

  1. 调用c(),c入栈,此时栈中内容为:GContext->c-contextC
  2. 接着调用b(),b入栈,此时栈中内容为:GContext->c->contextC->b->contextB
  3. 接着调用a(),a入栈,此时栈中内容为:GContext->c->contextC->b->contextB-c->contextC
  4. a执行完,a出栈,此时栈中内容为:GContext->c->contextC->b->contextB
  5. b执行完,b出栈,此时栈中内容为:GContext->c->contextC
  6. c执行完,b出栈,此时栈中内容为:GContext
  7. 全部执行完,释放资源

ok,上面是同步代码的执行,上面会涉及到两个核心概念:执行整个代码的线程我们称之为主线程,存放方法执行的地方我们称之为执行栈.

2.2、js异步代码执行过程

上面说完了同步过程,那这里来谈谈异步的过程。js引擎在遇到一个异步事件,不会一直等待返回结果而是将它挂起。当异步任务执行完之后会将结果加入到和执行栈中不同的任务队列当中,注意的是:此时放入队列不会立即执行其回调,而是当主线程执行完执行栈中所有的任务之后再去队列中查找是否有任务,如果有则取出排在第一位的事件然后将回调放入执行栈并执行其代码。如此反复就构成了事件循环。 image.png

这里同样有一个核心概念:任务队列

2.3、微任务、宏任务

上面提到js执行异步方法的时候会将其返回结果放到队列中,这是比较笼统的,具体来说,js会根据任务的类型将其放入不同的队列,任务类型有两种:微任务、宏任务。那么其对应的哪些是微任务、哪些是宏任务呢?

  • 微任务:Promise、process.nextTick()、整体代码script、Object.observer、MutationObserver
  • 宏任务:setTimeout()、setInterval()

浏览器在执行的时候,先从宏任务队列中取出一个宏任务执行宏,然后在执行该宏任务下的所有的微任务,这是一个循环;然后再取出并执行下一个宏任务,再执行所有的微任务,这是第二个循环,以此类推.

注意:整个javascript代码是第一个宏任务

const process = require('process')
setTimeout(function () {// 分发宏任务到EventQueue
    console.log("1");
}, 0);
setTimeout(() => {
    console.log("11");
}, 0);
setTimeout(() => {
    console.log("111");
}, 0);
new Promise(function (resolve) {
    console.log('2');
    resolve();
}).then(function () {// 发送微任务
    console.log('3');
});
// 输出
2
3
1
11
111

2.4、小结

在浏览器端,在我们执行一片script的时候,当遇到同步代码,依次进入执行栈,遇到异步代码,将其挂起,继续执行其它方法,当异步方法执行完之后根据任务类型进入到任务队列,在执行栈执行完,主线程空闲下来了之后会到任务队列中取任务回调并执行。

三、Node端

我自己认为Node的事件循环和浏览器端还是有点区别的,它的事件循环依靠libuv引擎。

image.png

该图来自官网,这里展示了在node的事件循环的6个阶段。

  • timers:该阶段执行定时器的回调,如setTimeout() 和 setInterval()。
  • I/O callbacks:该阶段执行除了close事件,定时器和setImmediate()的回调外的所有回调
  • idle, prepare:内部使用
  • poll:等待新的I/O事件,node在一些特殊情况下会阻塞在这里
  • check: setImmediate()的回调会在这个阶段执行
  • close callbacks: 例如socket.on(‘close’, …)这种close事件的回调

对于我们来说我们更关注 timer、poll、check这三个阶段即可。

poll 阶段有两个主要的功能:

  • 处理poll队列(poll quenue)的事件(callback);
  • 执行timers的callback,当到达timers指定的时间时;

poll 阶段的逻辑

  • 如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:

    • a、如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;

    • b、如果poll queue为空,将会发生下面情况:

      * 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
      * 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue;
      
      
  • 如果event loop进入了 poll阶段,且代码设定了timer:

    • 如果poll queue进入空状态时(即poll 阶段为空闲状态),event loop将检查timers,
    • 如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue

3.1、setTimeout、setImmediate

这两个函数的功能还是类似的,不同的是他们处于EventLoop的不同阶段:timer、check。

setImmediate(()=>console.log("setInterval"));
setTimeout(() => {console.log("setTimeout")},0);

上面两行代码会输出顺序是什么呢?其实两种可能都有. 1.当setTimeout的0ms并不能做到绝对0ms,如果已经过了timer阶段,那么此时setTimeout就会在下一次循环中执行,也就是说先setInterval、再setTimeout。 2.第二种可能就是正常流程了,先timer、再check

如果上面的代码再一个IO操作作呢?如:

require('fs').readFile(__filename,()=>{
    setImmediate(()=>console.log("setInterval"));
    setTimeout(() => {console.log("setTimeout")});
})

此时只可能出现一种情况,先setInterval、再setTimeout,因为在io中已经执行过了timer(readFile时处于IO callback)。 下面一起来看如下代码:

setTimeout(() => {
    console.log("timer1")
    Promise.resolve().then(() => console.log("promise1"));
    process.nextTick(() => console.log("nextTick1"))
}, 0);
setTimeout(() => {
    console.log("timer2")
    Promise.resolve().then(() => console.log("promise2"));
    process.nextTick(() => console.log("nextTick2"))
}, 0);

按照我的理解,它的输出应该是如下:先timer、然后切换阶段的时候执行微任务.

// 情况1
timer1
timer2
nextTick1
nextTick2
promise1
promise2

可是并不是,它的输出一直是:

// 情况2
timer1
nextTick1
promise1
timer2
nextTick2
promise2

后台晚上查资料因为Node11对EventLoop作了修改,为了和浏览器兼容。于是呼我切换到10.8.0,发现上面两种情况都有(情况1比例大于情况2)。这点暂时还未查明什么原因。

3.2、小结

node中的6个阶段每个阶段执行完都会伴随着执行微任务,同个MicroTask队列下process.tick()会优于Promise。

四 总结

本篇主要介绍了浏览器和Node对于事件循环机制实现,由于能力水平有限,其中可能有误之处欢迎指出。

欢迎关注公众号: 码农有道

[转载]原文链接:https://segmentfault.com/a/1190000019218477

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-65221.html

文章标题:浏览器和Node中的事件循环机制

相关文章
Vue获取DOM元素样式和样式更改示例
在 vue 中用 document 获取 dom 节点进行节点样式更改的时候有可能会出现 ‘style’ is not definde的错误,这时候可以在 mounted 里用 $refs 来获取样式,并进行更改: <template...
2017-03-13
JavaScript教程:JS中的原型
Keith Peters 几年前发表的一篇博文,关于学习没有“new”的世界,其中解释了使用原型继承代替构造函数。两者都是纯粹的原型编码。 标准方法(The Standard Way) 一直以来,我们学习的在 JavaScript 里创建对...
2015-11-12
NodeJS参考手册pdf版
下载地址:Nodejs参考手册PDF版下载 ...
2015-11-12
AJAX的浏览器支持
AJAX 的要点是 XMLHttpRequest 对象。 不同的浏览器创建 XMLHttpRequest 对象的方法是有差异的。 IE 浏览器使用 ActiveXObject,而其他的浏览器使用名为 XMLHttpRequest 的 Jav...
2015-11-12
typeof、instanceof和contructor的区别
typeof:以字符串的形式返回变量的原始类型,typeof在两种情况下会返回"undefined":一个变量没有被声明的时候,和一个变量的值是undefined的时候,注意,typeof null也会返回object,...
2015-11-12
Node.js学习(1)----HTTP服务器与客户端
Node.js 标准库提供了 http 模块,其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端。http.Server 是一个基于事件的 HTTP 服务器,它的核心由 Node.js 下层 C++部分实现,而接口由 Jav...
2015-11-12
2015年3月国内浏览器市场份额概括,chrome占32.97
本报告数据,来源于百度统计所覆盖的超过150万的站点,而不是baidu.com的流量数据。 注:奇虎360浏览器份额在2010年10月至2011年3月,和2012年9月以来,两次大幅下降,是因为360浏览器去掉了原本的浏览器特征(User...
2015-11-12
JS中的语音合成——Speech Synthesis API
JS中的语音合成——Speech Synthesis API 简介 HTML5中和Web Speech相关的API实际上有两类,一类是“语音识别(Speech Recognition)”,另外一个就是“语音合成(Speech Synthes...
2018-05-17
JavaScript 事件流、事件处理程序及事件对象总结
JS与HTML之间的交互通过事件实现。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用监听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式,支持页面的行为与页面的外观之间的松散耦合。...
2017-04-05
如何为高负载网络优化Nginx 和 Node.js?
译者:AlfredCheung 在搭建高吞吐量web应用这个议题上,NginX和Node.js可谓是天生一对。他们都是基于事件驱动模型而设计,可以轻易突破Apache等传统web服务器的C10K瓶颈。预设的配置已经可以获得很高的并发,不过,...
2015-11-12
回到顶部