本系列將從如下專題去總結:javascript
1. JS基礎知識深刻總結
2. 對象高級
3. 函數高級
4. 事件對象與事件機制css
暫時會對以上四個專題去總結,如今開始Part4: 事件對象與事件機制。下圖是我這篇的大綱。java
同步(Synchronous):你在作一件事情,不能同時去作另一件事。web
異步(Asynchronous):你在作一件事情,這件事可能會耗時好久,而此時你能夠在等待的過程當中,去作另一件事。ajax
好比煮開水這件事吧...在這過程,你擔憂水沸了而不去作其它事情,就等到水沸騰,那就是同步。chrome
而你以爲這過程耗時蠻久,能夠先去作其它事情,好比去掃地,直到水沸騰。這就是異步。windows
1.進程(process): 程序的一次執行, 它佔有一片獨有的內存空間。能夠經過windows
任務管理器查看進程。進程負責爲程序的運行提供必備的環境,至關於工廠的車間。跨域
2.線程(thread): 是進程內的一個獨立執行單元。 是CPU的最小的調度單元。 是程序執行的一個完整流程。線程負責執行進程中的程序,至關於工廠工做的工人。promise
3.圖解進程、線程和程序的關係: 瀏覽器
4.進程與線程
5.何爲多進程與多線程?
6.比較單線程與多線程?
JS是單線程運行的
在JS設計的本意只是對一些簡單的操做而已,好比提交表單用戶名和密碼之類的。當沒有JS時,那麼這些數據就會提交到服務器中,那麼這個數據處理將會特別大,首先假設有1000我的同時註冊,那麼這些請求就會到服務器上,服務器的加載量就會很大,並且,用戶體驗也很差,可能會延遲返回請求信息。這是若是這些操做在瀏覽器端來操做,那麼就會簡單不少。因此,JS當時設計的初衷也就單線程了,由於不須要太多的操做。單線程足矣應付,並且不佔太多的內存。固然後面會說道,由於他的功能(DOM操做等)也決定了它只能單線程。
但使用H5中的 Web Workers能夠多線程運行(主線程只有一個,要作其餘的事能夠啓動分線程)
8.瀏覽器運行是單進程仍是多進程?
9.如何查看瀏覽器是不是多進程運行的呢 ?
10.瀏覽器運行是單線程仍是多線程?
瀏覽器內核:支撐瀏覽器運行的最核心的程序。
webkit
內核Gecko
內核Trident
內核Trident + webkit
(雙核,嘻嘻,給你一個眼神~)主線程
分線程
onclick
..)1.如何證實js執行是單線程的?
setTimeout()
的回調函數是在主線程執行的2.爲何js要用單線程模式, 而不用多線程模式?
3.代碼的分類:
4.js引擎執行代碼的基本流程
<script type="text/javascript">
setTimeout(function () {
console.log('timeout 2222')
}, 2000)
setTimeout(function () {
console.log('timeout 1111')
}, 1000)
function fn() {
console.log('fn()')
}
fn()
console.log('alert()以前')
alert('------') //暫停當前主線程的執行, 同時暫停定時器的計時, 點擊肯定後, 恢復程序執行和計時。
console.log('alert()以後')
</script>
複製代碼
5.js是單線程執行的(回調函數也是在主線程)
6.H5提出了實現多線程的方案: Web Workers
7.只能是主線程更新界面
1.定時器真是定時執行的嗎?
2.定時器回調函數是在分線程執行的嗎?
3.定時器是如何實現的?
<button id="btn">啓動定時器</button>
<script type="text/javascript">
document.getElementById('btn').onclick = function () {
var start = Date.now()
console.log('啓動定時器前...')
setTimeout(function () {
console.log('定時器執行了', Date.now()-start)
}, 200)
console.log('啓動定時器後...')
// 作一個長時間的工做
for (var i = 0; i < 1000000000; i++) {
}
}
</script>
複製代碼
dom
事件監聽, 設置定時器, 發送ajax
請求的代碼dom
事件監聽, 設置定時器, 發送ajax
請求的各自的回調函數)如下這張圖就是event-driven interaction model(事件驅動模型)。
另外簡單的說一下request-response model(事件響應模型),這個就至關於瀏覽器去服務器請求一些數據,服務器接收到這些請求,去處理這些請求,緊接着返回給瀏覽器的請求數據,瀏覽器接收到數據解析到頁面上的一個過程。
如今咱們主要是看一下event-driven interaction model:
首先,這個圖分爲三個部分:JS引擎等主線程、瀏覽器內核的分線程、任務隊列。
在第一部分中:(堆內存和棧內存)
在第二部分中: 這一塊主要是交給瀏覽器的分線程處理。以setTimeout
定時器爲比較,他會拿到回調函數和延遲時間1000,當延遲時間過了以後,就會把回調函數推入隊列中。
在第三部分中:
臨時保存着回調函數,當執行棧爲空時,就會依次將其回調函數壓入執行棧中。
這個部分叫作callback queue
。也叫任務隊列(task queue)、消息隊列(message queue)、事件隊列(event queue)。指的都是同一個。
剛剛以定時器介紹了這個過程。咱們再以AJAX爲例看看是如何執行這些過程的?
上圖以AJAX異步請求爲例,發起異步任務後,由AJAX線程執行耗時的異步操做,而JS引擎線程繼續執行堆中的其餘同步任務,直到堆中的全部異步任務執行完畢。而後,從消息隊列中依次按照順序取出消息做爲一個同步任務在JS引擎線程中執行,那麼AJAX的回調函數就會在某一時刻被調用執行。
另一點,咱們看到事件機制模型圖有事件輪詢(event loop),就是從任務隊列中循環取出回調函數放入執行棧中處理(一個接一個)。JS引擎線程用來執行棧中的同步任務(初始化代碼),當全部同步任務(初始化代碼)執行完畢後,棧被清空,而後讀取消息隊列中的一個待處理任務,並把相關回調函數壓入棧中,單線程開始執行新的同步任務。JS引擎線程從消息隊列中讀取任務是不斷循環的,每次棧被清空後,都會在消息隊列中讀取新的任務,若是沒有新的任務,就會等待,直到有新的任務,這就叫事件輪詢。
宏任務隊列
微任務隊列
promise
對象的成功的回調和progress.nextTick()
function fun() {
console.log('程序開始執行', 11111111);
setTimeout(function () {
console.log('定時器開始執行',666666);
}, 0)
new Promise(function (resolve, reject) {
console.log('promise對象開始執行', 2222222);
for (var i = 0; i < 5; i++) {
console.log(i, 33333333);
}
resolve();
})
.then(() => {
console.log('promise對象成功的回調執行', 555555);
})
.then(() => {
console.log('promise對象失敗的回調執行', 555555);
});
console.log('程序執行完畢', 444444444444);
}
fun();
//以上程序執行順序結構就是上述的數字123456.
複製代碼
Web Workers 是 HTML5 提供的一個javascript多線程解決方案,咱們能夠將一些大計算量的代碼交由web Worker運行而不凍結用戶界面,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。
實現一個斐波那契數列,在頁面input
中輸入數列項的值,獲得相應的數列值。
<input type="text" placeholder="數值" id="number">
<button id="btn">計算</button>
<script type="text/javascript">
//斐波那契數列: 1 1 2 3 5 8 f(n) = f(n-1) + f(n-2)
function fibonacci(n) {
return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)
//遞歸調用(效率很低,時間複雜度很大)
}
var input = document.getElementById('number')
document.getElementById('btn').onclick = function () {
var number = input.value;
var result = fibonacci(number);
//主線程會一直在處理這個遞歸調用。致使凍結了用戶界面,也就是不能操做界面了。
alert(result)
}
</script>
複製代碼
以上操做會在js引擎的主線程中,在計算的過程當中,會凍結用戶界面,達到不佳的用戶體驗。
H5規範提供了js分線程的實現,取名爲: Web Workers。它支持JavaScript多線程的操做。
相關API
Worker
: 構造函數, 加載分線程執行的js文件Worker.prototype.onmessage
: 用於接收另外一個線程的回調函數Worker.prototype.postMessage
: 向另外一個線程發送消息使用步驟 步驟1:建立在分線程執行的js文件
//workers.js文件
function fibonacci(n) {
return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2) //遞歸調用
}
console.log(this)
//當接受到主線程的數據時
this.onmessage = function (event) {
var number = event.data
console.log('分線程接收到主線程發送的數據: '+number)
//計算(目的:讓複雜的、耗時的運算放在分線程中處理)
var result = fibonacci(number)
postMessage(result) //正由於能夠直接使用這個方法,是由於在全局對象中有這個方法
console.log('分線程向主線程返回數據: '+result)
// alert(result) alert是window的方法, 在分線程不能調用。
// 分線程中的全局對象再也不是window, 因此在分線程中不可能更新界面
}
複製代碼
步驟2:在主線程中的js中發消息並設置回調
//主線程
<input type="text" placeholder="數值" id="number">
<button id="btn">計算</button>
<script type="text/javascript">
var input = document.getElementById('number')
document.getElementById('btn').onclick = function () {
var number = input.value
//建立一個Worker對象
var worker = new Worker('worker.js')
// 綁定接收消息的監聽(這個位置與向分線程發送消息的代碼位置可交換)
worker.onmessage = function (event) {
console.log('主線程接收分線程返回的數據: '+event.data)
alert(event.data)
}
// 向分線程發送消息
worker.postMessage(number)
console.log('主線程向分線程發送數據: '+number)
}
// console.log(this) // window
</script>
複製代碼
回顧4.6.2的案例引入,咱們可知,那個是徹底在主線程中操做,帶來的弊端就是凍結了用戶界面。而使用Workers在分線程中處理耗時的運算,在主線程去接受計算好的數據,就能夠解決直接使用主線程的凍結用戶界面的弊端,這個時候不會凍結用戶界面,可是子線程徹底受主線程控制,且子線程不得操做DOM,由於其this
不是window
。
案例1
console.log("1");
setTimeout(function(){
console.log("2");
},1000);
console.log("3");
setTimeout(function(){
console.log("4");
},0);
複製代碼
輸出結果: 1->3->4->2.
案例1分析:
案例2
//同步code1
var t = true;
//異步code2
window.setTimeout(function (){
t = false;
},1000);
//同步code2
while (t){}
//同步code3
alert('end');
複製代碼
案例2分析:
同步code1
-> 同步code2
。同步code2
時while(true){}
,進入死循環,說明這個時候棧中的同步代碼永遠不會執行完,也就棧永遠不會清空出來,那麼任務隊列中的代碼就不會執行。也就是任務隊列中的異步的代碼就沒法執行。案例3
//只有用戶觸發點擊事件纔會被推入隊列中(若是點擊時間小於定時器指定的時間,則先於定時器推入,不然反之)
document.querySelector("#box").onclick = function(){
console.log("click");
};
//第一個推入隊列中
setTimeout(function(){
console.log("1");
},0);
//第三個推入隊列中
setTimeout(function(){
console.log("2");
},1000);
//第二個推入隊列中
setTimeout(function(){
console.log("3");
},0);
複製代碼
執行結果:如上面代碼段中的分析。
案例3分析:
以上都是異步代碼,包括onclick
那個。必定要分清哪些是異步的代碼。異步代碼中的回調函數都會定義在heap
中,也就是在右邊的堆分配一塊內存給他們,這個時候根據他們指定的時候結束後,把他們的回調函數放到任務隊列等待執行。
setTimeout
的做用是在間隔必定的時間後,將回調函數插入消息隊列中,等棧中的同步任務都執行完畢後,再執行。由於棧中的同步任務也會耗時,因此間隔的時間通常會大於等於指定的時間(指定的時間就是回調函數後面一個參數的毫秒值)。
setTimeout(fn, 0)
的意思是,將回調函數fn馬上插入消息隊列,等待執行,而不是當即執行。只有等待同步任務所有執行完,而後js引擎(js虛擬機)就去從任務隊列中拿出來去執行。
案例4
setTimeout(function() {
console.log("a")
}, 0)
for(let i=0; i<10000; i++) {}
console.log("b")
複製代碼
執行結果:先輸出b 再輸出a
案例4分析:
這個與案例3就差很少了。先執行for
循環的同步代碼。定時器是異步代碼,先等線程的同步代碼執行結束後在從任務隊列中去拿這些異步代碼段執行。
案例5
執行下面這段代碼,執行後,在 5s 內點擊兩下,過一段時間(>5s)後,再點擊兩下,整個過程的輸出結果是什麼?
//異步代碼
setTimeout(function(){
for(var i = 0; i < 100000000; i++){}
console.log('timer a');
}, 0)
//同步代碼
for(var j = 0; j < 5; j++){
console.log(j);
}
//異步代碼
setTimeout(function(){
console.log('timer b');
}, 0)
//函數
function waitFiveSeconds(){
var now = (new Date()).getTime();
while(((new Date()).getTime() - now) < 5000){}
console.log('finished waiting');
}
//異步代碼
document.addEventListener('click', function(){
console.log('click');
})
//同步代碼
console.log('click begin');
//同步代碼,調用函數,執行函數體
waitFiveSeconds();
複製代碼
案例5分析:
首先,先執行同步任務。其中waitFiveSeconds
是耗時操做,持續執行長達5s。
0
1
2
3
4
click begin
finished waiting
複製代碼
而後,在JS引擎線程執行的時候,timer a
對應的定時器產生的回調、 timer b
對應的定時器產生的回調和兩次click
對應的回調被前後放入消息隊列。因爲JS引擎線程空閒後,會先查看是否有事件可執行,接着再處理其餘異步任務。所以會產生 下面的輸出順序。
click
click
timer a
timer b
複製代碼
最後,5s 後的兩次 click 事件被放入消息隊列,因爲此時JS引擎線程空閒,便被當即執行了。
click
click
複製代碼
案例6
<script>
for (var i = 0; i < 5; i++){
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('Button ' + i));
btn.addEventListener('click', function (){
console.log(i);
});
document.body.appendChild(btn);
}
// 一、點擊 Button 4,會在控制檯輸出什麼? 5
/*An:無論點擊哪一個button都是輸出5.*/
// 2. 給出一種預期的實現方式
/* 將for循環中的var 變成 let 或者 用對象.屬性保存起來i的值 */
</script>
複製代碼
此文檔爲呂涯原創,可任意轉載,但請保留原連接,標明出處。 文章只在CSDN和掘金第一時間發佈: CSDN主頁:https://blog.csdn.net/LY_code 掘金主頁:https://juejin.im/user/5b220d93e51d4558e03cb948 如有錯誤,及時提出,一塊兒學習,共同進步。謝謝。 😝😝😝