基本概念
1、js的執行過程是單線程的模式,也就是同步進行,只有前面的代碼執行完了才會往下面執行
2、但是執行js代碼也只是瀏覽器的線程之一所負責的事情,這個線程被稱為js引擎,瀏覽器還具有其他線程:界面渲染線程(UI)、瀏覽器事件觸發線程(控制交互,響應用戶)、http請求線程(處理請求,而ajax發送請求則會委托瀏覽器新開一個http線程)、EventLoop輪詢線程(負責輪詢消息隊列)
3、瀏覽器中js代碼的作用:執行JavaScript代碼 、對用戶的輸入(包含鼠標點擊、鍵盤輸入等等)做出反應 、處理異步的網絡請求
js單線程
1、單線程的含義是js只能在一個線程上運行,也就說,js同時只能執行一個js任務,其它的任務則會排隊等待執行。
2、js是單線程的,並不代表js引擎線程只有一個。js引擎有多個線程,一個主線程,其它的后台配合主線程。
3、多線程之間會共享運行資源,瀏覽器端的js會操作dom,多個線程必然會帶來同步的問題,所有js核心選擇了單線程來避免處理這個麻煩。js可以操作dom,影響渲染,所以js引擎線程和UI線程是互斥的。這也就解釋了js執行時會阻塞頁面的渲染
js消息隊列
1、JavaScript運行時,除了一個運行線程,引擎還提供一個消息隊列,里面是各種需要當前程序處理的消息。新的消息進入隊列的時候,會自動排在隊列的尾端
2、單線程意味着js任務需要排隊,如果前一個任務出現大量的耗時操作,后面的任務得不到執行,任務的積累會導致頁面的“假死”。這也是js編程一直在強調需要回避的“坑”
js執行任務方式
1、首先js任務分兩種:同步任務、異步任務
2、同步任務:在主線程排隊支持的任務,前一個任務執行完畢后,執行后一個任務,形成一個執行棧,線程執行時在內存形成的空間為棧,進程形成堆結構,這是內存的結構。執行棧可以實現函數的層層調用。注意不要理解成同步代碼進入棧中,按棧的出棧順序來執行。
3、異步任務:會被主線程掛起,不會進入主線程,而是進入消息隊列,而且必須指定回調函數,只有消息隊列通知主線程,並且執行棧為空時,該消息對應的任務才會進入執行棧獲得執行的機會。
4、主線程說明:
(1)所有同步任務都在主線程上執行,形成一個執行棧。
(2)主線程之外,還存在一個”任務隊列”(消息隊列)。只要異步任務有了運行結果,就在”任務隊列”之中放置一個事件。
(3)一旦”執行棧”中的所有同步任務執行完畢,系統就會讀取”任務隊列”(消息隊列),看看里面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步。
5、消息隊列說明:
(1)消息隊列隊列(或者叫任務隊列)是一個事件的隊列,IO響應時(鼠標點擊等輸入輸出設備的操作),會往隊列中添加一個消息,此時說明相關的異步代碼到了執行的時機,可以進入主線程的執行棧了。
(2)主線程讀取消息隊列,可以讀取到對應的事件。
(3)消息隊列可以響應IO事件,還有用戶產生的事件(比如點擊鼠標,頁面滾動),只要指定了回調函數,就會進入消息隊列,等待EventLoop輪詢線程處理,是否可以進入主線程的執行棧。
(4)消息和回調函數相互聯系的含義:主線程讀到消息,就會執行相應的回調函數;進入消息隊列的消息,必須對應相應的回調函數,否則這個消息會被丟棄不會進入消息隊列。
(5)消息隊列是一個先進先出的隊列結構,這就決定了它的執行順序,先產生的消息會被主線程先讀取,會不會執行則會先檢查一下執行時間,因為存在setTimeout等定時函數,這類事件產生的消息進入到消息隊列,被執行的時機取決與它在隊列中的位置和執行時間有關。【使用setTimeout能夠避免阻塞UI線程就是這個原因】。
5、需要注意的是:執行棧中的代碼(同步任務),總是在讀取”任務隊列”(異步任務)之前執行。
EventLoop
1、主線程從”任務隊列”中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)。
2、簡單說,瀏覽器的兩個線程:一個負責程序本身的運行,稱為”主線程”;另一個負責主線程與其他進程(主要是各種I/O操作)的通信,被稱為”Event Loop線程”(可以譯為”消息線程”)。
3、由於js是運行在單線程上的,所有瀏覽器單獨開啟一個線程來處理事件消息的輪詢,避免阻塞js的執行。
異步代碼執行邏輯
1、每當遇到I/O的時候,主線程就讓EventLoop線程去通知相應的I/O程序,然后接着往后運行,所以不存在等待時間。等到I/O程序完成操作,EventLoop線程把消息添加到消息隊列,主線程就調用事先設定的回調函數,完成整個任務。
2、js的ajax是new XMLHttpRequest()對象實現的,瀏覽器會新開一個線程來處理http請求,這就是ajax能夠實現局部刷新的同時,還能響應用戶交互的原因。
定時器
1、前面也提到了定時器,定時器是會在進入消息隊列,這也就和異步代碼的執行邏輯一樣了。它在”消息隊列”的尾部添加一個消息,因此要等到同步任務和”消息隊列”現有的任務都處理完,才會得到執行的機會,還要看定時器設置的時間是否到了才會執行。
<script> for(var i = 0 ; i < 10; i++){ setTimeout(function(){ console.log(i);//打印10次10 },0); } </script>
所以,只有等到主線程的任務執行完之后,setTimeout中的事件才會被執行,雖然時間間隔是0秒,但是必須等主線程任務完成,所以最后打印的都是10
2、可以用閉包來解決問題,每次執行for循環的時候,把函數作為返回值給setTimeout作為參數,函數里面存着從for循環里面拿到的每一個值,下面代碼會返回多個函數,每個函數的j值都不一樣
<script> for(var i = 0; i< 3; i++){ function foo(j){ // var j; // j = 實參 //j = i return function(){ console.log(j); }; } //0 var f = foo(i); setTimeout(f, 0); } </script>
參考:
http://blog.csdn.net/w2765006513/article/details/53743051
https://www.cnblogs.com/haodawang/articles/5850822.html
