JavaScript

谈一谈JavaScript执行机制

关键字:同步执行、异步执行、事件循环、宏任务、微任务、队列

前言

首先,看一段有关异步执行的一段代码,想一想它会输出什么?

//怎么个打印顺序呢?
setTimeout(()=>{                                                               
    console.log("ssssss")
},0);

new Promise((resolve,reject)=>{
    console.log("怎么也飞不出")
    resolve();                                                               
}).then(()=>{
    console.log("花花的世界")                                                 
});

console.log("普普通通小输出");

其实我刚开始学异步的时候,只知道异步的作用是为了减少阻塞执行线程的情况。但是当我看到上面这种代码的时候,我就一直想到底是谁先输出呢?然后就大脑一片空白。我回头单纯复习setTimeout、Promise,也不能做到很说服自己的理解。但是当我学习和理解了JavaScript的执行机制后,这段代码看起来,就变得很轻松了。所以我总结了一下JavaScript的运行机制,结合了我个人的理解,希望对你有帮助。

同步与异步

谈一谈JavaScript的执行机制,我们也要了解同步和异步编程,想一想为什么我们需要异步编程。

首先,JavaScript是单线程的。JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内,且这个执行栈具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。

通俗明白地说,浏览器对声明语句进行预处理后,执行JavaScript时,代码会从上到下按顺序执行逻辑操作和函数的调用。这就是同步。可以想象,当某个事件处理的计算量很大或者等待服务器响应的时间过长,就会导致后面的代码没有办法执行,页面也可能一直处于一种假死状态。这样对使用者来说就很不爽。

异步编程的产生就允许了许多输入/输出操作可以同时发生。对于JavaScript,可以通过事件循环调用堆栈异步API来实现异步编程。

事件循环:微任务和宏任务

事件循环是JavaScript的运行机制。

事件循环的概念非常简单。它是一个在 JavaScript 引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换的无限循环。

举一个饭堂点餐例子来理解事件循环

我理解的事件循环 —— 一种通过不断地循环询问,确保代码公平公正有序地执行的一种行为。想象一下,我们在食堂点餐,阿姨不断地询问路过的同学是否要吃她们家的饭。你去点餐了,就相当于充当了执行栈里的一个任务。后边几个同学来了也在排队,形成一个队列,阿姨给你们点餐可以理解为一个个宏任务的执行。阿姨给了你干锅肥牛19号牌,然后你就在旁边等。这时你要取餐的行为就是一个等待被执行的异步任务。“肥牛19号”,你终于听到响应了,赶紧去取餐。阿姨给你端菜、放辣椒和打包好,她此时是没有办法处理其他同学的要求的(假设只有这一个阿姨)。那我们就把阿姨给你上菜打包的一系列操作称为微任务的处理。

循环就体现在:阿姨不断地帮排队要点餐的同学点餐,如果有同学点的菜做好了,阿姨会先把这些菜端给等待的同学先,送出所有做好的菜了,才会继续帮下一个正在排队的同学下单。不会为了拼命点餐下单,就让做好的菜放凉了~

JS里的事件循环的流程

再看回上面的事件循环定义,事件循环的概念就真的很简单了╰( ̄ω ̄o)。

JS引擎常驻于内存中,等待宿主将JS代码或函数传递给它,也就是等待宿主环境分配宏观任务,反复等待 – 执行即为事件循环(Event Loop)。

Event Loop中,每一次循环称为tick,每一次tick的任务如下: /—————————————————————————– ①所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束:

②检查是否存在微任务,有则会执行直至微任务队列为空

③如果宿主为浏览器,可能会渲染页面;当安排的(scheduled)setTimeout 时间到达时,任务就是执行其回调;(提一句:这里也解释了setTimeout设置的时间参数不一定就是你实际预期他该执行的时间,也许会慢一点);

④开始下一轮tick。一旦“执行栈”中的所有同步任务执行完毕,系统又会读取“异步任务队列”,看看里面有哪些事件就绪了。那些对应的异步任务就结束等待,以一个宏任务的身份进入执行栈继续执行。重复重复ing… /——————————————————————————

JavaScript把任务分为同步任务异步任务。同时,异步任务又包括了宏任务微任务(个人解说:宏任务可不是同步任务的代名词,但是宏任务和微任务最终都会被放入到主线程里成为同步任务被执行,有点点绕)也就是说异步队列里是包括了宏任务队列和微任务队列的。

那具体看,宏任务和微任务的分类如下(Node部分我没有列出(+_+)):

  宏任务 微任务
定义理解 可以理解为每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行) 可以理解是在当前task执行结束后立即执行的任务。微任务由JS引擎发起。
发起者 浏览器或Node JS引擎
常见具体事件 有包括整体代码script,dom事件回调,ajax回调,setTimeout和setInterval 由 promise 创建的:对 .then/catch/finally 处理程序的执行会成为微任务

那整体来看,JS的事件循环机制如下图所示?:

在这里插入图片描述

开头代码的输出结果分析

结合上面解释的JavaScript执行的运行机制和宏任务与微任务的具体事件分类,我就可以分析出最前面给出的代码的执行流程。

  • 第一次运行。顺序执行。setTimeout的代码先放入了宏任务队列里。Promise中的“怎么也飞不出”的输出属于同步任务,会立即被打印出来。.then后的代码被放入微任务队列。最后执行打印的“普普通通小输出”这个同步任务。
  • 接着准备执行宏任务队列中的任务,先询问一下微任务队列是不是空的,结果发现有个“花花的世界”可怜地等待着,就先执行打印“花花的世界”。
  • 保证了微任务队列是空的了,就回到宏任务队列中,把这个宏任务放入到主线程中执行输出“ssssss”。

同步任务:【“怎么也飞不出”,“普普通通小输出”】,放入主线程

异步任务中的宏任务:【“sssss”】 、微任务:【“花花的世界”】,放入异步队列。

在这里插入图片描述

结果图如上?:

(欢迎指出我的理解概念中理解不当之处o( ̄▽ ̄)ブ,或者来补充~)

参考链接: ? 事件循环:微任务和宏任务 ?宏任务和微任务到底是什么?

Tagged

2 thoughts on “谈一谈JavaScript执行机制

发表评论

邮箱地址不会被公开。 必填项已用*标注