资讯专栏INFORMATION COLUMN

JavaScript运行机制和事件循环

Ververica / 569人阅读

摘要:主线程不断重复上面的三步,此过程也就是常说的事件循环。所以主线程代码执行时间过长,会阻塞事件循环的执行。参考资料这一次,彻底弄懂执行机制任务队列的顺序机制事件循环搞懂异步事件轮询与中的事件循环

1. 说明

读过本文章后,您能知道:

JavaScript代码在浏览器中的执行机制和事件循环

面试中经常遇到的代码输出顺序问题

首先通过一段代码来验证你是否了解代码输出顺序,如果你不知道输出顺序,那么本文可以帮助你了解:

console.log(1)
setTimeout(function () {
  new Promise(function (resolve) {
    console.log(2)
    resolve()
  })
  .then(() => { console.log(3) })
})
setTimeout(function () {
  console.log(4)
})
console.log(5)
2. JavaScript执行机制

JavaScript语言的执行是单线程(single thread)的。

所谓的单线程,就是指一次只执行一个任务,如果有多个任务,就必须排队,前面一个任务完成,才能执行后面任务。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等待,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段JavaScript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,JavaScript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

JavaScript执行机制:

1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
2、主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",如果有有执行任务,则进入执行栈,开始执行。
4、主线程不断重复上面的三步,此过程也就是常说的Event Loop(事件循环)。
3. JavaScript执行机制中名词介绍 3.1 执行栈

当我们调用一个方法的时候,js会生成一个与这个方法相对应的执行环境,也叫执行上下文,这个执行环境存在着这个方法的私有作用域、参数、this对象等等。因为js是单线程的,同一时间只能执行一个方法,所以当一系列的方法被依次调用的时候,js会先解析这些方法,把其中的同步任务按照执行顺序排队到一个地方,这个地方叫做执行栈。

3.2 主线程

JavaScript是单线程的,那么这个单线程就成为主线程。而事件循环在主线程执行完执行栈代码后,才执行的。所以主线程代码执行时间过长,会阻塞事件循环的执行。只有当执行栈为空的时候(同步代码执行完毕),才会执行事件循环来观察有哪些事件回调需要执行,当事件循环检测到任务队列有事件就读取出回调放到执行栈由主线程执行。

3.3 任务队列

任务队列也有时称叫消息队列、回调队列。

异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如onclick, setTimeout,ajax处理的方式都不同,这些异步操作是由浏览器内核的webcore来执行的,webcore包含下图中的3种 webAPI,分别是DOM Binding、network、timer模块。

DOM Binding 模块处理一些DOM绑定事件,如onclick事件触发时,回调函数会立即被webcore添加到任务队列中。

network 模块处理Ajax请求,在网络请求返回时,才会将对应的回调函数添加到任务队列中。

timer 模块会对setTimeout等计时器进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。

3.4 事件循环

如上图所示,JavaScript整体执行过程:

主线程运行的时候会生成堆(heap)和栈(stack);

js从上到下解析方法,将其中的同步任务按照执行顺序排列到执行栈中;

当程序调用外部的API时,比如ajax、setTimeout等,会将此类异步任务挂起,继续执行执行栈中的任务,等异步任务返回结果后,再按照执行顺序排列到任务队列中;

主线程先将执行栈中的同步任务清空,然后检查任务队列中是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务排列到任务队列中。

主线程每次将执行栈清空后,就去任务队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个过程是循环往复的... ...,这个过程被称为“Event Loop 事件循环”。

也可以参考如下链接:https://html.spec.whatwg.org/...

4. 宏任务和微任务

出现Promise后,JavaScript对于任务的定义除了广义的同步任务和异步任务,又对任务做了更精细的定义,macrotask(宏任务)和 microtask(微任务):

macrotask(按优先级顺序排列): script(你的全部JS代码,“同步代码”), setTimeout, setInterval, setImmediate(node的), I/O,UI rendering

microtask(按优先级顺序排列):process.nextTick(node的),Promise(这里指浏览器原生实现的 Promise), Object.observe, MutationObserver

注意:宏任务、微任务中出现的nodejs中的方法是nodejs专有的,浏览器的JavaScript环境暂时没有支持。

4.1 事件循环对宏任务和微任务的处理

有了宏任务和微任务后,JavaScript事件循环对此处理方法如下形式:

js引擎首先从macrotask queue中取出第一个任务,执行完毕后,将microtask queue中的所有任务取出,按顺序全部执行;

然后再从macrotask queue(宏任务队列)中取下一个,执行完毕后,再次将microtask queue(微任务队列)中的全部取出;

