JavaScrip的执行机制
J a v a S c r i p t 是 一 门 单 线 程 语 言 , E v e n t L o o p 是 J a v a S c r i p t 的 执 行 机 制 JavaScript是一门单线程语言,Event Loop 是JavaScript的执行机制 JavaScript是一门单线程语言,EventLoop是JavaScript的执行机制
前言
我们都知道,JavaScript是一门 单线程语言 ,所以不少人得出这样的结论,JavaScript是按照语句出现的顺序来执行的。
有基础的同学知道不是这样的,可是又讲不清楚,但是又会用,于是不求甚解,它就慢慢变成了我们的知识盲区。
如下面的这段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('代码执行结束');
骚年,自信的亮出你的剑吧!你以为的代码,是这样的:
定时器开始啦 -> 马上执行for循环啦 -> 执行then函数啦 -> 代码执行结果
但真的是这样吗? 去chrome上验证下,发现不对,于是就懵逼了,觉得自己只是一个前端打字员。
# Chrome上的直接结果:
马上执行for循环啦 -> 代码执行结束 -> 执行then函数啦 -> 定时器开始啦
明确核心
首先,我们需要明确一点,那就是:JavaScript是一门单线程语言,如果你听到JavaScript版的“多线程”都是单线程模拟出来的。哪怕是最新HTML5中提出的 Web-Worker1
,都遵循单线程这一核心思想。
事件循环
既然js是单线程的,那如果我们需要打开淘宝,那是不是要等淘宝的图片全部呈现出来才可以滚动使用呢?如果这样,那岂不是要等待好久才能偶使用软件吗?为此,聪明的程序员将浏览器执行的任务分为两大类,
- 同步任务
- 异步任务
当我们再次打开淘宝时,页面首先渲染同步任务,如页面的骨架、元素等,而像图片、视频这些耗时久的任务就变成异步任务,在同步任务执行完后执行。
下图可以是浏览器的Evnet Loop运行流程,是一道经典的面试题哦!!!
我的愚见是这样的:
- 任务进入执行栈时,首先判断这段代码是同步任务还是异步任务
- 如果是同步任务,则进入到浏览器的主线程中运行,一直执行完
- 如果是异步任务,那么将进入
Event Table
中注册,注册完成后进入到Event Queue
中等待 - JS引擎会持续不断的检查主线程执行站是否为空,即主线程的同步任务是否全部完成
- 一旦主线程的所有同步任务完成,就会去
Event Queue
内读取对应的异步任务,将其调入到主线程中执行
我们人可以通过肉眼计算得知同步任务全部执行完了,那么计算机又是怎么知道的呢?经过研究发现,原来 JS引擎存在 monitoring process
进程中,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去调用 Event Queue
内的代码执行到主线程中。
说了这么多,有没有把你绕晕呢?还是直接上案例来更好的理解吧!
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
上面是一段简易的 ajax
请求代码,我们可知,
- 在ajax进入
Evnet Table
时注册函数success进入到Event Queue
中, - 然后执行主线程的同步任务,“代码执行结束”。
- 当主线程的任务执行完成时进入到
Event Queue
中调用注册的success函数任务。
异步任务
setTimeout
对于 setTimeout
,我们的第一反应是:异步任务+延时执行。
没错,不过我们需要注意的是:setTimeout(fn,0) 。这里的0指的并不是在JS代码中的0秒,而是指在主线程的同步任务完成后立即执行该函数。而且还有一点,0毫秒在实际上是达不到的哦,最快的也是4毫秒。
setInterval
我们既然介绍了 setTimeout
,当然不能只偏爱一个,也要介绍它的孪生兄弟:setInterval
。setInterval 会在每隔一段指定的时间内将注册的函数置入到 Event Queue
中执行。
这里需要注意的是,setInterval(fn,ms) 中ms秒会执行一次一次fn。但是如果 setInterval
的回调函数 fn 执行时间超过了延迟时间 ms,那么就完全看不出来我们设置的时间间隔了。
Promise与process.nextTick(callback)
在 new Promise
实例化的过程中所执行的代码都是同步执行的,而 .then
或 .catch
中的回调都是异步执行的。
所有的异步都是指事件回调中的那部分代码
执行顺序:同 > 微任务 > 宏任务
同步任务:
console.log
new Promise
实例化的过程
异步任务:
-
宏任务:
浏览器环境下 NODE环境下 I/O Y Y setTimeout Y Y setInterval Y Y setImmediate N Y requestAnimationFrame Y N UI事件 Y 注意:
在
Promise/A+
的规范中,Promise既可以是微任务也可以是宏任务,但是普遍将它当作微任务阵营的
requestAnimationFrame
在MDN中定义为:下次页面重回前所执行的操作,而重绘也是作为宏任务的一个步骤来存在的,且该步骤晚于微任务的执行 -
微任务:
浏览器环境下 NODE环境下 process.nextTick N Y MutationObserver Y N Promise中的 .then
、.catch
、.finally
Y N -
好处:
- 不会阻塞主线程上的执行,不会卡死浏览器
- 只需要设置好异步程序,就可以去干别的事情了
async/await函数
async/await函数本质上是基于 Promise
的一些封装,而 Promise
是属于为任务的一种.
所以,
async
函数在关键字await
之前的代码都相当于new Promise
时传入的同步代码;- 而
await
之后的所有代码都相当于Promise.then
中的异步代码
注意
- 一个Event Loop 可以有一个或多个事件队列,但是只有一个微任务队列
- 微任务队列全部执行完全会重新渲染一次
- 每个宏任务执行完都会重新渲染一次
requestAnimationFrame
处于渲染阶段,不在微任务队列中
面试题:
浏览器
Event Loop
的执行流程:答案已在文章内
Event Loop这个流程中的坑 ---- 定时器不准.
主线程中的全部同步任务执行时间如果大于定时器设置的时间,会导致定时器的回调函数在延后执行