最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript之同步與異步的使用及說明

 更新時間:2026年03月25日 09:17:37   作者:愛喝酸奶的一旬  
這篇文章主要介紹了JavaScript之同步與異步的使用及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

概要

異步,按照字面理解,指的是兩個或者兩個以上的對象或事件不同時存在或者發(fā)生(或者`多個相關(guān)事物的發(fā)生無需等待其一事物的完成),在JavaScript引擎中,

異步是相對于同步來說,所謂同步,大致可以理解為:

代碼執(zhí)行順序是按照其在腳本的順序來執(zhí)行(變量提升除外),腳本必須執(zhí)行完上一句代碼,才能接著執(zhí)行下一句語句。

在大多數(shù)情況下,這個是符合我們專注做一件事情的場景,但有時候,遇到一些很耗時間的任務(wù),如果采取同步的方法,會影響后續(xù)任務(wù)的執(zhí)行,只能等待這個耗時任務(wù)執(zhí)行完,才能接著執(zhí)行其他任務(wù),從而影響整體的效率。

為了解決這種耗時費(fèi)力的任務(wù)處理,JavaScript為我們提供了異步處理。所謂異步,可以理解為:把耗時的任務(wù)交給單獨(dú)的線程去處理,同時注冊一個回調(diào)函數(shù)。緊接著,JavaScript引擎無需等待這個任務(wù)處理完成,可以接著運(yùn)行下面的語句。

等到那個單獨(dú)線程處理完成任務(wù)后,告訴JavaScript引擎,JavaScript再執(zhí)行其注冊的回調(diào)函數(shù)。這個就是異步。

同步代碼

const name = "Miriam";
const greeting = `Hello, my name is ${name}!`;
console.log(greeting);
// "Hello, my name is Miriam!"

這段代碼:

  1. 聲明了一個叫做 name 的字符串常量
  2. 聲明了另一個叫做 greeting 的字符串常量(并使用了 name 常量的值)
  3. 將 greeting 常量輸出到 JavaScript 控制臺中。

異步代碼

var xhr = new XMLHttpRequest();
xhr.open("GET", "/bar/foo.txt", true);
xhr.onload = function (e) {
  console.log("比最后一段代碼執(zhí)行更晚")
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};
xhr.onerror = function (e) {
  console.error(xhr.statusText);
};
xhr.send(null);
console.log("執(zhí)行語句")

第 2 行中指定第三個參數(shù)為 true,表示該請求應(yīng)該以異步模式執(zhí)行。

第 3 行創(chuàng)建一個事件處理函數(shù)對象,并將其分配給請求的 onload 屬性。此處理程序查看請求的 readyState,以查看事務(wù)是否在第 4 行完成,如果是,并且 HTTP 狀態(tài)為 200,則轉(zhuǎn)儲接收到的內(nèi)容。如果發(fā)生錯誤,則顯示錯誤消息。

第 15 行實(shí)際上啟動了請求。只要請求的狀態(tài)發(fā)生變化,就會調(diào)用回調(diào)程序。

發(fā)起請求后,立馬執(zhí)行console.log("執(zhí)行語句"),等待請求狀態(tài)發(fā)送變化,才會觸發(fā)onload注冊的函數(shù),接著執(zhí)行console.log("比最后一段代碼執(zhí)行更晚")

JavaScript運(yùn)行機(jī)制

通過上面的代碼演示,我們可以發(fā)現(xiàn),JavaScript有自己的一套運(yùn)行機(jī)制,這套機(jī)制稱之為事件循環(huán),像異步處理這些屬于事件循環(huán)的并發(fā)模型機(jī)制。

事件循環(huán)負(fù)責(zé)執(zhí)行代碼,收集和處理事件以及執(zhí)行任務(wù)隊(duì)列中的子任務(wù)。

任務(wù)隊(duì)列子任務(wù),可以理解為異步事件注冊的回調(diào)函數(shù)。

運(yùn)行時概念

接下來的內(nèi)容解釋了這個理論模型?,F(xiàn)代 JavaScript 引擎實(shí)現(xiàn)并著重優(yōu)化了以下描述的這些語義。

棧(stack)

函數(shù)調(diào)用形成了一個由若干幀組成的棧。

function foo(b) {
  let a = 10;
  return a + b + 11;
}

function bar(x) {
  let y = 3;
  return foo(x * y);
}

console.log(bar(7)); // 返回 42
  1. 當(dāng)調(diào)用bar 函數(shù),第一個幀被創(chuàng)建并壓入棧中,幀中包含了bar 的參數(shù)和局部變量。
  2. 當(dāng)bar 調(diào)用foo 時,第二個幀被創(chuàng)建并被壓入棧中,放在第一個幀上面,幀中包含了foo 的參數(shù)和局部變量
  3. 當(dāng)foo執(zhí)行完畢后返回時,第二個幀就被彈出棧(剩下bar函數(shù)的調(diào)用幀)
  4. 當(dāng)bar 執(zhí)行完畢后,第一個幀也被彈出,棧就被清空了。

對象被分配在堆中,堆是一個用來表示一大塊(通常是非結(jié)構(gòu)化的)內(nèi)存區(qū)域的計(jì)算機(jī)術(shù)語。

隊(duì)列

一個JavaScript運(yùn)行時,包含一個待處理的消息隊(duì)列,每一個消息都關(guān)聯(lián)著一個用來處理這個消息的回調(diào)函數(shù)。

隊(duì)列和棧的元素被處理順序是相反的,隊(duì)列是先進(jìn)先出,和我們生活中先來后到的排隊(duì)機(jī)制一樣。因此,先被添加的消息,先執(zhí)行,消息要被全部執(zhí)行完畢,才能執(zhí)行下一個消息。

在事件循環(huán)期間的某個時刻,運(yùn)行時會從最先進(jìn)入隊(duì)列的消息開始處理隊(duì)列中的消息。被處理完成的消息會被移除隊(duì)列,并作為輸入?yún)?shù)來調(diào)用與之關(guān)聯(lián)的函數(shù)(也就是回調(diào)函數(shù)),正如前面所提到的,調(diào)用一個函數(shù)總是會為其創(chuàng)造一個新的棧幀。

函數(shù)處理一直進(jìn)行到執(zhí)行棧為空為止,然后事件循環(huán)將會處理下一個消息。

消息的添加

我們在上面了解到,隊(duì)列用來存儲消息,先被添加的消息先處理,那么,消息是何時被添加到消息隊(duì)列的呢?

JavaScript 中,每當(dāng)一個指定的事件真實(shí)發(fā)生了,并且綁定了相應(yīng)回調(diào)函數(shù),一個消息就會被添加到消息隊(duì)列。如果這個事件沒有回調(diào)函數(shù),這個事件將會丟失。

就像我們給某個元素綁定了點(diǎn)擊事件,實(shí)際上只是給某個元素注冊了一個點(diǎn)擊事件,這個事件注冊的回調(diào)代碼,不會立馬執(zhí)行。而是用戶真實(shí)點(diǎn)擊這個元素時,這個點(diǎn)擊事件才會真實(shí)發(fā)生,然后把消息添加到消息隊(duì)列。如果此時消息隊(duì)列,前面還有消息,那么會將點(diǎn)擊事件的消息排在后面,等待前面消息處理完成才處理這個消息,并調(diào)用相應(yīng)的回調(diào)函數(shù)。

例如以下代碼:

函數(shù) setTimeout 接受兩個參數(shù):待加入隊(duì)列的消息和一個時間值(可選,默認(rèn)為 0)。這個時間值代表了消息被實(shí)際加入到隊(duì)列的最小延遲時間。如果隊(duì)列中沒有其他消息并且棧為空,在這段延遲時間過去之后,消息會被馬上處理。但是,如果有其他消息setTimeout 消息必須等待其他消息處理完。因此第二個參數(shù)僅僅表示最少延遲時間,而非確切的等待時間。

(function () {
  console.log("這是開始");

  setTimeout(function cb() {
    console.log("這是來自第一個回調(diào)的消息");
  });

  console.log("這是一條消息");

  setTimeout(function cb1() {
    console.log("這是來自第二個回調(diào)的消息");
  }, 0);

  console.log("這是結(jié)束");
})();

// "這是開始"
// "這是一條消息"
// "這是結(jié)束"
// "這是來自第一個回調(diào)的消息"
// "這是來自第二個回調(diào)的消息"

因?yàn)閏b(),也是延遲0秒,因此先被添加到消息隊(duì)列。cb1排在其后面,因此晚執(zhí)行。

基本上,setTimeout 需要等待當(dāng)前隊(duì)列中所有的消息都處理完畢之后才能執(zhí)行,即使已經(jīng)超出了由第二參數(shù)所指定的時間。

異步場景

網(wǎng)絡(luò)請求

JavaScript處理后臺接口請求,一般都是使用XMLHttpRequest 進(jìn)行異步請求。如流行的axios庫,也是基于XMLHttpRequest。

下面代碼演示axios 請求接口

 //為給定 ID 的 user 創(chuàng)建請求
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
console.log("123")

上面代碼,使用axios 的get方法發(fā)起一個get請求,在then 注冊接口成功返回的函數(shù)。

最后console.log(“123”)的語句一般先于console.log(response);執(zhí)行。

  • JavaScript將網(wǎng)絡(luò)請求交給網(wǎng)絡(luò)請求線程處理,JavaScript引擎此時不需要等待其執(zhí)行完畢,就可以接著執(zhí)行console.log(“123”)
  • 如果請求接口返回了數(shù)據(jù),這個時候,JavaScript會將消息添加到消息隊(duì)列,如果此時隊(duì)列沒有其他消息,便可以立即處理這個消息,然后調(diào)用其關(guān)聯(lián)的回調(diào)函數(shù):function (response) {console.log(response);}

網(wǎng)絡(luò)請求線程與JavaScript引擎工作如下圖所示:

以 Chrome 為例,瀏覽器不僅有多個線程,還有多個進(jìn)程,如渲染進(jìn)程、GPU 進(jìn)程和插件進(jìn)程等。

每個 tab 標(biāo)簽頁都是一個獨(dú)立的渲染進(jìn)程,所以一個 tab 異常崩潰后,其他 tab 基本不會被影響。

渲染進(jìn)程下包含了 JS 引擎線程、HTTP 請求線程和定時器線程等,這些線程為 JS 在瀏覽器中完成異步任務(wù)提供了基礎(chǔ)。

異步編程優(yōu)化

在我們?nèi)粘>幊讨?,需要用到很多關(guān)于異步編程的地方,比如網(wǎng)絡(luò)請求,很多時候,碰到一些復(fù)雜的需求,比如請求A ,返回結(jié)果后,需要作為請求B的參數(shù),請求B返回結(jié)果,需要作為 C的請求參數(shù),如果用普通的異步編程,可以得到以下的代碼:

axios.get('A')
  .then(function (reA) {
	  axios.get('B',reA)
		  .then(function (reB) {
		    axios.get('C',reB)
			  .then(function (reC) {
			    console.log("請求完成")
			  })
			  .catch(function (errorC) {
			    console.log(errorC);
			  });
		    })
		  .catch(function (errorB) {
		    console.log(errorB);
		  });
  })
  .catch(function (errorA) {
    console.log(errorA);
  });

以上代碼我們稱為回調(diào)地獄,就是多層回調(diào)嵌套,造成可讀性和可維護(hù)性難度加大。

為了解決上訴的問題,ES6提供了 promise 。

我們可以使用promise 來優(yōu)化上面的回調(diào)地獄程序

async function geAllData() {
    const resA = await getData("A")
    const resB = await getData("B",resA)
    const resC = await getData("C",resB)
    console.log("請求完成",resC)
}

function getData(url, params) {
    return new Promise((resolve, reject) => {
        axios.get(url,params)
            .then(function (res) {
                resolve(res)
            })
            .catch(function (error) {
                reject(error);
            });
    })
}
geAllData()
console.log("geAllData調(diào)用后立馬執(zhí)行");

上面的代碼中,getData 函數(shù)里面,我們返回一個Promise 的實(shí)例對象,其中傳入一個回調(diào)函數(shù),參數(shù)為resolve, reject,當(dāng)我們接口返回成功時調(diào)用resolve失敗則調(diào)用reject。

geAllData 函數(shù)使用了 async 關(guān)鍵字,告訴JavaScript,這個是一個異步函數(shù),這個函數(shù)里面可以使用await關(guān)鍵字。

await關(guān)鍵字可以從字面上理解,就是等待,等待接口getData 函數(shù)執(zhí)行完resolve語句,代碼才會繼續(xù)往下執(zhí)行,如果一直沒有調(diào)用resolve或者reject,那么,就會一直等待,不會執(zhí)行下面的語句。

通過await 獲取的值,就是resolve 函數(shù)的參數(shù)值,比如const resA 就是 resolve(res)中res 的值。

注意事項(xiàng):

同步只有發(fā)生在async 修飾的函數(shù)內(nèi)部,在外部還是不影響其他代碼執(zhí)行的。比如console.log(“geAllData調(diào)用后立馬執(zhí)行”) 語句不會等待geAllData 執(zhí)行完console.log("請求完成",resC) 才執(zhí)行,而是調(diào)用geAllData函數(shù)后,立馬執(zhí)行的。

通過上面的優(yōu)化,我們可以將異步的代碼,以同步代碼的寫法來實(shí)現(xiàn),這樣提高了代碼可讀性,后續(xù)文章會針對Promise 做一個詳細(xì)介紹。

總結(jié)

  1. JavaScript引擎是單線程的,它通過事件循環(huán)機(jī)制來不斷處理消息隊(duì)列的消息
  2. 消息隊(duì)列的消息只有對應(yīng)的事件真實(shí)發(fā)生,并綁定了相應(yīng)的回調(diào)函數(shù),才會被添加到消息隊(duì)列
  3. 消息隊(duì)列先被添加的消息先被處理(先進(jìn)先出
  4. JavaScript引擎處理異步任務(wù),是交給另外線程去處理,等另一個線程處理完成,才把消息添加到JavaScript運(yùn)行時的消息隊(duì)列。
  5. setTimeout 延時0 秒,不代表0秒后立馬執(zhí)行,要看延時到指定時間后,消息隊(duì)列有無排在其前面的消息,如果有,則要先處理前面的消息,才執(zhí)行延時任務(wù)。
  6. 回調(diào)地獄可以使用Promise+async+await優(yōu)化
  7. async修飾的函數(shù)內(nèi)部,用await處理是同步執(zhí)行的,而async函數(shù)外部,不受影響。

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 淺析JavaScript中的特殊數(shù)據(jù)類型

    淺析JavaScript中的特殊數(shù)據(jù)類型

    在JavaScript中,有6大數(shù)據(jù)類型,分別包括string,number,boolean,undefined,null 和 object。下面通過通過本文給大家介紹JavaScript中的特殊數(shù)據(jù)類型,需要的朋友參考下吧
    2017-12-12
  • JS圖片放大效果簡單實(shí)現(xiàn)代碼

    JS圖片放大效果簡單實(shí)現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)圖片放大效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • Javascript基礎(chǔ)教程之比較null和undefined值

    Javascript基礎(chǔ)教程之比較null和undefined值

    這篇文章主要介紹了Javascript基礎(chǔ)教程之比較null和undefined值的相關(guān)知識,非常不錯,具有參考借鑒價值,感興趣的朋友一起學(xué)習(xí)吧
    2016-05-05
  • 修改js confirm alert 提示框文字的簡單實(shí)例

    修改js confirm alert 提示框文字的簡單實(shí)例

    下面小編就為大家?guī)硪黄薷膉s confirm alert 提示框文字的簡單實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • 微信小程序?qū)崿F(xiàn)分頁加載效果

    微信小程序?qū)崿F(xiàn)分頁加載效果

    這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)分頁加載效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • JavaScript實(shí)現(xiàn)多種彈窗效果實(shí)戰(zhàn)詳解(實(shí)現(xiàn)方法)

    JavaScript實(shí)現(xiàn)多種彈窗效果實(shí)戰(zhàn)詳解(實(shí)現(xiàn)方法)

    本文詳細(xì)講解如何使用JS結(jié)合HTML和CSS實(shí)現(xiàn)對聯(lián)彈窗、可移動彈窗和右下角消息彈窗,并涵蓋事件監(jiān)聽、元素定位、顯示控制等關(guān)鍵技術(shù),本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2025-10-10
  • 一文詳解HTML中script 標(biāo)簽的屬性

    一文詳解HTML中script 標(biāo)簽的屬性

    在HTML中,<script>標(biāo)簽用于嵌入或引用JavaScript代碼,在HTML中,<script>標(biāo)簽用于嵌入或引用JavaScript代碼,本文將詳細(xì)的而介紹這兩個標(biāo)簽,感興趣的同學(xué)可以參考一下
    2023-04-04
  • JavaScript實(shí)現(xiàn)定時器循環(huán)展示數(shù)組

    JavaScript實(shí)現(xiàn)定時器循環(huán)展示數(shù)組

    這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)定時器循環(huán)展示數(shù)組,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • 一文讀懂JS中的var/let/const和暫時性死區(qū)

    一文讀懂JS中的var/let/const和暫時性死區(qū)

    這篇文章主要為大家詳細(xì)介紹了JavaScript中的var、let、const和暫時性死區(qū)的異同,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-02-02
  • JS實(shí)現(xiàn)數(shù)組扁平化的方法總結(jié)

    JS實(shí)現(xiàn)數(shù)組扁平化的方法總結(jié)

    數(shù)組扁平化相信不少朋友在一些面試中被問到過,這在我們?nèi)粘>幊讨幸彩且粋€常規(guī)操作,它需要我們將一個多維數(shù)組轉(zhuǎn)化成一個一維數(shù)組,所以,借著這篇文章,我們今天就一起來匯總一下幾種數(shù)組扁平化的方式,需要的朋友可以參考下
    2024-02-02

最新評論

扎鲁特旗| 盐池县| 兴宁市| 光泽县| 大荔县| 筠连县| 长垣县| 始兴县| 乌拉特前旗| 抚松县| 河津市| 吴旗县| 贵港市| 闻喜县| 荣昌县| 龙州县| 肥西县| 华蓥市| 繁昌县| 贞丰县| 鹤庆县| 平顶山市| 原平市| 平武县| 麻城市| 静宁县| 郎溪县| 南华县| 嵊州市| 崇阳县| 浏阳市| 大宁县| 迁安市| 成都市| 隆子县| 凌源市| 岳池县| 关岭| 工布江达县| 银川市| 杂多县|