资讯专栏INFORMATION COLUMN

js的执行机制

guyan0319 / 1213人阅读

摘要:异步执行机制异步执行相对来说复杂些所以详细描述下关键是在各种使用情况下执行顺序问题在此就需要引入一个概念。在达到这个下限时间后执行和这些定时器设定的回调。执行设定的回调。

js在哪执行

js的执行引擎基于v8(c++编写),在chrome和node中都有应用,执行时有以下两部分构成

内存堆(内存分配)

调用栈(代码执行)

上述两部分的联系就是代码在调用栈中执行,执行过程中会存取一些对象在内存堆上。

我们写的js代码经过js引擎(解释器)转化为高效的机器码,现在的v8引擎由TurboFan和Ignition两部分构成,其中Ignition是解释器,而TurboFan主要对代码做些优化,以提高执行性能。

基于执行引擎的执行原理在代码层面我们可以做些优化,可以参考我之前的一篇文章

js如何执行 js同步执行

js按照代码顺序执行,在栈上分配执行空间,按照调用顺序,会有出栈入栈等各种情况,比较好分析,唯一值的说的地方就是js只有一个主线程,栈空间有限,如果递归执行过深会发生溢出,所以在编写代码层面需要注意这种情况。

js异步执行 为什么要有异步?

同步单线程代码处理起来方便,代码表达也容易,更符合我们的思维方式,为什么还会出现异步呢?
因为同步会发生阻塞,在现在这个高并发时代,不能很好的处理海量请求,同时也不能充分利用硬件资源(想想cpu和io之间处理速度差异你就深有体会)。
但是为什么不多线程呢,例如java,主要是单个线程上运行代码相对来多线程来说说容易写,不必考虑在多线程环境中出现的复杂场景,例如死锁等等。

异步执行机制?

异步执行相对来说复杂些,所以详细描述下,关键是在各种使用情况下执行顺序问题,在此就需要引入一个概念-->Event Loop。结合下面这幅图进行大致说明下:

    Event Loop的概念

所有任务在主线程上执行,形成一个执行栈(execution context stack),上图stack区域所示。

执行过程中可能会调用异步api,其中Background Threads负责具体异步任务执行,结束后将宏任务回调逻辑放入task queue中,微任务回调逻辑放入micro task队列中。

主线程执行完毕,检查microtask队列是否为空,会执行到队列空为止

从宏任务队列中取出一个在执行,执行完后,检查并取出执行microtask队列的任务,然后不断重复这个步骤,对于这整个循环过程,一个对应的描述名词就叫做event loop。

node中异步

异步任务分类

macrotask类型包括 script整体代码,setTimeout,setInterval,setImmediate,I/O……

microtask类型包括 Promise process.nextTick Object.observe MutaionObserver……

node中event loop各个阶段的操作如下图所示


说明,上图中每个盒子表示了event loop的一个阶段,每个阶段执行完毕后,或者执行的回调数量达到上限后,event loop会进入下个阶段。

timers: 在达到这个下限时间后执行setTimeout()和setInterval()这些定时器设定的回调。
I/O callbacks: 执行除了close回调,timer的回调,和setImmediate()的回调,例如操作系统回调tcp错误。
idle, prepare: 仅内部使用。
poll: 获取新的I/O事件,例如socket的读写事件;node会在适当条件下阻塞在这里,如果poll阶段空闲,才会进入下一阶段。
check: 执行setImmediate()设定的回调。
close callbacks: 执行比如socket.on("close", ...)的回调。

下面结合一些具体例子进行说明

require("fs").readFile("./case1.js", () => {
    setTimeout(() => {
        console.log("setTimeout in poll phase");
    });
    setImmediate(() => {
        console.log("setImmediate in poll phase");
    });
});
输出结果是:
setImmediate in poll phase
setTimeout in poll phase
Process finished with exit code 0

说明 setImmediate的回调永远先执行,因为readFile的回调执行是在 poll 阶段,所以接下来的 check 阶段会先执行 setImmediate 的回调。

setTimeout(() => console.log("setTimeout1"), 1000);
setTimeout(() => {
    console.log("setTimeout2");
    process.nextTick(() => console.log("nextTick1"));
}, 0);
setTimeout(() => console.log("setTimeout3"), 0);

process.nextTick(() => console.log("nextTick2"));
process.nextTick(() => {
    process.nextTick(console.log.bind(console, "nextTick3"));
});
 Promise.resolve("xxx").then(() => {
    console.log("promise");
    testPromise();
});
process.nextTick(() => console.log("nextTick4"));

结果是:

