JavaScript运行机制和事件循环

2019-01-14 admin

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整体执行过程:

  1. 主线程运行的时候会生成堆(heap)和栈(stack);
  2. js从上到下解析方法,将其中的同步任务按照执行顺序排列到执行栈中;
  3. 当程序调用外部的API时,比如ajax、setTimeout等,会将此类异步任务挂起,继续执行执行栈中的任务,等异步任务返回结果后,再按照执行顺序排列到任务队列中;
  4. 主线程先将执行栈中的同步任务清空,然后检查任务队列中是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务排列到任务队列中。
  5. 主线程每次将执行栈清空后,就去任务队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个过程是循环往复的… …,这个过程被称为“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://segmentfault.com/a/1190000017890121

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

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

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

文章标题:JavaScript运行机制和事件循环

相关文章
JavaScript常用特效chm下载
下载地址:JavaScript常用特效chm下载 对了,如果打开空白,在手册上右键属性解除锁定即可。 ...
2015-11-12
JavaScript教程:JS中的原型
Keith Peters 几年前发表的一篇博文,关于学习没有“new”的世界,其中解释了使用原型继承代替构造函数。两者都是纯粹的原型编码。 标准方法(The Standard Way) 一直以来,我们学习的在 JavaScript 里创建对...
2015-11-12
javascript是什么意思
avaScript是Netscape开发的一个对象脚本语言,它使用在世界各地数以百万计的网页和服务器应用程序上。 网景的JavaScript是ecma - 262版的标准脚本语言,和公布的标准只有轻微的差异。 与广为流行的错误理解相反,Ja...
2015-11-12
21天学通javascript
简介: 本书是Javascript入门教程。Javascript是Web开发中应用最早、发展最成熟、用户最多的脚本语言。其语法简洁,代码可读性在众多脚本语言中最好,它在使用时不用考虑数据类型,是真正意义上的动态语言。本书总分为四篇,共21章...
2015-11-16
typeof、instanceof和contructor的区别
typeof:以字符串的形式返回变量的原始类型,typeof在两种情况下会返回"undefined":一个变量没有被声明的时候,和一个变量的值是undefined的时候,注意,typeof null也会返回object,...
2015-11-12
JavaScript的组成
一个完整的JavaScript由3个部分组成:核心(ECMAScript) 文档对象模型(DOM) 浏览器对象模型(BOM) ECMAScript 描述了该语言的语法和基本对象 ; DOM 描述了处理网页内容的方法和接口 ; BOM 描...
2015-11-12
JavaScript 事件流、事件处理程序及事件对象总结
JS与HTML之间的交互通过事件实现。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用监听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式,支持页面的行为与页面的外观之间的松散耦合。...
2017-04-05
JavaScript变量的声明
声明变量 变量在脚本中的首次亮相是在其声明中。 在变量首次出现时将会在内存中设置它,因此您稍后可在脚本中引用它。 应在使用变量之前先声明变量。 可以使用 var 关键字实现此目的。 <span id=“mt9” class=“sent...
2015-11-12
如何为高负载网络优化Nginx 和 Node.js?
译者:AlfredCheung 在搭建高吞吐量web应用这个议题上,NginX和Node.js可谓是天生一对。他们都是基于事件驱动模型而设计,可以轻易突破Apache等传统web服务器的C10K瓶颈。预设的配置已经可以获得很高的并发,不过,...
2015-11-12
javaScript+turn.js实现图书翻页效果实例代码
为了实现图书翻页的效果我们在网上可以看到很多教程 在这里推荐turn.js 网上的turn.js 有api 不过是英文的  很多人看起来不方便 .关于代码也是奇形怪状在这里我将详细讲解如何使用turn.js实现翻页效果 ,本篇文章只是讲解 ...
2017-03-16
回到顶部