事件循环 基本介绍
JavaScript是一门单线程的编程语言,所以没有真正意义上的并行特性。
为了协调事件处理、页面交互、脚本调用、UI渲染、网络请求等行为对主线程造成的影响,事件循环(event loop)方案应运而生。
事件循环说白了就是一个不断的在等待任务、执行任务的方案。
在JavaScript中,根据执行方式的不同,有2种状态的任务,分别是同步任务和异步任务。
同步任务率先执行,而后执行异步任务,所有的异步任务由2个队列存储,分别是:
微任务队列
宏任务队列
主线程在执行完同步任务后,会不断的从这2个任务队列中按照先进先出的策略取出异步任务并执行。
并且在此期间也会有新的事件不断的加入至各个任务队列中,以此循环往复、永不阻塞。
如下图所示:
任务分类宏任务包括:
setInterval
setTimeout
setTimmediate Node.Js独有
XHR callbackfn
event callbackfn
requestAnimationFrame
UI rendering
微任务包括:
Promise.then
catch finally
process.nextTick Node.Js独有
MutationObserver
执行顺序根据任务的状态,任务的执行优先级也会有所不同,具体执行顺序如下所示:
同步任务(sync-task)
微任务(micro-task)
宏任务(macro-task)
而关于微任务和宏任务的执行,还有更详细的划分:
微任务队列中一旦有任务,将全部执行完成后再执行宏任务
宏任务队列中的任务在执行完成后,会检查微任务队列中是否有新添加的任务,如果有,那么将执行微任务队列中所有新添加的任务,如果没有则继续执行下一个宏任务
如下图所示:
代码测试:
"use strict"; // 宏任务,每5s添加一个微任务并执行 setInterval(() => { async function foo() { return "micro-task" } async function bar() { let result = await foo(); console.log(result); } bar(); }, 5000); // 宏任务,每1s执行一次 setInterval(() => { console.log("macro-task"); }, 1000); // 同步任务 (() => { console.log("hello world"); })();测试结果,虽然同步任务的代码在最下面,但是它会最先执行,而每添加一个微任务时,宏任务的执行会被插队:
Promise 认识PromisePromise是ES6中出现的新功能,用于在JavaScript中更加简单的实现异步编程。
我们可以使用new Promise()创建出一个Promise对象,它接收一个执行器函数,该函数需要指定resolve和reject参数用于改变当前Promise对象的执行状态。
由于Promise对象中执行器代码是属于同步任务,所以他会率先的进行执行,一个Promise对象拥有以下几种状态:
fulfilled:任务完成、使用resolve改变了任务状态
rejected:任务失败、使用reject改变了任务状态,或任务执行中抛出了异常
pending:正在等待、未使用resolve或reject改变任务状态
注意,每个Promise对象的状态只允许改变一次!不可以多次更改。
示例如下。
1)Promise中执行器任务是同步任务,所以会率先执行:
"use strict"; setInterval(() => { console.log("macro task 3"); }, 1000) let task = new Promise((resolve, reject) => { console.log("sync task 1"); }); console.log("sync task 2"); // sync task 1 // sync task 2 // macro task 32)使用resolve改变Promise对象的状态为fulfilled:
"use strict"; let task = new Promise((resolve, reject) => { let x = Math.floor(Math.random() * 100) + 1; let y = Math.floor(Math.random() * 100) + 1; let result = x + y; // 返回结果为resolve()中的值 resolve(result); }); console.log(task); // Promise {<fulfilled>: 83}3)使用reject改变Promise对象的状态为rejected, 它将引发一个异常:
"use strict"; let task = new Promise((resolve, reject) => { let x = Math.floor(Math.random() * 100) + 1; let y = Math.floor(Math.random() * 100) + 1; let result = x + y; // 返回结果为reject()中的值 reject("error!") }); console.log(task); // Promise {<rejected>: "error!"} // Uncaught (in promise) error!4)如果未使用resolve或reject改变Promise对象状态,那么该任务的状态将为pending:
"use strict"; let task = new Promise((resolve, reject) => { let x = Math.floor(Math.random() * 100) + 1; let y = Math.floor(Math.random() * 100) + 1; let result = x + y; }); console.log(task); // Promise {<pending>} then()我们可以在Promise对象后,添加一个用于处理任务状态的回调then()方法。
then()方法只有在Promise对象状态为fulfilled或者rejected时才会进行执行,它具有2个参数,接收2个回调函数:
onfulfilled:Promise对象状态为fulfilled将执行该函数,具有1个参数value,接收Promise任务中resolve()所传递的值
onrejected:Promise对象状态为rejected将执行该函数,具有1个参数reason,接收Promise任务中reject()或异常发生时所传递的值
此外,then()方法是属于微任务,所以他会插在宏任务之前进行执行。
代码示例如下: