JavaScript 事件循环
JS是单线程的脚本语言,在同一时间,只能做同一件事,为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程阻塞,Event Loop 方案应运而生.
单线程意味着,JavaScript 代码在执行的任何时候,都只有一个主线程来处理所有的任务。
而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如 I/O 事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。
浏览器环境下 js 引擎的事件循环机制
执行栈
当执行某个函数、用户点击一次鼠标,Ajax 完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入任务队列中,等待主线程读取,遵循先进先出原则。
执行任务队列中的某个任务,这个被执行的任务就称为执行栈。
主线程
要明确的一点是,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。
主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列Task Queue
要明确的一点是,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。
- 主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。 当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。 不太理解的话,可以运行一下下面的代码,或者点击一下这个 demo 结果是当 a、b、c 函数都执行完成之后,三个 setTimeout 才会依次执行。
let a = () => {
setTimeout(() => {
console.log("任务队列函数1");
}, 0);
for (let i = 0; i < 5000; i++) {
console.log("a的for循环");
}
console.log("a事件执行完");
};
let b = () => {
setTimeout(() => {
console.log("任务队列函数2");
}, 0);
for (let i = 0; i < 5000; i++) {
console.log("b的for循环");
}
console.log("b事件执行完");
};
let c = () => {
setTimeout(() => {
console.log("任务队列函数3");
}, 0);
for (let i = 0; i < 5000; i++) {
console.log("c的for循环");
}
console.log("c事件执行完");
};
a();
b();
c();
// 当a、b、c函数都执行完成之后,三个setTimeout才会依次执行
异步执行的运行机制
- 所有任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
主线程不断重复上面的第三步
JS 引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js 会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环
微任务与宏任务
不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)
微任务(micro task) 优先于 task 执行,所以如果有需要优先执行的逻辑,放入 微任务(micro task) 队列会比 task 更早的被执行。
当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
promise 异步是指的 then()方法,而不是指它的构造函数执行;
Promise 构造函数是同步执行的,resolve()/reject()后的代码也会执行。Promise.then()中的函数是异步执行的
-
宏任务(macro task)一个event loop有一个或者多个task队列。task任务源非常宽泛,比如ajax的onload,click事件,基本上我们经常绑定的各种事件都是task任务源,还有数据库操作(IndexedDB),需要注意的是setTimeout、setInterval、setImmediate也是task任务源。总结来说task任务源:- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
-
微任务(micro task)- new Promise()
- new MutaionObserver()
setTimeout(function() {
console.log(1);
});
new Promise(function(resolve, reject) {
console.log(2);
resolve(3);
}).then(function(val) {
console.log(val);
});
//2 3 1