JavaScript 進階(一)JS的"多線程"

這個系列的文章名爲「JavaScript 進階」,內容涉及JS中容易忽略可是頗有用的,偏JS底層的,以及複雜項目中的JS的實踐。主要來源於我幾年的開發過程當中遇到的問題。小弟第一次寫博客,寫的很差的地方請諸位斧正,以爲還有一些閱讀價值的請幫忙分享下。這個「JavaScript 進階」是一個系列文章,請你們鼓勵鼓勵,我儘快更新。另外,若是你有比較好的話題,也能夠在下面評論,咱們一塊兒研究提升。javascript

JS是多線程的嗎?

多線程編程相信你們都很熟悉,好比在界面開發中,若是一個事件的響應須要較長時間,那麼通常作法就是把事件處理程序寫在另一個線程中,在處理過程當中,在界面上面顯示相似進度條的元素。這樣界面就不會卡住,而且可以顯示任務執行進度。記得剛開始作前端的時候,老闆交代在界面上面作一個定時器,每秒更新用戶的在線時間。當時擁有Java和C++開發經驗的我自信滿滿的說我加一個線程就能夠分分鐘搞定了。因此查閱文檔,發現setTimeout和setInterval能夠很方便的實現該功能。那時候我就認爲這就是JS中的多線程。setTimeout至關於啓動一個線程,等待一段時間後執行函數,setInterval則是在另外的一個線程中,每隔一段時間執行函數。這個觀念在個人頭腦中存在了一年左右,直到遇到了這樣的一個問題。html

 

測試人員發現一個按鈕的點擊響應時間較長,在響應過程當中,界面卡住了,我檢查代碼發現代碼中作了這樣的事情。前端

 

  1. $("#submit").on("click", function() {  
  2.     bigTask(); // 這個函數須要較長時間來執行  
  3. })  

因此我想很簡單啊,把這個函數放在另外的一個線程執行就行了啊,因此代碼改爲了這樣, 覺得能夠輕鬆解決問題。可是事實上發現毫無用處,界面仍是原來同樣的行爲,點擊按鈕以後卡住了幾秒。

 

 

  1. $("#submit").on("click", function() {  
  2.     setTimeout(function() {  
  3.         bigTask()  
  4.     }, 0)  
  5. })  

 

這個問題我百思不得其解,最後多方查閱資料才明白了以下的內容:html5

 

  1. 瀏覽器中的JS是單線程的。
  2. setInterval和setTimeout並非多線程,這兩個函數根本上實際上是事件觸發函數

想證實setInterval和setTimeout不是多線程很簡單,你能夠試試這樣一段代碼java

 

  1. setTimeout(function() {  
  2.     while(true){}  
  3. }, 0)  
  4. setTimeout(function() {  
  5.     alert("foo")  
  6. }, 1000)  

 

不出意外,你的瀏覽器沒法響應了,頁面上面的按鈕不能點,Gif也不能動,那個alert確定也出不來。這是爲何呢?web

爲了解釋上面的問題,咱們來深刻解析一下瀏覽器。瀏覽器中有三個常駐的線程,分別是JS引擎,界面渲染,事件響應。因爲這三個線程同時要訪問DOM樹,因此爲了線程安全,瀏覽器內部須要作互斥:當JS引擎在執行代碼的時候,界面渲染和事件響應兩個線程是被暫停的。因此當JS出現死循環,瀏覽器沒法響應點擊,也沒法更新界面。如今的瀏覽器的JS引擎都是單線程的,儘管多線程功能強大,可是線程同步比較複雜,而且危險,稍有不慎就會崩潰死鎖。單線程的好處沒必要考慮線程同步這樣的複雜問題,簡單而安全。下面的一幅圖來簡要說明JS引擎的執行流程:編程

JS引擎基於事件來執行代碼。事件響應線程在接到事件後,把響應的代碼放到JS引擎的隊列中,JS引擎按順序執行代碼。在JS引擎沒有代碼能夠執行的時候,好比圖中藍色方框的間隙中,事件線程和渲染線程得以有機會運行。基於這些信息,可以的出下面的結論瀏覽器

 

  1. setTimeout,setInterval並非多線程,只是一個定時的事件觸發器,它們在合適的時間把一些JS代碼塞到JS引擎的隊列中。
  2. setTimeout(aFunction, 0),這行代碼看似的意思是在0秒以後執行aFunction, 但這並不意味着當即執行。其它真正的意思是馬上把aFunction的代碼放到當前JS引擎的隊列中。因此當前代碼塊執行完成以前,aFunction的代碼是得不到執行的。好比這段代碼,必定是world先出來,hello後出來。儘管setTimeout的參數是0,但這並不意味着當即執行
    1. setTimeout(function() {  
    2.     alert("hello");  
    3. }, 0)  
    4. alert("world")  
  3. 在一個事件的響應代碼執行完成以後,即便隊列中有待執行的代碼,瀏覽器也會先執行頁面渲染和響應事件,完成以後再執行隊列中的代碼。

異步Ajax

看到這邊相信各位應該對JS的單線程以及setTimeout,setInterval的本質有所瞭解了,那麼咱們再繼續討論下一個問題,異步Ajax。上文說了,JS是單線程的,當一個函數執行的時候,JS引擎會鎖住DOM樹,其餘事件的響應代碼只能在隊列中等待,而且此時頁面卡死。那麼異步Ajax是怎麼回事呢?一個經常使用的開發實踐就是發起一個異步的Ajax,界面顯示一個進度條樣式的Gif,說好的單線程呢?事實上異步Ajax確實用了多線程,只是Ajax請求的Http鏈接部分由瀏覽器另外開了一個線程執行,執行完畢以後給JS引擎發送一個事件,這時候異步請求的回調代碼得以執行。它的執行流程是這樣的:安全

Http請求的執行在另一個線程中,因爲這個線程並不會操做DOM樹,因此是能夠保證線程安全的。發起Ajax請求和回調函數中間是沒有JS執行的,因此頁面不會卡死。多線程

 

 

真正的多線程JS

 

在HTML5中,引入了Web Worker這個概念。它可以在另一個線程中執行計算密集的JS代碼而不引發頁面卡死,這是真正的多線程。然而爲了保證線程安全,Worker中的代碼是不能訪問DOM的。其具體使用方法在此不做贅述,請參考:http://www.w3school.com.cn/html5/html_5_webworkers.asp

總結

結合上面的分析,總結出出下面的一些實踐供各位參考。

      1. 避免編寫計算密集的前端代碼。
      2. 使用異步Ajax。
      3. 避免編寫一個須要較長時間來執行的JS代碼,好比生成一個大型的表。遇到這種狀況,能夠分批執行,好比用setInterval來每秒生成20行,或是用戶向下拖動滾動條時候再繼續產生新的行。
      4. 在頁面初始化時候不要執行不少的初始化代碼,不然會影響頁面渲染變慢。一些不須要當即執行的代碼能夠在頁面渲染完成以後再執行,好比綁定事件,生成菜單之類的控件。
      5. 對於複雜頁面(像淘寶首頁),能夠結合異步Ajax分批產生頁面。先生成頁面框架,頁面內容自上而下用異步Ajax逐步加載並填充到框架中。這樣可以讓用戶更早的看到頁面。
      6. setTimeout(function, 0)是有用的。它可讓callback做爲另一個事件響應代碼來執行。實現了當前事件的代碼執行完成以後,再渲染DOM,再執行setTimeout的callback。這樣可以讓一部分代碼延後執行,而且在這以前渲染DOM。
相關文章
相關標籤/搜索