用實例和知識點描述帶您清晰的瞭解瀏覽器事件環的每一步;javascript
在計算機內存中存取數據, 基本的數據結構分爲棧和隊列php
棧(Stack)是一種後進先出的數據結構; 棧的特色是 操做只在一端進行, 通常來講, 棧的操做只有兩種: 進棧和出棧; 第一個進棧的數據老是最後一個纔出來html
隊列(Queue)和棧相似, 可是它是先進先出的數據結構,它的特色是 操做在隊列兩端進行, 從一端進入再從另外一端出來; 先進入(從A端)的老是先出來(從B端)前端
名稱 | 進出特色 | 端的數量 |
---|---|---|
棧 | 後進先出 | 進出都在同一端 |
隊列 | 先進先出 | 進出是在不一樣端 |
// 隊列執行時按照放置的順序依次執行
setTimeout(function(){
console.log(1)
});
setTimeout(function(){
console.log(2)
});
setTimeout(function(){
console.log(3)
});
// => 1 2 3
複製代碼
// 在JavaScript中函數的執行就是一個典型的入棧與出棧的過程
function a(){
console.log('a')
function b(){
console.log('b');
function c(){
console.log('c');
}
c();
}
b();
}
a();
// => a b c
// 函數調用順序是a b c, 而做用域銷燬的過程依次是c b a
複製代碼
JavaScript是單線程的, 這裏所謂的單線程指的是主線程是單線程;java
爲何不是多線程呢? JavaScript最初設計是運行在瀏覽器中的, 假定是多線程, 有多個線程同時操做DOM, 豈不很混亂! 那會以哪一個爲準呢?面試
JavaScript爲單線程, 在一個線程中代碼會一行一行往下走,直到程序執行完畢; 若執行期間遇到較爲費時的操做, 那隻能等待了;promise
單線程的設計使得語言的執行效率變差, 爲了利用多核CPU的性能,javascript語言支持異步代碼; 當有較爲費時的操做時, 可將任務寫爲異步; 主線程在執行過程當中遇到異步代碼, 會先將該異步任務掛起, 繼續執行後面的同步代碼, 待同步執行完畢再回過頭來, 檢查是否有異步任務, 若是有異步任務就執行它;瀏覽器
PS: Java君加班有點累, 他想燒水衝一杯咖啡, 若是採用同步執行方式,那他就傻傻地等待,等水開了再衝咖啡;bash
PS: Java君加班有點累, 他想燒水衝一杯咖啡, 若是採用異步執行方式,那麼他在等待水燒開以前,他能夠聽聽歌,刷刷抖音啥的,等水開了再衝咖啡;數據結構
(-很明顯異步的方式效率會高一些);
JavaScript代碼是在棧裏執行的, 不管是同步仍是異步; 代碼分爲同步代碼和異步代碼, 異步代碼又分爲: {宏任務} 和 [微任務]
JavaScript是解釋型語言,它的執行過程是這樣的:
從以上步驟能夠看出,不論同步仍是異步, 都是在棧裏執行的, 棧裏的任務執行完成後一遍又一遍地回頭檢查隊列,這種方式就是所謂的"事件環"
// 先看個demo吧
console.log('start');
setTimeout(()=>{
console.log('hello');
}, 1000);
console.log('end');
// start end hello 上面代碼執行後, 輸出'start' 'end', 大約1s以後輸出'hello'
// ? 爲何'hello'不在end以前輸出呢
複製代碼
console.log('start');
setTimeout(() => {
console.log('hello');
},0);
console.log('end');
// start end hello
// 將上例微微調整,發現輸出結果仍是同樣的
// 由於setTimeout的回調函數只是會被添加到(事件)隊列中,而不會當即執行。 再回頭
複製代碼
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve('Sucess');
});
promise.then((data)=>{
console.log(data);
});
console.log('Hello World!');
// 'Promise' 'Hello World!' 'Sucess'
複製代碼
以前說到,異步任務又分爲: 宏任務和微任務, 那他們是怎樣執行的呢?
// 查看setTimeout和Promise.then的不一樣
console.log(1);
setTimeout(()=>{
console.log(2);
Promise.resolve().then(()=>{
console.log(6);
});
}, 0);
Promise.resolve(3).then((data)=>{
console.log(data); // 3
return data + 1;
}).then((data)=>{
console.log(data) // 4
setTimeout(()=>{
console.log(data+1) // 5
return data + 1;
}, 1000)
}).then((data)=>{
console.log(data); // 上一個then沒有任何返回值, 因此爲undefined
});
// 1 3 4 undefined 2 6 5
複製代碼
console.log(1);
,將其執行, 輸出 1setTimeout(()=>{ console.log(2); }, 0)
, 將其放入宏任務隊列,此時宏任務隊列:[s1]setTimeout(()=>{ console.log(data+1); return data + 1; })
,將其放入宏任務隊列(先標記,1s後異步執行完成後再將異步函數的回調放入隊列), 此時宏任務隊列:[s1,s2]setTimeout(()=>{
console.log(1);
Promise.resolve().then(data => {
console.log(2);
});
}, 0);
Promise.resolve().then(data=>{
console.log(3);
setTimeout(()=>{
console.log(4)
}, 0);
});
console.log('start');
// start -> 3 1 2 4
// 給方法分類: 宏任務 微任務
// 宏任務: setTimeout
// 微任務: then
/*
// 執行順序: 微任務會先執行
// 默認先執行主棧中的代碼,執行後完清空微任務;
// 以後微任務執行完畢,取出第一個宏任務到主棧中執行
// 第一個宏任務執行完後,若是有微任務會再次去清空微任務,以後再去取宏任務,這樣就造成事件環;
*/
複製代碼
解析:
setTimeout(function () {
console.log(1);
Promise.resolve().then(function () {
console.log(2);
}); // p2
}); // s1
setTimeout(function () {
console.log(3);
}); // s2
Promise.resolve().then(function () {
console.log(4);
}); // p1
console.log(5); // 5 4 1 2 3
複製代碼
解析
setTimeout(()=>{
console.log('A');
},0);
var obj={
func:function () {
setTimeout(function () {
console.log('B')
},0);
return new Promise(function (resolve) {
console.log('C');
resolve();
})
}
};
obj.func().then(function () {
console.log('D')
});
console.log('E');
// C E D A B
複製代碼
解析:
setTimeout(()=>{ console.log('A'); },0)
被加入到宏任務事件隊列中,此時宏任務中有[s1(輸出A)];setTimeout(()=>{console.log('B'); },0)
被加入到宏任務事件隊列中,此時宏任務中有[s1,s2(輸出B)];console.log('E')
,執行後輸出 'E'磕磕絆絆終因而理解了這一塊的知識點, 之前只是在不斷的搬磚, 卻從未停下來思考、認真學習, GET到以後感受解開了很多疑惑;
在寫文檔時候發現本身的語言描述能力竟然如此的不堪, 囉裏囉嗦寫了不少; 這大抵是成長的必經之路吧;
參考了一些朋友的文章, 從中學習到很多, 有知識點的學習也有大佬對知識點巧妙的描述技巧; 向大佬致敬!
參考文章: