在實(shí)例中重學(xué)JavaScript事件循環(huán)
單線程的JS
眾所周知js是一門單線程語言,即同一時(shí)間只能做一件事。為什么js是單線程的呢,主要與它的用途有關(guān)。
作為瀏覽器腳本語言,js的主要用途是和用戶互動(dòng)&操作DOM,我們并不想并行的操作DOM。如果不是單線程的話,我們一個(gè)線程在給DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程卻刪除了這個(gè)節(jié)點(diǎn),到底該以哪個(gè)為準(zhǔn)呢?
所以為了避免復(fù)雜性,從一誕生,JavaScript 就是單線程。
事件循環(huán)(event loop)
JS是一門單線程語言,意味著代碼要一行一行的執(zhí)行。所有任務(wù)都要排隊(duì),前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)。
但平時(shí)大家開發(fā)時(shí)常用到的ajax,setTimeOut,promise之類的并沒有阻塞進(jìn)程。如果瀏覽器只有一個(gè)js引擎構(gòu)成,遇到上面這些比較耗時(shí)的請求或操作時(shí),瀏覽器就會(huì)阻塞住,這肯定不是我們想要的。
其實(shí)js單線程是指瀏覽器在解釋和執(zhí)行js代碼時(shí)只有一個(gè)線程,即js引擎線程。但瀏覽器還包括一些其他的線程來處理這些異步的方法,比如Web APIs線程,GUI渲染線程等。
事件循環(huán)的處理流程:
JS線程依靠調(diào)用棧來處理執(zhí)行js代碼,當(dāng)遇到一些異步的操作時(shí),則將其移交給Web APIs,自己繼續(xù)往下進(jìn)行。
Web APIs線程則將收到的事件按一定的規(guī)則和順序添加到任務(wù)隊(duì)列里去。
JS線程處理完當(dāng)前所有任務(wù)(即執(zhí)行棧為空),則去檢查任務(wù)隊(duì)列里是否有等待被處理的事件,若有,則取出一個(gè)事件回調(diào)放入執(zhí)行棧中執(zhí)行。
然后不斷循環(huán)第三步。
宏任務(wù)與微任務(wù)
任務(wù)隊(duì)列又分為宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列:
- 宏任務(wù)隊(duì)列(macrotask queue):存放的是setTimeout, setInterval, setImmediate, I/O, UI rendering等。
- 微任務(wù)隊(duì)列(microtask queue):存放的是Promises, Object.observe, MutationObserver,process.nextTick等。
所以我們細(xì)化一下事件循環(huán)的處理流程(瀏覽器環(huán)境):
JS線程依靠調(diào)用棧來處理執(zhí)行js代碼,當(dāng)遇到一些異步的操作時(shí),則將其移交給Web APIs,自己繼續(xù)往下進(jìn)行。
Web APIs線程則將收到的事件按一定的規(guī)則和順序添加到任務(wù)隊(duì)列里去。宏任務(wù)事件則添加到宏任務(wù)隊(duì)列,微任務(wù)事件則添加到微任務(wù)隊(duì)列。
JS線程處理完當(dāng)前所有任務(wù)(即執(zhí)行棧為空),會(huì)先去微任務(wù)隊(duì)列檢查是否有待處理的事件,若有,會(huì)將微任務(wù)隊(duì)列里的所有事件一件件執(zhí)行完直到微任務(wù)隊(duì)列為空,再去宏任務(wù)隊(duì)列取出最前面的一個(gè)事件執(zhí)行,執(zhí)行完這一個(gè)宏任務(wù)事件后再去檢查微任務(wù)隊(duì)列是否有事件待處理。
然后不斷循環(huán)第三步。
實(shí)際需求中重學(xué)JavaScript事件循環(huán)
什么是JS事件循環(huán)?
在秋招的時(shí)候經(jīng)常會(huì)被問到這個(gè)問題,但自己的理解僅限于以上,然后刷過幾道輸出值順序的題目,沒有過業(yè)務(wù)中的實(shí)際應(yīng)用場景。后來拿到offer后就忘的一干二凈了,直到畢業(yè)入職后開始寫代碼重新遇到了這才有了更深一步的理解。
背景
用戶上傳多張圖片,前端拿到每張圖片的url和寬高發(fā)送給后端。
解決
首先是拿到用戶上傳的文件,做一些校驗(yàn)和限制
// 調(diào)用系統(tǒng)彈框添加圖片的方法 addFile(e) { let uploadFiles = e.target.files,self = this; self.getListData = []; // 要傳給后端的對象數(shù)組 self.testFiles(uploadFiles) // 對用戶上傳的文件做一些校驗(yàn)和限制 self.loadAll(uploadiles) // 調(diào)用loadAll方法 },
然后讓我們看一下loadAll,主要是遍歷上傳的這些圖片文件,然后每一個(gè)圖片文件都調(diào)用了loadImg
loadAll(files) { let promises = [],self = this // 遍歷文件流 files.forEach((file,i) => { // 創(chuàng)建對象,push到數(shù)組里 (self.getListData).push({ imageUrl: '', }); let eachPromise = self.loadImg(file,i) // 存儲(chǔ)當(dāng)前promise對象 promises.push(eachPromise) }) Promise.all(promises).then(() => { //全部完成,向后端發(fā)送請求 }).catch(err => { console.log(err) }) },
然后讓我們看一下loadImg,這個(gè)方法返回一個(gè)Promise對象,主要是為了保證拿到圖片的URL以及在img.onload里拿到圖片的寬高,因?yàn)檫@兩個(gè)事件都是異步事件。
實(shí)際上js主線程是不會(huì)等待這兩個(gè)結(jié)果,就會(huì)繼續(xù)往下執(zhí)行的。但因?yàn)槲覀冊趇mg.onload里才會(huì)把Promise給resolve出去,而loadAll方法里用了一個(gè)Promise.all來等待所有promise都完成,這樣就可以保證向后端發(fā)送請求時(shí)所有的圖片的url和寬高都能拿到。
loadImg(file,i) { return new Promise(async (resolve,reject) => { let self = this // 調(diào)用公司wos服務(wù),拿圖片文件的url let successRes = await _fileUpload.uploadFile(item) if(successRes && successRes !== 'error'){ self.getListData[i]['imageUrl'] = successRes.url } let img = new Image() img.src = successRes.url img.onload = () => { self.getListData[i]['width'] = img.width self.getListData[i]['height'] = img.height resolve() } img.onerror = (e) => { reject(e) } }) }
讓我們想一下如果把loadImg里拿圖片的url這個(gè)操作放到loadAll里呢?
loadAll(files) { let promises = [],self = this // 遍歷文件流 files.forEach(async(file,i) => { // 創(chuàng)建對象,push到數(shù)組里 (self.getListData).push({ imageUrl: '', }); // 調(diào)用公司wos服務(wù),拿圖片文件的url let successRes = await _fileUpload.uploadFile(item) if(successRes && successRes !== 'error'){ self.getListData[i]['imageUrl'] = successRes.url } let eachPromise = self.loadImg(file,i) // 存儲(chǔ)當(dāng)前promise對象 promises.push(eachPromise) }) Promise.all(promises).then(() => { //全部完成,向后端發(fā)送請求 }).catch(err => { console.log(err) }) },
如果變成這種寫法,因?yàn)閖s的主線程執(zhí)行棧不會(huì)等待await返回結(jié)果,循環(huán)里await _fileUpload.uploadFile(item)這行代碼后面的內(nèi)容會(huì)被交給Web APIs然后跳出async函數(shù)。繼續(xù)執(zhí)行主線程,而現(xiàn)在Promise.all的參數(shù)是一個(gè)空數(shù)組,然后就直接發(fā)了請求。但現(xiàn)在并沒有拿到圖片的URL和寬高。
關(guān)鍵字await只能使async函數(shù)一直等待,執(zhí)行棧當(dāng)然不可能停下來等待的,await將其后面的內(nèi)容包裝成Promise交給Web APIs后,執(zhí)行棧會(huì)跳出async函數(shù)繼續(xù)執(zhí)行,直到Promise執(zhí)行完并返回結(jié)果。await只在async函數(shù)里面奏效。
總結(jié)
從上面這個(gè)需求的實(shí)現(xiàn)中,好像對事件循環(huán)的理解更深刻了!像Promise.then里和await后面的代碼都會(huì)等待返回結(jié)果后再被放入對應(yīng)事件的任務(wù)隊(duì)列中等待執(zhí)行,JS線程會(huì)繼續(xù)向下執(zhí)行調(diào)用棧。包括vue中的watch handler也是被先放入了任務(wù)隊(duì)列里等待。
所以可知事件循環(huán)在實(shí)際工作中對寫代碼和優(yōu)化代碼都非常重要~如理解有誤請?jiān)谠u論區(qū)多多指教。
以上就是在實(shí)例中重學(xué)JavaScript事件循環(huán)的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 事件循環(huán)的資料請關(guān)注腳本之家其它相關(guān)文章!
- JS addEventListener()和attachEvent()方法實(shí)現(xiàn)注冊事件
- js重寫alert事件(避免alert彈框標(biāo)題出現(xiàn)網(wǎng)址)
- JS如何監(jiān)聽div的resize事件詳解
- 淺談javascript事件環(huán)微任務(wù)和宏任務(wù)隊(duì)列原理
- JavaScript事件循環(huán)及宏任務(wù)微任務(wù)原理解析
- vue.js click點(diǎn)擊事件獲取當(dāng)前元素對象的操作
- JavaScript代碼模擬鼠標(biāo)自動(dòng)點(diǎn)擊事件示例
- 詳解node.js 事件循環(huán)
- 實(shí)例講解JavaScript 計(jì)時(shí)事件
- JavaScript事件概念詳解(區(qū)分靜態(tài)注冊和動(dòng)態(tài)注冊)
相關(guān)文章
JavaScript中箭頭函數(shù)與普通函數(shù)的區(qū)別詳解
這篇文章主要介紹了JavaScript中箭頭函數(shù)與普通函數(shù)的區(qū)別,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08一文帶你掌握J(rèn)avaScript中的箭頭函數(shù)
在JavaScript中,箭頭函數(shù)是一種簡化的函數(shù)語法,它在ES6(ECMAScript?2015)引入,本文就來和大家深入講講JavaScript中的箭頭函數(shù)的使用吧2023-05-05原生JS 實(shí)現(xiàn)的input輸入時(shí)表格過濾操作示例
這篇文章主要介紹了原生JS 實(shí)現(xiàn)的input輸入時(shí)表格過濾操作,結(jié)合實(shí)例形式分析了JavaScript基于頁面元素遍歷、運(yùn)算、判斷實(shí)現(xiàn)的表格過濾相關(guān)操作技巧,需要的朋友可以參考下2019-08-08A標(biāo)簽中通過href和onclick傳遞的this對象實(shí)現(xiàn)思路
想傳遞當(dāng)前對象給一個(gè)函數(shù),于是就將這個(gè)URL寫成"Javascript:shoControlSidebar(this)",可是結(jié)果發(fā)現(xiàn)這并不可行,接下來為大家詳細(xì)介紹下解決方法2013-04-04uniapp實(shí)現(xiàn)注冊發(fā)送獲取驗(yàn)證碼功能
隨著網(wǎng)絡(luò)的發(fā)達(dá),總會(huì)有人惡意注冊網(wǎng)站,而使用輸入驗(yàn)證碼功能可以有效的阻止這一類現(xiàn)象的發(fā)生,下面這篇文章主要給大家介紹了關(guān)于uniapp實(shí)現(xiàn)注冊發(fā)送獲取驗(yàn)證碼功能的相關(guān)資料,需要的朋友可以參考下2022-11-11微信小程序?qū)崿F(xiàn)多選刪除列表數(shù)據(jù)功能示例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)多選刪除列表數(shù)據(jù)功能,涉及微信小程序列表數(shù)據(jù)讀取、顯示、刪除等相關(guān)操作技巧,需要的朋友可以參考下2019-01-01使用typescript+webpack構(gòu)建一個(gè)js庫的示例詳解
這篇文章主要介紹了typescript+webpack構(gòu)建一個(gè)js庫,本文主要記錄使用typescript配合webpack打包一個(gè)javascript library的配置過程,需要的朋友可以參考下2022-07-07