事件循环(浏览器环境)
什么是事件循环?
JavaScript的事件循环(Event Loop)是一种用于处理异步操作的机制,它负责管理JavaScript运行时环境中的任务队列。
具体来讲,对于异步事件它会先加入到事件队列中挂起,等主线程空闲时会去执行事件队列(Event Queue)中的事件。如此反复循环。
事件循环有什么好处?
事件循环的设计使得 JavaScript 可以在单线程下处理异步操作,避免了阻塞的情况,保证了程序的响应性和流畅性。
事件循环的过程?
- 执行一个宏任务(一般一开始是整体代码(
script)),如果没有可选的宏任务,则直接处理微任务 - 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 执行过程中如果遇到宏任务,就将它添加到宏任务的任务队列中
- 执行一个宏任务完成之后,就需要检测微任务队列有没有需要执行的任务,有的话,全部执行,没有的话,进入下一步
- 检查渲染,然后
GUI线程接管渲染,进行浏览器渲染 - 渲染完毕后,JS线程继续接管,开始下一个宏任务...(循环上面的步骤)
各种任务是什么?
同步任务
同步任务(Synchronous Task)是按照代码的顺序依次执行的任务,每个任务必须等待上一个任务执行完毕才能执行。同步任务会阻塞主线程的执行,直到任务执行完毕才会继续执行下一个任务。常见的同步任务包括变量声明、函数调用、循环等。
异步任务
异步任务(Asynchronous Task)是不会阻塞主线程的执行的任务,可以在后台执行,不需要等待上一个任务执行完毕。异步任务会在特定的条件满足时触发执行,比如定时器到期、网络请求返回、事件触发等。常见的异步任务包括定时器、网络请求、事件监听、Promise等。
宏任务
宏任务是指由宿主环境(如浏览器或Node.js)提供的任务,它们会被放入宏任务队列中等待执行。 当主线程空闲时,会从宏任务队列中取出一个宏任务执行。
script(整体代码)- 定时器回调:
setTimout/setInterval/setImmediate(node 独有) I/O操作:文件读写、网络请求UI render(浏览器独有)requestAnimationFrame(浏览器独有,在浏览器下一次重绘之前执行回调函数)MessageChannel:用于创建一个消息通道,可以通过postMessage方法发送消息,在接收到消息时执行回调函数。
微任务
微任务是指由JavaScript引擎提供的任务,它们会被放入微任务队列中等待执行。当一个宏任务执行完毕后,在下一个宏任务执行之前,会先执行所有微任务队列中的任务。
Promise回调Async/AwaitMutationObserver回调Object.observe(ES6开始已废弃)process.nextTick(node独有,用于在当前执行栈的末尾添加一个任务,以便在下一个事件循环之前执行)
promise & async await
promise.then() 才是异步
async函数返回一个 Promise 对象
async函数内部return语句返回的值,会成为then方法回调函数的参数。
await 是异步操作, 遇到 await 就会立即返回一个 pending 状态的 Promise 对象, 暂时返回执行代码的控制权,使得函数外的代码得以继续执行.
输出题
console.log('1')
async function async1() {
await async2()
console.log('2')
}
async function async2() {
console.log('3')
}
async1()
setTimeout(function() {
console.log('4')
}, 0)
new Promise(resolve => {
console.log('5')
resolve()
})
.then(function() {
console.log('6')
})
.then(function() {
console.log('7')
})
console.log('8')
// 输出顺序 1,3,5,8,2,6,7,4
console.log('script start')
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
setTimeout(() => {
console.log('setTimeout');
}, 0)
async function async2() {
console.log('async2')
}
async1()
new Promise(resolve => {
console.log('promise1 start')
resolve()
console.log('promise1 end')
}).then(() => {
console.log('promise2')
})
process.nextTick(() => {
console.log('nextTick');
})
console.log('script end')
// 输出顺序 script start,async1 start,async2,promise1 start,promise1 end,async1 end,promise2,setTimeout
// tips:浏览器中没有process.nextTick
console.log('start');
setTimeout(() => {
console.log('children2');
Promise.resolve().then(() => {
console.log('children3');
})
}, 0);
new Promise(function(resolve, reject) {
console.log('children4');
setTimeout(function() {
console.log('children5');
resolve('children6')
}, 0)
}).then((res) => {
console.log('children7');
setTimeout(() => {
console.log(res);
}, 0)
})
// start children4 children2 children3 children5 children7 children6