[TOC]
# 线程与进程
## 概念
我们经常说JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程?
官方的说法是:**进程是 CPU资源分配的最小单位;线程是 CPU调度的最小单位**。这两句话并不好理解,我们先来看张图:
![](https://user-gold-cdn.xitu.io/2019/1/9/168333c14c85d794?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
* 进程好比图中的工厂,有单独的专属自己的工厂资源。
* 线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n的关系。也就是说**一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线**;
* 工厂的空间是工人们共享的,这象征**一个进程的内存空间是共享的,每个线程都可用这些共享内存**。
* 多个工厂之间独立存在。
<br>
## 多进程与多线程
* 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
* 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
以Chrome浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程(下文会详细介绍),比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
<br>
<br>
# 浏览器内核
简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。
浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:
* GUI 渲染线程
* JavaScript引擎线程
* 定时触发器线程
* 事件触发线程
* 异步http请求线程
<br>
## GUI渲染线程
* 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。
* 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
* 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。
<br>
## JS引擎线程
* 该线程当然是主要负责处理 JavaScript脚本,执行代码。
* 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。
* 当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。
<br>
## 定时器触发线程
* 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。
* 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。
<br>
## 事件触发线程
* 主要负责将准备好的事件交给 JS引擎线程执行。
比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。
<br>
## 异步http请求线程
* 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax等。
* 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。
<br>
<br>
# 为什么要有event loop
因为Javascript设计之初就是一门单线程语言,因此为了实现主线程的不阻塞,Event Loop这样的方案应运而生。
当然,现如今人们也意识到,单线程在保证了执行顺序的同时也限制了javascript的效率,因此开发出了web worker技术。这项技术号称让javascript成为一门多线程语言。
然而,使用web worker技术开的多线程有着诸多限制,例如:所有新线程都受主线程的完全控制,不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。另外,这些子线程并没有执行I/O操作的权限,只能为主线程分担一些诸如计算等任务。所以严格来讲这些线程并没有完整的功能,也因此这项技术并非改变了javascript语言的单线程本质。
<br />
<br />
# 浏览器的event loop
## 1.Micro-Task 与 Macro-Task
浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。**宏任务队列可以有多个,微任务队列只有一个**。
* 常见的 macro-task 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。
* 常见的 micro-task 比如: new Promise().then(回调)、MutationObserver、await(可以转化为Promise) 等。
<br>
## Event Loop 过程解析
一个完整的 Event Loop 过程,可以概括为以下阶段:
![](https://box.kancloud.cn/858d18555aedfb66761f12a500c2a9bf_394x449.png)
* 一开始执行栈空,我们可以把**执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则**。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。
* 全局上下文(script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。
* 上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是**一个一个**执行的;而 micro-task 出队时,任务是**一队一队**执行的。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。
* **执行渲染操作,更新界面**
* 检查是否存在 Web worker 任务,如果有,则对其进行处理
* 上述过程循环往复,直到两个队列都清空
<br>
![](https://box.kancloud.cn/ada9ad8af2ab065579a4f7e408f939c6_628x132.png)
<br>
**当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。**
<br>
## 2个script中执行 setTimeout、Promise
~~~
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
~~~
![](https://box.kancloud.cn/ae6001a935d7bbef44e7eade2b78a86b_611x341.png)
<br>
<br>
~~~
<script>
console.log('1');
setTimeout(function () {
console.log('2');
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8')
})
setTimeout(function () {
console.log('9');
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12')
})
})
</script>
<script>
console.log('a');
setTimeout(function () {
console.log('b');
new Promise(function (resolve) {
console.log('c');
resolve();
}).then(function () {
console.log('d')
})
})
new Promise(function (resolve) {
console.log('e');
resolve();
}).then(function () {
console.log('f')
})
setTimeout(function () {
console.log('g');
new Promise(function (resolve) {
console.log('h');
resolve();
}).then(function () {
console.log('i')
})
})
</script>
~~~
<br>
结果为
~~~
1
7
8
a
e
f
2
4
5
9
11
12
b
c
d
g
h
i
~~~
### 解析
可以将2个script标签替换为setTimeout来理解
1. 2个script作为第一、二个宏任务进入主线程,记为macro1、macro2
2. 执行macro1中的同步任务,遇到`console.log`,**输出1**
3. 遇到`setTimeout`,其回调函数被分发到宏任务Event Queue中。记为macro3
4. 遇到Promise,new Promise直接执行,**输出7**。then被分发到微任务Event Queue中。我们记为micro1。
5. 遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,记为macro4
6. 下表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
| macro | micro |
| --- | --- |
| macro2 | micro1 |
| macro3 | |
| macro4 | |
7. 执行micro1,**输出8**
8. 微任务队列清空,执行下一个宏任务
9. 执行macro2(即第二个script标签)的同步任务,遇到`console.log`,**输出a**
10. 遇到`setTimeout`,其回调函数被分发到宏任务Event Queue中。记为macro5
11. 遇到Promise,new Promise直接执行,**输出e**。then被分发到微任务Event Queue中。我们记为micro2。
12. 遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,记为macro6
13. 下表是第二轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1 7 8 a e。
| macro | micro |
| --- | --- |
| macro3 | micro2 |
| macro4 | |
| macro5 | |
| macro6 | |
14. 执行micro2,**输出f**
15. 微任务队列清空,执行下一个宏任务
16. 执行macro3,遇到console,**输出2**
17. 遇到Promise,**输出4**,then被分发到微任务Event Queue中。我们记为micro3。
18. 执行微任务micro3,**输出5**
19. 执行macro4,遇到console,**输出9**
20. 遇到Promise,**输出11**,then被分发到微任务Event Queue中。我们记为micro4。
21. 执行微任务micro4,**输出12**
22. 剩下的依次执行macro5、macro6,流程与macro3、macro4相同
<br>
## async、await
~~~
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0);
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')
~~~
浏览器和Node返回结果相同,注意遇到 `await` 会立即执行,阻塞外部代码的同步代码
~~~
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
~~~
<br>
<br>
~~~
async function async1(){
await async2()
console.log('async1 then1 end')
console.log('async1 then2 end')
}
async function async2 () {console.log('async2 function')}
async1();
new Promise(function(resolve){
resolve();
}).then(function(){
console.log('promise2')
}).then(function() {
console.log('promise3')
}).then(function() {
console.log('promise4')
})
.then(function () {
console.log('promise5')
})
~~~
Node 8.12.0、Chrome 72返回
~~~
async2 function
async1 then1 end
async1 then2 end
promise2
promise3
promise4
promise5
~~~
而在 Node 10.11.0、Chrome 70 上返回
~~~
async2 function
promise2
promise3
async1 then1 end
async1 then2 end
promise4
promise5
~~~
### 关于73以下版本和73版本的区别
* 在老版本版本以下,先执行`promise1`和`promise2`,再执行`async1`。
* 在73版本,先执行`async1`再执行`promise1`和`promise2`。
**主要原因是因为在谷歌(金丝雀)73版本中更改了规范,如下图所示:**
![](https://box.kancloud.cn/cf7323783c7b7ecbd1cf7adbda33d311_668x243.png)
* 区别在于`RESOLVE(thenable)`和`Promise.resolve(thenable)`之间的区别。
**在老版本中**
* 首先,传递给 `await` 的值被包裹在一个 `Promise` 中。然后,处理程序附加到这个包装的 `Promise`,以便在 `Promise` 变为 `fulfilled` 后恢复该函数,并且暂停执行异步函数,一旦 `promise` 变为 `fulfilled`,恢复异步函数的执行。
* 每个 `await` 引擎必须创建两个额外的 Promise(即使右侧已经是一个 `Promise`)并且它需要至少三个 `microtask` 队列 `ticks`(`tick`为系统的相对时间单位,也被称为系统的时基,来源于定时器的周期性中断(输出脉冲),一次中断表示一个`tick`,也被称做一个“时钟滴答”、时标。)。
**引用贺老师知乎上的一个例子**
~~~
async function f() {
await p
console.log('ok')
}
~~~
简化理解为:
~~~
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}
~~~
* 如果 `RESOLVE(p)` 对于 `p` 为 `promise` 直接返回 `p` 的话,那么 `p`的 `then` 方法就会被马上调用,其回调就立即进入 `job` 队列。
* 而如果 `RESOLVE(p)` 严格按照标准,应该是产生一个新的 `promise`,尽管该 `promise`确定会 `resolve` 为 `p`,但这个过程本身是异步的,也就是现在进入 `job` 队列的是新 `promise` 的 `resolve`过程,所以该 `promise` 的 `then` 不会被立即调用,而要等到当前 `job` 队列执行到前述 `resolve` 过程才会被调用,然后其回调(也就是继续 `await` 之后的语句)才加入 `job` 队列,所以时序上就晚了。
**谷歌(金丝雀)73版本中**
* 使用对`PromiseResolve`的调用来更改`await`的语义,以减少在公共`awaitPromise`情况下的转换次数。
* 如果传递给 `await` 的值已经是一个 `Promise`,那么这种优化避免了再次创建 `Promise` 包装器,在这种情况下,我们从最少三个 `microtick` 到只有一个 `microtick`。
### 详细过程
**73以下版本**
* 首先,打印`script start`,调用`async1()`时,返回一个`Promise`,所以打印出来`async2 end`。
* 每个 `await`,会新产生一个`promise`,但这个过程本身是异步的,所以该`await`后面不会立即调用。
* 继续执行同步代码,打印`Promise`和`script end`,将`then`函数放入**微任务**队列中等待执行。
* 同步执行完成之后,检查**微任务**队列是否为`null`,然后按照先入先出规则,依次执行。
* 然后先执行打印`promise1`,此时`then`的回调函数返回`undefinde`,此时又有`then`的链式调用,又放入**微任务**队列中,再次打印`promise2`。
* 再回到`await`的位置执行返回的 `Promise` 的 `resolve` 函数,这又会把 `resolve` 丢到微任务队列中,打印`async1 end`。
* 当**微任务**队列为空时,执行宏任务,打印`setTimeout`。
**谷歌(金丝雀73版本)**
* 如果传递给 `await` 的值已经是一个 `Promise`,那么这种优化避免了再次创建 `Promise` 包装器,在这种情况下,我们从最少三个 `microtick` 到只有一个 `microtick`。
* 引擎不再需要为 `await` 创造 `throwaway Promise` - 在绝大部分时间。
* 现在 `promise` 指向了同一个 `Promise`,所以这个步骤什么也不需要做。然后引擎继续像以前一样,创建 `throwaway Promise`,安排 `PromiseReactionJob` 在 `microtask` 队列的下一个 `tick` 上恢复异步函数,暂停执行该函数,然后返回给调用者。
### Chrome 72以下 async 转换为 Promise 过程
`resolve(thenable)`和`Promise.resolve(thenable)`的转换关系是这样的
~~~
new Promise(resolve=>{
resolve(thenable)
})
~~~
会被转换成
~~~
new Promise(resolve => {
Promise.resolve().then(() => {
thenable.then(resolve)
})
})
~~~
所以`async1`就变成了这样:
~~~
async function async1() {
return new Promise(resolve => {
Promise.resolve().then(() => {
async2().then(resolve)
})
}).then(() => {
console.log('async1 end')
})
}
~~~
同样,因为`resolve()`就等价于`Promise.resolve()`,所以
~~~
new Promise(function(resolve){
resolve();
})
~~~
等价于
~~~
Promise.resolve()
~~~
所以题目等价于
~~~
async function async1 () {
return new Promise(resolve => {
Promise.resolve().then(() => {
async2().then(resolve)
})
}).then(() => {
console.log('async1 end')
})
}
async function async2 () {}
async1()
Promise.resolve()
.then(function () {
console.log('promise2')
})
.then(function () {
console.log('promise3')
})
.then(function () {
console.log('promise4')
})
~~~
### 结论
在 chrome canary 73及未来可能被解析为
~~~
async function async1 () {
async2().then(() => {
console.log('async1 end')
})
}
async function async2 () {}
async1()
new Promise(function (resolve) {
resolve()
})
.then(function () {
console.log('promise2')
})
.then(function () {
console.log('promise3')
})
.then(function () {
console.log('promise4')
})
//async1 end
//promise2
//promise3
//promise4
~~~
在 chrome 70 被解析为,
~~~
async function async1 () {
return new Promise(resolve => {
Promise.resolve().then(() => {
async2().then(resolve)
})
}).then(() => {
console.log('async1 end')
})
}
async function async2 () {}
async1()
Promise.resolve()
.then(function () {
console.log('promise2')
})
.then(function () {
console.log('promise3')
})
.then(function () {
console.log('promise4')
})
//promise2
//promise3
//async1 end
//promise4
~~~
<br />
<br />
# timer
[MDN的setTimeout文档](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout)中提到HTML规范最低延时为4ms:
> In fact, 4ms is specified by the HTML5 spec and is consistent across browsers released in 2010 and onward. Prior to (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), the minimum timeout value for nested timeouts was 10 ms.
(补充说明:最低延时的设置是为了给CPU留下休息时间)
~~~
setTimeout(() => {
console.log(2)
}, 2)
setTimeout(() => {
console.log(1)
}, 1)
setTimeout(() => {
console.log(0)
}, 0)
// 输出结果为 1、0、2
~~~
<br />
## Chrome中的timer
~~~
// https://chromium.googlesource.com/chromium/blink/+/master/Source/core/frame/DOMTimer.cpp#93
double intervalMilliseconds = std::max(oneMillisecond, interval * oneMillisecond);
~~~
这里interval就是传入的数值,可以看出传入0和传入1结果都是oneMillisecond,即1ms。
这样解释了为何1ms和0ms行为是一致的,那4ms到底是怎么回事?我再次确认了HTML规范,发现虽然有4ms的限制,但是是存在条件的,详见规范第11点:
> If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
> 如果嵌套级别大于5,并且timeout 小于4,则将timeout设置为4。
MDN英文文档的说明也已经贴合了这个规范。
<br />
## Node中的timer
~~~
// https://github.com/nodejs/node/blob/v8.9.4/lib/timers.js#L456
if (!(after >= 1 && after <= TIMEOUT_MAX))
after = 1; // schedule on next tick, follows browser behavior
~~~
<br />
<br />
## 应用
如果从规范来看,microtask优先于task执行。那如果有需要优先执行的逻辑,放入microtask队列会比task更早的被执行,这个特性可以被用于在框架中设计任务调度机制。
如果从node的实现来看,如果时机合适,microtask的执行甚至可以阻塞I/O,是一把双刃剑。
综上,高优先级的代码可以用Promise/process.nextTick注册执行。
<br />
<br />
# 执行效率
从node的实现来看,setTimeout这种timer类型的API,需要创建定时器对象和迭代等操作,任务的处理需要操作小根堆,时间复杂度为O(log(n))。而相对的,process.nextTick和setImmediate时间复杂度为O(1),效率更高。
如果对执行效率有要求,优先使用process.nextTick和setImmediate。
<br />
<br />
# 参考资料
* [一次弄懂Event Loop(彻底解决此类面试问题)](https://juejin.im/post/5c3d8956e51d4511dc72c200)
* [浏览器与Node的事件循环(Event Loop)有何区别?](https://juejin.im/post/5c337ae06fb9a049bc4cd218)
* [这一次,彻底弄懂 JavaScript 执行机制](https://juejin.im/post/59e85eebf265da430d571f89)
* [Event Loop的规范和实现](https://zhuanlan.zhihu.com/p/33087629)
* [前端面试之道 - 掘金小册](https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5be04a8e6fb9a04a072fd2cd)
* [Tasks, microtasks, queues and schedules - JakeArchibald.com](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/)
* [更快的异步函数和 Promise](https://v8.js.cn/blog/fast-async/)
* [async await 和 promise微任务执行顺序问题](https://segmentfault.com/q/1010000016147496)
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码