共计 10664 个字符,预计需要花费 27 分钟才能阅读完成。
一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 单线程和多线程 |
四 Event Loop |
4.1 Event Loop 执行过程 |
4.2 requestAnimationFrame |
4.2.1 requestAnimationFrame 介绍 |
4.2.2 requestAnimationFrame 应用原因 |
4.3 Web Worker |
4.3.1 Web Worker 应用 |
4.3.2 Web Worker 数据通讯 |
4.3.3 Web Worker 可操作 API |
4.3.4 Web Worker 兼容性 |
4.4 Node 和 浏览器 |
五 两个环境 Event Loop 比照 |
六 题目训练 |
6.1 同步工作 |
6.2 定时器 |
6.3 定时器 + Promise |
6.4 综合 |
七 参考文献 |
7.1 requestAnimationFrame 参考文献 |
7.2 Web Worker 参考文献 |
7.3 其余参考文献 |
二 前言
返回目录
Event Loop
即事件循环,是指浏览器或 Node
的一种解决 JavaScript 单线程运行时不会阻塞的一种机制,也就是咱们常常应用异步的原理。
三 单线程和多线程
返回目录
JavaScript 是一个单线程的语言。
单线程在程序执行时,所走的程序门路依照间断程序排下来,后面的必须解决好,前面的才会执行。
以 Chrome 浏览器中为例,当你关上一个 Tab 页时,其实就是创立了一个过程。
一个过程中能够有多个线程,比方渲染线程、JS 引擎线程、HTTP 申请线程等等。
当你发动一个申请时,其实就是创立了一个线程,当申请完结后,该线程可能就会被销毁。
- 浏览器内核是怎么的?
浏览器内核是多线程的,在内核管制下各线程相互配合以放弃同步,一个浏览器通常由以下常驻线程组成:
- GUI 渲染线程:解析 HTML、CSS 等。在 JavaScript 引擎线程运行脚本期间,GUI 渲染线程处于挂起状态,也就是被“解冻”了。
- JavaScript 引擎线程:负责解决 JavaScript 脚本。
- 定时触发器线程:
setTimeout
、setInterval
等。事件触发线程会将计数结束后的事件退出到工作队列的尾部,期待 JS 引擎线程执行。 - 事件触发线程:负责将筹备好的事件交给 JS 引擎执行。
- 异步
http
申请线程:负责执行异步申请之类函数的线程,例如Promise.then()
、ajax
等。
- 为什么不设计成多线程?
假如有个 DOM
节点,当初有线程 A
操作它,删除了这个 DOM
;
而后线程 B
又操作它,批改了这个 DOM
某局部。
那么当初问题来了,咱听谁的?
所以罗唆设计成一个单线程,平安稳当不出事。
哪怕前期 HTML5 出了个 Web Worker
也是不容许操作 DOM
构造的,能够实现一些分布式的计算。
Web Worker
在本文中有解说
- 为什么须要异步?
这时候又有问题了,如果调用某个接口(Ajax
),或者加载某张图片的时候,咱们卡住了,这样页面是不是就始终不能渲染?
而后因为单线程只能先让后面的程序走完,即使这个接口或者图片缓过去了,我上面还有其余工作没做呢,这不就卡死了么?
所以这时候异步来了:
在波及某些须要期待的操作的时候,咱们就抉择让程序持续运行。
期待接口或者图片返回过去后,就告诉程序我做好了,你能够持续调用了。
四 Event Loop
返回目录
- 为什么会有 Event Loop?
后面 jsliang 讲到:JavaScript 线程一次只能做一件事。
如果碰到一些须要期待的程序,例如 setTimeout
等,那就歇菜了。
所以,JavaScript 为了协调事件、用户交互、脚本、渲染、网络等,就搞进去一个 事件循环(Event Loop)。
- 什么是 Event Loop?
JavaScript 从 script
开始读取,而后一直循环,从“工作队列”中读取执行事件的过程,就是 事件循环(Event Loop)。
4.1 Event Loop 执行过程
返回目录
Event Loop 执行过程如下:
- 一开始整个脚本
script
作为一个宏工作执行 - 执行过程中,同步代码 间接执行, 宏工作 进入宏工作队列, 微工作 进入微工作队列。
- 以后宏工作执行完出队,查看微工作列表,有则顺次执行,直到全副执行结束。
- 执行浏览器
UI
线程的渲染工作。 - 查看是否有
Web Worker
工作,有则执行。 - 执行完本轮的宏工作,回到步骤 2,顺次循环,直到宏工作和微工作队列为空。
事件循环中的异步队列有两种:宏工作队列(MacroTask
)和 微工作队列(MicroTask
)。
Web Worker 是运行在后盾的 JS,独立于其余脚本,不会影响页面的性能。
宏工作队列能够有多个,微工作队列只有一个。
宏工作 包含:
script
setTimeout
setInterval
setImmediate
I/O
UI rendering
微工作 包含:
MutationObserver
Promise.then()/catch()
- 以
Promise
为根底开发的其余技术,例如fetch API
- V8 的垃圾回收过程
- Node 独有的
process.nextTick
4.2 requestAnimationFrame
返回目录
4.2.1 requestAnimationFrame 介绍
返回目录
window.requestAnimationFrame()
通知浏览器——你心愿执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
该办法须要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
requestAnimationFrame
简称 rAF
。
咱们看一下它应用状况:
<body>
<div class="animation"> 动画元素 </div>
<script>
window.onload = function() {const element = document.querySelector('.animation');
let start;
function step(timestamp) {if (start === undefined) {start = timestamp;}
const elapsed = timestamp - start;
// 这里应用 Math.min() 确保元素刚好停在 200px 的地位。element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';
// 在两秒后进行动画
if (elapsed < 2000) {window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
};
</script>
</body>
4.2.2 requestAnimationFrame 应用原因
返回目录
如果咱们应用 setTimeout
来实现动画成果,那么咱们会发现在某些低端机上呈现卡顿、抖动的景象,它产生的起因是:
setTimeout
的执行事件并不是确定的。它属于宏工作队列,只有当主线程上的工作执行结束后,才会调用队列中的工作判断是否开始执行。- 刷新频率受屏幕分辨率和屏幕尺寸影响,因而不同设施的刷新频率不同,而
setTimeout
只能固定一个工夫距离刷新。
在下面 Event Loop
的过程中,咱们晓得执行完微工作队列会有一步操作:
- 执行浏览器
UI
线程的渲染工作。
而 requestAnimationFrame
就在这里边执行,就不会等宏工作队列的排队,从而导致卡顿等问题了。
4.3 Web Worker
返回目录
Web Worker
为 Web 内容在后盾线程中运行脚本提供了一种简略的办法。
如咱们所知,JavaScript 始终是属于单线程环境,咱们无奈同时运行两个 JavaScript 脚本。
然而试想一下,如果咱们能够同时运行两个(或者多个)JavaScript 脚本,一个来解决 UI 界面(始终以来的用法),一个来解决一些简单计算,那么性能就会更好。
在 HTML5 的新标准中,实现了 Web Worker
来引入 JavaScript 的“多线程”技术,他的能力让咱们能够在页面主运行的 JavaScript 线程中加载运行另外独自的一个或者多个 JavaScript 线程。
留神:JavaScript 实质上还是单线程的,
Web Worker
只是浏览器(宿主环境)提供的一个得力 API。
4.3.1 Web Worker 应用
返回目录
调用 Web Worker:
index.js
console.log('index- 同步工作');
Promise.resolve().then((res) => {console.log('index-Promise');
});
setTimeout(() => {console.log('index-setTimeout');
}, 1000);
index.html
<script>
window.onload = function() {console.log('本地 - 同步工作');
// 微工作之间
Promise.resolve().then((res) => {console.log('本地 - 微工作 1');
})
const worker1 = new Worker('./index.js');
Promise.resolve().then((res) => {console.log('本地 - 微工作 2');
})
// 宏工作之间
setTimeout(() => {console.log('本地 - 宏工作 1');
}, 1000);
const worker2 = new Worker('./index.js');
setTimeout(() => {console.log('本地 - 宏工作 2');
}, 1000);
};
</script>
执行的时候打印后果:
本地 - 同步工作
本地 - 微工作 1
本地 - 微工作 2
index- 同步工作
index-Promise
index- 同步工作
index-Promise
本地 - 宏工作 1
本地 - 宏工作 2
index-setTimeout
index-setTimeout
能够看到:
- 先执行
script
中同步工作 - 再执行
script
中微工作 - 而后执行 UI 线程的渲染工作(这里在代码中没有体现,感兴趣的能够试试增加
rAF
) - 接着才执行
Web Worker
外面内容 - 再来是
index.html
中的宏工作 - 最初才是
Web Worker
文件中的宏工作
能够看出它仍合乎 Event Loop
流程。
4.3.2 Web Worker 数据通讯
返回目录
index.js
onmessage = (res) => {
// Worker 接收数据
console.log('Worker 收到数据:', res);
// Worker 收到数据:// MessageEvent {isTrusted: true, data: "查房,这里是 index.html!", origin: "", lastEventId:"", source: null, …}
// Worker 发送数据
postMessage('开门!这里是 index.js');
}
index.html
<script>
window.onload = function() {
// 实例化 Worker
const worker = new Worker('./index.js');
// index.html 接收数据
worker.addEventListener('message', (res) => {console.log('index.html 收到数据:', res);
// index.html 收到数据:// MessageEvent {isTrusted: true, data: "开门!这里是 index.js", origin: "", lastEventId:"", source: null, …}
});
// index.html 发送数据
worker.postMessage('查房,这里是 index.html!');
// 终止 Worker
worker.terminate();};
</script>
在 index.html
中,通过:
worker.addEventListener('message', callback)
。接管 Web Worker 传递的数据。worker.postMessage('xxx')
。发送数据给 Web Worker。worker.terminate()
。终止通信
在 index.js
中,通过:
onmessage = (res) => {console.log(res); // 在 onmessage 办法承受数据
postMessage('xxx'); // 通过 postMessage 发送数据
}
4.3.3 Web Worker 可操作 API
返回目录
setTimeout(),clearTimeout(),setInterval(),clearInterval()
:有了这几个函数,就能够在Web Worker
线程中执行定时操作了;XMLHttpRequest
对象:意味着咱们能够在Web Worker
线程中执行Ajax
申请;navigator
对象:能够获取到ppName
,appVersion
,platform
,userAgent
等信息;location
对象(只读):能够获取到无关以后 URL 的信息;
如果须要加载其余 JS 脚本:
importScripts('./index2.js', './index3.js');
// 或者
// importScripts('./index2.js');
// importScripts('./index3.js');
4.3.4 Web Worker 兼容性
返回目录
- IE:11 版本
- Edge:14+ 版本
- Firefox:51+ 版本
- Chrome:56+ 版本
- 其余:看 caniuse 链接
4.4 Node 和 浏览器
返回目录
为啥会有 浏览器 Event Loop 和 Node.js Event Loop?
简略来说:
- 你的页面放到了浏览器去展现,你的数据放到了后盾解决(将 Node.js 看成 PHP、Java 等后端语言),这两者能没有区别么?!
再认真一点:
- Node.js:Node.js 的
Event Loop
是基于libuv
。libuv
曾经对 Node.js 的Event Loop
作出了实现。 - 浏览器:浏览器的
Event Loop
是基于 HTML5 标准 的。而 HTML5 标准中只是定义了浏览器中的Event Loop
的模型,具体实现留给了浏览器厂商。
libuv
是一个多平台反对库,次要用于异步 I/O。它最后是为 Node.js 开发的,当初Luvit
、Julia
、pyuv
和其余的框架也应用它。Github – libuv 仓库
所以,咱们得将这两个 Event Loop
辨别开来,它们是不一样的东东哈~
五 两个环境 Event Loop 比照
返回目录
浏览器环境下,microtask
的工作队列是每个 macrotask
执行完之后执行。
而在 Node.js 中,microtask
会在事件循环的各个阶段之间执行,也就是一个阶段执行结束,就会去执行 microtask
队列的工作。
这里没有讲 Node.js 的工夫循环机制,第一个是因为 jsliang 对 Node 不熟,怕瞎写误导;第二个是因为面试官问的时候,基本上答复的都是浏览器的事件循环机制,偶然提一嘴 Event Loop
分为浏览器事件循环和 Node 事件循环算是加点小分了。
六 题目训练
返回目录
在训练之前,咱们先讲下考题范畴:
- 同步工作:碰到间接执行,不要管三七二十一。
- 宏工作:
script
、setTimeout
- 微工作:
Promise.then()
、async/await
临时就这么点内容,想来不会考错!
6.1 同步工作
返回目录
function bar() {console.log('bar');
}
function foo() {console.log('foo');
bar();}
foo();
这段内容输入啥?
foo
->bar
详情不须要解释。
6.2 定时器
返回目录
console.log("1");
setTimeout(function () {console.log("2");
}, 0);
setTimeout(function () {console.log("3");
}, 2000);
console.log("4");
- 宏工作队列:
script
、setTimeout(2)
、setTimeout(3)
- 微工作队列:无
所以输入:
1
4
2
3
6.3 定时器 + Promise
返回目录
- 题目 1:请输入上面代码打印状况
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
宏工作下:
- 宏工作
setTimeout
- 微工作
.then(promise1)
所以先执行同步代码,先输入:script start
-> script end
。
而后调用微工作,输入 promise1
,将 then(promise2)
放入微工作。
再次调用微工作,将 promise2
输入。
最初调用宏工作 setTimeout
,输入 setTimeout
。
因而输入程序:
script start
script end
promise1
promise2
setTimeout
- 题目 2:请输入上面代码打印状况
Promise.resolve().then(function promise1() {console.log('promise1');
})
setTimeout(function setTimeout1() {console.log('setTimeout1')
Promise.resolve().then(function promise2() {console.log('promise2');
})
}, 0)
setTimeout(function setTimeout2() {console.log('setTimeout2')
}, 0)
script
宏工作下:
- 同步工作:无
- 微工作:
Promise.then(promise1)
- 宏工作:
setTimeout(setTimeout1)
、setTimeout(setTimeout2)
所以先走同步工作,发现并没有,不理睬。
而后再走微工作 Promise.then(promise1)
,输入 promise1
。
接着推出宏工作,先走 setTimeout(setTimeout1)
:
- 同步工作:
console.log('setTimeout1')
- 微工作:
Promise.then(promise2)
- 宏工作:
setTimeout(setTimeout2)
(留神这里的宏工作是整体的)
所以先走同步工作,输入 setTimeout1
。
接着走微工作,输入 promise2
。
而后推出宏工作 setTimeout(setTimeout2)
。
setTimeout(setTimeout2)
环境下的微工作和宏工作都没有,所以走完同步工作,输入 setTimeout2
,就完结了。
因而,输入程序:
promise1
setTimeout1
promise2
setTimeout2
- 题目 3:请输入上面代码打印状况
setTimeout(function() {console.log(4);
}, 0);
const promise = new Promise((resolve) => {console.log(1);
for (var i = 0; i < 10000; i++) {i == 9999 && resolve();
}
console.log(2);
}).then(function() {console.log(5);
});
console.log(3);
script
下:
- 同步工作:
console.log(1)
、console.log(2)
、console.log(3)
。 - 微工作:
Promise.then()
(等到 9999 再增加进来) - 宏工作
setTimeout
所以先走同步工作,留神当咱们 new Promsie()
的时候,外部的代码会执行的,跟同步工作一样的,而 .then()
在 resolve()
的状况下才会增加到微工作。
因而先输入 1 -> 2 -> 3
。
而后推出微工作 Promise.then()
,所以输入 5。
最初推出宏工作 setTimeout
,输入 4。
后果程序为:
1
2
3
5
4
6.4 综合
返回目录
综合题目就不给答案解析了,请自行脑补。
- 题目 1:请输入上面代码打印状况
setTimeout(function () {console.log('timeout1');
}, 1000);
console.log('start');
Promise.resolve().then(function () {console.log('promise1');
Promise.resolve().then(function () {console.log('promise2');
});
setTimeout(function () {Promise.resolve().then(function () {console.log('promise3');
});
console.log('timeout2')
}, 0);
});
console.log('done');
后果:
start
done
promise1
promise2
timeout2
promise3
timeout1
- 题目 2:请输入上面代码打印状况
console.log("script start");
setTimeout(function() {console.log("setTimeout---0");
}, 0);
setTimeout(function() {console.log("setTimeout---200");
setTimeout(function() {console.log("inner-setTimeout---0");
});
Promise.resolve().then(function() {console.log("promise5");
});
}, 200);
Promise.resolve()
.then(function() {console.log("promise1");
})
.then(function() {console.log("promise2");
});
Promise.resolve().then(function() {console.log("promise3");
});
console.log("script end");
输入:
script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0
- 题目 3:请输入上面代码打印状况
console.log(1);
setTimeout(() => {console.log(2);
new Promise((resolve) => {console.log(3);
}).then(() => {console.log(4);
});
}, 200);
new Promise((resolve) => {console.log(5);
resolve();}).then(() => {console.log(6);
});
setTimeout(() => {console.log(7);
}, 0);
setTimeout(() => {console.log(8);
new Promise(function (resolve) {console.log(9);
resolve();}).then(() => {console.log(10);
});
}, 100);
new Promise(function (resolve) {console.log(11);
resolve();}).then(() => {console.log(12);
});
console.log(13);
输入:
1
5
11
13
6
12
7
8
9
10
2
3
七 参考文献
返回目录
- [x] 浏览器与 Node 的事件循环 (Event Loop) 有何区别?【浏览倡议:20min】
- [x] 一次弄懂 Event Loop(彻底解决此类面试问题)【浏览倡议:20min】
- [x] 事件循环机制的那些事【浏览倡议:10min】
- [x] 深刻了解 js 事件循环机制(Node.js 篇)【浏览倡议:无】
- [x] 详解 JavaScript 中的 Event Loop(事件循环)机制【浏览倡议:5min】
- [x] 深刻了解 JavaScript Event Loop【浏览倡议:20min】
- [x]【THE LAST TIME】彻底吃透 JavaScript 执行机制【浏览倡议:20min】
- [x] JavaScript:彻底了解同步、异步和事件循环(Event Loop)【浏览倡议:10min】
- [x] 从 event loop 标准探索 javaScript 异步及浏览器更新渲染机会【浏览倡议:20min】
- [x] Tasks, microtasks, queues and schedules【浏览倡议:无】
- [x] The Node.js Event Loop, Timers, and process.nextTick()【浏览倡议:无】
7.1 requestAnimationFrame 参考文献
返回目录
- [x] 再谈谈 Promise, setTimeout, rAF, rIC【浏览倡议:10min】
- [x] window.requestAnimationFrame【浏览倡议:10min】
7.2 Web Worker 参考文献
返回目录
- [x] JavaScript 中的多线程 — Web Worker【浏览倡议:30min】
- [x] 浅谈 HTML5 Web Worker【浏览倡议:10min】
- [x] JavaScript 性能利器 —— Web Worker【浏览倡议:10min】
7.3 其余参考文献
返回目录
- [x] 浏览器过程?线程?傻傻分不清楚!【浏览倡议:5min】
jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。