web前端tips:js的事件循環(huán)(Event Loop)
一、介紹
1. 什么是js的事件循環(huán)
JavaScript事件循環(huán)是一種處理異步事件和回調(diào)函數(shù)的機(jī)制,它是JavaScript實(shí)現(xiàn)異步編程的核心。它在瀏覽器或Node.js環(huán)境中運(yùn)行,用于管理任務(wù)隊(duì)列和調(diào)用棧,以及在適當(dāng)?shù)臅r(shí)候執(zhí)行回調(diào)函數(shù)。
2. 為什么會(huì)出現(xiàn)js的事件循環(huán)
JavaScript事件循環(huán)是為了解決JavaScript作為單線程語(yǔ)言時(shí)的并發(fā)性問題而設(shè)計(jì)的。由于JavaScript是單線程的,因此在執(zhí)行代碼時(shí)不能同時(shí)執(zhí)行多個(gè)任務(wù)。這種單一線程的特性可能會(huì)導(dǎo)致JavaScript在處理某些長(zhǎng)時(shí)間運(yùn)行的操作(如網(wǎng)絡(luò)請(qǐng)求、文件系統(tǒng)訪問等)時(shí)出現(xiàn)阻塞,從而影響用戶體驗(yàn)。
為了解決這些問題,JavaScript引入了異步編程模型和事件循環(huán)機(jī)制,它可以監(jiān)聽消息隊(duì)列中的事件并根據(jù)優(yōu)先級(jí)順序依次執(zhí)行相應(yīng)的回調(diào)函數(shù)。這種機(jī)制允許JavaScript在等待某些操作完成的同時(shí),可以執(zhí)行其他任務(wù),從而避免了阻塞,提高了效率和并發(fā)性,使得開發(fā)者可以使用異步編程模型來(lái)處理復(fù)雜的、長(zhǎng)時(shí)間運(yùn)行的操作,同時(shí)提供更好的用戶體驗(yàn)。
3. 事件循環(huán)的流程
事件循環(huán)中的任務(wù)分為兩類:同步任務(wù)和異步任務(wù)。同步任務(wù)是按照代碼順序依次執(zhí)行的任務(wù),而異步任務(wù)則是在任務(wù)隊(duì)列中等待執(zhí)行的任務(wù),例如定時(shí)器回調(diào)函數(shù)、事件回調(diào)函數(shù)和Promise回調(diào)函數(shù)等。異步任務(wù)又可以分為宏任務(wù)和微任務(wù),微任務(wù)的執(zhí)行優(yōu)先級(jí)高于宏任務(wù)。當(dāng)一個(gè)宏任務(wù)中的所有微任務(wù)都執(zhí)行完畢后,才會(huì)執(zhí)行下一個(gè)宏任務(wù)。事件循環(huán)的工作流程是不斷地從任務(wù)隊(duì)列中取出任務(wù)并執(zhí)行,直到隊(duì)列為空為止。
二、事件循環(huán)的應(yīng)用場(chǎng)景
DOM 事件處理:通過監(jiān)聽 DOM 事件(例如 click、scroll 等),可以使用事件循環(huán)來(lái)異步更新 UI 或執(zhí)行其他操作。
定時(shí)器:使用?setTimeout()?和?setInterval()?函數(shù)可以創(chuàng)建定時(shí)器,用于在指定時(shí)間間隔之后執(zhí)行相應(yīng)的操作。這些操作會(huì)被作為異步任務(wù)添加到任務(wù)隊(duì)列中等待執(zhí)行。
網(wǎng)絡(luò)請(qǐng)求:當(dāng) JavaScript 需要發(fā)送網(wǎng)絡(luò)請(qǐng)求時(shí),可以使用?XMLHttpRequest?或?fetch?API 發(fā)送異步請(qǐng)求,并將響應(yīng)數(shù)據(jù)作為異步任務(wù)加入到任務(wù)隊(duì)列中等待處理。
Promise 和 async/await:Promise 和 async/await 是 JavaScript 中常用的異步編程方式,實(shí)際上它們底層都是基于事件循環(huán)機(jī)制實(shí)現(xiàn)的。通過將回調(diào)函數(shù)封裝為 Promise 對(duì)象或 async 函數(shù),可以讓異步代碼更加易讀、易維護(hù)。
Web Workers:Web Workers 可以讓 JavaScript 在多線程環(huán)境下運(yùn)行,從而避免阻塞主線程。Web Workers 使用了與事件循環(huán)類似的消息隊(duì)列機(jī)制來(lái)實(shí)現(xiàn)異步通信。
三、例子
1. 定時(shí)器回調(diào)函數(shù)
console.log('start'); setTimeout(()?=>?{ ??console.log('timer'); },?0); console.log('end'); //?輸出結(jié)果 //?start //?end //?timer
這是因?yàn)閟etTimeout()方法是異步執(zhí)行的,它會(huì)在指定時(shí)間后將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中等待執(zhí)行。因此,在輸出"start"和"end"之后,程序立即返回,等到下一個(gè)事件循環(huán)時(shí)才執(zhí)行定時(shí)器回調(diào)函數(shù)。
2. Promise回調(diào)函數(shù)
console.log('start'); Promise.resolve().then(()?=>?{ ??console.log('promise'); }); console.log('end'); //?輸出結(jié)果 //?start //?end //?promise
這是因?yàn)镻romise回調(diào)函數(shù)是微任務(wù),它的執(zhí)行優(yōu)先級(jí)高于宏任務(wù),因此在輸出"start"和"end"之后,先執(zhí)行Promise回調(diào)函數(shù),再執(zhí)行下一個(gè)宏任務(wù)。
3. 事件回調(diào)函數(shù)
console.log('start'); document.addEventListener('click',?()?=>?{ ??console.log('click'); }); console.log('end'); //?輸出結(jié)果 //?start //?end
這是因?yàn)槭录卣{(diào)函數(shù)需要等待用戶操作才能觸發(fā),因此在程序執(zhí)行完畢之后,等待用戶操作后才會(huì)將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中等待執(zhí)行。
四、復(fù)雜例子思考
async?function?a()?{ ??console.log('async-a'); ??await?b(); ??console.log('async-b'); }; async?function?b()?{ ??console.log('async-b'); }; console.log('start'); setTimeout(()=>{ ??console.log('setTimeout-1'); },1000); setTimeout(()=>{ ??console.log('setTimeout-2'); ??new?Promise((resolve,reject)=>{ ????console.log('setTimeout-promise'); ????resolve('promise-1'); ??}).then(res?=>?{ ????console.log(res); ??}) },0); new?Promise((resolve,reject)=>{ ??console.log('promise'); ??resolve('promise-2'); }).then(res?=>?{ ??console.log(res); }); a(); console.log('end')
五、一句話總結(jié)
JavaScript事件循環(huán)是一種處理異步事件和回調(diào)函數(shù)的機(jī)制,它是JavaScript實(shí)現(xiàn)異步編程的核心。它會(huì)不斷地從任務(wù)隊(duì)列中取出任務(wù)并執(zhí)行,直到任務(wù)隊(duì)列為空為止。事件循環(huán)中的任務(wù)分為同步任務(wù)和異步任務(wù),異步任務(wù)又可以分為宏任務(wù)和微任務(wù),微任務(wù)的執(zhí)行優(yōu)先級(jí)高于宏任務(wù)。
六、結(jié)語(yǔ)
牽手 持續(xù)為你分享各類知識(shí)和軟件 ,歡迎訪問、關(guān)注、討論 并留下你的小心心?