循环往复,直到两个queue中的任务都取完。

注意::此处把执行同步代码算成第一个宏任务了。

5. 一个实际例子讲解JavaScript执行流程

如下代码:

console.log("1");
setTimeout(function() {
  console.log("2");
  new Promise(function(resolve) {
      console.log("3");
      resolve();
  }).then(function() {
      console.log("4")
  })
})
console.log("5");
setTimeout(function() {
  console.log("6");
  new Promise(function(resolve) {
      console.log("7");
      resolve();
  }).then(function() {
      console.log("8")
  })
})
console.log("9");

上面代码执行过程:

第一轮事件循环

整体script代码(同步代码)作为第一个宏任务进入主线程,遇到console.log,输出1

遇到setTimeout,其回调函数被放到宏任务队列中,暂记为setTmineout1

遇到console.log,输出5

遇到setTimeout,其回调函数被放到宏任务队列中,暂记为setTmineout2

遇到console.log,输出9

一个宏任务执行结束,去微任务队列查找是否有待执行的任务,没有,结束

第一轮循环结束,输出:1 5 9

第二轮事件循环

从宏任务队列中取出一个任务,即setTmineout1,开始执行

遇到console.log,输出2

遇到Promise,创建Promise,输出了3,同时把Promise.then回调函数放到微任务队列

一个宏任务执行结束,去微任务队列查找是否有待执行的任务, 发现有微任务,全部取出放到执行栈执行

执行微任务,此时就一个微任务,console.log,输出4

微任务执行结束

第二轮循环结束,输出:2 4

第三轮事件循环与第二轮一样,输出:6 7 8

事件循环发现所有任务都已经处理完毕,此时程序执行结束

全部的输出:1 5 9 2 3 4 6 7 8,可复制代码到chrome浏览器控制台中运行校验结果

注意:浏览器环境JavaScript的执行机制和node中JavaScript的执行机制是不同的。

参考资料

这一次,彻底弄懂 JavaScript 执行机制

JavaScript任务队列的顺序机制(事件循环)

搞懂 JavsScript 异步 —  事件轮询

JS与Node.js中的事件循环

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101012.html

相关文章

  • JavaScript 运行机制详解(理解同步、异步事件循环)

    摘要:从异步过程的角度看,函数就是异步过程的发起函数,事件监听函数就是异步过程的回调函数。事件触发时,表示异步任务完成,会将事件监听器函数封装成一条消息放到消息队列中,等待主线程执行。 1.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。JavaScrip...

    loonggg 评论0 收藏0
  • JS与Node.js中的事件循环

    摘要:的单线程,与它的用途有关。特点的显著特点异步机制事件驱动。队列的读取轮询线程,事件的消费者,的主角。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给引擎。 这两天跟同事同事讨论遇到的一个问题,js中的event loop,引出了chrome与node中运行具有setTimeout和Promise的程序时候执行结果不一样的问题,从而引出了Nodejs的...

    abson 评论0 收藏0
  • JS是单线程,你了解其运行机制吗?

    摘要:的单线程,与它的用途有关。事件循环事件循环是指主线程重复从消息队列中取消息执行的过程。到此为止,就完成了工作线程对主线程的通知,回调函数也就得到了执行。 一. 区分进程和线程 很多新手是区分不清线程和进程的,没有关系。这很正常。先看看下面这个形象的比喻: 进程是一个工厂,工厂有它的独立资源-工厂之间相互独立-线程是工厂中的工人,多个工人协作完成任务-工厂内有一个或多个工人-工人之间共享...

    AlphaGooo 评论0 收藏0
  • Javascript系列之javascript机制

    摘要:异步任务必须指定回调函数,当异步任务从任务队列回到执行栈,回调函数就会执行。事件循环主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为。事件循环事件循环是指主线程重复从消息队列中取消息执行的过程。 参考链接:这一次,彻底弄懂 JavaScript 执行机制https://zhuanlan.zhihu.com/p/...从浏览器多进程到JS单线程,JS运行机制...

    13651657101 评论0 收藏0
  • JavaScript执行机制

    摘要:事件循环事件循环是实现异步的一种方法,也是的执行机制。最后的最后是一门单线程语言是的执行机制部分内容转自 1.单线程 javascript是一门单线程语言 2.javascript事件循环 同步任务 异步任务 showImg(https://segmentfault.com/img/bVbufUd?w=1268&h=1062);除了广义的同步任务和异步任务,我们对任务有更精细的定义...

    ralap 评论0 收藏0

发表评论

0条评论

Ververica

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<