我们都知道在浏览器中由于dom操作,js是单线程的。 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
      多线程并不意味着速度就快,因为切换时间片需要时间。因为单线程的原因,事件循环就应运而生了。由于nodejs基于js,所以两者都在处理异步事件时都依赖于事件循环,不过两者的事件循环机制有相同又有不同。在了解两者的事件循环机制之前先了解关于异步任务的两个概念:

微任务(Microtask) 通常来说就是在当前 task 执行结束后立即执行的任务(也就是总是先于宏任务),例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。microtask 任务队列是一个与 task 任务队列相互独立的队列,microtask 任务将会在每一个 task 任务执行结束之后执行。每一个 task 中产生的 microtask 都将会添加到 microtask 队列中,microtask 中产生的 microtask 将会添加至当前队列的尾部,并且 microtask 会按序的处理完队列中的所有任务。

vue中的nextTick实现:Vue 在内部尝试对异步队列使用原生的setImmediate Promise.then和MessageChannel(旧版本采用mutationObserver,因为兼容性问题后面弃用),如果当前执行环境不支持,就采用setTimeout(fn, 0)代替。
// MutationObserver示例:let observe = new MutationObserver(function () {console.log('dom全部塞进去了');});// 也是一个微任务observe.observe(div,{childList:true});for (let i = 0; i < 100; i++) {let p = document.createElement('p');div.appendChild(p);}console.log(1);let img = document.createElement('p');div.appendChild(img);
复制代码
// MessageChannel用法:console.log(1);let channel = new MessageChannel();let port1 = channel.port1;let port2 = channel.port2;// 异步代码 vue 就是宏任务port1.postMessage('hello');port2.onmessage = function (e) {console.log(e.data);}console.log(2);// 1 2 hello
复制代码
// webwork(不能操作dom)相当于开了一个线程,用法:
// index.html中的jsconsole.log(1);let worker = new Worker('./worker.js');worker.postMessage(1000); // 发消息worker.onmessage = function (e) { // 接收消息console.log(e.data); // 消息中的数据}console.log(2);// worker.js
onmessage = function (e) {let sum = 0;for(var i = 0;i<e.data;i++){sum += i;}this.postMessage(sum)
}
复制代码

通过代码运行来理解浏览器和node事件循环机制的不同:

// 在浏览器,node下分别测试下面的一段代码:
setTimeout(() => {console.log('1')Promise.resolve('123').then(data => {console.log(2)});
});
setTimeout(() => {console.log('3');
});//浏览器下  1 2 3//node环境下 1 3 2
复制代码

原理:
浏览器 :执行栈中内容执行后执行微任务,微任务清空后再执行宏任务,到达条件的宏任务最终会在栈中执行,不停的循环event loop。所以上面的代码在输出1后,会先执行Promise微任务,然后再去执行任务队列里面;
node:微任务总是在新一轮事件循环开始之前执行,所以先执行完所有到达时间的setTimeout,然后在进入下一轮事件循环之前再执行Promise.resolve。下面的node事件环可以加强理解:

node启动过程

  • 1、调用platformInit方法 ,初始化 nodejs 的运行环境。
  • 2、调用 performance_node_start 方法,对 nodejs 进行性能统计。
  • 3、openssl设置的判断。
  • 4、调用v8_platform.Initialize,初始化 libuv 线程池。
  • 5、调用 V8::Initialize,初始化 V8 环境。
  • 6、创建一个nodejs运行实例。
  • 7、启动上一步创建好的实例。
  • 8、开始执行js文件,同步代码执行完毕后,进入事件循环。
  • 9、在没有任何可监听的事件时,销毁 nodejs 实例,程序执行完毕。

在libuv(是用C语言实现的一套异步功能库,nodejs高效的异步编程模型很大程度上归功于libuv的实现)内部有这样一个事件环机制。在node启动时会初始化事件环

┌───────────────────────┐┌─>│     timers(计时器)     │|  |   执行setTimeout以及   ||  |   setInterval的回调。  |│  └──────────┬────────────┘微任务│  ┌──────────┴────────────┐│  │     I/O callbacks     |│  | 处理网络、流、tcp的错误 ||  | callback              |│  └──────────┬────────────┘微任务│  ┌──────────┴────────────┐│  │     idle, prepare     │|  |     node内部使用       |│  └──────────┬────────────┘      微任务│  ┌──────────┴────────────┐       ┌───────────────┐ │  │       poll(轮询)      │       │   incoming:   │|  | 执行poll中的i/o队列    | <─────┤  connections, │|  | 检查定时器是否到时      |       │   data, etc.读取文件  |     │  └──────────┬────────────┘       └───────────────┘    微任务│  ┌──────────┴────────────┐      │  │      check(检查)      │|  | 存放setImmediate回调   |│  └──────────┬────────────┘微任务│  ┌──────────┴────────────┐└──┤    close callbacks    |│ 关闭的回调例如         || sockect.on('close')   |└───────────────────────┘这里每一个阶段都对应一个事件队列,当event loop执行到某个阶段时会将当前阶段对应的队列依次执行。当队列执行完毕或者执行的数量超过上线时,会转入下一个阶段。微任务总是在开始新一轮循环时执行。
复制代码

