欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

單線程的JavaScript為什么可以異步執(zhí)行任務(wù)

 更新時(shí)間:2025年08月07日 15:11:28   作者:佟格碼路  
JS作為單線程語(yǔ)言通過(guò)?事件循環(huán)機(jī)制實(shí)現(xiàn)異步任務(wù)處理,核心在于將耗時(shí)操作放入后臺(tái)線程處理,主線程繼續(xù)執(zhí)行其他任務(wù),本文剖析JavaScript的異步處理原理,從單線程設(shè)計(jì)的原因到具體實(shí)現(xiàn)機(jī)制,最后通過(guò)偽代碼模擬整個(gè)異步處理流程,感興趣的朋友跟隨小編一起看看吧

JavaScript作為一門單線程語(yǔ)言,卻能夠高效處理各種異步操作,這得益于其精巧的事件循環(huán)(Event Loop)機(jī)制。本文將全面剖析JavaScript的異步處理原理,從單線程設(shè)計(jì)的原因到具體實(shí)現(xiàn)機(jī)制,最后通過(guò)偽代碼模擬整個(gè)異步處理流程。

一、JavaScript為何選擇單線程設(shè)計(jì)

JavaScript誕生之初就被設(shè)計(jì)為單線程語(yǔ)言,這并非技術(shù)限制,而是經(jīng)過(guò)深思熟慮的設(shè)計(jì)選擇。這種設(shè)計(jì)與其最初的應(yīng)用場(chǎng)景密切相關(guān):

用途決定設(shè)計(jì)

  • JavaScript 最初被設(shè)計(jì)為瀏覽器腳本語(yǔ)言,主要用于與用戶交互和操作 DOM。
  • 如果 JavaScript 是多線程的,可能會(huì)出現(xiàn)多個(gè)線程同時(shí)操作同一個(gè) DOM 節(jié)點(diǎn)的情況。例如,一個(gè)線程在添加內(nèi)容,另一個(gè)線程在刪除該節(jié)點(diǎn),瀏覽器將無(wú)法確定以哪個(gè)線程的操作為準(zhǔn)。
// 假設(shè)多線程環(huán)境下可能出現(xiàn)的沖突場(chǎng)景
// 線程1:
document.getElementById('node').innerHTML = '新內(nèi)容';
// 線程2:
document.body.removeChild(document.getElementById('node'));
  • 這種復(fù)雜性會(huì)導(dǎo)致同步問(wèn)題,因此 JavaScript 被設(shè)計(jì)為單線程,確保操作的順序性和一致性。

避免復(fù)雜性

  • 多線程編程需要處理鎖、死鎖、線程同步等問(wèn)題,這會(huì)增加語(yǔ)言的復(fù)雜性和開(kāi)發(fā)難度。
  • 單線程模型簡(jiǎn)化了 JavaScript 的設(shè)計(jì),開(kāi)發(fā)者可以專注于業(yè)務(wù)邏輯,而不必?fù)?dān)心線程安全問(wèn)題。

HTML5 的 Web Worker

  • 為了利用多核 CPU 的計(jì)算能力,HTML5 提供了 ??Web Worker?? 標(biāo)準(zhǔn),允許 JavaScript 創(chuàng)建多個(gè)線程。
  • 但這些子線程完全受主線程控制,且不能直接操作 DOM,因此 JavaScript 的單線程本質(zhì)并未改變。

二、單線程如何實(shí)現(xiàn)異步處理

單線程意味著所有任務(wù)需要排隊(duì)執(zhí)行,如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng)(比如等待網(wǎng)絡(luò)請(qǐng)求),后續(xù)任務(wù)就會(huì)被阻塞。聰明的JavaScript設(shè)計(jì)者想到了解決辦法:當(dāng)遇到I/O等耗時(shí)操作時(shí),主線程不必傻等,而是先掛起這個(gè)任務(wù),繼續(xù)執(zhí)行后面的代碼。等到I/O操作完成,再將掛起的任務(wù)放入任務(wù)隊(duì)列等待執(zhí)行。

這種機(jī)制將任務(wù)分為兩類:同步任務(wù)異步任務(wù)

同步任務(wù)主線程上順序執(zhí)行,形成所謂的"執(zhí)行棧";
異步任務(wù)則委托給瀏覽器其他模塊處理,完成后將回調(diào)函數(shù)放入"任務(wù)隊(duì)列"。

當(dāng)主線程完成當(dāng)前執(zhí)行棧中的所有任務(wù),就會(huì)查看任務(wù)隊(duì)列,取出等待中的任務(wù)繼續(xù)執(zhí)行。

這就好比一位廚師在準(zhǔn)備多道菜品時(shí),遇到需要長(zhǎng)時(shí)間燉煮的菜,會(huì)先開(kāi)火燉上,然后轉(zhuǎn)身處理其他可以快速完成的菜品,等燉煮完成后再回來(lái)處理后續(xù)步驟。這種工作方式大大提高了整體效率。

