主要是由於最開始javascript
是單純的服務於瀏覽器的一種腳步語言(那時候沒有nodejs
)。瀏覽器是爲了渲染網頁,經過dom
與用戶交互,若是一個線程須要給dom
執行click
事件,而另外一個進程要刪除這個dom
,這2個動做可能同時進行,也可能前後進行(像java,c#
等語言中會引入鎖的概念,這樣會變得異常複雜),那麼就會形成不少不可預料的錯誤。javascript
因此,爲了不復雜性,從一誕生,JavaScript
就是單線程,這已經成了這門語言的核心特徵。爲了利用多核CPU的計算能力,HTML5
提出 Web Worker標準,容許JavaScript
腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM
。因此,這個新標準並無改變JavaScript
單線程的本質。
瀏覽器打開一個tab,就會單獨開一個進程,這個進程包含多個線程,參考:JS運行機制
主要包含的線程有:html
負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等。
當界面須要重繪(Repaint)或因爲某種操做引起迴流(reflow)時,該線程就會執行
注意, GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(至關於被凍結了),GUI更新會被保存在一個隊列中 等到JS引擎空閒時當即被執行。
也稱爲JS內核,負責處理Javascript腳本程序。(例如V8引擎)
JS引擎線程負責解析Javascript腳本,運行代碼。
JS引擎一直等待着任務隊列中任務的到來,而後加以處理,一個Tab頁(renderer進程)中不管何時都只有一個JS線程在運行JS程序
一樣注意, GUI渲染線程與JS引擎線程是互斥的,因此若是JS執行的時間過長,這樣就會形成頁面的渲染不連貫,致使頁面渲染加載阻塞。
歸屬於瀏覽器而不是JS引擎,用來控制事件循環(能夠理解,JS引擎本身都忙不過來,須要瀏覽器另開線程協助)
當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其餘線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
注意,因爲JS的單線程關係,因此這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時纔會去執行)
傳說中的setInterval
與setTimeout
所在線程
瀏覽器定時計數器並非由JavaScript引擎計數的,(由於JavaScript引擎是單線程的, 若是處於阻塞線程狀態就會影響記計時的準確)
所以經過單獨線程來計時並觸發定時(計時完畢後,添加到事件隊列中,等待JS引擎空閒後執行)
注意,W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算爲4ms。
在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>
能夠看到,一開始網頁和動畫正常運行,可是開始執行計算斐波那契數列後,動畫就中止了,頁面也中止響應鼠標的click
事件了,直到recurFib(40)
計算出結果後,動畫纔開始繼續執行,而期間積攢的click
事件也在一塊兒被執行。這就解釋了GUI渲染線程與JS引擎線程互斥。因爲這個弊端HTML5
提出Web Worker標準。java
Web Worker 有如下幾個使用注意點。node
1.同源限制
分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源。
2.DOM 限制
Worker 線程所在的全局對象,與主線程不同,沒法讀取主線程所在網頁的 DOM 對象,也沒法使用document
、window
、parent
這些對象。可是,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
來解決上面卡頓的問題。web
<!--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); } })
運行結果:c#
能夠看到整個運行過程動畫沒有卡頓,也能響應click
事件,因此在咱們遇到大型計算的時候,請單獨開啓一個worker
子線程來解決js
線程阻塞GUI
線程的問題。上文中只涉及到一部分worker API
。關於worker
更詳細更具體的用法能夠參見: Web Worker 使用教程segmentfault
能夠看到除了Opera Mini瀏覽器,連IE都能使用了,因此兼容性問題不大。api
javaScript
的最初設計特色,採用了單線程的運行機制。js
線程在運行時,會鎖死GUI
渲染線程,爲了利用多核CPU的計算能力,HTML5
提出Web Worker標準。Web Worker
的使用有一些限制,好比說:同源限制,DOM
限制,文件限制等,但能解決在js
須要大量計算工做時,頁面卡頓的問題。Web Worker
其實是js
線程的一個子線程,理論上js
仍是單線程的。學習如逆水行舟,不進則退,前端技術飛速發展,若是天天不堅持學習,就會跟不上,我會陪着你們,天天堅持推送博文,跟你們一同進步,但願你們能關注我,第一時間收到最新文章。
我的公衆號:瀏覽器