js是一种单线程的语言,所以它通过event loop机制实现了对异步任务和多线程。
首先你要对栈、队列的数据结构有一定的了解,其次还要会Promise才能看懂今天的内容。
宏任务:script全部代码、setTimeout、setInterval、setImmediate、I/O、UI Rendering。
微任务:Process.nextTick(Node 独有)、Promise 等。
event loop大体由三个部分组成:调用栈(call stack)、消息队列(Message Queue)、微任务队列(Microtask Queue)。这三个部分在event loop中非常重要。
下面通过一些例子举例说明什么是event loop
代码如下(示例):
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
console.log(7)
}
fn2();
首先event loop会从全局栈一行一行执行,首先遇到 fn2(),此时fn2()进入调用栈。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
console.log(7)
}
=>fn2();
调用栈 消息队列
| | -------------------------
| |
| fn2() | -------------------------
------------------------
随后进入到fn2()中,遇到 console.log ,将其压入栈。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
=> console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| console.log(6) |
| fn2() | -------------------------
------------------------
执行console.log(6)并弹出。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
=> console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| |
| fn2() | -------------------------
------------------------
//输出 6
遇到 fn1(),此时fn1()进入调用栈。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
=> fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| fn1() |
| fn2() | -------------------------
------------------------
//输出 6
进入fn1(),遇到console.log,将其压入栈并执行并弹出(跟上面一样),输出多了一个 5 。
function fn1(){
=> console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| fn1() |
| fn2() | -------------------------
------------------------
//输出 6 5
随后遇到setTimeout,将它的内容压入消息队列。消息队列的内容会在调用栈清空后再开始执行!
function fn1(){
console.log(5)
=> setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| fn1() | console.log(4)
| fn2() | -------------------------
------------------------
//输出 6 5
此时fn1执行完,从调用栈弹出,执行回到fn2,遇到console.log,将其压入调用栈。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
=> console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| console.log(7) | console.log(4)
| fn2() | -------------------------
------------------------
//输出 6 5
console.log执行后,输出7,随后fn2也执行完毕从调用栈弹出,此时调用栈为空。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| | console.log(4)
| | -------------------------
------------------------
//输出 6 5 7
最后执行消息队列中的内容。
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| |
| | -------------------------
------------------------
//输出 6 5 7 4
代码如下(示例):
let p = new Promise(function(resolve){
console.log(3)
resolve()
})
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
p.then(funtion(){
console.log(1)
})
console.log(6)
fn1()
console.log(7)
}
fn2();
注意这一段代码与之前的有什么不同?
我们在最开始声明了一个Promise对象,并在fn2中调用了它的then方法。
此时在fn2调用前唯一的不同就是,我们在最开始创建Promise对象的时候执行了一个console.log并输出3,然后将这个Promise的状态更改为successed。
let p = new Promise(function(resolve){
console.log(3)
resolve()
})
function fn1(){
console.log(5)
setTimeout(function(){
console.log(4)
},0)
}
function fn2(){
p.then(funtion(){
console.log(1)
})
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| |
| | -------------------------
------------------------
微任务队列
-------------------------
-------------------------
//输出 3
接下来执行到fn2中的then方法时,会将根据promise的状态将resolve的内容压入微任务队列(此处我们不考虑构造Promise对象时异步调用resolve的情况,此处如果不理解可以查看与Promise原理有关的资料)。
let p = new Promise(function(resolve){
console.log(3)
resolve()
})
function fn1(){
console.log(5)
setTimmout(function(){
console.log(4)
},0)
}
function fn2(){
p.then(funtion(){
console.log(1)
})
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| |
| fn2() | -------------------------
------------------------
微任务队列
-------------------------
consolo.log(1)
-------------------------
//输出 3
接下来的执行都与上面一致,我们直接来到fn2执行完毕。
let p = new Promise(function(resolve){
console.log(3)
resolve()
})
function fn1(){
console.log(5)
setTimmout(function(){
console.log(4)
},0)
}
function fn2(){
p.then(funtion(){
console.log(1)
})
console.log(6)
fn1()
console.log(7)
}
fn2();
调用栈 消息队列
| | -------------------------
| | consolo.log(4)
| | -------------------------
------------------------
微任务队列
-------------------------
consolo.log(1)
-------------------------
//输出 3 6 5 7
此时我们发现,调用栈被清空了,此时微任务队列要优先于消息队列执行,也就是说,先执行console.log(1)再执行console.log(4)。
最终的师叔也就是3 6 5 7 1 4。
以上内容为个人学习过event loop后的个人理解,如有不准确的地方请及时指出。