JS異步的執(zhí)行原理和回調(diào)詳解
一、JS異步的執(zhí)行原理
我們知道JavaScript是單線程的,而瀏覽器是多線程的。單線程執(zhí)行任務(wù)需要一個(gè)個(gè)排隊(duì)進(jìn)行,假如一個(gè)任務(wù)需要很長(zhǎng)時(shí)間執(zhí)行(像ajax需要較長(zhǎng)時(shí)間),會(huì)直接導(dǎo)致無(wú)響應(yīng),后面的任務(wù)一直在等待執(zhí)行。這時(shí)候就需要用到異步。
想了解異步,首先我們要知道瀏覽器有最基本的三個(gè)常駐線程: JS引擎線程,事件觸發(fā)線程,GUI渲染線程。
其中JS引擎線程和事件觸發(fā)線程共同構(gòu)成了一種事件循環(huán)機(jī)制,而GUI渲染線程與JS引擎是互斥的,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起,GUI更新保存在一個(gè)隊(duì)列中,當(dāng)JS引擎空閑時(shí),立即被執(zhí)行。
我們從它的事件循環(huán)機(jī)制解析:

JS引擎線程中分為同步和異步任務(wù):
1.同步任務(wù)全部通過(guò)主線程執(zhí)行,形成執(zhí)行棧。
2.當(dāng)有異步任務(wù)時(shí)交給異步進(jìn)程(WebAPIs):包含事件觸發(fā)線程或者定時(shí)器線程等處理,形成任務(wù)隊(duì)列。
3.當(dāng)執(zhí)行棧中的任務(wù)全部處理完成,主線程為空閑的時(shí)候,會(huì)從任務(wù)隊(duì)列中提取任務(wù)到執(zhí)行棧中執(zhí)行。
通俗來(lái)說(shuō),JavaScript除了主線程之外還存在一個(gè)任務(wù)隊(duì)列,任務(wù)隊(duì)列存放需要異步執(zhí)行的內(nèi)容,執(zhí)行完主線程后,就會(huì)不斷循環(huán)掃描執(zhí)行任務(wù)隊(duì)列的任務(wù),直至隊(duì)列清空。
畫解:

