做者:蚊子ajax
咱们知道 js 是单线程执行的,那么异步的代码 js 是怎么处理的呢?例以下面的代码是如何进行输出的:数组
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve(Date.now());
}).then(function() {
console.log(4);
});
console.log(5);
setTimeout(function() {
new Promise(function(resolve) {
console.log(6);
resolve(Date.now());
}).then(function() {
console.log(7);
});
}, 0);
复制代码
在不运行的状况能够先猜想下最终的输出,而后展开咱们要说的内容。浏览器
依据咱们多年编写 ajax 的经验:js 应该是按照语句前后顺序执行,在出现异步时,则发起异步请求后,接着往下执行,待异步结果返回后再接着执行。但他内部是怎样管理这些执行任务的呢?bash
在 js 中,任务分为宏任务(macrotask)和微任务(microtask),这两个任务分别维护一个队列,均采用先进先出的策略进行执行!同步执行的任务都在宏任务上执行。dom
宏任务主要有:script(总体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)。异步
微任务主要有:Promise.then、 MutationObserver、 process.nextTick(Node.js 环境)。async
具体的操做步骤以下:函数
这 4 步构成了一个事件的循环检测机制,即咱们所称的eventloop
。微服务
回到咱们上面说的代码:oop
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
new Promise(function(resolve) {
console.log(3);
resolve(Date.now());
}).then(function() {
console.log(4);
});
console.log(5);
setTimeout(function() {
new Promise(function(resolve) {
console.log(6);
resolve(Date.now());
}).then(function() {
console.log(7);
});
}, 0);
复制代码
执行步骤以下:
所以,最终的输出顺序为:1, 3, 5, 4, 2, 6, 7;
咱们在Promise.then实现一个稍微耗时的操做,这个步骤看起来会更加地明显:
console.log(1);
var start = Date.now();
setTimeout(function() {
console.log(2);
}, 0);
setTimeout(function() {
console.log(4, Date.now() - start);
}, 400);
Promise.resolve().then(function() {
var sum = function(a, b) {
return Number(a) + Number(b);
}
var res = [];
for(var i=0; i<5000000; i++) {
var a = Math.floor(Math.random()*100);
var b = Math.floor(Math.random()*200);
res.push(sum(a, b));
}
res = res.sort();
console.log(3);
})
复制代码
Promise.then中,先生成一个500万随机数的数组,而后对这个数组进行排序。运行这段代码能够发现:立刻会输出1,稍等一下子才会输出3,而后再输出2。不论等待多长时间输出3,2必定会在3的后面输出。这也就印证了eventloop中的第3步操做,必须等全部的微任务执行完毕后,才开始下一个宏任务。
同时,这段代码的输出颇有意思:
setTimeout(function() {
console.log(4, Date.now() - start); // 4, 1380 电脑状态的不一样,输出的时间差也不同
}, 400);
复制代码
原本要设定的是400ms后输出,但由于以前的任务耗时严重,致使以后的任务只能延迟日后排。也能说明,setTimeout和setInterval这种操做的延时是不许确的,这两个方法只能大概将任务400ms以后的宏任务中,但具体的执行时间,仍是要看线程是否空闲。若前一个任务中有耗时的操做,或者有无限的微任务加入进来时,则会阻塞下一个任务的执行。
从上面的代码中也能看到 Promise.then 中的代码是属于微服务,那么 async-await 的代码怎么执行呢?好比下面的代码:
function A() {
return Promise.resolve(Date.now());
}
async function B() {
console.log(Math.random());
let now = await A();
console.log(now);
}
console.log(1);
B();
console.log(2);
复制代码
其实,async-await 只是 Promise+generator 的一种语法糖而已。上面的代码咱们改写为这样,能够更加清晰一点:
function B() {
console.log(Math.random());
A().then(function(now) {
console.log(now);
})
}
console.log(1);
B();
console.log(2);
复制代码
这样咱们就能明白输出的前后顺序了: 1, 0.4793526730678652(随机数), 2, 1557830834679(时间戳);
requestAnimationFrame也属于执行是异步执行的方法,但我任务该方法既不属于宏任务,也不属于微任务。按照MDN中的定义:
window.requestAnimationFrame()
告诉浏览器——你但愿执行一个动画,而且要求浏览器在下次重绘以前调用指定的回调函数更新动画。该方法须要传入一个回调函数做为参数,该回调函数会在浏览器下一次重绘以前执行
requestAnimationFrame是GUI渲染以前执行,但在微服务以后,不过requestAnimationFrame不必定会在当前帧必须执行,由浏览器根据当前的策略自行决定在哪一帧执行。
咱们要记住最重要的两点:js是单线程和eventloop的循环机制。