知道html5 Web Worker標準嗎?能實現JavaScript的多線程?

js為什么是單線程?

主要是因為最開始javascript是單純的服務于瀏覽器的一種腳步語言(那時候沒有nodejs)。瀏覽器是為了渲染網頁,通過dom與用戶交互,如果一個線程需要給dom執行click事件,而另一個進程要刪除這個dom,這2個動作可能同時進行,也可能先后進行(像java,c#等語言中會引入鎖的概念,這樣會變得異常復雜),那么就會造成很多不可預料的錯誤。

所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征。為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。

瀏覽器是多線程的

瀏覽器打開一個tab,就會單獨開一個進程,這個進程包含多個線程,參考:JS運行機制
主要包含的線程有:

  1. GUI渲染線程

負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,布局和繪制等。
當界面需要重繪(Repaint)或由于某種操作引發回流(reflow)時,該線程就會執行
注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(相當于被凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執行。

  1. JS引擎線程

也稱為JS內核,負責處理Javascript腳本程序。(例如V8引擎)
JS引擎線程負責解析Javascript腳本,運行代碼。
JS引擎一直等待著任務隊列中任務的到來,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。

  1. 事件觸發線程

歸屬于瀏覽器而不是JS引擎,用來控制事件循環(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協助)
當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其他線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
注意,由于JS的單線程關系,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執行)

  1. 定時觸發器線程

傳說中的setIntervalsetTimeout所在線程
瀏覽器定時計數器并不是由JavaScript引擎計數的,(因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態就會影響記計時的準確)
因此通過單獨線程來計時并觸發定時(計時完畢后,添加到事件隊列中,等待JS引擎空閑后執行)
注意,W3C在HTML標準中規定,規定要求setTimeout中低于4ms的時間間隔算為4ms。

  1. 異步http請求線程

XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求
將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。

上面列出的線程之間,有一個重要的規則是:GUI渲染線程與JS引擎線程互斥,那么我們可以得出以下結論JS阻塞頁面加載,那么在js運行的這段時間內,GUI的渲染會停止,這段時間內的界面交互,DOM的重繪與回流會停止,會被保存到待執行隊列中,直到js線程空閑,才會執行這些隊列。
我們用下面的一段代碼和運行結果來說明這個機制:

<html>
<head>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin-top: 100px;
            background: #f09;
            animation: bounce 2s linear 0s infinite alternate;
            background-image: linear-gradient(45deg, #3023AE 0%, #f09 100%);
        }
        @keyframes bounce {
            0% {
                border-radius: 40% 60% 72% 28% / 70% 77% 23% 30%;
            }
            100% {
                border-radius: 75% 25% 24% 76% / 13% 15% 85% 87%;
            }
        }
    </style>
</head>
<body>
    <div class="box"></div>
</body>
<script>
    // 計算斐波那契數列,這個數列從第3項開始,每一項都等于前兩項之和。
    function recurFib(n) {
        if (n < 2) {
            return n;
        } else {
            return recurFib(n - 1) + recurFib(n - 2)
        }
    }

    window.onload = function () {
        setTimeout(function () {
            console.time("運算耗時:")
            // 計算n為40的結果
            console.log('結果:', recurFib(40))
            console.timeEnd("運算耗時:")
        }, 2000)
        document.getElementsByClassName("box")[0].addEventListener('click', function () {
            console.log('click')
        })
    }
</script>
</html>
5.gif

可以看到,一開始網頁和動畫正常運行,但是開始執行計算斐波那契數列后,動畫就停止了,頁面也停止響應鼠標的click事件了,直到recurFib(40)計算出結果后,動畫才開始繼續執行,而期間積攢的click事件也在一起被執行。這就解釋了GUI渲染線程與JS引擎線程互斥。由于這個弊端HTML5提出Web Worker標準。

利用Web Worker開啟一個子線程

Web Worker 有以下幾個使用注意點。

1.同源限制
分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源。
2.DOM 限制
Worker 線程所在的全局對象,與主線程不一樣,無法讀取主線程所在網頁的 DOM 對象,也無法使用documentwindowparent這些對象。但是,Worker 線程可以navigator對象和location對象。
3.通信聯系
Worker 線程和主線程不在同一個上下文環境,它們不能直接通信,必須通過消息完成。
4.腳本限制
Worker 線程不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對象發出 AJAX 請求。
5.文件限制
Worker 線程無法讀取本地文件,即不能打開本機的文件系統(file:),它所加載的腳本,必須來自網絡。

以上規則引用阮一峰老師的: Web Worker 使用教程
創建Worker時,JS引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的,完全受主線程控制,而且不能操作DOM)
JS引擎線程與worker線程間通過特定的方式通信(postMessage API,需要通過序列化對象來與線程交互特定的數據)。
下面我們用worker的相關api來解決上面卡頓的問題。