结合上面的流程图,可以总结出node事件循环原理:

  • node 的初始化

    • 初始化 node 环境。
    • 执行输入代码。
    • 执行 process.nextTick 回调。
    • 执行 microtasks。
  • 进入 event-loop

    • 进入 timers 阶段

      • 检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。
      • 检查是否有 process.nextTick 任务,如果有,全部执行。
      • 检查是否有microtask,如果有,全部执行。
      • 退出该阶段。
    • 进入IO callbacks阶段。

      • 检查是否有 pending 的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。
      • 检查是否有 process.nextTick 任务,如果有,全部执行。
      • 检查是否有microtask,如果有,全部执行。
      • 退出该阶段。
    • 进入 idle,prepare 阶段:

      • 这两个阶段与我们编程关系不大,暂且按下不表。
    • 进入 poll 阶段

      • 首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。

        • 第一种情况:

          • 如果有可用回调(可用回调包含到期的定时器还有一些IO事件等),执行所有可用回调。
          • 检查是否有 process.nextTick 回调,如果有,全部执行。
          • 检查是否有 microtaks,如果有,全部执行。
          • 退出该阶段。
        • 第二种情况:
          • 如果没有可用回调。
          • 检查是否有 immediate 回调,如果有,退出 poll 阶段。如果没有,阻塞在此阶段,等待新的事件通知。
      • 如果不存在尚未完成的回调,退出poll阶段。
    • 进入 check 阶段。

      • 如果有immediate回调,则执行所有immediate回调。
      • 检查是否有 process.nextTick 回调,如果有,全部执行。
      • 检查是否有 microtaks,如果有,全部执行。
      • 退出 check 阶段
    • 进入 closing 阶段。

      • 如果有immediate回调,则执行所有immediate回调。
      • 检查是否有 process.nextTick 回调,如果有,全部执行。
      • 检查是否有 microtaks,如果有,全部执行。
      • 退出 closing 阶段
    • 检查是否有活跃的 handles(定时器、IO等事件句柄)。

      • 如果有,继续下一轮循环。
      • 如果没有,结束事件循环,退出程序。

在事件循环的每一个子阶段退出之前都会按顺序执行如下过程: 检查是否有 process.nextTick 回调,如果有,全部执行。 检查是否有 microtaks,如果有,全部执行。 退出当前阶段。

下面通过一些可能遇到的面试题加强理解:

// node下执行以下代码
setImmediate(function(){
console.log('1');
});
setTimeout(function(){
console.log('2');
});// 由于node存在准备时间,两者的输出顺序是不一定的。
复制代码

// 但是稍做修改:
let fs =require('fs')
fs.readFile('./1.txt','utf8',()=>{setImmediate(function(){console.log('1');});setTimeout(function(){console.log('2');});
})// 1 2 结果永远是 1 2,即使setTimeout放在setImmediate前面。因为poll(轮询) 后就执行setImmediate。而setTimeout必须是在新一轮循环中才会执行
复制代码

//node下执行:

process.nextTick(function A() {console.log(1);process.nextTick(function B(){console.log(2);});
});setTimeout(function timeout() {console.log('TIMEOUT FIRED');
}, 0)// 1  2  TIMEOUT FIRED ;不管有多少个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行完之后执行。也就是说微任务总是在新一轮事件循环之前执行。
复制代码

//node下执行:

setImmediate(function () {console.log('4')
})
setImmediate(function () {console.log('5')
})
process.nextTick(function () {console.log('1')process.nextTick(function () {console.log('2')process.nextTick(function () {console.log('3')})})
})console.log('next')// 总是输出 next 1 2 3 4 5。在同步代码执行完后,执行微任务,然后进入下一轮事件循环
复制代码

总结:

异步任务分为:微任务和宏任务

  1. 浏览器端:宏任务主要有setTimeout,setInterval,setImmediate(ie),messageChannel,ajax请求,click事件等。微任务主要有Promise.then,mutationObserver。先执行栈里面的内容,栈执行完后,执行微任务,然后再读取异步队列,有到达执行条件的,读取到执行栈中执行。如此循环。
  2. node端:宏任务主要有setTimeout,setInterval,setImmediate,文件读写等。微任务主要有Promise.then,process.nextTick(比Promise.then更快执行)。

参考:
译文:JS事件循环机制(event loop)之宏任务、微任务: segmentfault.com/a/119000001…
[译] 深入理解 JavaScript 事件循环(二)— task and microtask:
www.cnblogs.com/dong-xu/p/7…
剖析nodejs的事件循环:juejin.im/post/5af141…
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理:juejin.im/post/5a6547…

