穿越了整个沙漠,只为寻eventloop的真相

216 阅读4分钟

今天主要讲一下eventloop在浏览器中与在node中运行的机制,首先,不得不来说一下,下面几个名词的概念。

Node是什么?

  • Node.js是一个基于 ChromeV8引擎的JavaScript运行环境(runtime),Node不是一门语言,而是让js运,行在后端的运行时,并且不包括javascript全集,因为在服务端中不包含DOM和BOM,Node也提供了一些新的模块例如http,fs模块等。

  • Node.js 使用了事件驱动、非阻塞式 I/O 的模型,使其轻量又高效并且Node.js 的包管理器 npm,是全球最大的开源库生态系统。

Node的优势

  • 高并发,是指在同一时间并发访问服务器

  • I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、解压、加密、解密

进程与线程

谈谈浏览器

  • 用户界面-包括地址栏、前进/后退按钮、书签菜单等

  • 浏览器引擎-在用户界面和呈现引擎之间传送指令(浏览器的主进程)

  • 渲染引擎,也被称为浏览器内核(浏览器渲染进程)

  • 一个插件对应一个进程(第三方插件进程)

  • GPU提高网页浏览的体验(GPU进程)

渲染引擎

  • 渲染引擎内部是多线程的,内部包含两个最为重要的线程ui线程和js线程。

  • 这里要特别注意ui线程和js线程是互斥的,因为JS运行结果会影响到ui线程的结果。

  • ui更新会被保存在队列中等到js线程空闲时立即被执行。

js单线程

  • javascript在最初设计时设计成了单线程,为什么不是多线程呢?

  • 如果多个线程同时操作DOM那岂不会很混乱?这里所谓的单线程指的是主线程是单线程的,所以在Node中主线程依旧是单线程的。

  • js只能是单线程的 不能两个线程同时操作一个dom

  • ui进程与js进程是互斥的,不能同时执行,执行js后空闲下来了,再去执行css

其他线程

  • 浏览器事件触发线程(用来控制事件循环,存放setTimeout、浏览器事件、ajax的回调函数)

  • 定时触发器线程(setTimeout定时器所在线程)

  • 异步HTTP请求线程(ajax请求线程)

队列&栈

  • 队列遵循:先进先出;

如下图所示:

  • 栈遵循:先进后出;

下列代码中,函数是放到栈中的,看看它的执行顺序


function fn1() {
    let a = 1;
    fn2();
    function fn2() {
        console.log(a);
        let b = 2;
        function fn3() {
            debugger;
            console.log(b);
        }
        fn3();
    }
}
fn1();

执行结果如下图所示:

宏任务&微任务

  • 常见的(macro-task)宏任务:setTimeout setImmediate(只兼容ie) MessageChannel

  • 常见的(micro-task)微任务:promise.then (MutationObserver);

了解了以上几个名词的概念之后,下面重点来了,先看一下EventLoop在浏览器中的运行机制

浏览器中的EventLoop

  • 先会执行栈中的内容,栈中内容执行后执行微任务,微任务清空后再执行宏任务,宏任务会在栈中执行,不停的循环;
Promise.resolve('1').then(data => { console.log('第一个promise') });
setTimeout(() => {
    console.log('setTimeout1')
    Promise.resolve('2').then(data => { console.log('第二个promise') });
});
setTimeout(() => {
    console.log('setTimeout2');
});

代码运行的结果为:

第一个promise
setTimeout1
第二个promise
setTimeout2
1,setTimeout有一个4ms的最短时间,也就是说不管你设定多少,反正最少都要间隔4ms才运行里面的回调,所以先执行了第一个promise异步回调,输出了第一个promise
2,setTimeout到运行时间了,输出了setTimeout1,
3,依次输出了第二个promise(Promise的任务会在当前事件循环末尾中执行)
4,最后,第二个setTimeout到时间了,输出了setTimeout2

EventLoop在node中的运行机制

Node中的EventLoop

  • nextTick(微任务)是队列切换时执行的

  • setTimeout和setImmediate顺序是不固定,看node准备时间

setImmediate(() => {
  console.log('setImmediate1')
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0);
})
setTimeout(()=>{
  process.nextTick(()=>console.log('nextTick'))
  console.log('setTimeout2')
  setImmediate(()=>{
    console.log('setImmediate2')
  })
},0);

第一种情况,如果先执行了setImmediate函数,那么它的执行顺序为:

setImmediate1
setTimeout2
setTimeout1
nextTick
setImmediate2
1,根据上图所示,如果先执行了setImmediate回调,把setImmediate1输出了,
2,接下来,会执行setTimeout回调,输出setTimeout2,
3,接着输出setTimeout1,接着微任务nextTick,
4,最后,输出setImmediate2

第二种情况,如果先执行了setTimeout函数,那么它的执行顺序为:

setTimeout2
nextTick
setImmediate1
setImmediate2
setTimeout1
1,根据上图所示,如果先执行了setTimeout回调,把setTimeout2输出了,
2,接下来,会执行poll轮询,那就是输出了nextTick,
3,接下来执行check队列中setImmediate函数,把setImmediate1输出了,check队列里的内容得先执行完,才能切换下一个队列,所以输出了setImmediate2,
4,最后循环到了setTimeout回调,输出了setTimeout1

结语:

关于eventloop在不同环境中运行的机制,首先要对上述提到的,队列&栈、微任务&宏任务要有深入的理解,希望这篇内容对大家有所帮助,当然也希望大家如果有什么问题,可以随时加关注,进行讨论!