JS中所谓的任务包括执行JS代码、对用户的输入(如:鼠标点击、键盘输入)作出反应、处理异步的网络请求等等,按照执行方式可以分为分为同步任务和异步任务,现在我们清楚的知道浏览器的一个渲染进程只分配给js一个主线程(JS引擎线程),用来执行代码,这意味着所有的任务最终都要进入主线程通过调用栈来执行。
JS同步任务
同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
JS异步任务
异步任指的是不会进入主线程,而是被放到任务队列 (task queue)中排队等待执行,当主线程的任务全部执行完,主线程会从"任务队列"中读取事件执行代码,这个过程是循环不断的,所以整个的这种运行机制又称为事件循环(Event Loop)。
Event Loop是一种执行的规则,打个比方,假设JS主线程是一排道的马路,现在有ABCDE五辆车顺序通行,但现在B车要1分钟后出发,C车一旦雨停就出发,D车等人送来驾照出发,这样会导致E车在一定时间内只能等待,效率很低,为了提高效率,可以让A车和E车进入车道正常排序行驶,B、C、D先去一边,一旦能走了,车道上又没有车,就可以上道出发。
与之类似,假设我们代码中有ABCDE五个任务排队等待执行,当任务进入执行栈,主线程会判断是同步还是异步任务,如果是同步任务,则立即执行,执行完毕出栈;
如果是异步任务,则将其回调函数挂起,即:进入Event Table并注册回调函数,将任务交给异步模块(如:浏览器内核的 DOM Binding 模块、 timer 模块、network 模块)进行处理,当异步有了结果,Event Table将注册的函数移入Event Queue中。
JS主线程执行栈为空的时候,会去Event Queue读取对应的函数,进入主线程执行,不断的重复这个过程,就是事件循环Event Loop。
function taskA(){
console.log('A出发')
}
function taskB(){
setTimeout(function () {
console.log('时间到了,B出发');
},60000);
};
function taskC(){
var btn=document.getElementById("btn");
btn.addEventListener("click", function () {
console.log('雨停了,C出发');
});
};
function taskD(){
var xhr = new XMLHttpRequest(); //创建一个XHR对象
xhr.open("GET","https://www.fastmock.site/mock/d6b39fde63cbe98a4f2fb92ff5b25a6d/api/A");
xhr.onload = function() {
if(xhr.status=='200'){
console.log('驾照送来了,D出发');
}
};
xhr.send(null);
};
function taskE(){
console.log('E出发')
};
taskA();
taskB();
taskC();
taskD();
taskE();
JS运行机制
Event Loop是Javascript的执行机制,也是js实现异步的一种方法,具体说:
JS的执行时单线程的,首先判断JS是同步还是异步,同步就进入主线程,异步的进入Event Table并注册函数,由浏览器其他模块处理,JS引擎继续执行代码,当异步有了结果,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行,循环不断的重复这个过程。
宏任务和微任务
任务分为同步任务和异步任务,异步任务又分为宏任务和微任务,浏览器中他们的执行顺序是同步->微任务->宏任务。
宏任务(macro-task)
由宿主环境(这里指浏览器)提供的叫宏任务,如上边提到的setTimeout、网络请求Ajax、用户I\O(输入输出)。
包含:script(整体代码), setTimeout, setInterval, I/O, UI rendering;
微任务(micro-task ):
由ECMAScript提供的叫微任务,如ES6提供的Promise 。
包含: Promise, Object.observe, MutationObserver
-
Promise对象用来传递异步操作的消息,为异步编程提供了一种解决方案。请参考:ES6-Promise对象获取异步操作的消息
下面的例子中会打印出1-2-5-4-3,:
1、JS首先执行同步任务,打印出1,打印出2(这条语句是同步任务),然后将setTimeout的回调加入宏队列,继续执行,resolve方法被调用,Promise状态变为resolved,再将then里的回调加入微队列,继续执行打印出5。
2、接着执行微任务,因为Promise的异步已经完成,所以执行微任务,打印4
3、执行宏任务,打印3console.log('1'); new Promise(function(resolve) { console.log('2'); setTimeout(function(){ console.log('3'); }) resolve(); }).then(function() { console.log('4'); }) console.log('5');
-
Object.observe API可以被称为一种“可以对任何对象的属性值修改进行监视的事件处理函数”。
-
MutationObserver 是现代浏览器提供的用来检测 DOM 变化的网页接口。
宏任务和微任务的执行顺序
宏任务需要多次事件循环才能执行完,微任务是一次性执行完。
对应宏任务和微任务,有两种任务队列,宏观任务队列(macrotask queue)和是微观任务队列(microtask queue)。JS解析器先执行主线程同步任务,执行栈为空后一次性执行微任务,再去执行宏任务队列的第一个任务,执行完毕后从微队列中取出位于队首的回调任务,放入调用栈中执行,执行完后microtask queue长度减1,继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到把microtask queue中的所有任务都执行完毕。
注意:如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行。
参考:https://www.jianshu.com/p/4516ad4b3048
参考:http://www.ruanyifeng.com/blog/2014/10/event-loop.html
参考:https://segmentfault.com/q/1010000008960948/a-1020000008962511