阮一峰 node循环:www.ruanyifeng.com/blog/2018/0…

转载于:https://juejin.im/post/5afc519af265da0b851d0bff

浏览器与node事件循环相关推荐

  1. 浏览器和node事件循环

    什么是事件循环 每一个浏览器都至少有一个事件循环,一个事件循环至少有一个任务队列.循环指的是其永远处于一个"无限循环"中.不断将注册的回调函数推入到执行栈 浏览器的事件循环标准是由 ...

  2. 浏览器中的事件循环机制

    浏览器中的事件循环机制 网上一搜事件循环, 很多文章标题的前面会加上 JavaScript, 但是我觉得事件循环机制跟 JavaScript 没什么关系, JavaScript 只是一门解释型语言, ...

  3. JavaScript执行机制-node事件循环

    node环境下的事件循环机制 和浏览器有什么不同? 在node中,事件循环表现出来的状态和浏览器大致相同,但是node有一套自己的模型. node事件循环依靠libuv引擎,node选择chrome ...

  4. JavaScript重难点解析5(对象高级、浏览器内核与事件循环模型(js异步机制))

    JavaScript重难点解析5(对象高级.浏览器内核与事件循环模型(js异步机制) 对象高级 对象创建模式 Object构造函数模式 对象字面量模式 工厂模式 自定义构造函数模式 构造函数+原型的组 ...

  5. 浏览器事件循环与node事件循环

    前言 最近看到一些关于 事件队列,浏览器执行机制的文章推荐,联想到很早以前遇到的一些面试题,才惊觉自己对这块一直都不怎么了解,借助这个机会好好记录一番.顺便感叹一句,阮一峰大神的 blog真是应有尽有 ...

  6. node事件循环 EventEmitter 异步I/O Buffer缓冲区 模块

    node.js事件循环 node.js单进程,单线程的程序 每一个api都支持回调 所有的事件机制都是设计模式中的 一共是23种设计模式 http://design-patterns.readthed ...

  7. node事件循环 EventEmitter 异步I/O Buffer缓冲区 模块

    node.js事件循环 node.js单进程,单线程的程序 每一个api都支持回调 所有的事件机制都是设计模式中的 一共是23种设计模式 http://design-patterns.readthed ...

  8. js事件循环机制(await-async-事件循环)

    await和async 异步函数 async function async关键字用于声明一个异步函数: async是asynchronous单词的缩写,异步.非同步: sync是synchronous ...

  9. Node.js event loop 和 JS 浏览器环境下的事件循环的区别

    Node.js  event loop 和 JS 浏览器环境下的事件循环的区别: 1.线程与进程: JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 进程是 CPU ...

最新文章

  1. CocoaPods远程私有库
  2. 小程序在输入npm命令_微信小程序使用npm包步骤
  3. python之multiprocessing创建进程
  4. C++:常用数据类型及常见操作
  5. mybatis批量更新报错XXXXX-Inline
  6. np.random.randint产生一个范围内的数据
  7. pytorch——torch.backends.cudnn.benchmark = True
  8. python爬取微博内容_请问该如何通过python调用新浪微博的API来爬取数据?
  9. mui框架mui-active高亮当前栏目 - 代码说明
  10. 【增速】人工智能之计算机视觉工业领域落地一览
  11. win10动态桌面_win10动态桌面如何设置?电脑炫酷动态壁纸
  12. 如何在Proteus中模拟Arduino
  13. 旧手机改文件储存服务器,旧手机改云服务器
  14. insist用法扫描
  15. html5跳跳蛙小游戏分析,中班科学优秀教案《跳跳蛙》(5页)-原创力文档
  16. APP打包后上传遇到ERROR ITMS-90096解决办法
  17. 手机连WIFI显示【正在获取IP】地址解决办法
  18. 【Vue报错】This is probably not a problem with npm. There is likely additional logging output above
  19. npm --save 的含义
  20. Flink 如何分流数据

热门文章

  1. 码云克隆项目到IntelliJ IDEA中
  2. ROS保姆级0基础入门教程⭐ |第一章 ROS的概述与环境搭建(4万字教程,建议收藏)
  3. 基于SpringBoot的在线音乐播放系统
  4. win7下如何删除不需要的windows服务
  5. spring无法连接mysql_初学Spring——连接MySQL数据库的问题和解决
  6. rstudio server docker 部署_Docker环境运行Spring Cloud项目
  7. sql 查询关联字段 最好取别名 不然会被第一个覆盖
  8. camera驱动电源配置_电源行业发展前景如何?
  9. 学计算机和电脑办公的区别,自学编程和计算机科班出身的差别在哪?
  10. 行人属性数据集pa100k_Attribute-Recognition行人属性识别资料