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语句),async/await中await后面部分,process.nextTick
此时js执行机制如下:
1、开始执行js整体代码,也就是第一个宏任务,然后按从上往下的顺序依次执行下去,当遇到像刚才提到的setTimeout,setInterval这些任务时,判定为宏任务,就会暂时先将他们整体的代码先放到宏任务的【事件队列】中,如果期间遇到像Promise(then语句)process.nextTick等任务时,就将它们放到微任务的【事件队列】里;
2、当前第一个宏任务执行完成后(js代码从上往下执行过一边,同步代码被执行完了,宏任务和微任务都被放到了各自的事件队列中),此时会查看微任务的【事件队列】,并将里面全部的微任务依次执行完。
3、此时算是第一轮事件循环结束了,然后如果宏任务【事件队列】中还存在宏任务的话,就会开始执行下一个宏任务,然后重复上述的1、2顺序,这也就是整个事件循环
了解完以上执行机制,我们再回过头来看刚才那个demo3的例子:
- 首先执行整体js代码,开启了第一个宏任务的执行,执行到console.log(111),他就直接输出打印了,接下来遇到setTimeout语句,判定为宏任务类型,于是就把setTimeout整体代码放入了宏任务事件队列中,还是接下去执行,遇到了new
Promise代码,promise构造函数中的语句是同步立即执行的,所以此时输出打印333,然后遇到.then()语句,判定为微任务,所以将其放入到了微任务事件队列中,然后又遇到console语句,还是直接执行输出555,执行到这算是第一个宏任务执行完毕了- 此时会去微任务事件队列中寻找,是否有事件要执行的?发现有一个Promise.then的语句,所以此时会输出打印444,执行完这步后,第一轮事件循环结束了
- 此时会去宏任务事件队列中看是否还存在其他宏任务,有一个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
标签:console,log,js,任务,机制,执行,事件队列 来源: https://blog.csdn.net/Ronychen/article/details/122251029