## js必知必会之了解浏览器
1. 浏览器中输入url后,页面是如何展示出来的?
2. 浏览器缓存原理
3. 浏览器的事件循环
1. 输入地址
2. 浏览器查找域名的 IP 地址
3. 浏览器向 web 服务器发送HTTP 请求
4. 服务器的永久重定向响应
5. 浏览器跟踪重定向地址
6. 服务器处理请求
7. 服务器返回一个 HTTP 响应
8. 浏览器显示 HTML
9. 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)
### 1. 输入地址
- 浏览器会从“历史记录”、“书签”等地方匹配并展示你可能想输入的地址
- 有的浏览器甚至会直接从缓存中把网页展示出来
### 2. 浏览器查找域名的 IP 地址
1. 本地硬盘的 hosts 文件
2. 发出一个DNS请求到本地DNS服务器,先查询缓存,再递归查询(本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动)
3. 本地DNS服务器还要向DNS根服务器进行递归查询
4. 得到域名的解析服务器的地址
5. 本地DNS服务器向域名的解析服务器发出请求,从而收到一个域名和IP地址对应关系,并加入缓存
### 递归查询

### 迭代解析

### 3. 浏览器向 web 服务器发送HTTP 请求
拿到域名对应的IP地址之后,浏览器会以一个随机端口(1024。这个连接请求到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的TCP/IP协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达WEB程序,最终建立了TCP/IP的连接。发起一个http请求。
(扩展:TCP三次握手、四次挥手)
### TCP三次握手
- 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三路握手的一部分。客户端把这段连接的序号设定为随机数A。
- 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK的确认码应为A+1,SYN/ACK包本身又有一个随机产生的序号B。
- 最后,客户端再发送一个ACK。当服务端收到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包的序号被设定为收到的确认号A+1,而响应号则为B+1。

### TCP四次挥手
- 任意一侧(客户端或者服务器)发出FIN
- 对侧收到,并响应ACK。(那么本侧就会超时等待2*MSL时间,然后关闭连接。已关闭的一侧仍然应接收数据,直至对侧也关闭了连接。)
- 对侧发出FIN
- 本侧收到,并响应ACK

### 标志符
- ACK—为1表示确认号字段有效
- SYN—为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
- FIN—为1表示发送方没有数据要传输了,要求释放连接。
### http请求
- 请求行
- 请求头
- 请求体

### 4. 服务器的永久重定向响应
- 301 永久重定向
- 302 临时重定向(利于SEO)

### http响应状态码

⬇️
## 浏览器缓存原理
### 为什么需要缓存?
通过网络获取内容既速度缓慢又开销巨大。较大的响应需要在客户端与服务器之间进行多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费用。因此,缓存并重复利用之前获取的资源的能力成为性能优化的一个关键方面。
- Request
- Pragma(HTTP/1.0)
- Cache-Control(HTTP/1.1)
- If-Modified-Since
- If-None-Match
- Response
- Cache-Control(HTTP/1.1)
- Expires(如果设置了max-age,则Expires被忽略)
- Etag(与If-None-Match对应,和文件的hash值类似)
- Last-Modified(与If-Modified-Since对应,文件最后修改时间,精确到秒)
### Pragma
作用: 防止页面被缓存, 在HTTP/1.1版本中,它和Cache-Control:no-cache作用一模一样。
Pargma只有一个用法, 例如: Pragma: no-cache
*注意: 在HTTP/1.0版本中,只实现了Pragema:no-cache, 没有实现Cache-Control*
### Cache-Control 指令和说明
|||
| :--: | :--: |
| max-age=86400 | 浏览器以及任何中间缓存均可将响应(如果是“public”响应)缓存长达 1 天(60 秒 x 60 分钟 x 24 小时)。 |
| private | 不允许任何中间缓存对其进行缓存(浏览器可以,CDN不行) |
| public, max-age=600 | 客户端的浏览器只能将响应缓存最长 10 分钟(60 秒 x 10 分钟)。 |
| no-store | 不允许缓存响应,每次请求都必须完整获取。 |
#### 定义最佳 Cache-Control 策略

### 举个例子总结一下
当浏览器需要下载一个文件的时候,首先回去缓存里面查找;如果能找到,若文件未过期,则直接获取文件,若文件以经过期,则向服务端发送一个http请求,请求里面带上(If-Modify-since: Fri, 09 Mar 2018 06:39:44 GMT, If-None-Match: 39d3f65bbb1a81810820fc0fad400c82),服务端接收到请求之后,校验相应的文件的修改时间或者Etag值(一般情况这两个校验只存在一个,etag比文件的修改时间更精确,后者只能精确到秒,如果文件在1s内被修改过,文件的最后修改时间就是一样的了),
如果文件在服务端 判断为没有发生变化,则返回一个304状态码的响应,并且响应不包含文件内容,(Cache-Control: max-age=86400,If-Modify-since: Fri, 09 Mar 2018 06:39:44 GMT, If-None-Match: 39d3f65bbb1a81810820fc0fad400c82),浏览器收到响应之后,继续使用本地缓存的文件内容;如果文件在服务端判断为发生了变化,则返回200状态码,并且返回文件的内容,和重新设置文件缓存时间。
### 5. 浏览器跟踪重定向地址
### 6. 服务器处理请求
### 7. 服务器返回一个 HTTP 响应(扩展:http状态码)
### 8. 浏览器显示 HTML
解析html以构建dom树 -> 构建render树 -> 布局render树(重排) -> 绘制render树(重绘)
浏览器在解析html文件时,会”自上而下“加载,并在加载过程中进行解析渲染。在解析过程中,如果遇到请求外部资源时,如图片、外链的css、javascript、iconfont等,请求过程是异步的,并不会影响html文档进行加载。
### 9. 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)
当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以我们平时的代码中,js是放在html文档末尾的。
### 同步任务和异步任务
JS的解析是由浏览器中的JS解析引擎完成的,比如谷歌的是V8。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。
## 浏览器的事件循环
脚本运行时先依次运行*执行栈*(同步代码),然后会从*任务队列*里提取事件(异步代码),运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
### 执行栈
当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆内存(heap)和栈内存(stock)中来加以区分;对象存放在堆内存里,基础类型变量(undefined, null, bool, number, string, symbol)、对象的指针存放在栈内存里。
### 执行上下文
- 创建阶段 [当函数被调用, 但内部的代码还没开始执行]
- 创建 作用域链
- 创建变量、函数以及参数
- 决定 "this"的值
- 激活 / 代码执行阶段
- 赋值, 寻找函数引用以及解释 /执行代码
```javascript
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
```

变量提升
```
(function() {
console.log(typeof foo); // function
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello fun';
}
console.log(foo); // ???
}());
```
### 事件队列(Task Queue)
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。
### 宏任务(macro task)
- setInterval()
- setTimeout()
### 微任务(micro task)
- new Promise()
- new MutaionObserver()
*当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。*
```javascript
setTimeout(function() {
console.log('setTimeout');
new Promise(function(resolve, reject) {
resolve('setTimeout promise');
}
).then(function(data) {
console.log(data);
});
}, 0);
new Promise(function(resolve, reject) {
console.log('立即执行');
setTimeout(function() {
resolve('promise setTimeout');
}, 0);
new Promise(function(resolve, reject) {
resolve('promise promise');
}
).then(function(data) {
console.log(data);
});
}).then(function(data) {
console.log(data);
new Promise(function(resolve, reject) {
resolve('promise then promise');
}
).then(function(data) {
console.log(data);
});
});
(new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
})).observe(document.querySelector('body'), {
attributes: true,
childList: true,
characterData: true
});
document.querySelector('body').style.background = 'red';
new Promise(function(resolve, reject) {
resolve('promise');
}).then(function(data) {
console.log(data);
});
```
### step 1
- *micro task*:
- *macro task*: [setTimeout[promise['setTimeout promise']]]
### step 2
*打印: 立即执行*
- *micro task*: [Promise['promise promise']]
- *macro task*: [setTimeout[promise['setTimeout promise']], setTimeout['promise setTimeout']]
### step 3
- *micro task*: [Promise['promise promise'], MutationObserver['mutation.type']]
- *macro task*: [setTimeout[promise['setTimeout promise']], setTimeout['promise setTimeout']]
### step 4
- *micro task*: [Promise['promise promise'], MutationObserver['mutation.type'], Promise['promise']]
- *macro task*: [setTimeout[promise['setTimeout promise']], setTimeout['promise setTimeout']]
### step 5 *主线程空闲后*
*执行:Promise['promise promise'], 打印:'promise promise'*
- *micro task*: [MutationObserver['mutation.type'], Promise['promise']]
- *macro task*: [setTimeout[promise['setTimeout promise']], setTimeout['promise setTimeout']]
### step 6
*执行:MutationObserver['mutation.type'], 打印:'mutation.type attributes'*
- *micro task*: [Promise['promise']]
- *macro task*: [setTimeout[promise['setTimeout promise']], setTimeout['promise setTimeout']]
### step 7
*执行:Promise['promise'],打印:'promise'*
- *micro task*: []
- *macro task*: [setTimeout[promise['setTimeout promise']], setTimeout['promise setTimeout']]
### step 8
*执行:setTimeout,打印:'setTimeout'*
- *micro task*: [promise['setTimeout promise']]
- *macro task*: [setTimeout['promise setTimeout']]
### step 9
*执行:promise['setTimeout promise'],打印:'setTimeout promise'*
- *micro task*: []
- *macro task*: [setTimeout['promise setTimeout']]
### step 10
*执行:setTimeout['promise setTimeout'],打印:'promise setTimeout'*
- *micro task*: [Promise['promise then promise']]
- *macro task*: []
### step 11
*执行:Promise['promise then promise'],打印:'promise then promise'*
- *micro task*: []
- *macro task*: []
## 参考:
- [JQ的网站](https://jqroom.com/collection/20170627/knowledge)
- [传输控制协议](https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE)
- [从输入 URL 到页面展示到底发生了什么](http://web.jobbole.com/91239/)
- [HTTP 缓存](https://developers.google.cn/web/fundamentals/performance/optimizing-content-efficiency/http-caching)
- [详解JavaScript中的Event Loop(事件循环)机制](https://zhuanlan.zhihu.com/p/33058983)
- [JavaScript 中的执行上下文和调用栈是什么?](https://juejin.im/entry/599e949251882524472239c4)
- [MDN MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)
That`s all today
Thanks for your attendtion!