本帖最后由 小刀葛小伦 于 2019-5-16 14:36 编辑
1.JS引擎
js 引擎是用来解释和执行js代码。
2.js引擎的执行机制
1.js是单线程语言
2.js需要异步
如果JavaScript中不存在异步, 只能自上而下执行, 如果上一行解析时间很长, 那么下面的代码就会被阻塞.对于用户而言, 阻塞就意味着 "卡死", 这样就导致了很差的用户体验.所以, JavaScript中存在异步执行
3.js如何实现异步
EventLoop(任务队列/事件循环)
- 1.所有的同步任务都在主线程上执行形成一个执行栈(stack);
- 2.主线程之外还存在一个任务队列(Event Loop),异步任务在event table中注册函数,当满足触发条件(即dom、ajax、setTimeout、setImmediate有返回结果了)后,被推入任务队列(Event Loop)。一旦主线程上的同步任务执行完成开始,系统就会开始读取任务队列(Event Loop), 看看里面有哪些对应得异步任务,于是结束等待状态,进入执行栈,开始执行。
- 3.主线程不断执行上面的第3步
console.log(1)
setTimeout(
function() {
console.log(2)
},
0)
console.log(3)
运行的结果是:1,3,2
代码分析:
1.console.log(1)是同步任务,放入主线程里
2.setTimeout是异步任务,被放入event table,0秒之后被推入任务队列(Event Loop)里
3.console.log(3)是同步任务,放到主线程里.
当1,3在控制台被打印后,主线程去Event Loop(事件队列)里查看是否有可执行的函数,执行setTimeout里的函数,这就是Event Loop
Event Loop
主线程从任务队列(Event Loop) 中读取事件, 这个过程是循环不断的, 所以整个的这种运行机制又称为Event Loop(事件循环).
除了广义的定义同步任务和异步任务还有:
1.macro-task(宏任务):包括整体代码script,setTimeout,setInterval,setImmediate
2.micro-task(微任务):Promise,process.nextTick,Object.observe(已废弃)、 MutationObserver
1. 宏任务:当前调用栈中执行的代码成为宏任务。(主代码快,定时器等等)。
2. 微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件。(promise.then,proness.nextTick等等)。
3. 宏任务中的事件放在callback queue中,由事件触发线程维护;微任务的事件放在微任务队列中,由js引擎线程维护。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用一段代码说明
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
执行过程
整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
宏任务Event Queue | 微任务Event Queue | setTimeout1 | process1 | setTimeout2 | then1 |
上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
我们发现了process1和then1两个微任务。
执行process1,输出6。
执行then1,输出8。
好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。
那么第二轮时间循环从setTimeout1宏任务开始:
首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
宏任务Event Queue | 微任务Event Queue | setTimeout2 | process2 | then2 | |
第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。
输出3。
输出5。
第二轮事件循环结束,第二轮输出2,4,3,5。
第三轮事件循环开始,此时只剩setTimeout2了,执行。
直接输出9。
将process.nextTick()分发到微任务Event Queue中。记为process3。
直接执行new Promise,输出11。
将then分发到微任务Event Queue中,记为then3。
宏任务Event Queue | 微任务Event Queue | | process3 | | then3 |
第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
输出10。
输出12。
第三轮事件循环结束,第三轮输出9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。==(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)
|
|