下圖就是主線程和任務(wù)隊(duì)列的示意圖。

只要主線程空了,就會(huì)去讀取"任務(wù)隊(duì)列",這就是JavaScript的運(yùn)行機(jī)制。這個(gè)過(guò)程會(huì)不斷重復(fù)。

三、事件循環(huán)(Event Loop)核心原理

事件循環(huán)是JavaScript異步處理的核心機(jī)制,它像一位不知疲倦的調(diào)度員,持續(xù)監(jiān)控著執(zhí)行棧和任務(wù)隊(duì)列的狀態(tài)。其工作流程可以形象地描述為:

  1. 主線程首先處理執(zhí)行棧中的所有同步任務(wù),就像廚師按順序準(zhǔn)備食材。
  2. 遇到異步操作(如setTimeout或AJAX請(qǐng)求)時(shí),主線程會(huì)將這些任務(wù)交給對(duì)應(yīng)的瀏覽器模塊處理,就像廚師把需要長(zhǎng)時(shí)間處理的食材交給專門設(shè)備。
  3. 瀏覽器模塊在后臺(tái)處理這些異步任務(wù),任務(wù)完成后將回調(diào)函數(shù)放入任務(wù)隊(duì)列,就像助手把處理好的食材放在待取區(qū)。
  4. 當(dāng)執(zhí)行棧清空后,事件循環(huán)會(huì)檢查任務(wù)隊(duì)列,取出最早進(jìn)入隊(duì)列的任務(wù)推入執(zhí)行棧執(zhí)行,就像廚師完成手頭工作后去待取區(qū)拿下一個(gè)要處理的食材。
  5. 這個(gè)過(guò)程不斷循環(huán),形成所謂的"事件循環(huán)"。

為了更好地理解,讓我們看一個(gè)實(shí)際的Ajax操作示例:

var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function(){ };    // 回調(diào)函數(shù)
req.onerror = function(){ };   // 回調(diào)函數(shù)
req.send(); // 異步任務(wù)

這段代碼中的req.send()是異步操作,它的回調(diào)函數(shù)(onload/onerror)在代碼中的位置并不重要,因?yàn)橹挥挟?dāng)前腳本的所有同步代碼執(zhí)行完,系統(tǒng)才會(huì)去讀取"任務(wù)隊(duì)列"。因此,它與下面的寫法完全等價(jià):

var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function(){ };
req.onerror = function(){ };

需要注意的是,現(xiàn)代瀏覽器將任務(wù)隊(duì)列細(xì)分為宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列。微任務(wù)隊(duì)列中的任務(wù)(如Promise回調(diào))會(huì)在當(dāng)前宏任務(wù)執(zhí)行完畢后立即執(zhí)行,比下一個(gè)宏任務(wù)(如setTimeout回調(diào))擁有更高的優(yōu)先級(jí)。這就像廚師在處理完一道主菜后,會(huì)優(yōu)先完成與之配套的醬汁調(diào)制,然后再開(kāi)始下一道主菜。

四、setTimeout與Promise的運(yùn)行機(jī)制

除了放置異步任務(wù)的事件,"任務(wù)隊(duì)列"還可以放置定時(shí)事件,即指定某些代碼在多少時(shí)間之后執(zhí)行。這叫做"定時(shí)器"(timer)功能,也就是定時(shí)執(zhí)行的代碼。

定時(shí)器功能主要由setTimeout()和setInterval()實(shí)現(xiàn),它們的內(nèi)部機(jī)制相同,區(qū)別在于前者執(zhí)行一次,后者重復(fù)執(zhí)行。看一個(gè)典型例子:

console.log(1);
setTimeout(function(){ console.log(2); }, 1000);
console.log(3);
// 輸出順序:1, 3, 2

上面代碼的執(zhí)行結(jié)果是1,3,2,因?yàn)閟etTimeout()將第二行推遲到1000毫秒之后執(zhí)行。

如果將setTimeout()的第二個(gè)參數(shù)設(shè)為0,就表示當(dāng)前代碼執(zhí)行完(執(zhí)行棧清空)以后,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。

setTimeout(function(){ console.log(1); }, 0);
console.log(2);
// 輸出順序總是:2, 1

上面代碼的執(zhí)行結(jié)果總是2,1,因?yàn)橹挥性趫?zhí)行完第二行以后,系統(tǒng)才會(huì)去執(zhí)行"任務(wù)隊(duì)列"中的回調(diào)函數(shù)。

總之,setTimeout(fn,0)的含義是,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,也就是說(shuō),盡可能早得執(zhí)行。它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件,因此要等到同步任務(wù)和"任務(wù)隊(duì)列"現(xiàn)有的事件都處理完,才會(huì)得到執(zhí)行。

HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個(gè)參數(shù)的最小值(最短間隔),不得低于4毫秒,如果低于這個(gè)值,就會(huì)自動(dòng)增加。在此之前,老版本的瀏覽器都將最短間隔設(shè)為10毫秒。另外,對(duì)于那些DOM的變動(dòng)(尤其是涉及頁(yè)面重新渲染的部分),通常不會(huì)立即執(zhí)行,而是每16毫秒執(zhí)行一次。這時(shí)使用requestAnimationFrame()的效果要好于setTimeout()。

需要注意的是,setTimeout()只是將事件插入了"任務(wù)隊(duì)列",必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼耗時(shí)很長(zhǎng),有可能要等很久,所以并沒(méi)有辦法保證,回調(diào)函數(shù)一定會(huì)在setTimeout()指定的時(shí)間執(zhí)行。

注:以上內(nèi)容摘自 阮一峰《JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop》

對(duì)于Promise,情況則稍有不同。

console.log('腳本開(kāi)始');
setTimeout(() => {
    console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
    console.log('Promise');
});
console.log('腳本結(jié)束');

以上代碼的輸出順序?yàn)椋?/span>

1. 腳本開(kāi)始

2. 腳本結(jié)束

3. Promise

4. setTimeout

這是因?yàn)镻romise的回調(diào)會(huì)進(jìn)入微任務(wù)隊(duì)列,而setTimeout進(jìn)入宏任務(wù)隊(duì)列。事件循環(huán)會(huì)在一輪循環(huán)中先執(zhí)行所有微任務(wù),再執(zhí)行一個(gè)宏任務(wù)。

五、偽代碼模擬異步處理機(jī)制

為了更直觀地理解JavaScript的異步處理,我們可以用偽代碼模擬整個(gè)事件循環(huán)系統(tǒng):

class EventLoop
{
    constructor()
    {
        this.callStack = []; // 執(zhí)行棧,存儲(chǔ)同步任務(wù)
        this.macroQueue = []; // 宏任務(wù)隊(duì)列
        this.microQueue = []; // 微任務(wù)隊(duì)列
        this.isRunning = false; // 運(yùn)行狀態(tài)標(biāo)志
    }
    // 啟動(dòng)事件循環(huán)
    start()
    {
        this.isRunning = true;
        while (this.isRunning)
        {
            // 執(zhí)行所有同步代碼
            while (this.callStack.length > 0)
            {
                const task = this.callStack.pop();
                execute(task); // 執(zhí)行當(dāng)前任務(wù)
            }
            // 執(zhí)行所有微任務(wù)
            while (this.microQueue.length > 0)
            {
                const microTask = this.microQueue.shift();
                this.callStack.push(microTask); // 將微任務(wù)推入執(zhí)行棧
            }
            // 執(zhí)行一個(gè)宏任務(wù)
            if (this.macroQueue.length > 0)
            {
                const macroTask = this.macroQueue.shift();
                this.callStack.push(macroTask); // 將宏任務(wù)推入執(zhí)行棧
            }
            // 如果所有隊(duì)列都為空,暫停循環(huán)
            if (this.callStack.length === 0 &&
               this.macroQueue.length === 0 &&
               this.microQueue.length === 0)
            {
                this.isRunning = false;
            }
        }
    }
    // 模擬setTimeout
    setTimeout(callback, delay)
    {
        // 使用瀏覽器定時(shí)器API
        externalTimerAPI.set(() => {
            this.macroQueue.push(callback); // 時(shí)間到后加入宏任務(wù)隊(duì)列
        }, delay);
    }
    // 模擬Promise
    Promise(executor)
    {
        // 立即執(zhí)行executor
        executor(
          value => this.resolve(value),
          reason => this.reject(reason)
        );
    }
    resolve(value)
    {
        this.microQueue.push(() => {
            // 這里處理Promise的成功回調(diào)
            handleThenCallbacks(value);
        });
    }
}
// 使用示例
const loop = new EventLoop();
// 添加同步任務(wù)
loop.callStack.push(() => console.log('開(kāi)始執(zhí)行'));
// 添加宏任務(wù)
loop.setTimeout(() => console.log('宏任務(wù)執(zhí)行'), 0);
// 添加微任務(wù)
loop.microQueue.push(() => console.log('微任務(wù)執(zhí)行'));
// 啟動(dòng)事件循環(huán)
loop.start();

段偽代碼清晰地展示了:

  1. 同步任務(wù)直接進(jìn)入執(zhí)行棧立即執(zhí)行
  2. setTimeout回調(diào)進(jìn)入宏任務(wù)隊(duì)列
  3. Promise回調(diào)進(jìn)入微任務(wù)隊(duì)列
  4. 事件循環(huán)優(yōu)先處理微任務(wù),再處理宏任務(wù)
  5. 整個(gè)過(guò)程循環(huán)往復(fù),直到所有任務(wù)完成

