灵魂三问

  • JS为什么是单线程的

    • 我们都知道,JS是单线程的语言,那为什么呢?我的理解是JS设计之初就是为了在浏览器端完成DOM操作和一些简单交互的,既然涉及到DOM操作如果是多线程就会带来复杂的同步问题,比较极端的例子就是两个线程可能一个在删除某个DOM节点一个却在修改这个DOM节点,这是浏览器以哪个线程为准呢?
  • 为什么需要异步
    • 如果没有异步,单线程的JS从上到下执行遇到一段代码需要执行比较久时就会阻塞页面的渲染,造成页面假死,这显然是一种很差的用户体验
  • 单线程又是如何实现异步的呢
    • 单线程之所以能实现异步是因为在处理异步任务时并不是马上运行的,而是通过一个事件循环(event loop)机制来管理任务的执行。貌似是浏览器提供了这么一个管理任务的event loop线程,它来管理和负责向我们的主线程上输送需要执行的任务。所以理解了event loop的机制才能更好地理解JS的运行机制,理解JS的运行机制才能更准确地把握我们代码的运行规律。

由一个面试题引发的思考

首先来看一道考察JS执行机制的面试题,原题是今日头条的前端面试题,我稍微进行了一点改造:

async function async1() {
console.log( 'async1 start' )
await async2()
console.log( 'async1 end' )
} async function async2() {
new Promise( function ( resolve ) {
console.log( '11' )
resolve();
}).then( function () {
console.log( '22' )
})
} console.log( 'script start' ) setTimeout( function () {
console.log( 'setTimeout' )
}, 0 ) async1(); new Promise( function ( resolve ) {
console.log( 'promise1' )
resolve();
}).then( function () {
console.log( 'promise2' )
}) console.log( 'script end' )

看完之后如果不在浏览器端运行一下你能有自己的答案吗,并且能自圆其说吗?得到答案都不难,放在浏览器里自然就有了输出:

script start
async1 start
11
promise1
script end
22
promise2
async1 end
setTimeout

因为题目是千变万化的,日常开发中的情况也是多种多样的,正确理解了其中的执行规律才能更好地开发,当然如果你得到的结果是一样的并且能够自圆其说那也就没必要看下去了,如果你还对这个结果有点懵那就听听我的理解吧。

微任务与宏任务

事实上,我们的JS代码用同步和异步这两种划分方式来决定执行的先后顺序显然是不够的,从而有了另一种划分方式,具体谁提出来的我就没考证了,大体上大家是这样分的:

  • macro-task(宏任务):整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise的then回调,process.nextTick(Node)

因为我们上面的面试题是前端面试题,所以我们讨论的都是浏览器环境下的表现,node环境下的event loop貌似有些不一样,这里就不讨论了。

event loop的运行机制

既然前端谈到了JS是单线程的,同时只能处理一个任务,而我们又将各种各样的任务分为了宏任务和微任务,那到底哪种任务先执行了,这个运行逻辑就是event loop的判断逻辑。

先说说我的理解,再来印证上面代码的运行顺序:

  • 一段代码执行时先执行宏任务中的同步代码
  • 如果遇到像setTimeout这类宏任务就会把代码方式【宏任务队列】中
  • 如果遇到像Promise.then()这类微任务会放入【微任务队列】
  • 在本轮宏任务中的同步代码执行完之后就会依次执行本轮微任务队列中的代码,然后执行下一轮中的宏任务代码

说回上面的面试题,我们模拟event loop来首先给他们归个队

  • 首先遇到了console.log( 'script start' ),直接打印
  • 然后遇到了setTimeout这个宏任务,就被推到宏任务队列中
  • 然后是async1(),直接打印同步代码console.log( 'async1 start' )
  • 关于await async2(),实际上是从右到左先执行了async2()里的代码,然后遇到await从而交出线程的控制权的,async2()执行之后先打印了11,console.log( '22' )被推入了微任务队列
  • 接着执行Promise中的同步代码console.log( 'promise1' ),然后将console.log( 'promise2' )推入微任务队列
  • 接着打印同步代码console.log( 'script end' ),到此,所有同步代码执行完毕
  • 接下来就要执行本轮代码中的微任务队列了,所以先打印22,再打印promise2
  • 至此,await等待的async2()执行完了,可以执行await下面的代码了,所以接下来打印async1 end
  • 最后,就是执行下一轮event loop中的宏任务setTimeout,最后打印setTimeout

单独说一下async和await

async本质上是加上了Generator函数并且内置了执行器的一个语法糖,并且async函数返回的是Promise对象。唯一需要注意的是await后面无论接的是同步代码还是异步代码都要等他们执行完毕才能执行await结果之后的代码。并且经过验证,当遇到await语句时是从右到左先执行的await后面的代码,然后才交出线程的控制权直到await等待的结果运行完毕。

总结

总体上,算是能对上面那个题目的执行过程有了一个能自圆其说的解释,只是吧,为什么规则是这样的呢?这些规则怎么证伪呢?这是我查资料的时候最纠结的问题。后来跟同事交流了之后吧,觉得也没必要纠结,毕竟最终的解释器是C写的,不懂规则可以去看源码啊,可是我看不懂啊,哈哈。。。所以,既然大家大部分人都是这么说,也能说得通,暂且先记着吧,至少,还是能够解释日常中形形色色代码的运行规律的。