nextTick2
nextTick4
nextTick3
promise
setTimeout2
setTimeout3
nextTick1
setTimeout1

在描述什么是event loop中,大概描述了microtask机制,但具体到nextTick比较特别,有一个Tick-Task-Queue专门用于存放process.nextTick的任务,且有调用深度限制,上限是1000。js引擎执行 Macro Task 任务结束后,会先遍历执行Tick-Task-Queue的所有任务,紧接着再遍历 Micro Task Queue 的所有任务。具体执行逻辑可以下面代码表示。

for (macroTask of macroTaskQueue) {

    // 1. Handle current MACRO-TASK
    handleMacroTask();

    // 2. Handle all NEXT-TICK
    for (nextTick of nextTickQueue) {
        handleNextTick(nextTick);
    }

    // 3. Handle all MICRO-TASK
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}

所以才会先输出process.nextTick然后才会是promise,其它的输出顺序不在赘述,前面讲event-loop机制时已经说明了。根据上面代码表述的执行逻辑,很显然可以得到下面的个结论,当递归调用时会发生死循环,而宏任务就不会。

testPromise();
function testPromise() {
    promise = Promise.resolve("xxx").then(() => {
        console.log("promise");
        testPromise();
    });
}
//将之前步骤的promise任务换成这个,setTimeout2以及之后的输出永远没机会出来,类比到nextTick也是这种效果

看了一些书,参考了很多资料,将自己学习的东西,理解后在输出,希望大家辩证的看待,有空的话接下来研究一下源码,毕竟通过demo验证结论的说服力没有源码来的那么直接。

参考链接
https://jakearchibald.com/201...
https://blog.sessionstack.com...
https://cnodejs.org/topic/592...
https://developer.mozilla.org...
https://nodejs.org/en/docs/gu...

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

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

相关文章

  • 10分钟理解JS引擎执行机制

    摘要:深入理解引擎的执行机制灵魂三问为什么是单线程的为什么需要异步单线程又是如何实现异步的呢中的中的说说首先请牢记点是单线程语言的是的执行机制。 深入理解JS引擎的执行机制 1.灵魂三问 : JS为什么是单线程的? 为什么需要异步? 单线程又是如何实现异步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.说说setTimeout 首先,请牢记2...

    zzbo 评论0 收藏0
  • 深入理解js引擎执行机制

    摘要:深入理解引擎的执行机制最近在反省,很多知识都是只会用,不理解底层的知识。在阅读之前,请先记住两点是单线程语言的是的执行机制。所以,是存在异步执行的,比如单线程是怎么实现异步的场景描述通过事件循环,所以说,理解了机制,也就理解了的执行机制啦。 深入理解js引擎的执行机制 最近在反省,很多知识都是只会用,不理解底层的知识。所以在开发过程中遇到一些奇怪的比较难解决的bug,在思考的时候就会收...

    feng409 评论0 收藏0
  • 前端进阶系列(八):JS执行机制

    摘要:一直以来,对的执行机制都是模棱两可,知道今天看了文章这一次,彻底弄懂执行机制和的规范和实现,才对的执行机制有了深入的理解,下面是我的学习总结。个要点是单线程语言是的执行机制,为了实现主线程的不阻塞,就这么诞生了。 一直以来,对JS的执行机制都是模棱两可,知道今天看了文章—《这一次,彻底弄懂JavaScript执行机制》和《Event Loop的规范和实现》,才对JS的执行机制有了深入的...

    JackJiang 评论0 收藏0
  • Event Loop - JS执行机制

    摘要:心塞塞根据规范,事件循环是通过任务队列的机制来进行协调的。等便是任务源,而进入任务队列的是他们指定的具体执行任务回调函数。然后当前本轮的结束,主线程可以继续取下一个执行。 依然是:经济基础决定上层建筑。 说明 首先,旨在搞清常用的同步异步执行机制 其次,暂时不讨论node.js的Event Loop执行机制,以下关于浏览器的Event Loop执行机制 最后,借鉴了很多前辈的研究文...

    muddyway 评论0 收藏0
  • JS执行机制

    摘要:事件表每次调用函数或执行异步操作时,都会将其添加到事件表中,事件表负责记录每个事件完成后执行的回调函数,并监听事件,事件完成后会把事件的回调函数发送到事件队列。事件队列事件队列接收来自事件表的回调函数,并根据顺序执行。 前言 先上一段代码,可以根据自己之前对JS执行机制的理解,进行分析 console.log(script start); setTimeout(function() ...

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

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

    13651657101 评论0 收藏0

发表评论

0条评论

guyan0319

|高级讲师

TA的文章

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