如圖小明因?yàn)閷W(xué)習(xí)耗時(shí)長(zhǎng)會(huì),如果沒做完就會(huì)一直無(wú)法玩DNF游戲了,就把學(xué)習(xí)放到了異步任務(wù)隊(duì)列中,等玩完游戲(主線程)再學(xué)習(xí)(任務(wù)隊(duì)列)。期間母親添加學(xué)習(xí)事件(DOM事件),小明每完成一個(gè)學(xué)習(xí)任務(wù)就看看還有啥任務(wù)(循環(huán)掃描),直至最后做完.
下面再看一個(gè)例子(瀏覽器刷新不斷點(diǎn)擊按鈕):
let myData = null
//ajax請(qǐng)求
function ajax() {
//騰訊新冠實(shí)時(shí)數(shù)據(jù)接口,僅做學(xué)習(xí)
axios.get('https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=chinaDayList,chinaDayAddList,nowConfirmStatis,provinceCompare')
.then(data => {
console.log("ajax返回成功");
myData = data.data
console.log(myData);
})
.catch(error => {
console.log("ajax返回失敗");
})
}
console.log(myData);
ajax()
setTimeout(() => {
console.log('定時(shí)器');
}, 2000);
console.log(myData);
const btn = document.querySelector('button')
btn.onclick = () => {
console.log("點(diǎn)擊了");
}
null
null
ajax返回成功
Object
點(diǎn)擊了
定時(shí)器
點(diǎn)擊了
可以看到,console在主線程中是同步執(zhí)行的,先執(zhí)行,而在主線程外的任務(wù)隊(duì)列,存放著異步執(zhí)行的內(nèi)容,這里是setTimeout,ajax和DOM事件,按照任務(wù)隊(duì)列順序執(zhí)行(循環(huán)掃描隊(duì)列)。
為什么要循環(huán)掃描呢?
通過(guò)點(diǎn)擊事件可以看出,當(dāng)用戶進(jìn)行交互時(shí)(點(diǎn)擊事件,滾動(dòng)事件,窗口大小變化事件等),會(huì)向事件循環(huán)中的任務(wù)隊(duì)列添加新事件,然后等待執(zhí)行,所以需要循環(huán)掃描。
二、JS異步中的回調(diào)
既然異步都是放在最后的任務(wù)隊(duì)列執(zhí)行,那么我們很多邏輯就難以實(shí)現(xiàn),這時(shí)候我們需要處理這種異步邏輯,最常用的方式是回調(diào)——回頭調(diào)用。
回調(diào)函數(shù):簡(jiǎn)單來(lái)說(shuō)就是,函數(shù)A中傳入函數(shù)B作為參數(shù)時(shí),函數(shù)B即為A函數(shù)執(zhí)行的回調(diào)函數(shù)?;卣{(diào)有嵌套回調(diào)和鏈?zhǔn)交卣{(diào)兩種。
下面是回調(diào)的一個(gè)簡(jiǎn)單用法:
let myData = null
console.log(myData);
setTimeout(() => {
console.log('定時(shí)器');
}, 2000);
const btn = document.querySelector('button')
btn.onclick = () => {
console.log("點(diǎn)擊了");
}
let name = "張三"
function hr(callback) {
setTimeout(() => {
console.log(`我是${name}`);
callback();
}, 2001);
}
console.log(myData);
function gj() {
console.log(`${name}你好,我是李四,認(rèn)識(shí)一下吧`);
}
hr(gj)
null
null
點(diǎn)擊了
定時(shí)器
我是張三
張三你好,我是李四,認(rèn)識(shí)一下吧
點(diǎn)擊了
很明顯的看到,當(dāng)我們函數(shù)需要用到數(shù)據(jù)的時(shí)候就用到了回調(diào),這里用到的是異步回調(diào)。
回調(diào)雖然是解決異步常用的方法,可是伴隨著JS日益復(fù)雜的需求。同步異步需要越來(lái)越多的回調(diào)實(shí)現(xiàn)邏輯。同異步的混雜和過(guò)多的回調(diào)嵌套和縮進(jìn)使得代碼變得難以解讀和維護(hù),形成“回調(diào)地獄”。

我們看一個(gè)例子:
const verifyUser = function(username, password, callback){
dataBase.verifyUser(username, password, (error, userInfo) => {
if (error) {
callback(error)
}else{
dataBase.getRoles(username, (error, roles) => {
if (error){
callback(error)
}else {
dataBase.logAccess(username, (error) => {
if (error){
callback(error);
}else{
callback(null, userInfo, roles);
}
})
}
})
}
})
};
大多數(shù)人光是看到上面的代碼就感受到了腦子凍結(jié)的滋味,如果一個(gè)項(xiàng)目里擁有上百個(gè)這樣的代碼塊,過(guò)一段時(shí)間,我相信連編寫他的人都會(huì)頭疼。來(lái)到自己的項(xiàng)目就像是來(lái)到了地獄。
最主要的是,與此同時(shí)回調(diào)還存在信任問題,他把執(zhí)行控制權(quán)交給了某個(gè)第三方(比如ajax)。為了解決信任問題,我們必須在程序?qū)懜鞣N邏輯來(lái)解決回調(diào)帶來(lái)的信任問題。
·調(diào)用過(guò)早
·調(diào)用過(guò)完
·調(diào)用次數(shù)過(guò)多過(guò)少,沒有把需要的參數(shù)成功傳給回調(diào)函數(shù),
·可能出現(xiàn)的錯(cuò)誤被吞。
可以發(fā)現(xiàn)寫特定邏輯來(lái)解決特定的信任問題,已經(jīng)使得難度大于本身應(yīng)用價(jià)值了,還會(huì)造成代碼冗雜,可讀性差等問題。
綜上:回調(diào)解決異步存在缺陷:
1)不符合人對(duì)任務(wù)處理的邏輯思維
2)回調(diào)帶來(lái)的信任問題。
面對(duì)回調(diào)日益明顯的弊端,ES6更新了Promise用來(lái)解決異步問題。下一篇寫ES6——Promise。
總結(jié)
到此這篇關(guān)于JS異步的執(zhí)行原理和回調(diào)的文章就介紹到這了,更多相關(guān)JS異步執(zhí)行原理回調(diào)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)標(biāo)簽頁(yè)切換效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)標(biāo)簽頁(yè)切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
一文帶你搞懂JS中導(dǎo)入模塊import和require的區(qū)別
JavaScript中,模塊是一種可重用的代碼塊,它將一些代碼打包成一個(gè)單獨(dú)的單元,并且可以在其他代碼中進(jìn)行導(dǎo)入和使用。JavaScript中有兩種常用的方式:使用import和require,本文主要聊聊他們二者的區(qū)別2023-03-03
JavaScript實(shí)現(xiàn)文本相似度對(duì)比
這篇文章主要介紹了JavaScript實(shí)現(xiàn)文本相似度對(duì)比,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06
完美解決IE9瀏覽器出現(xiàn)的對(duì)象未定義問題
下面小編就為大家?guī)?lái)一篇完美解決IE9瀏覽器出現(xiàn)的對(duì)象未定義問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,祝大家游戲愉快哦2016-09-09
關(guān)于JavaScript實(shí)現(xiàn)動(dòng)畫時(shí)動(dòng)畫抖動(dòng)的原因與解決方法
最近在使用JS動(dòng)畫做一些練習(xí)的時(shí)候我發(fā)現(xiàn)在動(dòng)畫執(zhí)行時(shí)間內(nèi)快速移開鼠標(biāo)時(shí)會(huì)出現(xiàn)動(dòng)畫因鼠標(biāo)移動(dòng)過(guò)快從而導(dǎo)致代碼沖突讓畫面抖動(dòng)的bug,這篇文章主要給大家介紹了關(guān)于JavaScript實(shí)現(xiàn)動(dòng)畫時(shí)動(dòng)畫抖動(dòng)的原因與解決方法,需要的朋友可以參考下2022-06-06
JavaScript實(shí)現(xiàn)數(shù)組對(duì)象轉(zhuǎn)換為鍵值對(duì)的四種方式
本文探討了將包含 {icon: "abc", url: "123"} 形式對(duì)象的數(shù)組轉(zhuǎn)換為鍵值對(duì)形式的四種方法,并從實(shí)現(xiàn)方式的簡(jiǎn)潔性、可讀性和性能角度進(jìn)行了分析比較,感興趣的朋友可以參考下2024-02-02
JavaScript 高級(jí)篇之閉包、模擬類,繼承(五)
本篇主要分享我對(duì)閉包的理解及使用閉包完成私有屬性、模擬類、繼承等,結(jié)合大量例子,希望大家能快速掌握!首先讓我們先從一些基本的術(shù)語(yǔ)開始吧2012-04-04
JS把字符串轉(zhuǎn)成json對(duì)象的三種方法示例詳解
這篇文章主要介紹了js?把字符串轉(zhuǎn)成json對(duì)象的三種方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
javascript實(shí)現(xiàn)顏色漸變的方法
這篇文章介紹了javascript實(shí)現(xiàn)顏色漸變的方法,有需要的朋友可以參考一下2013-10-10
chrome下img加載對(duì)height()的影響示例探討
這篇文章主要介紹了chrome下img加載對(duì)height()的影響,需要的朋友可以參考下2014-05-05

