为什么呈现事情循环
在了解阅读事情循环之前,咱们首要要弄理解为什么会呈现事情循环?为什么会发生音讯行列?
第一版线程模型
- 使命 1:1+2
- 使命 2:20/5
- 使命 3:7*8
- 使命 4:打印出使命 1、使命 2、使命 3 的运算成果
void MainThread(){
int num1 = 1+2; // 使命 1
int num2 = 20/5; // 使命 2
int num3 = 7*8; // 使命 3
print(" 终究计算的值为:%d,%d,%d",num,num2,num3); // 使命 4
}
当咱们需求处理以上四个使命的时分,咱们会把以上使命按照次序写进主线程里,等线程履行时,这些使命会按照次序在线程中依次被履行;等一切使命履行完结之后,线程会自动退出。
第二版线程模型(引进事情循环)
在使命处理进程中,不是一切使命都是提前准备好的。咱们可能在使命履行的进程中,接受到一个新的使命,这时分咱们就要引进事情循环,来监听是否有新的使命。
现在的线程模型是这样
这时分,咱们就发现了一个问题。烘托进程会频繁的接受到IO线程的音讯,就会形成页面烘托的卡顿。那么咱们如何改善呢?
第三版线程模型(引进音讯行列)
经过改造之后,使命履行可分为三个步骤
- 添加一个音讯行列
- IO线程会发生使命放进队尾
- 烘托主进程会循环轮询音讯行列
区分宏使命和微使命的必要性
了解了什么是时刻循环之后,咱们来谈谈为什么要有宏使命和微使命?
处理事情的优先级
比如当咱们想要监听DOM元素的删除和修改等,当DOM元素发生变化的时分,咱们应该是想立刻看到页面的变化。假如咱们把DOM元素的优先级设置成最高,直接放到音讯行列的队首,当操作DOM的时刻很长时,其他使命就会在音讯行列中长时刻的等候,形成功率的下降。假如咱们放到队尾,又会影响实时性,由于音讯行列中可能有很多使命在等候中。
针对这种状况,宏使命和微使命就应运而生。咱们将音讯行列中的使命称为宏使命,每个宏使命在履行的进程中会发生相应的微使命行列。宏使命履行完结之后,烘托引擎并不会着急履行下一个宏使命,而是履行当前宏使命发生的微使命行列。
事情循环的履行进程
当咱们大致了解了事情循环和宏使命,微使命之后,咱们来了解一下事情循环详细的履行进程。
宏使命
(macro)task首要包含:script(全体代码)、setTimeout、setInterval、I/O、UI交互事情、postMessage、MessageChannel、setImmediate(Node.js 环境)
微使命
microtask首要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
事情循环履行进程
代码输命题
单独来讲的话太抽象了,咱们直接来看几个代码输命题吧
题一
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
首要,script(全体代码)是一个宏使命,先履行的是全体代码,先输出 ‘script start’ ,然后再履行new Promise里边executor函数, 输出 ‘promise1’ ,再输出 ‘promise1 end’ 然后履行resolve()。 然后将then()里边的函数添加到微使命行列中,再履行setTimeout,并将添加到下一个宏使命行列里边。 此刻, script全体代码(宏使命)履行完毕,然后再履行微使命行列,输出 ‘promise2’ 。此刻微使命履行完毕, 履行下一个宏使命行列, 输出 ‘settimeout’。
题二
在看题二之前,咱们再温习一下Promise
resolve(参数)有以下几种状况
- 1.普通的值或者目标
- 2.传入一个promise,那么当前Promise的状况由传入的Promise决议,相当于状况进行了移送
- 3.传入一个目标,而且这个目标中有then办法(thenable),那么也会履行该then办法,而且由该then办法决议后续状况
第二种状况,传递一个promise,promise的状况由传入的newPromise的状况决议
const newPromise = new Promise((resolve,reject) => {
// resolve('aaa');
// reject('err')
})
new Promise((resolve, reject) => {
resolve(newPromise)
}).then(res => {
console.log(res)
},err => {
console.log(err)
})
第三种状况,传入目标中有then办法
new Promise((resolve,reject) => {
const obj = {
then:function (resolve,reject) {
reject('err')
}
}
resolve(obj)
}).then((res)=> {
console.log('res',res)
},err => {
console.log('err',err)
})
// 输出 err err
好了,咱们能够正式来看第二题了
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => console.log(2));
console.log(4)
}).then(t => console.log(t));
console.log(3);
都看到这里了,相信大家肯定是知道先输出4 3 ,那么2 1是谁先输出呢?
在阮一峰老师的Es6中,Promise.resolve()
办法答应调用时不带参数,直接返回一个resolved
状况的Promise 目标。
需求留意的是,立即resolve()
的 Promise 目标,是在本轮“事情循环”(event loop)的结束时履行,而不是鄙人一轮“事情循环”的开始时。
es6.ruanyifeng.com/#docs/promi…
这段代码的流程大致如下:
- script 使命先运转。首要遇到
Promise
实例,构造函数首要履行,所以首要输出了 4。此刻 microtask 的使命有t2
和t1
- script 使命继续运转,输出 3。至此,第一个宏使命履行完结。
- 履行一切的微使命,先后取出
t2
和t1
,别离输出 2 和 1 - 代码履行完毕
综上,上述代码的输出是:4321
为什么t2
会先履行呢?理由如下:
- 根据Promises/A+标准:
实践中要确保 onFulfilled 和 onRejected 办法异步履行,且应该在
then
办法被调用的那一轮事情循环之后的新履行栈中履行
所以,t2
比t1
会先进入 microtask 的Promise
行列。
自己思考一下撒
setTimeout(()=>{
console.log('setTimeout')
},0)
Promise.resolve().then(()=>{
console.log('promise1')
Promise.resolve().then(() => {
console.log('promise2')
})
})
console.log('main')
let thenable = {
then: function(resolve, reject) {
console.log(0)
resolve(42);
}
};
new Promise(resolve => {
resolve(1);
Promise.resolve(thenable).then((t) => {
console.log(t)
});
console.log(4)
}).then(t => {
console.log(t)
});
console.log(3);
自己看了些材料然后总结了一下事情循环,假如有不对的地方欢迎大家一起交流哈!!!
参阅文档
从一道题浅说 JavaScript 的事情循环 Issue #61 dwqs/blog (github.com)