Js 的事件循环(Event Loop)机制以及实例讲解

前言

你们都知道js是单线程的脚本语言,在同一时间,只能作同一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理等行为,防止主线程阻塞,Event Loop方案应运而生...html

我的博客了解一下:obkoro1.com面试


为何js是单线程?

js做为主要运行在浏览器的脚本语言,js主要用途之一是操做DOM。ajax

在js高程中举过一个栗子,若是js同时有两个线程,同时对同一个dom进行操做,这时浏览器应该听哪一个线程的,如何判断优先级?浏览器

为了不这种问题,js必须是一门单线程语言,而且在将来这个特色也不会改变。网络


执行栈与任务队列

由于js是单线程语言,当遇到异步任务(如ajax操做等)时,不可能一直等待异步完成,再继续往下执行,在这期间浏览器是空闲状态,显而易见这会致使巨大的资源浪费。dom

执行栈

当执行某个函数、用户点击一次鼠标,Ajax完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入任务队列中,等待主线程读取,遵循先进先出原则。异步

执行任务队列中的某个任务,这个被执行的任务就称为执行栈。函数

主线程

要明确的一点是,主线程跟执行栈是不一样概念,主线程规定如今执行执行栈中的哪一个事件。oop

主线程循环:即主线程会不停的从执行栈中读取事件,会执行完全部栈中的同步代码。post

当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不一样的队列中,咱们称之为任务队列(Task Queue)。

当主线程将执行栈中全部的代码执行完以后,主线程将会去查看任务队列是否有任务。若是有,那么主线程会依次执行那些任务队列中的回调函数。

不太理解的话,能够运行一下下面的代码,或者点击一下这个demo

结果是当a、b、c函数都执行完成以后,三个setTimeout才会依次执行。

let a = () => {
  setTimeout(() => {
    console.log('任务队列函数1')
  }, 0)
  for (let i = 0; i < 5000; i++) {
    console.log('a的for循环')
  }
  console.log('a事件执行完')
}
let b = () => {
  setTimeout(() => {
    console.log('任务队列函数2')
  }, 0)
  for (let i = 0; i < 5000; i++) {
    console.log('b的for循环')
  }
  console.log('b事件执行完')
}
let c = () => {
  setTimeout(() => {
    console.log('任务队列函数3')
  }, 0)
  for (let i = 0; i < 5000; i++) {
    console.log('c的for循环')
  }
  console.log('c事件执行完')
}
a();
b();
c();
// 当a、b、c函数都执行完成以后,三个setTimeout才会依次执行
复制代码

js 异步执行的运行机制。

  1. 全部任务都在主线程上执行,造成一个执行栈。
  2. 主线程以外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
  4. 主线程不断重复上面的第三步

宏任务与微任务:

异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不一样的API注册的任务会依次进入自身对应的队列中,而后等待 Event Loop 将它们依次压入执行栈中执行。

宏任务(macrotask):

script(总体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

微任务(microtask):

Promise、 MutaionObserver、process.nextTick(Node.js环境)

Event Loop(事件循环):

Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务以下:

  • 执行栈选择最早进入队列的宏任务(一般是script总体代码),若是有则执行
  • 检查是否存在 Microtask,若是存在则不停的执行,直至清空 microtask 队列
  • 更新render(每一次事件循环,浏览器均可能会去更新渲染)
  • 重复以上步骤

宏任务 > 全部微任务 > 宏任务,以下图所示:

从上图咱们能够看出:

  1. 将全部任务当作两个队列:执行队列与事件队列。
  2. 执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列以后,事件队列以前。
  3. 当执行完同步代码以后,就会执行位于执行列表以后的微任务,而后再执行事件列表中的宏任务

上面提到的demo结果能够这么理解:先执行script宏任务,执行完了以后,再执行其余两个定时器宏任务。


面试题实践

下面这个题,不少人都应该看过/遇到过,从新来看会不会以为清晰不少:

// 执行顺序问题,考察频率挺高的,先本身想答案**
    setTimeout(function () {
        console.log(1);
    });
    new Promise(function(resolve,reject){
        console.log(2)
        resolve(3)
    }).then(function(val){
        console.log(val);
    })
    console.log(4);
复制代码

根据本文的解析,咱们能够获得:

  1. 先执行script同步代码

    先执行new Promise中的console.log(2),then后面的不执行属于微任务
     而后执行console.log(4)
    复制代码
  2. 执行完script宏任务后,执行微任务,console.log(3),没有其余微任务了。

  3. 执行另外一个宏任务,定时器,console.log(1)。

根据本文的内容,能够很轻松,且有理有据的猜出写出正确答案:2,4,3,1.


结语

相似上文的面试题还有不少,实则都大同小异,只要掌握了事件循环的机制,这些问题都会变得很简单。

文章若有不正确的地方欢迎各位路过的大佬鞭策!但愿你们看完能够有所收获,喜欢的话,赶忙点波订阅关注/喜欢。

看完的朋友能够点个喜欢/关注,您的支持是对我最大的鼓励。

我的blog and 掘金我的主页,如需转载,请放上原文连接并署名。码字不易,感谢支持!

若是喜欢本文的话,欢迎关注个人订阅号,漫漫技术路,期待将来共同窗习成长。

以上2018.6.16

参考资料:

详解JavaScript中的Event Loop(事件循环)机制

JavaScript中的事件循环 Event Loop

JavaScript 运行机制详解:再谈Event Loop

相关文章
相关标签/搜索