├── .gitbook
└── assets
│ ├── 1606183293-1-.jpg
│ ├── 1606269234-1-.jpg
│ ├── 1606269283-1-.jpg
│ ├── 1606272131-1-.jpg
│ ├── 1606272131-2-.jpg
│ ├── 1606272131.jpg
│ ├── 1606272266-1-.jpg
│ ├── 1606272270-1-.jpg
│ ├── 1606272277-1-.jpg
│ ├── 1606272284-1-.jpg
│ ├── 1606272411-1-.jpg
│ ├── 1606272420-1-.jpg
│ ├── 1606272466-1-.jpg
│ ├── 1606272474-1-.jpg
│ ├── 1606272482-1-.jpg
│ ├── 1606272558-1-.jpg
│ ├── 1606272586-1-.jpg
│ ├── __proto__.jpg
│ ├── browsereventloop.png
│ ├── chromef12memory1.jpg
│ ├── chromef12memory2.jpg
│ ├── chromef12memory3.jpg
│ ├── chromef12memory4.jpg
│ ├── chromef12memory5.jpg
│ ├── chromef12memory6.jpg
│ ├── chromef12memory7.jpg
│ ├── chromef12memory8.jpg
│ ├── chromef12memory9.jpg
│ ├── chromef12performance1.jpg
│ ├── chromef12performance2.jpg
│ ├── chromef12performance3.jpg
│ ├── chromef12performance4.jpg
│ ├── constructorand__proto__.jpg
│ ├── constructorandprototype (1).jpg
│ ├── constructorandprototype.jpg
│ ├── duang.jpg
│ ├── executioncontextstack.gif
│ ├── functionand__proto__.jpg
│ ├── image (1).png
│ ├── image.png
│ ├── jseventloop.jpg
│ ├── jseventloopsimple.jpg
│ ├── kedakeda.jpg
│ ├── microandmacro.png
│ ├── outfunvo.jpg
│ ├── prototypechain.png
│ ├── prototypepollution.jpg
│ ├── qian-duan-mian-shi.png
│ ├── scopeline.png
│ ├── stackandqueue.jpg
│ ├── stackoverflowerror.jpg
│ ├── v2-7352dada85c0e791262828ca3375bcc7_r.jpg
│ ├── v2-e62b3d17489d5b7e87df0389d87acc40_r.jpg
│ └── vue-lifecycle.png
├── README.md
├── SUMMARY.md
├── basic-of-computer
└── http-tcp-ip.md
├── browser
└── event-loop-in-browser.md
├── es6
├── babel.md
├── build-babel-compiling-environment.md
└── let-and-const.md
├── frontend-frame
├── vue-basic.md
├── vue-frame.md
└── vue-ji-chu.md
├── javascript-basics
├── execution-context.md
├── javascript-basics.md
├── javascript-type-transform.md
├── modularization.md
├── prototype-chain-and-inheritance.md
└── scope-and-closure.md
└── performance-optimization
└── deounce-throttle.md
/.gitbook/assets/1606183293-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606183293-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606269234-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606269234-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606269283-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606269283-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272131-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272131-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272131-2-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272131-2-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272131.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272131.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272266-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272266-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272270-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272270-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272277-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272277-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272284-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272284-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272411-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272411-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272420-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272420-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272466-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272466-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272474-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272474-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272482-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272482-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272558-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272558-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/1606272586-1-.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/1606272586-1-.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/__proto__.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/__proto__.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/browsereventloop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/browsereventloop.png
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory1.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory2.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory3.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory4.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory5.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory6.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory7.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory8.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12memory9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12memory9.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12performance1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12performance1.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12performance2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12performance2.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12performance3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12performance3.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/chromef12performance4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/chromef12performance4.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/constructorand__proto__.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/constructorand__proto__.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/constructorandprototype (1).jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/constructorandprototype (1).jpg
--------------------------------------------------------------------------------
/.gitbook/assets/constructorandprototype.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/constructorandprototype.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/duang.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/duang.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/executioncontextstack.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/executioncontextstack.gif
--------------------------------------------------------------------------------
/.gitbook/assets/functionand__proto__.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/functionand__proto__.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/image (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/image (1).png
--------------------------------------------------------------------------------
/.gitbook/assets/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/image.png
--------------------------------------------------------------------------------
/.gitbook/assets/jseventloop.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/jseventloop.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/jseventloopsimple.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/jseventloopsimple.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/kedakeda.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/kedakeda.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/microandmacro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/microandmacro.png
--------------------------------------------------------------------------------
/.gitbook/assets/outfunvo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/outfunvo.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/prototypechain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/prototypechain.png
--------------------------------------------------------------------------------
/.gitbook/assets/prototypepollution.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/prototypepollution.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/qian-duan-mian-shi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/qian-duan-mian-shi.png
--------------------------------------------------------------------------------
/.gitbook/assets/scopeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/scopeline.png
--------------------------------------------------------------------------------
/.gitbook/assets/stackandqueue.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/stackandqueue.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/stackoverflowerror.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/stackoverflowerror.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/v2-7352dada85c0e791262828ca3375bcc7_r.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/v2-7352dada85c0e791262828ca3375bcc7_r.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/v2-e62b3d17489d5b7e87df0389d87acc40_r.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/v2-e62b3d17489d5b7e87df0389d87acc40_r.jpg
--------------------------------------------------------------------------------
/.gitbook/assets/vue-lifecycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CRONWMMM/gitbook-FrontendInterviewGuide/4ed9a519840a2d42e7bd20fe848da830a24c16c8/.gitbook/assets/vue-lifecycle.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: 长风破浪会有时,直挂云帆济沧海。
3 | ---
4 |
5 | # 序章
6 |
7 | > [《前端面试指南》Github 版](https://github.com/CRONWMMM/gitbook-FrontendInterviewGuide) 已同步更新,欢迎各位大佬 `star` `fork` 和 `issue` 😃😃
8 |
9 | 写这本 `gitbook` 的初衷其实很简单,就是为了面试。
10 |
11 | 在过去的两年多的工作时间里,我经历了两次面试准备。每次复习时, 那种面对着茫茫知识海洋却无处下手的迷茫感真的令人绝望,每个知识点好像都能聊个大概,但是零零碎碎地散落脑海里,无法串联起来,更不要说理解知识点里面的深刻机制。好不容易在网上东拼西凑找到相关内容复习完成,也只是够应付个普通面试,等入职后写点业务代码马上忘光,等下次还要从头再来一遍……
12 |
13 | 既然我不是个聪明的人,那还是找个地方把它们记录下来吧。
14 |
15 | 在编写指南的过程中,我逐渐意识到,这本指南并不仅仅只针对面试,更重要的是它在帮助我构建前端知识的体系。将零碎的知识点串联并且深入挖掘,不时翻阅,可以帮助我发现问题及复习总结。虽然写作的过程十分漫长,但是它确实是在帮助我进步,我也希望,它能够帮到你。
16 |
17 | 这篇指南,涵盖了**“js基础”、“ES6”、“浏览器”、“CSS”、“框架”、“性能优化”、“自动化构建”**在内的大部分面试常见考点。这是个非常庞大的工程,很多领域我自己也没有很深的涉入,但是我会一直编写和完善。
18 |
19 | 
20 |
21 | {% hint style="success" %}
22 | 每篇文章下方我会注明相关参考的链接,如果有遗漏请 `issue` 联系我,确实有所参考我会第一时间加上。同时,如果有什么想要了解的内容也可以在 `issue` 上给我留言😃
23 | {% endhint %}
24 |
25 | 希望这本指南能帮助大家找到理想的工作,梳理出自己的一套前端体系,我们开始吧。
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [序章](README.md)
4 |
5 | ## javascript 基础
6 |
7 | * [javascript 基础](javascript-basics/javascript-basics.md)
8 | * [javascript 类型转换](javascript-basics/javascript-type-transform.md)
9 | * [原型链及继承](javascript-basics/prototype-chain-and-inheritance.md)
10 | * [执行上下文](javascript-basics/execution-context.md)
11 | * [作用域和闭包](javascript-basics/scope-and-closure.md)
12 | * [模块化](javascript-basics/modularization.md)
13 |
14 | ## 性能优化
15 |
16 | * [防抖和节流](performance-optimization/deounce-throttle.md)
17 |
18 | ## ES6
19 |
20 | * [babel](es6/babel.md)
21 | * [搭建 babel 编译环境](es6/build-babel-compiling-environment.md)
22 | * [let 和 const](es6/let-and-const.md)
23 |
24 | ## 前端框架
25 |
26 | * [Vue基础](frontend-frame/vue-basic.md)
27 | * [Vue框架](frontend-frame/vue-frame.md)
28 |
29 | ## 浏览器
30 |
31 | * [浏览器下的 Event Loop](browser/event-loop-in-browser.md)
32 |
33 | ## 计算机基础
34 |
35 | * [HTTP/TCP/IP](basic-of-computer/http-tcp-ip.md)
36 |
37 |
--------------------------------------------------------------------------------
/basic-of-computer/http-tcp-ip.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: >-
3 | http/tcp/ip
4 | 作为面试时最常问到的基础问题,同时也是作为程序员的计算机基础,确实值得花精力好好准备。同时,这三个概念会引出许多面试问题,其中就有一条:当用户在地址栏中敲下回车到看到页面,这过程中发生了什么?事实上,这段过程中发生的事情,其实有一部分就与
5 | 与 HTTP TCP/IP 有关。
6 | ---
7 |
8 | # HTTP/TCP/IP
9 |
10 | ### 访问网页时发生了什么
11 |
12 | 当用户在浏览器地址栏输入地址,敲下回车键,直到看到网页界面,一般时间不过两三秒左右。然而在这瞬时间,计算机实际上已经完成了非常复杂的操作。这段过程中发生的事情,其实有很大一部分就与 `HTTP` `TCP/IP` 有关,我们可以简要的概括一下大概的流程。
13 |
14 | #### 第一步,找服务器 IP
15 |
16 | 当用户输入一个网址并按下回车键的时候,浏览器得到了一个域名。而在实际通信过程中,浏览器需要的是一个 `IP` 地址。为了获得 `IP` 地址,浏览器会做如下操作,一般我们把浏览器通过域名查找对应 `IP` 的行为叫做 `DNS` 解析。 1. 先找浏览器的本地的缓存 2. 再找电脑硬盘里的 `host` 文件,有没有记录这个域名和 `IP` 的映射关系 3. 实在没找到,只好通过网络链路去域名供应商那里查询
17 |
18 | #### 第二步,建立 TCP/IP 连接
19 |
20 | 1. 浏览器获取到了服务器对应 `IP`,就会向对应 `IP` 的服务器发送 `TCP` 连接请求。
21 | 2. 服务器收到请求后回应,双方多次确认后建立起 `TCP 双向连接`。
22 |
23 | > 从客户端发起连接请求一直到 `TCP` 连接建立,这个过程,叫做 `三次握手`。
24 |
25 | {% hint style="info" %}
26 | 如果请求是 HTTPS 的,还需要在 `TCP` 连接上,再通过 `SSL` 或 `TLS` 提供加密处理数据、验证对方身份以及数据完整性,来保证数据传输的安全。
27 | {% endhint %}
28 |
29 | #### 第三步,请求资源
30 |
31 | 1. `TCP` 连接创建完成,浏览器开始向服务端发送正式的 `HTTP` 请求的数据包。
32 | 2. 服务端接受请求,对请求进行解析,经过数据操作后,返回客户端需要的数据包。
33 |
34 | #### 第四步,浏览器渲染
35 |
36 | 浏览器获取到需要的数据以后,对数据进行拼接、解析、执行,最终将完整的网页绘制在页面上。
37 |
38 | #### 第五步,浏览器缓存
39 |
40 | 浏览器拿到服务端返回的数据后,会根据一定的策略进行数据的缓存,这样在下一次请求同样数据的时候,就可直接到缓存拿取,不再请求服务器。
41 |
42 | 上述流程可以看作是一个应用在完整网络通信过程中的实践场景,其中带出了很多网络通信的知识点,下面就以这条线为索引,对其中涉及到的知识碎片进行阐述和说明。
43 |
44 | ### 经典网络五层模型
45 |
46 | 
47 |
48 | 在每台计算机设备上,都有这么一套系统链路的关系,来保证网络传输的正常进行,因为统一集成了这么一套经典模型,所以自己使用的计算机也是可以作为一台服务器来提供网络服务的。
49 |
50 | #### 应用层:
51 |
52 | 应用层包含了我们所说的 `HTTP` 协议,为各个应用软件提供了很多服务,常见的应用层服务有:`HTTP` 服务 、`FTP` 服务 、`Email` 服务等。应用层屏蔽了底层模型的相关细节,作为应用支持,只提供给使用者一些必要的使用方式。
53 |
54 | #### 传输层
55 |
56 | 常见的传输层协议有 `TCP` 和 `UDP` ,传输层作为为应用层的基础,定义了“端到端(end to end)”之间数据间的传输方式,比如:两台设备如何建立连接?设备之间需要以何种规范进行数据传输?需要以什么方式进行数据的分片、重组、拼接?这些都是传输层为我们定义好的。
57 |
58 | #### 网络层
59 |
60 | 通常我们常说的 `IP` 协议就位于这一层。网络层为数据在结点之间传输创建逻辑链路,当我们在浏览器敲下域名,浏览器在网络里如何通过这个域名,找到对应的 `IP` 映射,这个查询的逻辑关系和链路,是网络层规范和定义的。
61 |
62 | #### 数据链路层
63 |
64 | 数据链路层在通信实体间建立数据链路连接,物理设备连接完成以后,需要相应的软件和驱动来连接和打通这些物理设备,创建电路的连接。
65 |
66 | #### 物理层
67 |
68 | 定义物理设备如何传输数据,常见的物理层有网线,光缆,网卡,声卡等,物理层是一切软件的基础。
69 |
70 | ### URI、URL 和 URN
71 |
72 | 对于 `URL` 我们基本比较熟悉,然而对 `URI` 和 `URN` 的了解可能比较少,`URI`、`URL` 和 `URN` 是识别、定位和命名互联网上的资源的标准途径。
73 |
74 | 当我们在浏览器地址栏里输入域名的那一刻,其实已经和这三个概念牵扯上了联系。
75 |
76 | 
77 |
78 | #### URI
79 |
80 | * Uniform Resource Identifier,统一资源标识符,简称为 `URI`。
81 | * 每个 Web 服务器都有一个 `URI` 标识符,它在世界范围内唯一标识并定位信息资源,一个资源信息有了 `URI` 标识以后,在互联网上就能通过一个固定的地址访问到这个资源。
82 | * 它具有两种形式,URN (统一资源名)、URL(统一资源定位符),也就是说 `URL` 和 `URN` 是它的子集。
83 |
84 | #### URL
85 |
86 | Uniform Resource Locator,统一资源定位符,简称 `URL`,下图是一个完整的 `URL` 组成。
87 |
88 | 
89 |
90 | 一个完整的 `URL` 从左到右包含如下部分: 1. **schema** 标识了这个资源地址所基于的访问协议,常见的比如:`HTTP` 和 `FTP`。 2. **user information** 标识了用户信息(如果这个资源需要用户信息认证的话),不过一般现在的认证都不采用这种方式,一来输入非常麻烦,二来不安全。 3. **host** 标识了资源的域信息,可以是域名,也可以是 `IP` ,这块的作用主要是找到资源所存放的物理服务器地址。 4. **port** 端口号,一个物理服务器,通过开启不同的端口,就同时可以运行多个 web 服务器,资源文件会部署在某一个 web 服务器的某一个地方,而端口号就是用来定位资源存在的 web 服务器的。 5. **path** 路径,或者叫路由,一个 web 服务器下有许多目录,一般 path 就是用来定位到资源文件所存放的目录的。由于现在很多的 web 应用非常庞大,这个路径也不一定就是目录地址,也可能是 web 服务器指定的静态资源文件的请求地址。 6. **query** 查询字符串,一般用于 `GET` 查询,传递查询参数。 7. **fragment** 片段,哈希,或者叫锚点,主要用于前端文档的定位,或者是前端渲染时控制路由跳转的手段。
91 |
92 | {% hint style="info" %}
93 | 这里需要注意将 `URL` 与网址区别开来。 `URL` 不仅仅包含了网页的资源地址,还包含了组成网页所需的图片、视频等超文本资源,以及 css js 等资源地址。 网址本质上是 `IP` 地址的一个更有辨别度的映射,在通过 `DNS` 解析之后,浏览器最先拿到的是 html 文档的 `URL` 地址,根据浏览器对 Html 文档的解析,继续通过网页内其他资源文件的 `URL` 获取对应的资源文件。
94 | {% endhint %}
95 |
96 | #### URN
97 |
98 | Uniform Resource Name,统一资源名称,简称 `URN`,它的用处简单说就是永久定位资源,因为同一个资源可能会更换存储位置,存储位置一旦更换,再访问原来的 url 肯定是拿不到的,URN 就是解决这个问题的,不管资源位置怎么移动,只要访问同一个 `URN` 都能定位到。
99 |
100 | ### TCP/IP 协议族
101 |
102 | `TCP` 连接的建立,是网络通信成功不可或缺的一步,因此,`TCP/IP` 也成为了一个十分重要的知识点。
103 |
104 | {% hint style="info" %}
105 | `TCP/IP` 协议(传输控制协议/互联网协议)不是简单的一个协议,而是一组特别的协议,包括:TCP,IP,UDP,ARP等,这些被称为子协议。在这些协议中,最重要、最著名的就是 `TCP` 和 `IP`。因此我们习惯将整个协议族称为 `TCP/IP`。
106 | {% endhint %}
107 |
108 | * **IP 协议**
109 | * `IP` 协议使互联网成为一个允许连接不同类型的计算机和不同操作系统的网络。
110 | * `IP` 地址是 `IP` 协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,相当于这台机器的暂用名,别的机器可以通过这个名字找到它,进而能互相建立起连接进行通信和交流。
111 | * **TCP 协议**
112 | * `TCP` 协议是面向连接的全双工协议,因此不管是客户端还是服务端都能在 `TCP` 连接通道下向对端接收和发送数据。
113 | * `TCP` 相比于 `UDP` 的优势在于它的传输稳定性,在数据传输之前必须经过三次握手建立连接;在数据传输过程中必须保证数据有序完整地传到对端。
114 | * `TCP` 相比于 `UDP` 的劣势在于它的复杂度,连接建立、断开都是比较大的性能开销,而且数据传输过程中一旦卡住,则必须等前面的数据发送完毕以后,后续数据才能继续传输。
115 | * 每台服务器可提供支持的 `TCP` 连接数量是有限的,所以这也使得 `TCP` 连接变成了稀缺资源,经不起浪费。
116 | * **UDP 协议**
117 | * `UDP` 协议是面向无连接的,不需要在传输数据前先建立连接,想发就发想传就传。
118 | * `UDP` 做的工作只是报文搬运,不负责有序且不丢失地传递到对端,因此容易出现丢包的情况。
119 | * `UDP` 不仅支持一对一的传输方式,还支持一对多、多对多、多对一的方式,也就是说 `UPD` 提供了单播、多播、广播的功能。
120 | * `UDP` 相比于 `TCP` 的优势在于它的轻量、高效和灵活,在一些对于实时性应用要求较高的场景下需要使用到 `UDP`,比如直播、视频会议、LOL等实时对战游戏。
121 | * `UDP` 相比于 `TCP` 的劣势在于它的不可靠性和不稳定性。
122 |
123 | ### TCP 连接
124 |
125 | 在客户端发送正式的 `HTTP` 请求之前,需要先创建一个 `TCP` 连接,在创建的 `TCP Connect` 通道下,所有的 `HTTP` 请求和响应才能正常的发送和接受。
126 |
127 | 在不同的 `HTTP` 协议版本里,这个 `TCP` 连接通道的创建和持续机制也有所不同。
128 |
129 | * 在 `HTTP1.0` 中,每一次 `HTTP` 请求都会创建一个 `TCP` 连接,在请求发送完成,服务器响应以后,这个 `TCP` 连接就自动断开了。
130 | * 在 `HTTP1.1` 中,可以通过手动设置 `Connection: keep-alive` 请求头来建立 `TCP` 的持久连接,多个 `HTTP` 请求可以共用一个 `TCP` 连接。但是 `TCP` 连接存在线头阻塞,即若干个请求排队等待发送,一旦有某请求超时等,后续请求只能被阻塞。
131 | * 在 `HTTP2` 中,采用了信道复用,使 `TCP` 连接支持并发请求,即多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行吗,这样一来,大部分请求可以使用一个 `TCP` 连接,而不用创建新的 `TCP` 连接通道,既节省了三次握手的开销,又节约了服务端维护 `TCP` 端口的成本。
132 |
133 | ### TCP 的三次握手和四次挥手
134 |
135 | #### 三次握手
136 |
137 | 
138 |
139 | {% hint style="info" %}
140 | 关于 `ACK`、`FIN`、`SYN` 状态码的含义 1. `ACK` 用于确认,表示通知对方,我已经收到你发来的信息了。 2. `FIN` 用于结束,表示告知对方,我这边已经结束,数据全部发送完毕,没有后续输出,请求终止连接。 3. `SYN` 用于同步和建立连接,表示告知对方,我这边请求同步建立连接。
141 | {% endhint %}
142 |
143 | 1. 第一次握手:由客户端向服务端发送连接请求 `SYN` 报文,该报文段中包含自身的数据通讯初始序号,请求发送后,客户端便进入 `SYN-SENT` 状态。
144 | 2. 第二次握手:服务端收到连接请求报文段后,如果同意连接,**则会发送一个包含了 `ACK` 和 `SYN` 报文信息的应答,该应答中也会包含自身的数据通讯初始序号**(在断开连接的“四次挥手”时,`ACK` 和 `SYN` 这两个报文是作为两次应答,独立开来发送的,因此会有四次挥手),服务端发送完成后便进入 `SYN-RECEIVED` 状态。
145 | 3. 第三次握手:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 `ESTABLISHED` 状态,服务端收到这个应答后也进入 `ESTABLISHED` 状态,此时连接建立成功。
146 |
147 | {% hint style="info" %}
148 | 面试时可能会问的一个问题就是,明明两次握手就能确定的连接,为什么需要三次握手? 因为由于很多不可控制的因素,例如网络原因,可能会造成第一次请求隔了很久才到达服务端,这个时候客户端已经等待响应等了很久,之前发起的请求已超时,已经被客户端废弃掉不再继续守着监听了。 然而服务端过了很久,收到了废弃的延迟请求,发起回应的同时又开启了一个新的 `TCP` 连接端口,在那里呆等客户端。 而服务端能维护的 `TCP` 连接是有限的,这种闲置的无用链接会造成服务端的资源浪费。 因此在服务端发送了 `SYN` 和 `ACK` 响应后,需要收到客户端接的再次确认,双方连接才能正式建立起来。三次握手就是为了规避这种由于网络延迟而导致服务器额外开销的问题。
149 | {% endhint %}
150 |
151 | #### 四次挥手
152 |
153 | 
154 |
155 | 和建立 `TCP` 连接类似,断开 `TCP` 连接也同样需要客户端于服务端的双向交流,因为整个断开动作需要双端共发送 4 个数据包才能完成,所以简称为“四次挥手”。 1. 第一次挥手:客户端认为自己这边的数据已经全部发送完毕了,于是发送一个 `FIN` 用来关闭客户端到服务端的数据传输,发送完成以后,客户端进入 `FIN_WAIT_1` 状态。 2. 第二次挥手:服务端收到客户端发送回来的 `FIN` 以后,会告诉应用层要释放 TCP 链接,并且发送一个 `ACK` 给客户端,表明已经收到客户端的释放请求了,不会再接受客户端发来的数据,自此,服务端进入 `CLOSE_WAIT` 的状态。 3. 第三次挥手:服务端如果此时还有未发送完的数据可以继续发送,发送完毕后,服务端也会发送一个释放连接的 `FIN` 请求用来关闭服务端到客户端的数据传送,然后服务端进入 `LAST_ACK` 状态。 4. 第四次挥手:客户端接收到服务端的 `FIN` 请求后,发送最后一个 `ACK` 给服务端,接着进入 `TIME_WAIT_2` 状态,该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,客户端就进入 `CLOSED` 状态.服务端在收到应答消息后,也会进入 `CLOSED` 状态,至此完成四次挥手的过程,双方正式断开连接。
156 |
157 | 上面的内容可能还是有些不够直观,所以我还准备了一段人话来描述整个过程: 1. 客户端:喂,我好了。 2. 服务端:噢,你好了是吧,我知道了,我还没好,你等一哈。 3. 服务端:OK,现在我也好了。 4. 客户端:收到,这次玩的很开心,我们下次再约。
158 |
159 | {% hint style="info" %}
160 | 可能有些面试中会问,为什么建立连接有三次握手,而断开连接却有四次? 这是因为在建立连接过程中,服务端在收到客户但建立连接请求的 `SYN` 报文后,会把 `ACK` 和 `SYN` 放在一个报文里发送给客户端。 而关闭连接时,服务端收到客户端的 `FIN` 报文,只是表示客户端不再发送数据了,但是还能接收数据,而且这会儿服务端可能还有数据没有发送完,不能马上发送 `FIN` 报文,只能先发送 `ACK` 报文,先响应客户端,在确认自己这边所有数据发送完毕以后,才会发送 `FIN`。 所以,在断开连接时,服务器的 `ACK` 和 `FIN` 一般都会单独发送,这就导致了断开连接比请求连接多了一次发送操作。
161 | {% endhint %}
162 |
163 | ### HTTP 定义
164 |
165 | 一旦端对端成功建立起了 `TCP` 连接,下一步就要开始发送正式的 `HTTP` 请求了。流淌在 `TCP Connect` 通道里的 `HTTP` 只负责传输数据包,并没有连接的概念,因此 `HTTP` 也被叫做“无状态协议”。
166 |
167 | {% hint style="info" %}
168 | `HTTP` 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,它通常运行在 `TCP` 之上,通过浏览器和服务器进行数据交互,进行超文本(文本、图片、视频等)传输的规定。也就是说,`HTTP` 协议规定了超文本传输所要遵守的规则。
169 | {% endhint %}
170 |
171 | 1. **`HTTP` 协议是无状态的**。这意味着客户端和服务端之间无法知晓当前对方的状态信息,`HTTP` 请求本身是不带有任何状态存储的。但实际情况下,客户端和服务端必然需要状态的认证和交互,所以就引入了 `Cookie`, 用于存储当前浏览器的一些状态信息,每次通过独立的 `HTTP` 请求进行收发,从而解决这个问题。
172 | 2. **`HTTP` 请求互相独立**。`HTTP` 互相之间都是一个独立的个体请求,在客户端请求网页时多数情况下并不是一次请求就能成功的,服务端首先是响应 `HTML` 页面,然后浏览器收到响应之后发现页面还引用了其他的资源,例如,CSS,JS文件,图片等等,还会自动发送 `HTTP` 请求获取这些需要的资源。
173 | 3. **`HTTP` 协议基于 `TCP` 协议**。`HTTP` 协议目的是规定客户端和服务端数据传输的格式和数据交互行为,并不负责数据传输的细节,底层是基于 `TCP` 实现的。现在使用的版本当中是默认持久连接的,也就是多次 `HTTP` 请求使用一个 `TCP` 连接。
174 |
175 | {% hint style="info" %}
176 | `HTTP` 请求和 `TCP` 连接是不一样的,`HTTP` 是在 `TCP` 连接建立的基础上而发起的传输请求,在同一个 `TCP` 连接通道下,可以发送多个 `HTTP` 请求,举个例子的话就是高速公路和车子的关系。
177 | {% endhint %}
178 |
179 | ### HTTP 发展历史
180 |
181 | #### HTTP 0.9 版本
182 |
183 | * 只有一个 `GET` 命令。
184 | * 没有请求头和响应头来描述传输相关的数据信息。
185 | * 服务器发送完数据后,直接关闭 `TCP` 连接,不支持 `TCP` 持久化连接。
186 |
187 | #### HTTP 1.0 版本
188 |
189 | * 增加了很多命令,`HEAD`、`POST`、`PUT`、`DELETE` 等。
190 | * 增设了 `status code` 状态码和 `header` 请求头和响应头。
191 | * 增加了多字符集支持、多部分发送、权限、缓存等。
192 | * 可通过开启 `Connection: keep-alive` 来指定使用 `TCP` 长连接
193 |
194 | #### HTTP 1.1 (目前普遍使用)
195 |
196 | * 默认支持持久连接
197 | * 默认支持长连接(PersistentConnection),即默认开启 `Connection: keep-alive`。
198 | * 支持请求的流水线(Pipelining)处理,即在一个 `TCP` 连接上可以传送多个 `HTTP` 请求和响应。
199 | * 增加了 `host` 请求头字段,通过对 `host` 解析,就能够允许在同一台物理服务器上运行多个软件服务,极大提高了服务器的使用率。目前的 `nginx` 反向代理就是根据 `HTTP` 请求头中的 `host` 来分辨不同的请求,从而将这些请求代理到同一台服务器不同的软件服务上。
200 |
201 | #### HTTP 2.0
202 |
203 | * `HTTP1.x` 的解析是基于文本,存在解析上的缺陷;而 `HTTP2.0` 直接使用二进制的解析方式来替代 `HTTP 1.X` 的字符串解析,更为高效和健壮。
204 | * `HTTP2.0` 所有数据以“帧”的方式进行传输,因此同一个连接中发送的多个请求不再需要按照顺序进行返回处理,可以达到并行的数据传输。
205 | * `HTTP2.0` 压缩头信息进行传输数据量的优化。`HTTP1.x` 的请求头带有大量信息,而且每次都要重复发送,`HTTP2.0` 使用 `encoder` 来减少需要传输的请求头大小,通讯双方各自缓存一份 `header fields` 表,既避免了重复的传输,又减小了传输信息的大小。
206 | * `HTTP2.0` 新增了 `server push`(服务端推送) 的概念,服务端可以主动发起一些数据推送。比如,服务端在接收到浏览器发来的 `HTML` 请求的同时,可以主动推送相关的资源文件(js/css)给客户端,并行发送,提高网页的传输和渲染效率。
207 | * 目前如果要使用 `HTTP2` 需要首先使用 `HTTPS` 在这基础上,才能使用 `HTTP2`
208 |
209 | {% hint style="info" %}
210 | `HTTP 2.0` 相比于 `HTTP 1` 最直观的图片加载性能提升,可以看 [`HTTP 2` 性能提升的官方演示](https://link.zhihu.com/?target=https://http2.akamai.com/demo)
211 | {% endhint %}
212 |
213 | #### HTTPS
214 |
215 | 我们经常会在有些网页上看到悬浮的弹窗或者广告,有的时候甚至会在自己编写的上线网页上也看到这些垃圾广告,然而开发者明明没有写过这些东西,可是这种垃圾信息是怎么上去的呢? 究其根本原因就在于各种代理服务,当我们从客户端发起一个 `HTTP` 请求,并不是直接就能传递到目标服务器的,期间会经过层层的代理服务,我们常用的 `nginx` ,以及在 `DNS` 解析过程中要经过的宽带运营商,都是一种代理服务。 由于 `HTTP` 时使用明文字符串来传递数据的,那么这些数据就能很轻易地被中间服务读取甚至篡改,那么中间服务拿到了原始的 `HTML` 数据,想插入点小广告进去自然不是难事。
216 |
217 | `HTTPS` 是为了解决 `HTTP` 明文传输而出现的安全问题而出现的一种解决机制 ———— 对 `HTTP` 请求中的信息进行加密之后传输,从而有效地防止了中间代理服务截获或篡改信息的问题。
218 |
219 | `HTTPS` 其实就是一个安全加强版的 `HTTP 1.1` ,有几点需要注意的是: 1. `HTTPS` 协议需要到 `CA` 申请证书,一般免费证书很少,需要交费 2. `HTTP` 协议运行在 `TCP` 之上,所有传输的内容都是明文,`HTTPS` 运行在 `SSL/TLS` 之上,`SSL/TLS` 运行在 `TCP` 之上,所有传输的内容都经过加密的。 3. `HTTP` 和 `HTTPS` 使用的是完全不同的连接方式,用的端口也不一样,前者是 **80**,后者是 **443**。 4. `HTTPS` 可以有效的防止运营商劫持,解决了防劫持的一个大问题。
220 |
221 | ### HTTP 的报文组成
222 |
223 | `HTTP` 是以请求和响应的形式存在的,由于发起方主动发起一个 `HTTP` 请求,然后由响应方回应,双方按照一定的报文格式进行数据的互传,一个完整的 `HTTP` 报文通常由 **首行**、**首部** 和 **主体** 构成。
224 |
225 | 
226 |
227 | #### 首行
228 |
229 | 首行并不属于 `Http Headers` ,它包含了: 1. **HTTP Method**(`GET`、`POST`、`PUT`、`DELETE` 等 ),不同的 `HTTP Method` 有不同的语意。
230 |
231 | | HTTP Method | 对应予以 |
232 | | :--- | :--- |
233 | | GET | 一般用于获取服务器资源 |
234 | | POST | 一般用于传输实体主体 |
235 | | PUT | 一般用于传输文件 |
236 | | DELETE | 用于删除文件 |
237 | | HEAD | 用于获取报文首部,不返回报文主体 |
238 | | OPTIONS | 用于预检请求中,询问请求URI资源支持的方法 |
239 |
240 | {% hint style="info" %}
241 | `HTTP Method` 只是 `HTTP` 协议推崇的一种规范,就像 `ESLint`,你可以选择遵循,也可以选择不遵循,它们所作的事情实质上没有差别,只是语义化更明确。
242 | {% endhint %}
243 |
244 | 1. **URL**请求资源的地址,这个地址只会包含请求的路由地址。
245 | 2. **协议的版本**,`HTTP 1.0 / HTTP 1.1 / HTTP 2`。
246 | 3. **HTTP 返回状态码**(响应报文首行包含)
247 |
248 | > `HTTP` 定义了40个标准状态代码,可用于传递客户端请求的结果,状态代码分为以下五类,关于各个分段下的返回状态码信息可以参考 [HTTP 响应码](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status):
249 |
250 | 
251 |
252 | {% hint style="warning" %}
253 | 这边需要注意的一点是,一个好的 HTTP 应用服务应该是有完善的 HTTP status code 的返回信息的,即访问者单从 HTTP status > code 上就能得知当前 HTTP 请求的状态信息。 而目前我们大部分的开发模式下的 HTTP 返回码,只有 `200` 和 `500`。服务端的同学会先把 `200` 返回过来,然后再告诉你出了什么 “没登录” / “没认证” / “没权限” 这一类的问题。 业界也有一句戏言:**又不是不能用**,其实这种开发方式是不正确的,不管从代码的维护性还是个人自身发展角度,我们都需要> 尽量避免这种问题。
254 | {% endhint %}
255 |
256 | #### HTTP 头信息
257 |
258 | HTTP 头信息,即 `HTTP Header`,首行换行后的信息都是 `HTTP Header`。`HTTP header` 里一般存放了客户端和服务端之间交互的非业务信息,例如:本次请求的数据类型、请求日期、字符集支持、自定义头部字段、一些通信凭证和缓存支持等。 `HTTP Header` 完整字段列表:[传送门](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers)
259 |
260 | #### 主体
261 |
262 | 主体,即 `HTTP body`,`HTTP Header` 信息和主体信息间以一个空行 + 一个换行来区分。`HTTP body` 里一般会放置请求的一些具体业务信息
263 |
264 | ### HTTP 数据协商
265 |
266 | 在 HTTP 协议中,数据协商是这样一种机制,客户端通过请求头告知服务端本次请求希望获取的数据格式、展现形式、以及数据的压缩方式等。常见的数据协商例如,文档使用的自然语言,图片的格式,或者内容编码形式。 服务端可以对请求头中携带的数据协商字段进行解析,然后在返回客户端数据的时候,也会用**相对字段**来通知客户端:本次返回的数据格式、压缩方式等信息。这样浏览器就可以使用特定的解析方式,来对这些资源进行解析、处理和渲染。
267 |
268 | 下面简单列举一些常用的数据协商字段,[完整的数据协商信息传送门](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Content_negotiation)
269 |
270 | * `Accept` 请求头字段,指定期望获得的数据类型
271 | * `Accept-Encoding` 请求头字段,指定期望获得的数据需要以什么样的编码方式进行传输,常用于限制服务端对数据的压缩方式,常见的 JS 文件包大小优化的 GZIP 压缩,就使用了这个方法
272 | * `Accept-Language` 请求头字段,指定期望获得的数据语言类型:中文、英语、还是其他语言,这个头信息字段,一般是浏览器自动加上的
273 | * `User-Agent` 请求头字段,指定本次请求的浏览器信息,服务端可根据此信息选择不同兼容性的页面返回给用户,或者是做用户使用浏览器信息、操作系统等数据的统计
274 | * `Content-Type` 响应头字段,请求头里的 `Accept` 字段可能会指定好几种可以接受的数据格式,服务端最终会返回一种数据格式给客户端
275 | * `Content-Encoding` 响应头字段,对应 `Accept-Encoding`
276 | * `Content-Language` 响应头字段,对应 `Accept-Language`
277 |
278 | ### HTTP 长连接
279 |
280 | 每一个 `HTTP` 请求都需要在 `TCP` 连接通道里才能完成发送和接受。在 `HTTP` 协议的早期版本里,每一条 `HTTP` 请求发送之前,都会创建一条新的 `TCP` 连接通道,在这个请求完成以后,该条 `TCP` 通道就会自动关闭。 这样带来的问题就是,单条 `TCP` 连接没有办法复用,造成很大的新能浪费。好在这一问题随着 `HTTP` 协议的逐步完善已经得到解决。
281 |
282 | 在 `HTTP 1.0` 中引入的 `Connection` 头字段,允许对其设置 `Keep-Alive` 或者是 `Close` 来决定是否需要复用 TCP 连接,还是说在一次请求完成之后直接关闭。而在 `HTTP 1.1` 中默认双端都会默认开启这个字段,即默认支持 `HTTP` 的长连接。
283 |
284 | 
285 |
286 | > 需要注意的是:`Connection: Keep-Alive` 需要双端同时开启才能启动 `HTTP` 长连接,如果任何一段手动设置 `Connection` 为 `Close`,长连接都无法位置,因为 TCP 连接的建立和持久保持是一个双端交互的过程。
287 |
288 | 那么我们在本地如何看到 `TCP` 的连接 ID 呢,可以打开 `Chrome` 的调试工具来查看:
289 |
290 | 
291 |
292 | 图上可以看到有不同的 `Connection ID`,这就代表着本次请求实际上是开启了一个新的 `TCP` 连接,最下面的请求的 `Connection ID` 都是相同的,代表着多个 `HTTP` 请求复用了同一个 `TCP` 连接。
293 |
294 | {% hint style="info" %}
295 | Chrome 浏览器所能够支持的最大并发 TCP 连接数是 **6个**,并且在 `HTTP 2.0` 以下的 `HTTP` 版本中,请求是阻塞的。也就是说,一旦六个连接开满,前面的请求未完成,那么后续请求就会被阻塞,直到前面的请求返回,后续才能继续发送。
296 | {% endhint %}
297 |
298 | ### HTTP 缓存
299 |
300 | > 虽然 HTTP 缓存不是必须的,但重用缓存的资源通常是必要的。然而常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。缓存的关键主要包括 `request method` 和目标 URI(一般只有 GET 请求才会被缓存)。
301 |
302 | #### 缓存读取策略
303 |
304 | 前端环境下的文件缓存,分为几个不同的位置。当我们打开 Chrome 控制台,查看 Network 下每条请求记录的 size 选项,会发现非常丰富的来源信息。
305 |
306 | 
307 |
308 | 对于前端浏览器环境来说,缓存读取位置是由先后顺序的,顺序分别是(由上到下寻找,找到即返回;找不到则继续)
309 |
310 | * Service Worker
311 | * Memory Cache
312 | * Disk Cache
313 | * 网络请求
314 |
315 | **Service Worker**
316 |
317 | {% hint style="info" %}
318 | Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
319 | {% endhint %}
320 |
321 | * 浏览器优先查找。
322 | * 持久存储。
323 | * 可以更加灵活地控制存储的内容,可以选择缓存哪些文件、定义缓存文件的路由匹配规则等。
324 | * 可以从 Chrome 的 F12 中,Application -> Cache Storage 查看。
325 |
326 | **Memory Cache**
327 |
328 | * `memory cache` 是内存中的缓存存储。
329 | * 读取速度快。
330 | * 存储空间较小。
331 | * 存储时间短,当浏览器的 `tab` 页被关闭,内存资源即被释放。
332 | * 如果明确指定了 `Cache-Control` 为 `no-store`,浏览器则不会使用 `memory-cache`。
333 |
334 | **Disk Cache**
335 |
336 | * `Disk Cache` 是硬盘中的缓存存储。
337 | * 读取速度慢于 `Memory Cache` ,快于网络请求。
338 | * 存储空间较大。
339 | * 持久存储。
340 | * `Disk Cache` 严格依照 `HTTP` 头信息中的字段来判断资源是否可缓存、是否要认证等。
341 | * 经常听到的“强制缓存”,“对比缓存”,以及 `Cache-Control` 等,归于此类。
342 |
343 | **网络请求**
344 |
345 | 如果一个请求的资源文件均未命中上述缓存策略,那么就会发起网络请求。浏览器拿到资源后,会把这个新资源加入缓存。
346 |
347 | #### Cache-Control
348 |
349 | {% hint style="info" %}
350 | HTTP/1.1定义的 Cache-Control 头用来区分对缓存机制的支持情况, 请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。需要注意的是,数据变化频率很快的场景并不适合开启 `Cache-Control`。
351 | {% endhint %}
352 |
353 | | 指令 | 作用 |
354 | | :--- | :--- |
355 | | public | 公共缓存:表示该响应可以被任何中间人(比如中间代理、CDN等)缓存。 |
356 | | private | 私有缓存:表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。 |
357 | | max-age | (单位/秒)设置缓存的过期时间,过期需要重新请求,否则就读取本地缓存,并不实际发送请求 |
358 | | s-maxage | (单位/秒)覆盖 max-age,作用一样,只在代理服务器中生效 |
359 | | max-stale | (单位/秒)表示即使缓存过期,也使用这个过期缓存 |
360 | | no-store | 禁止进行缓存 |
361 | | no-transform | 不得对资源进行转换或压缩等操作,Content-Encoding、Content-Range、Content-Type 等 HTTP 头不能由代理修改(有时候资源比较大的情况下,代理服务器可能会自行做压缩处理,这个指令就是为了防止这种情况)。 |
362 | | no-cache | 强制确认缓存:即每次使用本地缓存之前,需要请求服务器,查看缓存是否失效,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本。 |
363 | | must-revalidate | 缓存验证确认:意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用 |
364 | | proxy-revalidate | 与 must-revalidate 作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。 |
365 |
366 | 
367 |
368 | #### 缓存校验
369 |
370 | {% hint style="info" %}
371 | 在浏览器使用缓存的过程中,为了配合 Cache-Control 中 no-cache ,我们还需要一个机制来验证缓存是否有效。比如服务器的资源更新了,客户端需要及时刷新缓存;又或者客户端的资源过了有效期,但服务器上的资源还是旧的,此时并不需要重新发送。 缓存校验就是用来解决这些问题的,在http 1.1 中,我们主要关注下 `Last-Modified` 和 `ETag` 这两个字段。
372 | {% endhint %}
373 |
374 | **Last-Modified**
375 |
376 | 顾名思义,就是资源的最新一次修改时间。当客户端访问服务端的资源,服务端会将这个 `Last-Modified` 值返回给客户端,客户端收到之后,下次发送请求就会将服务端返回回来的 `Last-Modified` 值装在 `If-Modified-Since` 或者 `If-Unmodified-Since` 里,发送给服务端进行缓存校验。
377 |
378 | 这样服务器就可以通过读取 `If-Modified-Since` (较常用)或 `If-UnModified-Since` 的值,和本地的 `Last-Modified` 值做对比校验。如果校验发现这两个值是一样的,就代表本次请求的资源文件没有被修改过,那么服务器就会告诉浏览器,资源有效,可以继续使用,否则就需要使用最新的资源。
379 |
380 | 来看一下下面的两张图:
381 |
382 | 当请求服务端的 `script.js` 的脚本资源时,可以看到服务端返回了 `Last-Modified`,里面记录了该资源最后一次的修改时间
383 |
384 | 
385 |
386 | 当客户端下次再次发起请求,会携带上这个过期时间给服务端进行验证
387 |
388 | 
389 |
390 | 来看下服务端的部分代码:
391 |
392 | ```javascript
393 | const http = require('http');
394 | const fs = require('fs');
395 | http.createServer((request, response) => {
396 | const ifModifiedSince = request.headers['If-Modified-Since'];
397 | const lastModified = 'Web Aug 19 2019 19:01:15 GMT+0800 (China Standard Time)';
398 |
399 | if (request.url === '/') {
400 | const html = fs.readFileSync('test.html', 'utf-8');
401 |
402 | response.writeHead(200, {
403 | 'Content-Type': 'text/html'
404 | });
405 | response.end(html);
406 | }
407 | if (request.url === '/script.js') {
408 | const js = fs.readFileSync('script.js', 'utf-8');
409 | let status = 200;
410 | // 如果读取到的 If-Modified-Since 和 lastModified 相同,则设置头部 304 表示可使用缓存
411 | if (ifModifiedSince === lastModified) {
412 | status = 304;
413 | response.end('');
414 | }
415 | response.writeHead(status, {
416 | 'Content-Type': 'text/javascript',
417 | 'Cache-Control': 'no-cache,max-age=2000',
418 | 'Last-Modified': lastModified
419 | });
420 | response.end(js);
421 | }
422 | });
423 | ```
424 |
425 | **ETag**
426 |
427 | `Etag` 的作用本质上和 `Last-Modified` 差别不大。相比于 `Last-Modified` 使用最后修改日期来比较资源是否失效的缓存校验策略,`ETag` 则是通过**数据签名**来做一个更加严格的缓存验证。
428 |
429 | 所谓**数据签名**,其实就是通过对资源内容进行一个唯一的签名标记,一旦资源内容改变,那么签名必将改变,服务端就以此签名作为暗号,来标记缓存的有效性。典型的做法是针对资源内容进行一个 hash 计算,类似于 webpack 打包线上资源所加的 hash 标识
430 |
431 | 和 `Last-Modified` 对应 `If-Modified-Since` 相同,`ETag` 也会对应 `If-Match` 或者 `If-None-Match`(`If-None-Match` 比较常用),如果前后的签名相同,则不需要返回新的资源内容。
432 |
433 | **缓存校验的合理使用**
434 |
435 | > `Last-Modified` 和 `ETag` 只是给服务端提供了一个控制缓存有效期的手段,并没有任何强制缓存的作用,最终决定是否使用缓存、还是使用新的资源文件,还是需要靠服务端指定对应的 `http code` 来决定。 对于保存在服务器上的文件,都有最后修改日期的属性,当使用 `Last-Modified` 可以利用这个有效的属性进行数据缓存验证;或者在数据库存入一个 `updatetime` 字段来标识具体的修改日期,从而判断缓存是否有效。 具体如何构建一个能够合理使用缓存的服务器,就比较涉及后端知识了,这里不做具体描述。
436 |
437 | ### 浏览器的同源策略
438 |
439 | > 浏览器的同源限制:当**浏览器**访问 URL 地址的**协议(schema)/ 端口(port)/ 域名(host)**,三者中有任何一个与当前的 URL 片段信息不匹配的时候,便存在跨域问题。
440 |
441 | | 当前地址 | 请求地址 | 请求是否成功 |
442 | | :--- | :--- | :--- |
443 | | [https://www.juejin.com:80](https://www.juejin.com:80) | [http://www.juejin.com:80](http://www.juejin.com:80) | 跨域(协议不同) |
444 | | [https://www.juejin.com:80](https://www.juejin.com:80) | [https://www.juejin.cn:80](https://www.juejin.cn:80) | 跨域(域名不同) |
445 | | [https://www.juejin.com:80](https://www.juejin.com:80) | [https://www.juejin.com:90](https://www.juejin.com:90) | 跨域(端口不同) |
446 |
447 | 对于跨域的几点需要明确: 1. **跨域,是浏览器提供的一种保护手段**,服务端是不存在跨域这一说的。这也就是为什么现在前后端分离的开发模式下,前端比较依赖 `webpack-dev-server` 启动代理服务来中转和代理后台接口的原因,因为两个服务器之间相互通信是没有跨域障碍的。 2. **跨域,是对于 XMLHttpRequest 来说的,浏览器获取不同源服务器下的静态资源,是没有跨域限制的**,这也是 `JSONP` 跨域请求得以实现的本质。 3. 不同于 XMLHttpRequest 的是,**通过 src 属性加载的脚本资源,浏览器限制了 Javascript 的权限,使其不能读写、返回内容**。 4. 对于浏览器来说,除了 DOM 、Cookie、XMLHttpRequest 会收到同源策略限制以外,一些常见的插件,比如 Flash、Java Applet 、Silverlight、Google Gears 等也都有自己的控制策略。
448 |
449 | {% hint style="warning" %}
450 | 当浏览器向不同域的服务器发送请求时,请求是真能发出去,对方服务端也是真能接收到请求,并且真能给你的浏览器响应,浏览器也真能接收到有效数据。 但是,如果在跨域的情况下、服务端返回数据的响应头里的 `Access-Control-Allow-Origin` 字段,没有把当前域名列进白名单,那么浏览器会把服务端返回的数据给藏起来,不告诉你,然后给你抛个 `Access-Control-Allow-Origin` 的错误。
451 | {% endhint %}
452 |
453 | {% hint style="info" %}
454 | 至于为什么资源文件不受同源策略限制呢?可以试想一下,如果资源文件也被限制跨域,那么现在大量使用的 CDN 缓存策略基本就没办法用了。而且现在很多网站的资源文件,都会放到云服务器的 OSS 上,OSS 资源对应的 url 地址肯定是不同域的,那这些资源也不能使用了。
455 | {% endhint %}
456 |
457 | #### Access-Control-Allow-Origin
458 |
459 | `Access-Control-Allow-Origin` 标识了服务器允许的跨域白名单,它有以下几种设置方法: 1. 直接设置 `*` 通配符,简单粗暴,但是这么做等于把服务器的所有接口资源对外完全暴露,是不安全的。 2. 设置制定域,比如 `Access-Control-Allow-Origin: https://www.baidu.com` ,这样只会允许指定域的请求进行跨域访问。 3. 由后端动态设置。`Access-Control-Allow-Origin` 限制只能写一个白名单,但是当我们有多个域都需要跨域请求怎么呢?这个时候,这时可以由服务端自己维护一套白名单列表,在请求进来的时候对请求的源 `host` 进行白名单比对,如果在白名单中,就将这个 `Access-Control-Allow-Origin` 动态设置上去,然后返回响应。
460 |
461 | #### CORS 的预请求
462 |
463 | 如果我们像上面一样,只设置的 `Access-Control-Allow-Origin` 白名单,是否就可以完全畅通无阻地进行跨域了呢?并不是。 就算对端开启了域名白名单认证,然鹅有一些操作仍然是需要进一步认证的,这种进一步的认证操作,就是 `CORS 预请求`。
464 |
465 | #### 预请求触发过程
466 |
467 | 浏览器预请求的触发条件,**是判断本次请求是否属于一个简单请求**。 如果本次请求属于一个**复杂请求**,那么在发送正式的跨域请求之前,浏览器会先准备一个名为 `OPTIONS` 的 `HTTP Method` ,作为预请求发送。 在服务器通过预请求后,下面浏览器才会发生正式的数据请求。整个请求过程其实是发生了两次请求:一个预检请求,以及后续的实际数据请求。
468 |
469 | 
470 |
471 | #### 简单请求
472 |
473 | 1. 请求方式只能是 `GET` `POST` `HEAD`
474 | 2. 请求头字段只允许:
475 | * `Accept`
476 | * `Accept-Language`
477 | * `Content-Language`
478 | * `Content-Type`
479 | 3. `Content-Type` 的值仅限于:
480 | * `text/plain`
481 | * `multipart/form-data`
482 | * `application/x-www-form-urlencoded`
483 | 4. `XMLHttpRequestUpload` 对象均没有注册任何事件监听器(了解就好)。
484 | 5. 请求中没有使用 `ReadableStream` 对象(了解就好)。
485 |
486 | #### 复杂请求
487 |
488 | 除了简单请求里定义的,都是复杂请求,统统需要预请求。
489 |
490 | #### 预请求的验证
491 |
492 | 那么怎样使预检请求成功认证呢?还是需要服务端继续帮忙设置请求头的白名单:
493 |
494 | 1. `Access-Control-Allow-Headers`,设置允许的额外请求头字段。
495 | 2. `Access-Control-Allow-Methods`,设置允许的额外请求方法。
496 | 3. `Access-Control-Max-Age` (单位/秒),指定了预请求的结果能够被缓存多久,在这个时间范围内,再次发送跨域请求不会被预检。
497 |
498 | > 更多、更具体的跨域限制策略可以[点击这里查看更多](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS)
499 |
500 | ### HTTP 性能优化方案
501 |
502 | 1. 合理使用 `HTTP` 的缓存策略,避免同一资源多次请求服务端而导致的额外性能开销
503 | 2. 尽量使用 `HTTP` 长连接,避免每次重建 `TCP` 连接带来的时间损耗
504 | 3. 尽量使用 `HTTPS` 来保证网络传输的安全性。
505 | 4. 可以使用 `HTTP2` 来大幅提高数据传输的效率,使用 `server push` 开启 `HTTP2` 的服务端推送功能
506 | 5. 客户端开启 `Accept-Encoding` 压缩方式的支持,服务端传输压缩后的文件,减少传输数据的大小
507 |
508 |
--------------------------------------------------------------------------------
/browser/event-loop-in-browser.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: >-
3 | javascript
4 | 是单线程语言,使用的是异步非阻塞的运行方式,很多情况下需要通过事件和回调函数进行驱动,那么这些注册的回调函数,是在什么时候被运行环境调用的,彼此之间又是以怎样的顺序执行的?这就绕不开一个机制——Event
5 | Loop ,也就是事件循环。
6 | ---
7 |
8 | # 浏览器下的 Event Loop
9 |
10 | ### 什么是 JS Event Loop
11 |
12 | {% hint style="info" %}
13 | JS Event Loop 即事件循环,是运行在浏览器环境 / Node 环境中的一种消息通信机制,它是主线程之外的独立线程。当主线程内需要执行某些可能导致线程阻塞的耗时操作时(比如请求发送与接收响应、文件 I/O、数据计算)主线程会注册一个回调函数并抛给 Event Loop 线程进行监听,自己则继续往下执行,一旦有消息返回并且主线程空闲的情况下,Event Loop 会及时通知主线程,执行对应的回调函数获取信息,以此达到非阻塞的目的。
14 | {% endhint %}
15 |
16 | ### 执行栈和消息队列
17 |
18 | 在解析 `Event Loop` 运行机制之前,我们要先理解栈(`stack`)和队列(`queue`)的概念。
19 |
20 | 栈和队列,两者都是线性结构,但是栈遵循的是后进先出\(`last in first off LIFO`\),开口封底。而队列遵循的是先进先出 \(`fisrt in first out,FIFO`\),两头通透。
21 |
22 | 
23 |
24 | `Event Loop`得以顺利执行,它所依赖的容器环境,就和这两个概念有关。
25 |
26 | 我们知道,在 `js` 代码执行过程中,会生成一个当前环境的**执行上下文( 执行环境 / 作用域)**,用于存放当前环境中的变量,这个上下文环境被生成以后,就会被推入`js`的执行栈。一旦执行完成,那么这个执行上下文就会被执行栈弹出,里面相关的变量会被销毁,在下一轮垃圾收集到来的时候,环境里的变量占据的内存就能得以释放。
27 |
28 | 这个执行栈,也可以理解为浏览器执行`JavaScript`的主线程,所有代码都跑在这个里面,以同步阻塞的方式依次执行,这是同步的场景。
29 |
30 | 那么异步场景呢?显然就需要一个独立于执行栈之外的容器,专门管理这些异步的状态,于是在“主线程”、“执行栈”之外,有了一个 `Task` 的**消息队列**结构,专门用于管理异步逻辑。所有异步操作的回调,都会暂时被塞入这个队列。`Event Loop` 处在两者之间,扮演一个大管家的角色,它会以一个固定的时间间隔不断轮询,当它发现主线程空闲,就会去到 `Task` 队列里拿一个异步回调,把它塞入执行栈中执行,一段时间后,主线程执行完成,执行栈弹出上下文环境,再次空闲,`Event Loop` 又会执行同样的操作。。。依次循环,于是构成了一套完整的事件循环运行机制。
31 |
32 | 
33 |
34 | {% hint style="info" %}
35 | 上图比较简洁地描绘了整个过程,只不过其中多了 `heap` (堆)的概念,堆和栈,简单来说,堆是留给开发者分配的内存空间,而栈是原生编译器要使用的内存空间,二者独立。
36 | {% endhint %}
37 |
38 | ### microtask 和 macrotask
39 |
40 | 如果只想应付普通点的面试,上面一节的内容就足够了,但是想要答出下面的这条面试题,就必须再次深入 `Event Loop` ,了解任务队列的深层原理:`microtask`(微任务)和 `macrotask`(宏任务)。
41 |
42 | ```javascript
43 | // 请给出下面这段代码执行后,log 的打印顺序
44 | console.log('script start')
45 |
46 | async function async1() {
47 | await async2()
48 | console.log('async1 end')
49 | }
50 | async function async2() {
51 | console.log('async2 end')
52 | }
53 | async1()
54 |
55 | setTimeout(function() {
56 | console.log('setTimeout')
57 | }, 0)
58 |
59 | new Promise(resolve => {
60 | console.log('Promise')
61 | resolve()
62 | })
63 | .then(function() {
64 | console.log('promise1')
65 | })
66 | .then(function() {z
67 | console.log('promise2')
68 | })
69 |
70 | console.log('script end')
71 |
72 | // log 打印顺序:script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout
73 | ```
74 |
75 | 如果只有一个单一的 `Task` 队列,就不存在上面的顺序问题了。但事实情况是,浏览器会根据任务性质的不同,将不同的任务源塞入不同的队列中,任务源可以分为微任务(`microtask`) 和宏任务(`macrotask`),介于浏览器对两种不同任务源队列中回调函数的读取机制,造成了上述代码中的执行顺序问题。
76 |
77 | 
78 |
79 | **微任务**包括 `process.nextTick` ,`promise` ,`MutationObserver`,其中 `process.nextTick` 为 Node 独有。
80 |
81 | **宏任务**包括 `script` , `setTimeout` ,`setInterval` ,`setImmediate` ,`I/O` ,`UI rendering`。
82 |
83 | ### 浏览器 Event Loop 的执行机制
84 |
85 | 了解了微任务宏任务的概念后,我们就可以完整地分析一边 Event Loop 的执行机制了。
86 |
87 | * **初始状态:**执行栈为空,micro 队列为空,macro 队列里有且只有一个 script 脚本(整体代码)
88 | * **script 脚本执行:**全局上下文(script 任务)被推入执行栈,代码以同步的方式以此执行。在执行的过程中,可能会产生新的 `macro-task` 与 `micro-task`,它们会分别被推入各自的任务队列里
89 | * **script 脚本出队:**同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是宏任务队列的执行和出队的过程。
90 | * **微任务队列执行:**上一步已经将`script` 宏任务执行并出队了,这时候执行栈为空,Event Loop 会去 `micro-task` 中将微任务推入主线程执行,这里的微任务的执行方式和宏任务的执行方式有个很重要的区别,就是:**宏任务是一个一个执行,而微任务是一队一队执行的。也就是说,执行一个宏任务,要执行一队的微任务。**(注意:在执行微任务的过程中,仍有可能有新的微任务插入 `micro-task` 那么这种情况下,Event Loop 仍然需要将本次 `Tick (循环)` 下的微任务拿到主线程中执行完毕)
91 |
92 | 
93 |
94 | * 浏览器执行渲染操作,更新界面(这块是重点)
95 | * 检查是否存在 Web worker 任务,如果有,则对其进行处理 。
96 | * 上述过程循环往复,直到两个队列都清空
97 |
98 | 
99 |
100 | ### 浏览器渲染时机
101 |
102 | 在上面浏览器 Event Loop 的执行机制中,有很重要的一块内容,就是浏览器的渲染时机,浏览器会等到当前的 `micro-task` 为空的时候,进行一次重新渲染。**所以如果你需要在异步的操作后重新渲染 DOM 最好的方法是将它包装成 `micro` 任务,这样 DOM 渲染将会在本次 `Tick` 内就完成**。
103 |
104 | ### 面试题解析
105 |
106 | 看到这里,相信你已经明白上面的那条面试题是怎么一回事了,我们可以用对 Event Loop 的理解来分析一下这道题目的执行:
107 |
108 | ```javascript
109 | // 请给出下面这段代码执行后,log 的打印顺序
110 | console.log('script start')
111 |
112 | // 这边的 await 可能不太好理解,我换成了另一种写法
113 | function async1() {
114 | async2().then(res => {
115 | console.log('async1 end')
116 | })
117 | }
118 | function async2() {
119 | console.log('async2 end')
120 | return Promise.resolve(undefined);
121 | }
122 | async1()
123 |
124 | setTimeout(function() {
125 | console.log('setTimeout')
126 | }, 0)
127 |
128 | new Promise(resolve => {
129 | console.log('Promise')
130 | resolve()
131 | })
132 | .then(function() {
133 | console.log('promise1')
134 | })
135 | .then(function() {
136 | console.log('promise2')
137 | })
138 |
139 | console.log('script end')
140 |
141 | // log 打印顺序:script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout
142 | ```
143 |
144 | * `script` 脚本开头碰到了 `console.log` 于是打印 `script start`
145 | * 解析至 `async1()` ,`async1` 执行环境被推入执行栈,解析引擎进入 `async1` 内部
146 | * 发现 `async1` 内部调用了 `async2`,于是继续进入 `async 2`,并将 `async 2` 执行环境推入执行栈
147 | * 碰到 `console.log`,于是打印 `async2 end`
148 | * `async2` 函数执行完成,弹出执行栈,并返回了一个 `Promise.resolve(undefined)`,此时,由于 Promise 已经变成 resolve 状态,于是`async1` `then` 注册的回调被推入 **microtask**
149 | * 解析至 `setTimeout`,等待 0ms 后将其回调推入 **macrotask**
150 | * 继续执行,直到碰到了 `Promise`,`new Promise` 的内部注册的回调是立即执行的,解析进入注入函数的内部,碰到 `console.log`,于是打印 `'Promise'`,再往下,碰到了 `resolve`,将第一个 `then` 中的回调函数推入 `micro-task` ,然后碰到了第二个 then ,继续将其中的回调函数推入 `micro-task`。
151 | * 执行到最后一段代码,打印 `script end`
152 | * 自此,第一轮 Tick 中的一个宏任务执行完成,开始执行微任务队列,通过前面的分析可以得知,目前 `micro-task` 中有三个任务,依次为:`console.log('async 1')`、`console.log('promise1')`、`console.log('promise2')`于是 Event Loop 会将这三个回调依次取到主线程执行,控制台打印:async1、promise1、promise2
153 | * 自此,`micro-task` 为空,浏览器开始重新渲染(如果有 DOM 操作的话)
154 | * Event Loop 再次启动一个新的 Tick ,从宏任务队列中拿出一个(唯一的一个)宏任务执行,打印出:setTimeout
155 |
156 | ### 相关参考
157 |
158 | 1. [前端性能优化原理与实践](https://juejin.im/book/5b936540f265da0a9624b04b/section/5bb1815c6fb9a05d2d0233ad#heading-1)
159 | 2. [前端面试之道](https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5be04a8e6fb9a04a072fd2cd)
160 |
161 |
--------------------------------------------------------------------------------
/es6/babel.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: >-
3 | 近年来 ECMAScript
4 | 发展十分迅速,越来越多的新的语法及提案出台,但是浏览器的更新速度却远远跟不上语言的迭代速度,这种情况下,就需要有一个工具能将新的语法、提案转换为浏览器能够识别的脚本语言,于是
5 | babel 应运而生。
6 | ---
7 |
8 | # babel
9 |
10 | ### 什么是 babel
11 |
12 | {% hint style="info" %}
13 | `babel` 负责解析新的 `ESMAScript` 的标准,`babel` 运行编译时,会从项目根目录下读取 `.babelrc` 的配置文件,通过分析预设(presets)和插件(plugins)等配置项目,来对最新语法进行编译。
14 | {% endhint %}
15 |
16 | ### babel、babel-polyfill 和 babel-runtime
17 |
18 | * `babel` 将 `Javascript` 分为 `synax`(语法)和 `api` 两部分,`babel` 只负责编译 `ECMAScript` 的最新语法,比如 `let`、 `const`、 箭头函数等,而对于新的 `api` 比如 `Promise`、`include`、`Object.assign` 这种则不进行编译,而是交给 `babel-polyfill` 进行编译。
19 | * `babel-polyfill` 负责将新的 API 封装成浏览器可识别的 API 来覆盖原生,并将所有生成的覆盖代码插入项目源码。
20 | * `babel-polyfill` 虽然提供了新的 API 支持,但是缺陷也比较明显:
21 | * 全局注册覆盖的 API,污染全局变量
22 | * 会将新的 API 全部封装导出到源码,不管项目中有没有使用过相关内容,导致导出项目包增大。
23 | * `babel-runtime` 针对 `babel-polyfill` 的问题做了优化:
24 | * 通过导出包 / 引入包的方式提供新封装的 API ,解决了污染全局变量的问题
25 | * 结合 `babel-transform-runtime-plugin` 实现自动检测项目所用到的 API ,有选择的编译和引入,解决了导出包增大的问题。
26 | * 但是 `babel-runtime` 有个问题,就是不能支持实例上的新增 API 方法,例如`[1,2,3].includes(1)` 这种,具体见 [stackOverFlow](https://stackoverflow.com/questions/31781756/is-there-any-practical-difference-between-using-babel-runtime-and-the-babel-poly)。
27 | * `babel-polyfill` 和 `babel-runtime` 对比:
28 | * `babel-polyfill` 适合在比较大的项目中引入。
29 | * `babel-runtime` 适合在组件、类库中引用。
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/es6/build-babel-compiling-environment.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: >-
3 | 俗话说:工欲善其事,必先利其器。学习 ES6 必不可少的一步就是搭建 babel 的编译环境,毕竟现在主流浏览器对 ES6 的很多语法和 API
4 | 支持不全面,还是需要 babel 转换成 ES5 的语法。
5 | ---
6 |
7 | # 搭建 babel 编译环境
8 |
9 | ### 创建项目
10 |
11 | * `mkdir ES6Learning && cd ES6Learning`
12 |
13 | ### 安装依赖
14 |
15 | * `npm init` (初始化 `package.json` 文件)
16 | * `npm install @babel/core @babel/cli @babel/preset-env` (安装 `babel` 编译所用到的相关模块)
17 |
18 | ### babel 配置文件
19 |
20 | * `touch .babelrc` (生成 babel 的配置文件)
21 | * 在 `.babelrc` 文件中写入如下信息:
22 |
23 | ```javascript
24 | {
25 | presets: [
26 | "@babel/preset-env"
27 | ]
28 | }
29 | ```
30 |
31 | ### 编写一个测试文件
32 |
33 | `touch test.js` ,创建完成后用 `IDE` 工具打开,在里面写点 `ES6` 代码然后保存,比如:
34 |
35 | ```javascript
36 | let str = 'Hello ES6!';
37 | connsole.log(str);
38 | ```
39 |
40 | ### 运行 babel 编译
41 |
42 | 使用 `npx babel test.js –o demo.js` 命令来运行 babel 编译,将 `test.js` 文件内容编译到同目录下的 `demo.js` 文件中去,等待项目目录下生成 `demo.js` 文件后,打开 `demo.js` ,可以查看编译后的内容:
43 |
44 | ```javascript
45 | "use strict";
46 |
47 | var str = 'Hello ES6!';
48 | console.log(str);
49 | ```
50 |
51 | 至此,babel 编译环境已经构建成功!
52 |
53 | {% hint style="info" %}
54 | `npx` 是 `npm 5.2` 版开始新增的一个命令,主要用途就是用项目内部安装的模块,比如说以 `mocha` 举例,之前是这样调用 `mocha` 的:
55 |
56 | ```text
57 | # 项目的根目录下执行
58 | $ node-modules/.bin/mocha --version
59 | ```
60 |
61 | 那么使用 npx 之后就可以这样调用:
62 |
63 | ```bash
64 | $ npx mocha --version
65 | ```
66 |
67 | 更多使用细节可见:[npx 使用教程](http://www.ruanyifeng.com/blog/2019/02/npx.html)
68 | {% endhint %}
69 |
70 | ### 实时编译
71 |
72 | 虽然上面的步骤已经将环境搭建完成,但我们在 test.js 中改一个字,就要重新编译一次,非常不痛快。我们可以开启 `babel` 的 `watch` 功能:
73 |
74 | `npx babel test.js –o demo.js --watch`
75 |
76 | 这样 `test.js` 文件发生了任何修改,`babel` 就能自动编译到 `demo.js` 中,是不是很方便?我们可以将这段代码写入 `package.json` 的 `script` 中,下次只需要运行 `npm start` 命令就能一键启动监听:
77 |
78 | ```javascript
79 | {
80 | // ...
81 | "scripts": {
82 | "start": "npx babel test.js -o demo.js --watch"
83 | }
84 | // ...
85 | }
86 | ```
87 |
88 | 好了,`babel` 编译环境已经搞定,下面就可以愉快的使用 `ES6` 了~
89 |
90 | ### 相关参考
91 |
92 | * [ES6-babel 编译环境搭建](https://www.cnblogs.com/padding1015/p/10801214.html)
93 |
94 |
--------------------------------------------------------------------------------
/es6/let-and-const.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: let 和 const 是 ES6 新增的用于声明变量和常量的关键字,他们的作用是什么?它们又有什么特性?它们与 var 定义的变量又有何区别?
3 | ---
4 |
5 | # let 和 const
6 |
7 | ### let 和 const
8 |
9 | {% hint style="info" %}
10 | 定义以后可改变的量就是变量,定义后不可改变的量就是常量。
11 | {% endhint %}
12 |
13 | #### 1. 变量和常量
14 |
15 | 在 `ES6` 中,使用 `let` 命令定义变量,使用 `const` 命令定义常量,也就是说 `let` 定义后变量是可修改的,`const` 定义后的常量不能被修改。这一特性目前被大多数浏览器原生支持,但是针对少部分不能支持的浏览器,我们可以使用 `babel` 将它编译成 `ES5` 语法,下面可以看下两者用 `babel` 编译后的代码有何区别:
16 |
17 | ```javascript
18 | // ES6
19 | let a = 1;
20 | const b = 2;
21 | ```
22 |
23 | ```javascript
24 | // babel 编译后
25 | "use strict";
26 |
27 | var a = 1;
28 | var b = 2;
29 | ```
30 |
31 | 感觉没区别啊??不要急,再加一段代码:
32 |
33 | ```javascript
34 | // ES6
35 | let a = 1;
36 | const b = 2;
37 | b = 3;
38 | ```
39 |
40 | ```javascript
41 | // babel 编译后
42 | "use strict";
43 |
44 | function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }
45 |
46 | var a = 1;
47 | var b = 2;
48 | b = (_readOnlyError("b"), 3);
49 | ```
50 |
51 | 这样应该很容易看出区别了,当我们对一个常量进行变值操作,就会抛出一个错误告诉你:这个值是只读的。
52 |
53 | {% hint style="info" %}
54 | `b = (_readOnlyError("b"), 3)`这种操作我也没见过,不过猜测应该类似于 `b = _readOnlyError("b") && 3`
55 | {% endhint %}
56 |
57 | {% hint style="warning" %}
58 | **注意:**由于`const` 定义的值是不可变的,这点在用 `const` 定义引用类型的时候要特别注意!如果是引用类型的 `const` 值,改变其中的属性是可行的,但是通常不建议这么做。
59 | {% endhint %}
60 |
61 | ```javascript
62 | const obj = {};
63 | obj.a = 1;
64 | obj.b = 2;
65 | obj; // => {a: 1, b: 2}
66 |
67 | obj = {}; // => 报错
68 | ```
69 |
70 | #### 2. 定义就要初始化
71 |
72 | 由于 `const` 定义的常量,定义后就不能修改的特性,决定了它定义的时候必须就初始化,否则就报错;而 `let` 则没有这种限制,它定义的变量完全可以在后面再初始化:
73 |
74 | ```javascript
75 | let a;
76 | a = 1;
77 |
78 | const b; // Uncaught SyntaxError: Missing initializer in const declaration
79 | b = 2;
80 | ```
81 |
82 | {% hint style="warning" %}
83 | 上面这段代码由于直接违反了 `const` 的语法特性,因此在 `babel` 编译阶段就无法通过
84 | {% endhint %}
85 |
86 | ### let 和 var
87 |
88 | `let` 和 `var` 是两种声明变量的方式,二者主要有以下区别:
89 |
90 | #### 1. 作用域不同
91 |
92 | * `let` 所声明的变量会创建自己的块级作用域,创建的作用域是定义它的块级代码及其中包括的子块中,且无法自动往全局变量 `window` 上绑定属性。
93 | * `var` 定义的变量,作用域为定义它的函数,或者全局,并且是能自动往全局对象 `window` 上绑定属性的。
94 |
95 | 能否创建自己的块级作用域这一差别,就会涉及到一道被问烂的面试题:
96 |
97 | ```javascript
98 | var result = [];
99 | (function () {
100 | for (var i = 0; i < 5; i++) {
101 | result.push(function () {
102 | console.log(i);
103 | });
104 | }
105 | })();
106 | result.forEach(function (item) {
107 | item()
108 | });
109 | // => 打印出五个 5
110 | ```
111 |
112 | 为什么打印出了五个5,而不是预期的 0,1,2,3,4 ?因为 `var` 不会创建自己的作用域,而 `js` 本身又是没有块级作用域这个概念的,`for` 循环中定义的变量就等于是直接定义在匿名函数中的变量,于是当这5个函数被掏出来执行的时候,循环早已完成,而函数读取的上面一层作用域中存储的变量 `i`,也早已经被累加成了5。
113 |
114 | 然而,这个问题只要将 `var` 关键字换成 `let` 就迎刃而解:
115 |
116 | ```javascript
117 | var result = [];
118 | (function () {
119 | for (let i = 0; i < 5; i++) {
120 | result.push(function () {
121 | console.log(i);
122 | });
123 | }
124 | })();
125 | result.forEach(function (item) {
126 | item()
127 | }); // => 0,1,2,3,4
128 | ```
129 |
130 | 我们可以看看这段使用 `let` 的代码,最后被 `babel` 转译成什么样:
131 |
132 | ```javascript
133 | "use strict";
134 |
135 | var result = [];
136 |
137 | (function () {
138 | var _loop = function _loop(i) {
139 | result.push(function () {
140 | console.log(i);
141 | });
142 | };
143 |
144 | for (var i = 0; i < 5; i++) {
145 | _loop(i);
146 | }
147 | })();
148 |
149 | result.forEach(function (item) {
150 | item();
151 | });
152 | ```
153 |
154 | 从上面的代码我们就可以看出,`let` 创建作用域的方式,其实就是**创建了一个函数,在函数内定义一个同名变量并于外部将这个变量传入其中**,以此达到创建作用域的目的。
155 |
156 | #### 2. 变量声明提升
157 |
158 | * `let` 定义的变量不会进行变量声明提升操作,也就是说在访问该变量之前必须要先定义。
159 | * `var` 定义的变量存在变量声明提升,因此在变量定义前就能访问到变量,值是 `undefined`。
160 |
161 | ```javascript
162 | console.log(a); // undefined
163 | var a = 1;
164 |
165 | console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
166 | let b = 2;
167 | ```
168 |
169 | 同样我们来看下上面这段代码被编译后的样子:
170 |
171 | ```javascript
172 | "use strict";
173 |
174 | console.log(a);
175 | var a = 1;
176 |
177 | console.log(b);
178 | var b = 2;
179 | ```
180 |
181 | 看起来好像 `Babel` 无法编译这种阻止变量声明提升的语法,`let` 声明的变量无法提升的特性应该是浏览器内部的 JS 执行引擎支持和实现的。
182 |
183 | #### 3. 暂时性死区
184 |
185 | {% hint style="info" %}
186 | 只要块级作用域内存在`let / const`命令,它所声明的变量 / 常量就“绑定”(binding)这个区域,不再受外部的影响。ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错。这种特性也被成为暂时性死区。
187 | {% endhint %}
188 |
189 | ```javascript
190 | var tmp = 123;
191 |
192 | if (true) {
193 | tmp = 'abc'; // ReferenceError: tmp is not defined
194 | let tmp;
195 | }
196 | ```
197 |
198 | 同样,这个特性也是被浏览器内部的 JS 执行引擎支持和实现的,`babel` 无法支持这种特性的编译,只能简单的将 `let` 编译成 `var`。但是有意思的是,由于 `let` 在 `if` 块中是可以构建自己的独立作用域的,`babel` 将 `tmp` 这个变量换了个名字来模拟实现块级作用域的创建:
199 |
200 | ```javascript
201 | "use strict";
202 |
203 | var tmp = 123;
204 |
205 | if (true) {
206 | _tmp = 'abc';
207 |
208 | var _tmp;
209 | }
210 | ```
211 |
212 | #### 4. 可否重复定义
213 |
214 | * `let` 定义的变量,一旦定义便不允许被重新定义。
215 | * `var` 定义的变量,可以被重新定义。
216 |
217 | ```javascript
218 | var a = 1;
219 | var a = 2;
220 |
221 | let b = 3;
222 | let b = 4; // Uncaught SyntaxError: Identifier 'b' has already been declared
223 | ```
224 |
225 | {% hint style="warning" %}
226 | 上面这段代码由于直接违反了 `let` 定义的变量无法重新定义的语法特性,因此同样在 `babel` 编译阶段就无法通过。
227 | {% endhint %}
228 |
229 | ### 总结
230 |
231 | 这块内容并不复杂,我们可以做一个简单的总结:
232 |
233 | **`let` 特性:**
234 |
235 | * 创建块级作用域。
236 | * 定义后不能重新定义。
237 | * 不存在变量提升。
238 | * 存在暂时性死区。
239 | * 全局作用域下定义时不会被挂载到顶层对象上(`window对象 / global 对象`)
240 |
241 | ```javascript
242 | // 浏览器环境
243 | var a = 1;
244 | window.a; // => 1
245 |
246 | let b = 2;
247 | window.b; // => undefined
248 | ```
249 |
250 | **`const` 特性:**
251 |
252 | * 同 `let`
253 | * 一旦初始化赋值,后面不能被修改
254 | * 定义时就必须初始化
255 |
256 | ### 相关参考
257 |
258 | * [let 和 const 命令](https://es6.ruanyifeng.com/#docs/let)
259 |
260 |
--------------------------------------------------------------------------------
/frontend-frame/vue-basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: 对着 Vue 官网一行一行扒下来,知识点总结包含大部分 Vue 的基础使用内容。
3 | ---
4 |
5 | # Vue基础
6 |
7 | ## 数据与方法
8 |
9 | 当一个 Vue 实例被创建时,它将 `data` 对象中的所有的 property 加入到 Vue 的**响应式系统**中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。
10 |
11 | {% hint style="warning" %}
12 | **需要注意:**只有当实例被创建时就已经存在于 `data` 中的 property 才是**响应式**的。
13 | {% endhint %}
14 |
15 | 也就是说如果你添加一个新的 property,那么后续对于该 property 所作的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值。
16 |
17 | {% hint style="warning" %}
18 | 当使用 Object.freeze\(\) 对属性进行冻结时,Vue 系统将不再追踪 property 的变化
19 | {% endhint %}
20 |
21 | #### 为什么 data 需要是函数?
22 |
23 | 组件复用时所有组件实例都会共享 `data`,如果 `data` 是对象的话,就会造成一个组件修改 `data` 以后会影响到其他所有组件,所以需要将 `data` 写成函数,每次用到就调用一次函数获得新的数据。
24 |
25 | 当我们使用 `new Vue()` 的方式的时候,无论我们将 `data` 设置为对象还是函数都是可以的,因为 `new Vue()` 的方式是生成一个根组件,该组件不会复用,也就不存在共享 `data` 的情况了。
26 |
27 | ## **模板**
28 |
29 | 模板这块没有特别需要说明的,只有一处需要注意一下:
30 |
31 | 有些 HTML 元素,诸如 `
`、``、`` 和 `