JS引擎执行机制

从JS是一门单线程语言讲起

灵魂三问:

  • 为什么JS是一门单线程语言
  • 为什么JS需要异步
  • JS单线程如何实现异步

为什么JS是一门单线程语言

如果说在浏览器中,JS是多线程的,有如下场景:

在浏览器中,有两个进程process1和process2,因为JS是多进程的,所以他们对同一个dom同时进行操作。process1删除了该dom,而process2编辑了该dom,同时下达两个矛盾的命令,浏览器如何执行?

为什么JS需要异步

如果JS中不存在异步,只能自上而下按顺序执行,如果上一行解析需要很长时间,那么下面的代码就会被阻塞,对于用户而言阻塞就会导致页面卡死,进而影响用户体验

JS单线程如何实现异步

通过事件循环(event loop),理解了该机制,也就明白了JS的执行机制

观察如下代码

1
2
3
4
5
6
7
console.log(1)

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

console.log(3)

浏览器会分别打印出:1 3 2

setTimeout里的函数并没有立即执行,而是延迟了一段时间,满足了一定条件后才去执行,这类代码叫做异步代码。所以按照上面代码可以将任务分成同步任务和异步任务,按照这种分类方式,JS的执行机制是:

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

但仅仅是上面这样就结束了吗???再看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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是异步任务,进入event table里
  • new Promise是同步任务,放到主进程里,控制台打印:马上执行for循环啦
  • .then函数里的是异步任务,放到event table里
  • console.log是同步代码,放到主进程里,打印:代码执行结束
    照这样打印出的顺序是:马上执行for循环啦—代码执行结束—定时器开始啦—执行then函数啦

但是实际执行后的结果却是:马上执行for循环啦—代码执行结束—执行then函数啦—定时器开始啦

这么说按照同步任务和异步任务划分并不准确!!而准确的方式是(妈ma 咪mi 宏任务在前,微任务在后)

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

按照这种方式,JS的执行机制是:

  • 执行一个宏任务,过程中遇到微任务,将它放到微任务的事件队列里
  • 当前宏任务执行完成后,会去查看微任务的事件队列,并将里面的微任务一次执行完,然后才会进入下一个宏任务
  • 重复上面的两个步骤

下面再次分析第二段代码:

  • 首先执行script中的宏任务,遇到setTimeout,将它放到宏任务的事件队列里
  • 遇到new Promise立即执行(它是同步的),打印:马上执行for循环啦
  • 遇到then方法,是微任务,将它放到微任务的事件队列里
  • 打印:代码执行结束
  • 本轮宏任务执行完,查看微任务,发现有一个then方法里的函数,打印:执行then函数啦
  • 到这儿本轮的事件循环(event loop)结束
  • 进入下一轮循环,先执行一个宏任务,发现宏任务队列里有一个setTimeout函数,执行并打印:定时器开始啦

于是,真正的代码执行顺序是:马上执行for循环啦—代码执行结束—执行then函数啦—定时器开始啦

setTimeout

1
2
3
setTimeout(function(){
console.log('执行了')
}, 3000)

我们一般会解释为3秒之后执行setTimeout里的函数,但这种说法不严谨。准确来说,3秒后,setTimeout里的函数会被推到事件队列里(event queue),而事件队列里的任务只有在主线程空闲时才会去执行

所以要满足<1> 3秒后; <2> 主线程空闲; 同时满足时才会在3秒后执行该函数

如果主线程任务很多,执行时间超过了3秒,比方执行了5秒,那这个函数也只能在5秒后被执行

0%