<!--index.html主線程-->
<html>
<head>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin-top: 100px;
            background: #f09;
            animation: bounce 2s linear 0s infinite alternate;
            background-image: linear-gradient(45deg, #3023AE 0%, #f09 100%);
        }
        @keyframes bounce {
            0% {
                border-radius: 40% 60% 72% 28% / 70% 77% 23% 30%;
            }
            100% {
                border-radius: 75% 25% 24% 76% / 13% 15% 85% 87%;
            }
        }
    </style>
</head>
<body>
    <div class="box"></div>
</body>
<script>
    window.onload = function () {
        // 創建一個子線程worker實例
        var worker = new Worker('./test.js');
        setTimeout(function () {
            // 通信:向子線程發送消息
            worker.postMessage('start')
        }, 2000)
        worker.addEventListener('message', function(res) {
            //  通信:收到子線程消息
            console.log('result:',JSON.stringify(res.data));
            // 關閉worker線程
            worker.terminate();
        })
        document.getElementsByClassName("box")[0].addEventListener('click', function () {
            console.log('click')
        })
    }
</script>
</html>
// test.js子線程代碼
// 通過監聽message來接受主線程中的消息
addEventListener('message', function(res) {
    // 子線程向主線程中發生消息
    // 計算斐波那契數列,這個數列從第3項開始,每一項都等于前兩項之和。
    if(res.data === 'start') {
        // 開始運算
        console.log('收到主線程消息,開始運算')
        function recurFib(n) {
            if(n < 2){
                // 主動關閉子線程
                // this.close()
                return n ;
            }else {
                return recurFib(n-1)+recurFib(n-2)
            }
        }
        console.time("運算時間:")
        // 計算n為40的結果
        var count = recurFib(40)
        console.timeEnd("運算時間:")
        // 向主線程發送消息
        console.log('運算完畢,發送消息給主線程!')
        this.postMessage(count);
    }
})

運行結果:


7.png

可以看到整個運行過程動畫沒有卡頓,也能響應click事件,所以在我們遇到大型計算的時候,請單獨開啟一個worker子線程來解決js線程阻塞GUI線程的問題。上文中只涉及到一部分worker API。關于worker更詳細更具體的用法可以參見: Web Worker 使用教程

兼容性

1.png

可以看到除了Opera Mini瀏覽器,連IE都能使用了,所以兼容性問題不大。

總結

  1. 由于javaScript的最初設計特點,采用了單線程的運行機制。
  2. 瀏覽器是多個線程相互協作來工作的,但是GUI渲染線程與JS引擎線程互斥
  3. js線程在運行時,會鎖死GUI渲染線程,為了利用多核CPU的計算能力,HTML5提出Web Worker標準。
  4. Web Worker的使用有一些限制,比如說:同源限制,DOM限制,文件限制等,但能解決在js需要大量計算工作時,頁面卡頓的問題。
  5. Web Worker實際上是js線程的一個子線程,理論上js還是單線程的。

學習如逆水行舟,不進則退,前端技術飛速發展,如果每天不堅持學習,就會跟不上,我會陪著大家,每天堅持推送博文,跟大家一同進步,希望大家能關注我,第一時間收到最新文章。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372