看了几篇不同观点的文章之后重新梳理一下event loop的顺序:

  • 先执行宏任务中的同步代码
  • 执行栈清空之后查询任务队列
  • 如果任务队列中有微任务,先执行
  • 执行完了微任务之后开始下一轮event loop,执行队列中宏任务的异步代码

参考文章中有几篇文章比我讲的生动一些,没看懂的可以参考一下,我主要是梳理一下自己的理解,有不同想法的欢迎交流。

参考文章

我所理解的event loop的更多相关文章

  1. Node.js Event Loop 的理解 Timers,process.nextTick()

    写这篇文章的目的是将自己对该文章的理解做一个记录,官方文档链接The Node.js Event Loop, Timers, and process.nextTick() 文章内容可能有错误理解的地方 ...

  2. javascript基础修炼(5)—Event Loop(Node.js)

    开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 一道考察异步知识的面试题 题目是这样的,要求写出下面代码的输出: setTimeout(() => { co ...

  3. JavaScipt 中的事件循环(event loop),以及微任务 和宏任务的概念

    说事件循环(event loop)之前先要搞清楚几个问题. 1. js为什么是单线程的? 试想一下,如果js不是单线程的,同时有两个方法作用dom,一个删除,一个修改,那么这时候浏览器该听谁的?   ...

  4. event loop、进程和线程、任务队列

    本文原链接:https://cloud.tencent.com/developer/article/1106531 https://cloud.tencent.com/developer/articl ...

  5. 为什么JS是单线程?JS中的Event Loop(事件循环)?JS如何实现异步?setimeout?

    https://segmentfault.com/a/1190000012806637 https://www.jianshu.com/p/93d756db8c81 首先,请牢记2点: (1) JS是 ...

  6. event loop 与 vue

    结论 对于event loop 可以抽象成一段简单的代码表示 for (macroTask of macroTaskQueue) { // 1. Handle current MACRO-TASK h ...

  7. JavaScript 运行机制详解:深入理解Event Loop

    Philip Roberts的演讲<Help, I'm stuck in an event-loop>,详细.完整.正确地描述JavaScript引擎的内部运行机制. 一.为什么JavaS ...

  8. 深入理解 JavaScript 事件循环(一)— event loop

    引言 相信所有学过 JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的异步概念 .在初期许多人会把异步理解成类似多线程的编程模式 ...

  9. 深入理解Javascript单线程谈Event Loop

    假如面试回答js的运行机制时,你可能说出这么一段话:"Javascript的事件分同步任务和异步任务,遇到同步任务就放在执行栈中执行,而碰到异步任务就放到任务队列之中,等到执行栈执行完毕之后 ...

随机推荐

  1. Django国际化和本地化

    把django的这篇文档看了一遍,基本弄懂了,讲的也挺详细的 https://docs.djangoproject.com/en/1.6/topics/i18n/ 首先是国际化和本地化概念: 1,国际 ...

  2. 20145101《Java程序设计》第4周学习总结

    20145101<Java程序设计>第4周学习总结 教材学习内容总结 第六章 继承与多态 继承:避免多个类间重复定义共同行为. 把相同代码提升为父类 运用extends关键字的子类会继承扩 ...

  3. 20145310《网络对抗》逆向及Bof基础

    实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. 该程序同时包含另一个代码片段,getShe ...

  4. np.random.normal()正态分布

    高斯分布的概率密度函数 numpy中 numpy.random.normal(loc=0.0, scale=1.0, size=None) 参数的意义为: loc:float 概率分布的均值,对应着整 ...

  5. bzoj 1010 玩具装箱toy -斜率优化

    P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中.P教授有编号为1...N的N件玩具,第i件玩具 ...

  6. linux 之awk命令详解

    awk是一种程序语言,对文档资料的处理具有很强的功能.awk名称是由它三个最初设计者的姓氏的第一个字母而命名的: Alfred V. Aho.Peter J. We i n b e rg e r.Br ...

  7. 将kali linux装入U盘 制作随身携带的kali linux

    一 准备工作 USB3.0 U盘 不小于32G USB2.0的U盘安装速度要比3.0的慢一倍以上,运行也会有明显差别,所以建议使用3.0U盘.安装好之后差不多就得占用十几G,所以16G的太小了,尽量用 ...

  8. DDMS files not found

    在eclipse中启动新建的android项目的时候,控制台提示如图: 方法1.cmd中adb kill-server,然后adb -startserver 方法2.方法1不管用,那么在任务管理器中杀 ...

  9. thinkphp中的Ueditor的使用, 以及如何传递编辑器内容到后台?

    在线编辑器有很多很多, 而且大多是开源的. uediotr基于mit协议, 开源, 可以用于商业和非商业的 任意使用和修改都可以 如果两个相连接的 相邻的 元素之间 因为边框重叠 而显得中间的边框线很 ...

  10. P2709 小B的询问

    题目描述 小B有一个序列,包含N个1~K之间的整数.他一共有M个询问,每个询问给定一个区间[L..R],求Sigma(c(i)^2)的值,其中i的值从1到K,其中c(i)表示数字i在[L..R]中的重 ...