2

深入理解js引擎的执行机制

最近在反省,很多知识都是只会用,不理解底层的知识。所以在开发过程中遇到一些奇怪的比较难解决的bug,在思考的时候就会收到限制。所以,在这里一点一点补充基础知识吧。

在阅读之前,请先记住两点:

  • js是单线程语言
  • js的Event Loop是js的执行机制。深入理解js的执行,就等与深入理解js的Event Loop

好啦,下面进入正题

1. 灵魂三问:js为什么是单线程的?为什么需要异步?单线程又是怎么实现异步的呢?

技术的出现,都跟现实世界里的场景密切相关。同样的,我们就结合现实场景,来回答这三个问题。

(1)js为什么是单线程的?

        js是一种脚本语言,脚本语言是为了缩短传统的编写-编译-链接-运行过程而创建的计算机编程语言,脚本语言不需要编译,可以直接用,由解释器来负责解释。
        js最初被设计用在浏览器中,那么想象一下,如果浏览器中的js是多线程的。。。
场景描述:
        现在有两个进程:process1和process2,由于是多线程的js,所以它们对同一个DOM同时进行操作process1删除了该DOM,而process2编辑了该DOM,同时下达了两个矛盾的命令,你这让浏览器怎么执行呢?
        这样一想,是不是就理解了js为什么被设计成单线程了吧~~~


(2)js为什么是异步的?

场景描述:
        如果js不存在异步,只能自上而下执行,如果上一行执行时间很长,比如说没有网了,那么下面的代码就会被阻塞,对于用户来说,阻塞就意味着“卡死”,这样导致用户体验很差。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
        所以,js是存在异步执行的,比如setTimeout、setInterval、ajax、promise


(3)单线程是怎么实现异步的?

场景描述:
        通过Event Loop(事件循环),所以说,理解了Event Loop机制,也就理解了js的执行机制啦。

2. js中的Event Loop第一课

举个栗子:观察下它的执行机制

console.log(1)
setTimeout(function(){
    console.log(2)
},0);
console.log(3)

毫无疑问:运行结果是1 3 2

也就是说:setTimeout里的函数并没有立即执行,而是延迟了一段时间,满足一定条件后才去执行的,我们叫做异步代码。

所以这里我们首先知道了js里的一种分类方式,就是将任务分为:同步任务和异步任务。

按照这种分类方式:js的执行机制是

  • 首先判断js是同步的还是一步的,同步的就进入主进程,异步的就进入event table(事件表)
  • 异步任务在Event table中注册函数,当满足触发条件后,被推入event queue(事件队列)
  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中

以上三步循环执行,这就是Event Loop
所以上面的栗子,你是否可以描述它的执行顺序了呢?

console.log(1)          //是同步任务,放进主进程里
setTimeout(function(){  //是异步任务,放进event table,0s之后被推入event queue里
    console.log(2)
},0);
console.log(3)          //是同步任务,放进主进程里
//当1、3在浏览器控制台被打印后,主线程去event queue中查看是否有可执行的函数,执行setTimeout里的函数。
3. js中的Event Loop第二课

所以,上面关于Event Loop就是我对js执行机制的理解,是不是很简单呢。。。。
慢着!看看下面的这段代码
又一个栗子:

setTimeout(function(){
    console.log("定时器开始啦~~~");
})
new Promise(function(resolve){
    console.log("马上执行for循环啦");
    for(var i=0;i<10000;i++){
        i==99&&resolve();
    }
}).then(function(){
    console.log("执行then函数啦")
});
console.log("表演完毕!");

有没有很熟悉,好像面试过程中经常碰见这种问题呀~~~
现在,我们按照上面学到的js执行机制去分析一下下

setTimeout(function(){                 //是异步任务,被放进event table中
    console.log("定时器开始啦~~~");
})
new Promise(function(resolve){         //是同步任务,被放进主进程中,直接执行
    console.log("马上执行for循环啦");
    for(var i=0;i<10000;i++){
        i==99&&resolve();
    }
}).then(function(){                    //是异步任务,被放进event table中
    console.log("执行then函数啦")
});
console.log("表演完毕!");              //是同步任务,被放进主进程中,直接执行

运行结果是:【马上执行for循环啦----表演完毕!----执行then函数啦----定时器开始啦~~~】

那么,难道是异步任务的执行顺序不是前后顺序,而是另有规定?事实上,按照同步和异步的划分方式,并不准确。

所以这里我们首先知道了js里的一种分类方式,就是将任务分为:同步任务和异步任务。

叨叨了半天,原来都是一些“假大空”的东西,搞什么搞嘛,哼!!!

准确的划分方式是:

  • macro-task(宏任务):包括整体代码script、setTimeout、setInterval
  • micro-task(微任务):Promise、process.nextTick

图片描述

按照这种分类方式:js的执行机制就是

执行一个宏任务,执行过程中如果遇到微任务,就将其放在微任务的event queue里
当前宏任务执行完成后,会查看微任务的event queue,并将里面的全部微任务依次执行完。
重复以上两个步骤,结合Event Loop第一课和Event Loop第二课,就是更为准确的js执行机制了。
尝试按照刚才学到的执行机制,去分析第二个栗子:

setTimeout(function(){              
    console.log("定时器开始啦~~~");
})
new Promise(function(resolve){     
    console.log("马上执行for循环啦");
    for(var i=0;i<10000;i++){
        i==99&&resolve();
    }
}).then(function(){               
    console.log("执行then函数啦")    
});
console.log("表演完毕!");              
//首先执行script中的宏任务;
//遇到setTimeout,放进宏任务的event queue中;
//遇到new Promise直接执行,打印“马上执行for循环啦”;
//遇到then方法,是微任务,被放进微任务的event queue
//执行打印“表演完毕!”,本轮宏任务执行完毕;
//查看本轮微任务,发现then方法里的函数,执行打印“执行then函数啦”
//到此,本轮Event Loop全部完成
//下一轮循环里,先执行一个宏任务,发现宏任务的event queue中有一个setTimeout中的函数,打印“定时器开始啦”

运行结果是:【马上执行for循环啦----表演完毕!----执行then函数啦----定时器开始啦~~~】

4.谈谈setTimeout

下面这段setTimeout代码是什么意思?我们一般说:“3s后,会执行setTimeout里的函数”

setTimeout(function(){
    console.log("执行了");
},3000)

但是这种说法并不严谨,准确的解释是:
        3s后,setTimeout里的函数会被推入到event queue中,而event queue(事件队列)里的任务,只有在主线程空闲时才会执行。
所以:只有在满足

  1. 3s后
  2. 主线程空闲

这两个条件同时满足,才会在3s后执行该函数。如果主线程执行内容很多,执行时间超过3s,比如执行了10s,那么这个函数只有在10s后执行啦


到此,js引擎的执行机制解读完啦,前端的水很深,慢慢摸着吧
感谢文章的提供者:ziwei3749


miomiomio
147 声望5 粉丝

人生昧履,砥砺而行