浏览器内核是多线程,
Javascript
是单线程。
楼主之前讲解了js
的异步处理,只是讲解了异步处理的方法,但是对于异步处理的原理还是不是很了解,这篇文章是对于浏览器的线程方面对JavaScript
的运行机制进行分析。
我们经常说,js
的执行环境是一个单线程,会按顺序执行代码,但是JavaScript
又是可以异步,这两者感觉有冲突,但是本质上,如果理解浏览器的事件循环机制(event loop),就会觉得并不冲突。
浏览器里面不仅只有解释JavaScript
的引擎,还包括很多其它的引擎。
浏览器主要包括:
我们今天主要讨论的是浏览器引擎(浏览器内核)和 JavaScript 解释器(V8引擎)之间的交互和沟通。
浏览器内核的是一个多线程处理,它主要包含如下几个线程
html
元素dom
渲染他们之间有一下的联系:
dom
一边渲染页面js
引擎的主线程执行栈是否为空,如果为空就会去取事件触发线程存放在事件队列中的回调函数执行。由于js的运行环境是单线程的,一些异步操作还是需要借助于浏览器这个宿主来实现。这里简单的一个图来描述js
运行的时候的流程。主要运用了浏览器的js引擎线程和事件触发线程,有时候开启网络服务和定时器也会用到其他的线程。
浏览器的事件循环依靠已事件队列,但是一个进程中不止一个事件队列,大致可以分为micro task
和macro task
,常见的微任务和宏任务分别包括:
micro task :
macro task:
主要部分: 事件队列在同步队列执行完后,首先会执行nextTick,等nextTick执行完成后,然后会先执行micro task, 等micro task队列空了之后,才会去执行macro task,如果中间添加了micro task加入了micro task队列,会继续去执行micro task队列,然后再回到macro task队列。js引擎存在monitoring process进程, 会不停的监听task queue
它们的细节可以参考Tasks和Microtasks
一段代码块就是一个宏任务。所有一般执行代码块的时候,也就是程序执行进入主线程了,主线程会根据不同的代码再分微任务和宏任务等待主线程执行完成后,不停地循环执行。
主线程(宏任务) => 微任务 => 宏任务 => 主线程
下图是一个简易的时间循环:
console.log(‘start‘)
Promise.resolve().then((resolve) => {
console.log(1)
}))
console.log(‘end‘)
大致流程:
console.log(‘start‘)
开始执行Promise
是micro-task,把它的回调函数放入微任务Event Queue中console.log
开始执行,执行完后,这个代码块宏任务完成,js
监听进程发现主线程空了,就会去寻找微任务。图解浏览器内部执行:
这里我们通过2个复杂的代码来检验是否已经基本了解了事件循环的机制:
// 来至于谷友的一到面试题
<script>
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‘);
</script>
整个代码块作为一个宏任务,进入主线程
看到函数申明但没有执行,遇到函数console.log执行,输出script start
遇到setTimeout()
,把它的回调函数放入宏任务(setTimeout1)。
宏任务 | 微任务 |
---|---|
setTimeout1 |
遇到执行async1()
, 进入async
的执行上下文之后,遇到console.log
输出async1 start
然后遇到await async2()
,由于()
的优先级高,所有先执行async2()
,进入async2()
的执行上下文。
看到console.log
输出async2
,之后没有返回值,结束函数,返回undefined
,返回async1
的执行上下文的await undefined
,由于async
函数使用await
后得语句会被放入一个回调函数中,所以把下面的放入微任务中。
宏任务 | 微任务 |
---|---|
setTimeout1 | async1=> awati 后面的语句 |
结束async1
,返回全局上下文,遇到Promise
构造函数,里面的函数立马执行, 输出promise1
, 之后的回调函数进入微任务
宏任务 | 微任务 |
---|---|
setTimeout1 | async1=> awati 后面的语句 |
new Promise() => 后的then |
执行完Promise(),遇到console.log,输出script end
,这里一个宏任务代码块执行完毕。
在主线程执行的过程中,事件触发线程会一直监听异步事件,当异步事件处理完成后,把它的回调函数放入事件队列,等待执行。
主线程现在空闲下来后,执行事件队列中的微任务,然后继续向下执行,遇到new Promise()
后面的回调函数,执行代码,输出promise2
(这里2个微任务的优先级,promise高于async)。
看到async1
中await
后面的回调函数,执行代码,输出async1 end
宏任务 | 微任务 |
---|---|
setTimeout1 | 空 |
此时微任务中的队列为空,开始执行队列中的宏任务,进入一个新的代码块。遇到console.log
,输出setTimeout
执行完成,最后结果为
```javascript
script start => async1 start => async2 => promise1 => script end => promise2 => async1 end => setTimeout
```
链接参考:
从Promise来看JavaScript中的Event Loop、Tasks和Microtasks
原文:https://www.cnblogs.com/zhilin/p/11411599.html