JavaScript 浏览器事件循环及练习题

180 阅读4分钟

事件循环(浏览器环境)

什么是事件循环?

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/Await
  • MutationObserver回调
  • 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