通過(guò)這個(gè)模擬,我們可以更深入地理解JavaScript如何在單線程環(huán)境下實(shí)現(xiàn)高效的異步處理,以及各種異步API在事件循環(huán)中的不同表現(xiàn)。

參考資料:

阮一峰《JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop》

到此這篇關(guān)于單線程的JavaScript為什么可以異步執(zhí)行任務(wù)的文章就介紹到這了,更多相關(guān)js單線程異步任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • javascript觀察者模式實(shí)現(xiàn)自動(dòng)刷新效果

    javascript觀察者模式實(shí)現(xiàn)自動(dòng)刷新效果

    這篇文章主要為大家詳細(xì)介紹了javascript觀察者模式實(shí)現(xiàn)自動(dòng)刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • uniapp手機(jī)號(hào)一鍵登錄實(shí)現(xiàn)保姆級(jí)教程(含前端和后端)

    uniapp手機(jī)號(hào)一鍵登錄實(shí)現(xiàn)保姆級(jí)教程(含前端和后端)

    這篇文章主要介紹了uniapp手機(jī)號(hào)一鍵登錄實(shí)現(xiàn)的相關(guān)資料,本文指導(dǎo)如何創(chuàng)建uniapp項(xiàng)目、關(guān)聯(lián)uniCloud云空間,并配置一鍵登錄功能,,整個(gè)過(guò)程涉及創(chuàng)建云開(kāi)發(fā)環(huán)境、關(guān)聯(lián)云服務(wù)空間、配置登錄服務(wù)和編寫云函數(shù),需要的朋友可以參考下
    2024-10-10
  • JavaScript Set與Map數(shù)據(jù)結(jié)構(gòu)詳細(xì)分析

    JavaScript Set與Map數(shù)據(jù)結(jié)構(gòu)詳細(xì)分析

    大家心里是否產(chǎn)生過(guò)這樣的疑問(wèn),JS中既然已經(jīng)有對(duì)象這種數(shù)據(jù)結(jié)構(gòu),我們?yōu)槭裁催€要再單獨(dú)去使用Set或者M(jìn)ap呢?下面這篇文章主要給大家介紹了關(guān)于ES6中Set和Map數(shù)據(jù)結(jié)構(gòu)的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • js中位數(shù)不足自動(dòng)補(bǔ)位擴(kuò)展padLeft、padRight實(shí)現(xiàn)代碼

    js中位數(shù)不足自動(dòng)補(bǔ)位擴(kuò)展padLeft、padRight實(shí)現(xiàn)代碼

    這篇文章主要介紹了js中位數(shù)不足自動(dòng)補(bǔ)位擴(kuò)展之padLeft、padRight實(shí)現(xiàn)方法,主要是通過(guò)String.prototype擴(kuò)展實(shí)現(xiàn),需要的朋友可以參考下
    2020-04-04
  • js模糊查詢實(shí)例分享

    js模糊查詢實(shí)例分享

    本文主要分享了js模糊查詢的實(shí)現(xiàn)代碼,沒(méi)有接觸到后臺(tái)數(shù)據(jù)的知識(shí),只是查詢當(dāng)前表格中每一行所包含的關(guān)鍵字。具有一定的參考價(jià)值,需要的朋友一起來(lái)看下吧
    2016-12-12
  • javascript if 的簡(jiǎn)化代碼

    javascript if 的簡(jiǎn)化代碼

    a=0 if(a<1)alert(a),alert(1-a) VS if(a==0){alert(a);alert(1-a)}
    2008-02-02
  • JS中位置與大小的獲取方法

    JS中位置與大小的獲取方法

    下面小編就為大家?guī)?lái)一篇JS中位置與大小的獲取方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-11-11
  • Ajax 加載數(shù)據(jù) 練習(xí)代碼

    Ajax 加載數(shù)據(jù) 練習(xí)代碼

    這篇文章主要介紹了Ajax 加載數(shù)據(jù),主要是為了查詢表的顯示,不用嵌入php通過(guò)ajax調(diào)用,現(xiàn)在手機(jī)端的更多顯示功能都是通過(guò)ajax實(shí)現(xiàn)的
    2017-01-01
  • 限制復(fù)選框最多選擇項(xiàng)的實(shí)現(xiàn)代碼

    限制復(fù)選框最多選擇項(xiàng)的實(shí)現(xiàn)代碼

    下面小編就為大家?guī)?lái)一篇限制復(fù)選框最多選擇項(xiàng)的實(shí)現(xiàn)代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-05-05
  • JS實(shí)現(xiàn)前端分頁(yè)效果

    JS實(shí)現(xiàn)前端分頁(yè)效果

    這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)前端分頁(yè)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08

最新評(píng)論