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

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

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

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

一、JavaScript為何選擇單線程設計

JavaScript誕生之初就被設計為單線程語言,這并非技術限制,而是經過深思熟慮的設計選擇。這種設計與其最初的應用場景密切相關:

用途決定設計

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

避免復雜性

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

HTML5 的 Web Worker

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

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

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

這種機制將任務分為兩類:同步任務異步任務

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

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

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

下圖就是主線程和任務隊列的示意圖。

只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重復。

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

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

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

為了更好地理解,讓我們看一個實際的Ajax操作示例:

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

這段代碼中的req.send()是異步操作,它的回調函數(shù)(onload/onerror)在代碼中的位置并不重要,因為只有當前腳本的所有同步代碼執(zhí)行完,系統(tǒng)才會去讀取"任務隊列"。因此,它與下面的寫法完全等價:

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

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

四、setTimeout與Promise的運行機制

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

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

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

上面代碼的執(zhí)行結果是1,3,2,因為setTimeout()將第二行推遲到1000毫秒之后執(zhí)行。

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

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

上面代碼的執(zhí)行結果總是2,1,因為只有在執(zhí)行完第二行以后,系統(tǒng)才會去執(zhí)行"任務隊列"中的回調函數(shù)。

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

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

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

注:以上內容摘自 阮一峰《JavaScript 運行機制詳解:再談Event Loop》

對于Promise,情況則稍有不同。

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

以上代碼的輸出順序為:

1. 腳本開始

2. 腳本結束

3. Promise

4. setTimeout

這是因為Promise的回調會進入微任務隊列,而setTimeout進入宏任務隊列。事件循環(huán)會在一輪循環(huán)中先執(zhí)行所有微任務,再執(zhí)行一個宏任務。

五、偽代碼模擬異步處理機制

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

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

段偽代碼清晰地展示了:

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

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

參考資料:

阮一峰《JavaScript 運行機制詳解:再談Event Loop》

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

相關文章

  • JavaScript制作游戲搖桿方向盤

    JavaScript制作游戲搖桿方向盤

    本文主要介紹了JavaScript制作游戲搖桿方向盤,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2014-04-04
  • uniapp實現(xiàn)上拉加載更多功能的全過程

    uniapp實現(xiàn)上拉加載更多功能的全過程

    我們在項目中經常使用到上拉加載更多,下面這篇文章主要給大家介紹了關于uniapp實現(xiàn)上拉加載更多功能的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-10-10
  • 使用html2canvas截圖不全問題的解決辦法

    使用html2canvas截圖不全問題的解決辦法

    這篇文章主要給大家介紹了關于使用html2canvas截圖不全問題的解決辦法,最近在項目中遇到一個需求,需要提供網(wǎng)頁截圖的功能,這里將遇到的問題分享給大家,需要的朋友可以參考下
    2023-07-07
  • JavaScript處理數(shù)組數(shù)據(jù)的示例詳解

    JavaScript處理數(shù)組數(shù)據(jù)的示例詳解

    這篇文章主要為大家詳細介紹了JavaScript如何處理數(shù)組數(shù)據(jù),包括數(shù)據(jù)匹配和剔除,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-10-10
  • JS使用onerror捕獲異常示例

    JS使用onerror捕獲異常示例

    這篇文章主要介紹了JS使用onerror捕獲異常的方法,結合實例形式分析了javascript基于onerror事件處理器捕獲異常的具體流程與相關操作技巧,需要的朋友可以參考下
    2016-08-08
  • js實現(xiàn)簡單折疊、展開菜單的方法

    js實現(xiàn)簡單折疊、展開菜單的方法

    這篇文章主要介紹了js實現(xiàn)簡單折疊、展開菜單的方法,涉及javascript動態(tài)操作頁面元素變換的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • 深入探究V8引擎的底層原理

    深入探究V8引擎的底層原理

    V8引擎是一款由Google開發(fā)的JavaScript引擎,V8引擎采用了眾多的優(yōu)化措施,使得其在性能上得到了極大的提升,能夠高效地執(zhí)行JavaScript代碼,本文將和大家一起探討一下V8引擎底層原理,探究其優(yōu)異性能之謎
    2023-06-06
  • js表格排序實例分析(支持int,float,date,string四種數(shù)據(jù)類型)

    js表格排序實例分析(支持int,float,date,string四種數(shù)據(jù)類型)

    這篇文章主要介紹了js表格排序實例分析(支持int,float,date,string四種數(shù)據(jù)類型),涉及javascript常用的升序、降序及數(shù)據(jù)類型轉換等相關技巧,需要的朋友可以參考下
    2015-05-05
  • JS中setTimeout()的用法詳解

    JS中setTimeout()的用法詳解

    setTimeout( ) 是屬于 window 的 method, 但我們都是略去 window 這頂層物件名稱, 這是用來設定一個時間, 時間到了, 就會執(zhí)行一個指定的 method
    2013-04-04
  • Javascript中的 “&” 和 “|” 詳解

    Javascript中的 “&” 和 “|” 詳解

    本文主要介紹了Javascript中的 “&” 和 “|” 的相關知識。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02

最新評論

张家川| 文山县| 肃北| 荣昌县| 湖北省| 忻城县| 苍梧县| 嘉义市| 盖州市| 长葛市| 昌平区| 阿城市| 义乌市| 兰考县| 阿拉善右旗| 天长市| 贵德县| 鞍山市| 吉林市| 闵行区| 潜山县| 富阳市| 闻喜县| 定西市| 顺平县| 凌源市| 时尚| 阳山县| 南开区| 古蔺县| 巴中市| 永福县| 余姚市| 旬邑县| 永定县| 南涧| 台中市| 泽普县| 梅河口市| 吕梁市| 海宁市|