总所周知,JavaScript是一门单线程语言!
JS作为一种脚本语言,多被用于浏览器中进行Web开发,它的主要用途是通过操作DOM,BOM等达到与用户进行交互的目的。这也就意味着它只能是单线程的,以此来避免多个线程对同一DOM元素进行冲突的操作。
虽然H5出了一个新特性WebWorker为JS创造多线程环境,但多用来计算密集型或高延迟的任务,让主线程流程不被阻塞。但WebWorker也有诸多限制,同源限制,DOM限制,通信限制,脚本限制及文件限制等,这里不多做叙述。
如果JS在执行一行代码时,解析时间过长,例如:大量数据的循环遍历等,那么后面的代码将会被阻塞,暂时无法执行,对用户而言,页面就卡住了,没办法进行迅速有效的交互,此时就需要JS进行异步操作!
异步的实现,也就是JS的执行机制 — 事件循环(event loop)
因为JS是单线程的,所以所有的任务都是需要排队的,这里将JS的任务分为两种:
同步任务(synchronous):在主线程上排队执行的任务异步任务(asynchronous):不进入主线程,而是进入“任务队列”(task queue)的任务任务队列也是事件队形,运行机制大致如下:
同步任务在主线程上执行,形成一个执行栈(先进先出)当执行栈执行完所有同步任务后,主线程将从“任务队列”中读取事件,执行异步任务异步任务又细分为:
宏观任务:宏观即指宿主,也是就是写代码的人发起的任务(整体代码script,setTimeout,setInterval)微观任务:微观即指JS,由JS引擎发起的任务(Promise.then是Promise的回调函数,process.nextTick)完整的事件循环:
第一轮循环将整体JS当作一个宏任务来运行,主线程判断任务类型,同步任务逐步执行,当遇到异步任务将其推入事件队列(宏任务推入宏任务队列,微任务推入当前宏任务的微任务队列)主线程执行完一轮轮同步(宏)任务后,依次执行当前的所有微任务执行完当前所有任务,结束第一轮循环,开始第二轮循环执行同步(宏)任务,将微任务推入当前的宏任务的微任务队列开始循环往复示例1:
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') }) })示例2:
async function testSometing() { console.log("执行testSometing"); return "testSometing"; } async function testAsync() { console.log("执行testAsync"); return Promise.resolve("hello async"); } async function test() { console.log("test start..."); const v1 = await testSometing(); console.log(v1); const v2 = await testAsync(); console.log(v2); console.log(v1, v2); } test(); var promise = new Promise((resolve) => { console.log("promise start.."); resolve("promise"); }); promise.then((val) => console.log(val)); setTimeout(()=>{console.log("setTime1")},3000); console.log("test end...")