【js进阶】-js执行机制

一、前序

关于js执行机制的内容,其实我早些时间也做过内容的分享,近期我是想着再对js的基础核心内容做一轮系统性的巩固和复习,所以本文相对于之前的文章会有部分细节点的更新,会让读者更加地系统理解这块的知识点,这里我也贴一下原先文章的地址(深入理解js执行机制

我们知道页面的渲染JS的执行事件的循环,都是在浏览器内核中进行的,也就是浏览器渲染进程,所以今天要讨论的js的整个执行机制其实就是在浏览器内核中处理的,这些概念性的理解,如果有同学还不太清楚的,可以去看下我另外一篇文章(点我跳转

二、 js执行机制相关的知识

1、关于javascript语言

javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的。

2、JS为什么是单线程的?

这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变

3、JS为什么需要异步?

因为如果js中只有同步,不存在异步的话,任务只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验

4、JS单线程又是如何实现异步的呢?

其实本质就是通过js的执行机制来实现的,js执行机制中最核心的就是事件循环(EventLoop)

三、javascript的同步和异步

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去,于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)

同步任务:指的是,在主线程(这里说的主线程我的理解应该是JS引擎线程,)上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务:指的是,不进入主线程、而进入由事件触发线程管理着的一个任务队列"(task queue),只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程(js引擎)执行。
在这里插入图片描述1、同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
2、当Event Table中指定的事情完成时,会将这个注册号的函数移入事件队列中(Event Queue)。
3、主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
4、我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数

接下来我们来看一些demo:

Demo1:

console.log('哈哈');

setTimeout(function() {
    console.log(‘呵呵’);
},1000)

console.log(‘嘻嘻’);

代码解读
执行结果:哈哈、嘻嘻、呵呵
同步任务,按照顺序一步一步执行
异步任务,放入事件队列中,等待同步任务执行结束,读取事件队列执行

是不是很简单,我们再来看个demo
Demo2:

console.log('张三');
setTimeout(function() {
    console.log('李四');
},1000)
setTimeout(function() {
    console.log('王五');
},0)
console.log('赵六');

现在再看看这个执行结果是什么?

代码解读
猜测是:张三、赵六、李四、王五 但实际上是:张三、赵六、王五、李四
同步任务,按照顺序一步一步执行
异步任务,当读取到异步任务的时候,将异步任务放置到Event table(事件表格)
中,当满足某种条件或者说指定事情完成了(这里的是时间分别是达到了0ms和1000ms)当指定事件完成了才从Event table中注册到Event Queue(事件队列),当同步事件完成了,便从Event Queue中读取事件执行。(因为王五的事情先完成了,所以先从Event table中注册到Event Queue中,所以先执行的是王五而不是在前面的李四)

补充
1、setTimeout事件的时间由谁控制的?这里要说明下,当调用setTimeout后,任务就被放到了Event table中,具体等多久才能被推入Event Queue(事件队列)这个时间不是由js引擎线程来控制的,而是由定时器线程控制(因为JS引擎自己都忙不过来,根本无暇分身);
2、为什么要单独的定时器线程?因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时
3、什么时候会用到定时器线程?当使用setTimeout或setInterval时,它需要定时器线程计时,计时完成后就会将特定的事件推入事件队列中

如果,以上两问题都ok,说明同步异步问题已经掌握差不多了,再来看个demo
Demo3:

console.log(111);
setTimeout(function() {
    console.log(222)
},1000);
 
new Promise(function(resolve) {
    console.log(333);
    resolve();
}
).then(function() {
    console.log(444)
});
console.log(555);

代码解读:
以同步异步的方式来判断的结果应该是:111、311、555、222、444
但是事实上结果是:111、333、555、444、222
为什么是这样呢?因为以同步异步的方式来解释执行机制并不是很准确的,因为他只是区分了同步和异步,并没有对任务做更进一步的细化,比方说同样是异步,哪个应该执行呢? 所以更加准确的方式是将js任务分为宏任务和微任务:

四、javascript的宏任务和微任务

刚提到了宏任务和微任务,那么哪些代码属于宏任务?哪些属于微任务呢?

宏任务:包括整体代码script,setTimeout,setInterval
微任务:Promise(then、catch、finally语句),async/await中await后面部分,process.nextTick

此时js执行机制如下:

1、开始执行js整体代码,也就是第一个宏任务,然后按从上往下的顺序依次执行下去,当遇到像刚才提到的setTimeout,setInterval这些任务时,判定为宏任务,就会暂时先将他们整体的代码先放到宏任务的【事件队列】中,如果期间遇到像Promise(then语句)process.nextTick等任务时,就将它们放到微任务的【事件队列】里;

2、当前第一个宏任务执行完成后(js代码从上往下执行过一边,同步代码被执行完了,宏任务和微任务都被放到了各自的事件队列中),此时会查看微任务的【事件队列】,并将里面全部的微任务依次执行完。

3、此时算是第一轮事件循环结束了,然后如果宏任务【事件队列】中还存在宏任务的话,就会开始执行下一个宏任务,然后重复上述的1、2顺序,这也就是整个事件循环
在这里插入图片描述

了解完以上执行机制,我们再回过头来看刚才那个demo3的例子:

  1. 首先执行整体js代码,开启了第一个宏任务的执行,执行到console.log(111),他就直接输出打印了,接下来遇到setTimeout语句,判定为宏任务类型,于是就把setTimeout整体代码放入了宏任务事件队列中,还是接下去执行,遇到了new
    Promise代码,promise构造函数中的语句是同步立即执行的,所以此时输出打印333,然后遇到.then()语句,判定为微任务,所以将其放入到了微任务事件队列中,然后又遇到console语句,还是直接执行输出555,执行到这算是第一个宏任务执行完毕了
  2. 此时会去微任务事件队列中寻找,是否有事件要执行的?发现有一个Promise.then的语句,所以此时会输出打印444,执行完这步后,第一轮事件循环结束了
  3. 此时会去宏任务事件队列中看是否还存在其他宏任务,有一个setTimeout事件,所以此时输出打印222,所以最终的执行顺序就是:111、333、555、444、222

五、事件循环(EventLoop)

先执行执行栈中的所有任务,当执行栈为空时,主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环),Event Loop也可以说就是javascript的执行机制

六、关于宏、微任务的常见面试题

我们来看下这道面试题

async  function  async1 ()  {
    console.log('async1 start');
    await  async2();
    console.log('async1 end')
}
async  function  async2 ()  {
    console.log('async2')
}
console.log('script start');
setTimeout(function ()  {
    console.log('setTimeout')
},  0);
async1();
new  Promise(function (resolve)  {
    console.log('promise1');
    resolve()
}).then(function ()  {
    console.log('promise2')
});
console.log('script end')

代码解读
1、首先整个js代码开始执行,开始了第一个宏任务,遇到async 修饰的函数,因为是函数创建,所以不会执行函数内部的代码,所以首先输出script start ,然后遇到setTimeOut代码,将其放入宏任务事件队列中,然后执行到async1()代码时,开始执行async1函数,所以输出了async1 start以及async2函数中的async2,然后执行完await右边的代码后,await下面的代码会被放入微任务事件队列中,此时并不会立即执行,所以继续执行
Promise这块构造器中的同步代码,输出打印promise1,promise1.then中的代码也会被放入微任务队列中,此时继续执行输出script end,执行到这,第一个宏任务执行完了
2、然后会去微任务事件队列中看是否有要执行的微任务,发现有,会按被放入的顺序依次执行,所以此时输出async1 end 和promise2,当执行完所有微任务后,第一轮事件循环结束了
3、此时会去宏任务事件队列中寻找是否有要执行的宏任务,有,就执行下一个宏任务,开启了事件循环的第二轮循环,所以此时输出setTimeout
4、所以最终这段代码的执行顺序是:script start、async1 start、async2、promise1、script end、async1 end 、promise2、setTimeout

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ronychen’s blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值