程序 線程
- CPU 配置設定資源的最小機關是程序,同一個時間内單個 CPU 隻能運作一個程序,單個 CPU 一次隻能運作一個任務
- CPU 排程的最小機關是線程,一個程序裡面包含多個線程。
- 可以看看阮老師的這篇文章,程序與線程的一個簡單解釋
浏覽器的程序
- 浏覽器主程序
- 負責界面展示,使用者互動,子程序管理,檔案存取等
- 插件程序
- GPU 程序
- 3d繪制
- 網絡程序
- 渲染程序
- 主要負責将 HTML, CSS, JavaScript 轉換為使用者可互動的網頁,排版引擎 Blink 和
- JavaScript 引擎 V8 就運作在渲染程序,預設每個 tab 一個渲染程序
- 浏覽器核心
浏覽器的渲染流程
- 解析 HTML 檔案,生成 DOM tree;同時解析 CSS 檔案以及樣式元素中的樣式資料,生成 CSS Rules
- 建構 render tree:根據 DOM tree 和 CSS Rules 來建構 render tree,它可以讓浏覽器按照正确的順序繪制内容
- 布局(layout / reflow):計算各元素尺寸、位置。
- 繪制(paint):繪制頁面像素資訊。
- 浏覽器将各層資訊發送給 GPU,GPU 将各層資訊合成(composite),顯示在螢幕上。
浏覽器核心
- GUI 渲染線程
- JS 引擎線程
- JavaScript 是單線程的
- GUI 渲染線程 與 JS 引擎線程是互斥的
- JS 阻塞頁面加載
- 事件觸發線程
- 定時觸發器線程
- 異步 http 請求線程
宏任務,微任務
- 事件輪詢解釋:事件輪詢-阮一峰
- JS 單線程
- 單線程中如果同步執行的代碼很慢,會讓其它程度等待很長時間,這是同步阻塞。
- 多線程會占用多倍的資源也會浪費多倍的資源,也不合理。
- event_loop 是把需要等待的程式放到異步隊列,主調用函數執行完之後監聽,再執行異步隊列,整個過程就是個不斷的循環
- JS 引擎遇到一個異步事件後并不會一直等待其傳回結果,而是會将這個事件挂起,繼續執行執行棧中的其他任務。當一個異步事件傳回結果後,js會将這個事件加入與目前執行棧不同的另一個隊列,我們稱之為事件隊列。被放入事件隊列不會立刻執行其回調,而是等待目前執行棧中的所有任務都執行完畢, 主線程處于閑置狀态時,主線程會去查找事件隊列是否有任務。如果有,那麼主線程會從中取出排在第一位的事件,并把這個事件對應的回調放入執行棧中,然後執行其中的同步代碼…,如此反複,這樣就形成了一個無限的循環。這就是這個過程被稱為“事件環(Event Loop)”
- 宏任務 宿主環境提供的異步方法 都是宏任務 script,ui 渲染,setTimeout,setInterval、setImmediate
- 微任務 語言标準提供 promise,mutationObserver,process.nextTick,Object.observe(已廢棄)
- 預設先執行 script 腳本中的代碼 -> 會清空微任務(将所有的微任務全部執行完) -> 渲染頁面 -> 取出一個宏任務執行 -> 執行完畢後會再次情空微任務...
微任務和宏任務執行
- 浏覽器黃色
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- script是一個宏任務 -->
<script>
document.body.style.background = 'red';
console.log(1);
// 在 setTimeout 執行前 會觸發一次渲染
Promise.resolve().then(()=>{
console.log(2);
document.body.style.background = 'yellow';
});
console.log(3);
</script>
</body>
</html>
- 按鈕事件執行順序
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="button">點我啊</button>
<script>
button.addEventListener('click', ()=>{
console.log('listener1');
Promise.resolve().then(()=>console.log('micro task1'));
});
button.addEventListener('click', ()=>{
console.log('listener2');
Promise.resolve().then(()=>console.log('micro task2'));
});
// 點選按鈕之後的順序
// listener1
// micro task1
// listener2
// micro task2
// 一個個的函數來取 每次執行完 會先處理目前定義的微任務
button.click(); // click1() click2()
// listener1 listener2 micro task1 micro task2
</script>
</body>
</html>
- promise 和 setTimeout
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
Promise.resolve().then(() => {
console.log('Promise1')
setTimeout(() => {
console.log('setTimeout2');
}, 0);
});
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(() => {
console.log('Promise2');
});
}, 0);
// Promise1 setTimeout1 Promise2 setTimeout2
</script>
</body>
</html>
- 執行順序
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
console.log(1);
async function async () {
console.log(2);
await console.log(3); // Promise.resolve(console.log(3)).then(console.log(4))
console.log(4);
}
setTimeout(() => {
console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
console.log(6); // new Promise 代碼會立即執行
resolve(7);
})
promise.then(res => { // 第一個微任務
console.log(res)
});
async ();
console.log(8);
// 1 6 2 3 8 7 4 5
</script>
</body>
</html>
- 執行順序
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start
// script end
// promise1
// promise2
// setTimeout
</script>
</body>
</html>
- 一段變态的代碼
// 1 7 6 8
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
});
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
});
}, 0);
process.nextTick(function() {
console.log('6');
});
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
});
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
});
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
});
}, 0);