JavaScript-- 事件循环(Event Loop)
一、是什么
为什么 JavaScript 在浏览器中有事件循环的机制?
JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事。为什么要这么设计,跟JavaScript的应用场景有关。
JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)。
事件循环(Event Loop)
在JavaScript中,所有的任务都可以分为:
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行;
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等。
同步任务与异步任务的运行流程图如下:
从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环。
二、两种任务(宏任务与微任务)
宏任务:遵循先进先出的原则。
常见的宏任务有:
- script (可以理解为外层同步代码、整体代码);
- setTimeout / setInterval;
- postMessage、MessageChannel;
- setImmediate、I/O(Node.js)
微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。
常见的微任务有:
- new Promise().then();
- MutationObserver(前端的回溯);
- process.nextTick(Node.js)
事件循环,宏任务,微任务的关系如图所示:
按照这个流程,它的执行机制是:
- 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中;
- 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完。
Node中的事件循环和浏览器中的事件循环有什么区别
宏任务的执行顺序:
- timer定时器:执行已经安排的 setTimeout 和 setInterval 的回调函数;
- pending callback 待定回调:执行延迟到下一个循环迭代 I/O 回调;
- idle,prepare:仅系统内部使用;
- poll:检索新的 I/O 事件,执行与 I/O 相关的回调;
- check: 执行 setImmediate() 回调的函数;
- close callbacks:socket.on(‘close’, () => {})
微任务和宏任务在 node 的执行顺序:
Node v10及之前:
- 执行完一个阶段中的所有任务;
- 执行 nextTick 队列里的内容;
- 执行完微任务队列的内容
Node v10以后:
和浏览器的行为统一。
三、async与await
async 是异步的意思,await则可以理解为等待。
放到一起可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行。
async
async函数返回一个promise对象,下面两种方法是等效的。
function f() {
return Promise.resolve('TEST');
}
// asyncF is equivalent to f!
async function asyncF() {
return 'TEST';
}
await
正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值。
不管await后面跟着的是什么,await都会阻塞后面的代码。
上代码:
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');
})
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
})
console.log('script end');
//script start
//async1 start
//async2
//promise1
//script end
//async1 end
//promise2
//settimeout
分析:
- 定义 async1 函数,不执行;
- 定义 async2 函数,不执行;
- 执行 console.log(‘script start’) 并打印 ‘script start’;
- setTimeout 定时器,放到下一个宏任务(等该次宏任务结束后并清空玩微任务再执行);
- 执行 async1 函数,并打印 ‘async1 start’ ,然后执行 async2 函数并打印 ‘async2’ ,因为 async2 方法前使用了 await,后面的代码被阻塞了,放入到微任务中等待被执行(await可以理解替换为new Promise(),await 后面的代码可以理解为在 .then() 中被执行);
- 执行 new Promise 中的代码并打印 ‘promise1’ ,.then() 中的代码进入微任务中待执行;
- 打印 ‘script end’;
- 接下来按顺序清空第一轮的微任务,打印 ‘async1 end’、‘promise2’;
- 最后执行下一轮的宏任务(setTimeout);打印 ‘settimeout’。
需要注意的是:
new Promise() 代码中没有执行 resolve() 方法,则就不会执行 .then() 里的代码;
如果.then() 里是一个匿名函数,则匿名函数中的代码放入到微任务中去执行;
如果.then() 里的是语句,则可看成把里面的代码直接放到 new Promise() 主体中直接执行;
如果有多个 Promise 并且有多个 .then ,则 .then 方法需按 Promise 和 then 的顺序分别执行。
console.log(1);
setTimeout(function () {
console.log(2);
})
new Promise(function (resolve) {
console.log(3);
resolve();
}).then(
console.log(4)
).then(function () {
console.log(8)
}
).then(function () {
console.log(11)
}
)
console.log(10);
new Promise(function (resolve) {
setTimeout(function () {
console.log(5);
});
resolve();
}).then(function () {
console.log( 6)
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log(9)
});
setTimeout(function () {
new Promise(function (resolve) {
console.log(7);
});
})
//1、3、4、10、6、9、8、11、2、 5、7
四、结尾
声明: 本文都是摘抄网上一些好的文章整合而成,如有侵权请留言
参考来源