从输入 URL 到页面加载的过程所涉及到的知识点
本文讲述了从输入 URL 到页面加载完成所涉及到的部分浏览器知识、网络知识、前端相关知识等,内容参考了若干大佬的文章,若有错误,烦请不吝赐教!
总览图
浏览器基础
多进程浏览器
- 1 个浏览器进程
- 多个渲染进程(
renderer
) - 多个插件进程
- 1 个
GPU
进程 - 1 个网络进程
多进程的好处可以避免单个 page
、单个插件 crash
影响整个浏览器,也充分利用多核优势,提高浏览器稳定性,速度,安全性。
进程策略:多个 tab
同域名下可能会合并到一个渲染进程。通过页面右上角的... -> 更多工具 -> 任务管理器
打开面板。
进程是系统分配的独立资源,是
CPU
资源分配的基本单位,进程是由一个或者多个线程组成的。 线程是进程的执行流,是CPU
调度和分派的基本单位,同个进程之中的多个线程之间是共享该进程的资源的。
进程间的交互(网页加载过程中)
- 浏览器进程接收到用户输入的
URL
请求,判断是关键词还是URL
(关键词则跳转默认搜索引擎搜索),然后将该URL
转发给网络进程发起真正的请求(进程间通信IPC
)。 - 得到请求来的数据后,网络进程解析响应头数据,并将数据转发给浏览器进程,进而浏览器进程发送 "确认导航" 消息到渲染进程。
- 渲染进程收到消息后,与网络进程建立数据管道,开始准备接收 HTML 数据;准备就绪后,渲染进程向浏览器进程发送 "确认提交" 消息,浏览器开始移除旧文档,更新浏览器进程中的页面状态。
- 一旦文档被提交,渲染进程便开始页面解析和子资源加载,页面渲染完成后,渲染进程会发送一个消息给浏览器进程做完成处理。
浏览器内核:渲染进程/排版引擎/浏览器引擎/页面渲染引擎/样板引擎 (Blink/Webkit 等)
GUI
渲染线程- 负责渲染页面,解析
HTML
,CSS
构成DOM
树等,当页面重绘或者由于某种操作引起回流都会调起该线程 - 和
JS
引擎线程是互斥的,当JS
引擎线程在工作的时候,GUI
渲染线程会被挂起,GUI
更新被放入在JS
任务队列中,等待JS
引擎线程空闲的时候继续执行
- 负责渲染页面,解析
JS
引擎线程(如v8
引擎等)- 单线程工作,负责将
JavaScript
翻译成 CPU 指令(机器码
) - 和
GUI
渲染线程互斥,JS
运行耗时过长就会导致页面阻塞 - 一个浏览器
Tab
(renderer
进程)只有一个js
线程运行 - 更多见下文 v8 引擎工作原理
- 单线程工作,负责将
- 事件触发线程
- 控制事件循环,管理一个任务队列/事件队列,异步任务触发条件达成,将回调事件放到任务队列中
- 当事件符合触发条件被触发时,该线程会把对应的事件回调函数添加到任务队列的队尾,等待
JS
引擎处理
- 定时器触发线程
setInterval
与setTimeout
所在线程- 之所以有单独的线程,是因为
JS
引擎阻塞会导致计时不准确 - 开启定时器触发线程来计时并触发计时,计时完成后会被添加到任务队列中,等待
JS
引擎处理
- 异步
http
请求线程- 每次
http
请求的时候都会新开启一条请求线程 - 请求完成有结果了之后,将请求的回调函数添加到任务队列中,等待
JS
引擎处理
- 每次
- 合成线程
- 在
GUI
渲染线程后执行,将GUI
渲染线程生成的待绘制列表转换为位图
- 在
- IO 线程
- 用来和其他进程进行通信
浏览器内核
主要分为:
Webkit内核
:苹果基于 KHTML 开发、开源的,用于 Safari,Google Chrome 之前也在使用,由 WebCore 和 JavaScriptCore 组成;Blink内核
:是 Webkit 的一个分支,Google 开发,目前应用于 Google Chrome、Edge、Opera 等;
JS 引擎
主要分为:
SpiderMonkey
:第一款 JavaScript 引擎,由 Brendan Eich 开发(也就是 JavaScript 作者)Chakra
:微软开发,用于 IT 浏览器JavaScriptCore
:WebKit 中的 JavaScript 引擎,Apple 公司开发V8
:Google 开发的强大 JavaScript 引擎,也帮助 Chrome 从众多浏览器中脱颖而出
web workers
- 原理:
JS
引擎向浏览器新申请开一个子线程,与子线程通过postMessage API
通信,子线程完全受主线程控制 - 作用:后台运行计算,将结果发到主线程,解决单线程的
JS
引擎进行密集型计算会堵塞页面的问题 - 子线程不能影响用户界面,即不能操作
dom
等,在一个新的全局上下文 - 除了
webworker
(属于renderer
进程) 还有SharedWorker
(多个标签页、iframe
共享,不属于某个renderer
进程,自己就是一个进程),Service Workers
,ChromeWorker
等 - 更多查阅 1,查阅 2
网络基础
用户输入网址,浏览器检查缓存后,发起 DNS 解析查询到对应 IP(应用层),根据 IP 协议找到目标服务器(网络层),通过 Mac 寻址找到服务器硬件接口(数据链路层),然后通过网线 wifi 等向服务器硬件接口传输比特信息(物理层),即开始建立 tcp 连接(传输层),如果是 HTTPS 则先建立 SSL(会话层、表示层),以上建立完成后,开始发送 HTTP 请求(应用层)。
服务端的接收就是反过来的步骤。
七层因特网协议栈(ISO)
- 应用层(http,ftp,dns) 这一层为操作系统或网络应用程序提供访问网络服务的接口,如 DNS 解析成 IP 并发送 http 请求
- 表示层(SSL 握手协议) 主要处理两个通信系统中交换信息的表示方式,包括数据格式交换,数据加密与解密,数据压缩与终端类型转换等
- 会话层(SSL 记录协议) 这一层管理主机之间的会话进程,即负责建立、管理、终止进程之间的会话,如控制登陆和注销过程 (QoS)
- 传输层(tcp,udp) 建立 tcp/udp 连接,数据的单位称为数据段(segment)(四层交换机)
- 网络层(IP,ARP) IP 寻址,数据的单位称为数据包(packet)(路由器、三层交换机)
- 数据链路层(PPP) 将 bit 流封装成 frame 帧(网桥、二层交换机,以太网)
- 物理层(传输 bit 流) 物理传输(传输通过双绞线,电磁波,光纤,中继器,集线器,网线接口等各种介质)
- ip 寻址算法包括:RIP 协议(IP 路由跳转的次数最小,适合小型网络),OSPF 协议(IP 路由跳转的速度最快,适合大型网络)。
- ARP 协议 是一个通过解析
IP
地址来找寻Mac
地址的协议,IP
地址转换成Mac
地址后,就能进行以太网数据传输了。 - 以太网属于数据链路层,它主要负责相邻设备的通信。原理是通过查询交换机
Mac
表,找到通信双方的物理接口,进而开始通信。
DNS
URL
一般包括几大部分:
protocol
,协议头,譬如有 http,ftp 等host
,主机域名或 IP 地址port
,端口号path
,目录路径query
,即查询参数fragment
,即#
后的 hash 值,一般用来定位到某个位置
URI
是统一资源标识符,而URL
是统一资源定位符。每个URL
都是URI
,但不一定每个URI
都是URL
。 如URI
:mailto:cay@horstman.com
如果输入的是域名,需要进行 DNS(Domain Name System,域名系统)
解析成 IP
,即域名解析。DNS
是一个将域名和 IP
地址相互映射的一个分布式数据库。根据以下优先级查找对应的 IP
地址:
- 浏览器缓存
- 本机缓存(
host
文件等) - 本地域名解析服务器(电信,联通等运营商)
DNS
根域名解析服务器、DNS
二根域名解析服务器
如果请求的是静态资源,那么流量有可能到达 CDN 服务器;如果请求的是动态资源,那么情况更加复杂,流量可能依次经过代理/网关、Web 服务器、应用服务器、数据库.
dns
解析是很耗时的,因此如果解析域名过多,会让首屏加载变得过慢,可以考虑dns-prefetch
优化。减少域名 DNS 解析时间将网页加载速度提升新层次
TCP
有了 IP
地址之后,在 http
请求之前,客户端和服务器端先建立 TCP
连接。TCP 是一种全双工的、面向连接的、可靠的、基于字节流的传输层通信协议。
名词解释
- SYN(synchronous 建立联机)同步序列号
- ACK(acknowledgement 确认)应答码
- PSH(push 传送)
- FIN(finish 结束)
- RST(reset 重置)
- URG(urgent 紧急)
- Sequence number(seq 顺序号码) 对方上次的 ack(首次发送时 seq 为系统随机生成)
- Acknowledge number(ack 确认号码)对方的 seq+1(无数据传输时) 或者 seq+L(报文数据的长度 L)
三次握手的步骤
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时,将触发三次握手。
- 客户端发送
SYN包
(SYN=1,seq=x,x 为随机数)的数据包到服务器,并进入SYN_SEND
状态; - 服务器收到
SYN包
后,发送一个ACK包
(ACK number=x+1,ACK=1)确认,同时自己也发送一个SYN包
(SYN=1,seq=y),即SYN+ACK包
,此时服务器进入SYN_RCVD
状态; - 客户端收到
SYN+ACK包
后,发送一个ACK包
(ACK number=y+1,ACK=1)确认,发送完毕客户端进入ESTABLISHED
状态,服务端收到包后也进入ESTABLISHED
状态,完成三次握手
建立连接成功后,接下来就正式传输数据。
四次挥手的步骤
客户端或服务器均可主动发起挥手动作,在 socket
编程中,任何一方执行 close()
操作即可产生挥手操作。假设客户端想关闭连接:
- 客户端向服务器发送
FIN=1,seq=x
包,表示客户端主动要关闭连接,然后进入FIN_WAIT_1
状态。(此后客户端不能再向服务器发送数据,但能读取数据。) - 服务器收到
FIN
包后,向客户端发送ACK=1,ACKnum=x+1
确认包,然后进入CLOSE_WAIT
状态。(此后服务器不能再读取数据,但可以继续向客户端发送数据。同时客户端收到这个确认包后,进入FIN_WAIT_2
状态)。 - 服务器完成数据的发送后,向客户端发送
FIN=1,seq=y
包,然后进入LAST_ACK
状态,等待客户端返回最后一个ACK
包。此后服务器既不能读取数据,也不能发送数据。 - 客户端收到
FIN
包后,向服务器发送ACK=1,ACKnum=y+1
确认包,然后进入TIME_WAIT
状态,接着等待足够长的时间(2MSL
),没有收到服务器端的ACK
,认为服务器端已经正常关闭连接,于是自己关闭链接进入CLOSED
状态,释放网络资源。
注:2,3 次挥手不会一起发送,当服务器收到 FIN
报文时,很可能并不会立即关闭 SOCKET
,所以只能先回复一个 ACK
报文,告诉 Client端
,"你发的 FIN报文
我收到了"。只有等到我服务器所有的报文都发送完了,我才能发送 FIN报文
,因此不能一起发送。
SYN 攻击
攻击客户端在短时间内伪造大量不存在的 IP 地址,向服务器不断地发送 SYN 包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,正常的 SYN 请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。
防御:
- 缩短超时(SYN Timeout)时间
- 增加最大半连接数
- 过滤网关防护
- SYN cookies 技术
TCP KeepAlive
隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。
tcp/ip 的并发限制
- 在
HTTP/1.0
中,一个服务器在发送完一个HTTP
响应后,会断开TCP
链接。但是这样每次请求都会重新建立和断开TCP
连接,代价过大。 - 所以
HTTP/1.1
就把 Connection 头写进标准(Connection: keep-alive
),并且默认开启持久连接
,除非请求中写明Connection: close
,那么浏览器和服务器之间是会维持一段时间的TCP
连接,不会一个请求结束就断掉,这样SSL
的开销也可以避免,故刷新页面也不会重新建立TCP 和 SSL
。(在chrome
浏览器里通过network
标签 ->connection ID
表示TCP连接
的重用。) - 在
HTTP/1.1
中,单个TCP连接
在同一时刻只能处理一个请求,顺序处理多个请求; 在HTTP2
中由于Multiplexing
特点的存在,多个HTTP 请求
可以在同一个TCP 连接
中并行进行。 - 浏览器对同一域名下并发的
TCP连接
是有限制的(2-10 个不等),Chrome 最多允许对同一个Host
建立六个TCP连接
。
get 和 post 的区别
get
安全幂等,从服务器获取资源;post
向URI
指定的资源提交数据,数据就放在报文的body
里;get
请求时,浏览器会把headers
和data
一起发送出去,服务器响应 200 返回数据(发送一个tcp
数据包);post
请求时,浏览器先发送headers
,服务器响应100 continue
, 浏览器再发送data
,服务器响应 200 返回数据(发送两个tcp
数据包)。
TCP/UDP 的区别
- TCP 是面向连接的,UDP 是面向无连接的。TCP 在通信之前必须通过三次握手机制与对方建立连接,而 UDP 通信不必与对方建立连接,不管对方的状态就直接把数据发送给对方
- TCP 连接过程耗时,UDP 不耗时
- TCP 连接过程中出现的延时增加了被攻击的可能,安全性不高,而 UDP 不需要连接,安全性较高
- TCP 是可靠的,保证数据传输的正确性,不易丢包;UDP 是不可靠的,易丢包
- TCP 传输速率较慢,实时性差,udp 传输速率较快。tcp 建立连接需要耗时,并且 tcp 首部信息太多,每次传输的有用信息较少,实时性差
- TCP 是流模式,udp 是数据包模式。tcp 只要不超过缓冲区的大小就可以连续发送数据到缓冲区上,接收端只要缓冲区上有数据就可以读取,可以一次读取多个数据包,而 udp 一次只能读取一个数据包,数据包之间独立
TCP 可靠性的六大手段
- 顺序编号:tcp 在传输文件的时候,会将文件拆分为多个 tcp 数据包(http 请求消息太长),每个装满的数据包大小大约在 1k 左右,tcp 协议为保证可靠传输,会将这些数据包顺序编号
- 确认机制:当数据包成功的被发送方发送给接收方,接收方会根据 tcp 协议反馈给发送方一个成功接收的 ACK 信号,信号中包含了当前包的序号(停止等待协议 ARQ)
- 超时重传:当发送方发送数据包给接收方时,会为每一个数据包设置一个定时器,当在设定的时间内,发送方仍没有收到接收方的 ACK 信号,会再次发送该数据包,直到收到接收方的 ACK 信号或者连接已断开(停止等待协议 ARQ)
- 校验信息:tcp 首部校验信息较多,udp 首部校验信息较少
- 流量控制:如果发送者发送数据过快,接收者来不及接收,那么就会有分组丢失。为了避免分组丢失,控制发送者的发送速度,使得接收者来得及接收,这就是流量控制。由滑动窗口协议(连续 ARQ 协议)实现。滑动窗口协议既保证了分组无差错、有序接收,也实现了流量控制。主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送
- 拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:慢开始、拥塞避免;快重传、快恢复
HTTP
TCP
三次握手结束后,开始发送 HTTP
(超文本传输协议,HyperText Transfer Protocol) 请求报文。
- HTTP 最凸出的优点是 简单、灵活和易于扩展、应用广泛和跨平台。
- HTTP 最大双刃剑是 无状态、明文传输。
- HTTP 最大缺点是 不安全。
HTTP 工作流程
- 地址解析
- 封装 HTTP 请求数据包
- 封装成 TCP 包,建立 TCP 连接(TCP 的三次握手)
- 客户机发送请求命令
- 服务器响应
- 服务器关闭 TCP 连接
客户机会将请求封装成 http 数据包-->封装成 Tcp 数据包-->封装成 Ip 数据包--->封装成数据帧--->硬件将帧数据转换成 bit 流(二进制数据)-->最后通过物理硬件(网卡芯片)发送到指定地点。
服务器硬件首先收到 bit 流,然后转换为数据帧,然后转换成 ip 数据包。于是通过 ip 协议解析 Ip 数据包,然后又发现里面是 tcp 数据包,就通过 tcp 协议解析 Tcp 数据包,接着发现是 http 数据包通过 http 协议再解析 http 数据包得到数据。
HTTP 报文结构
报文一般包括了:通用头部(General)
,请求/响应头部
,请求/响应体
通用头部
Request Url
: 请求的 web 服务器地址Request Method
: 请求方式(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)Status Code
: 请求的返回状态码,如 200 代表成功Remote Address
: 请求的远程服务器地址(会转为 IP)Referrer-Policy
: 控制请求头中 referrer 的内容 查阅
在跨域拒绝时,可能是method
为options
,状态码为404/405
等(当然,实际上可能的组合有很多)
其中,Method
的话一般分为两批次:
HTTP1.0
定义了三种请求方法:GET, POST 和 HEAD方法
。以及几种Additional Request Methods:PUT、DELETE、LINK、UNLINK
HTTP1.1
定义了八种请求方法:GET、POST、HEAD、OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
列举下状态码不同范围状态的意义,更多查阅:
1xx
——指示信息,表示请求已接收,继续处理2xx
——成功,表示请求已被成功接收、理解、接受3xx
——重定向,要完成请求必须进行更进一步的操作4xx
——客户端错误,请求有语法错误或请求无法实现5xx
——服务器端错误,服务器未能实现合法的请求
请求/响应头部
常用的请求头部(部分):
Accept
: 接收类型,表示浏览器支持的 MIME 类型,对应Content-Type
Accept-Encoding
:浏览器支持的压缩类型,如 gzip 等,超出类型不能接收Content-Type
:客户端发送出去实体内容的类型Cookie
: 有 cookie 并且同域访问时会自动带上Connection
: 当浏览器与服务器通信时对于长连接如何进行处理,如 keep-aliveHost
:请求的服务器 URLOrigin
:最初的请求是从哪里发起的(只会精确到端口),Origin 比 Referer 更尊重隐私Referer
:该页面的来源 URL(适用于所有类型的请求,会精确到详细页面地址,csrf 拦截常用到这个字段)User-Agent
:用户客户端的一些必要信息,如 UA 头部等If-Modified-Since
:http1.0 弱缓存,对应Last-Modified
Expires
:http1.0 强缓存、If-None-Match
:http1.1 弱缓存,对应ETag
Cache-Control
: http1.1 强缓存
常用的响应头部(部分):
Access-Control-Allow-Headers
: 服务器端允许的请求 HeadersAccess-Control-Allow-Methods
: 服务器端允许的请求方法Access-Control-Allow-Origin
: 服务器端允许的请求 Origin 头部(譬如为*)Content-Type
:服务端返回的实体内容的类型Content-Length
:表明本次回应的数据长度Content-Encoding
:表示服务器返回的数据使用了什么压缩格式Date
:数据从服务器发送的时间Set-Cookie
:设置和页面关联的 cookie,服务器通过这个头部把 cookie 传给客户端Keep-Alive
:如果客户端有 keep-alive,服务端也会有响应(如 timeout=38)Server
:服务器的一些相关信息Cache-Control
:告诉浏览器或其他客户,什么环境可以安全的缓存文档Last-Modified
:请求资源的最后修改时间Expires
:应该在什么时候认为文档已经过期,从而不再缓存它ETag
:请求变量的实体标签的当前值
一般来说,请求头部和响应头部是匹配分析的。
- 请求头部的
Accept
要和响应头部的Content-Type
匹配,否则会报错 - 跨域请求时,请求头部的
Origin
要匹配响应头部的Access-Control-Allow-Origin
,否则会报跨域错误 - 在使用缓存时,请求头部的
If-Modified-Since、If-None-Match
分别和响应头部的Last-Modified、ETag
对应
请求/响应实体
- 请求实体中会将一些需要的参数都放入进入(用于 post 请求)如实体中可以放参数的序列化形式(a=1&b=2 这种),或者直接放表单对象(Form Data 对象,上传时可以夹杂参数以及文件)等等
- 响应实体中就是放服务端需要传给客户端的内容,一般现在的接口请求时,实体中就是对于的信息的 json 格式,而像页面请求这种,里面就是直接放了一个 html 字符串,然后浏览器自己解析并渲染。
Form Data 与 Request Payload
post/put
提交中:
Content-Type: application/json
时,为Request Payload
(json
)Content-Type: application/x-www-form-urlencoded
或multipart/form-data
时,为Form Data
(string
,格式为key1=value1&key2=value2
,类似GET
请求的QueryString
格式)
他们只是因为 Content-Type
设置的不同,并不是数据提交方式的不同,这两种提交都会将数据放在 message-body
中。但是 chrome
浏览器的开发者工具会根据这个 ContentType
区分显示方式。
CRLF
CRLF(Carriage-Return Line-Feed),意思是回车换行,一般作为分隔符存在,请求头和实体消息之间有一个 CRLF 分隔,响应头部和响应实体之间用一个 CRLF 分隔
CRLF->Windows-style
LF->Unix Style
CR->Mac Style
HTTP 1.1 性能瓶颈
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
- 发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
- 串行的文件传输:服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
HTTP 与 TCP/IP 区别
TPC/IP
协议是传输层协议,主要解决数据如何在网络中传输HTTP
是应用层协议,主要解决如何包装数据WEB
使用HTTP
协议作应用层协议,以封装HTTP
文本信息,然后使用TCP/IP
做传输层协议将它发到网络上。
长连接与短连接
tcp/ip
层面:
- 长连接:一个
tcp/ip
连接上可以连续发送多个数据包,在tcp连接
保持期间,如果没有数据包发送,需要双方发检测包以维持此连接,一般需要自己做在线维持(类似于心跳包) - 短连接:通信双方有数据交互时,就建立一个
tcp连接
,数据发送完成后,则断开此tcp连接
http
层面:
http1.0
中,默认使用的是短连接,也就是说,浏览器每进行一次http
操作,就建立一次连接,任务结束就中断连接,譬如每一个静态资源请求时都是一个单独的连接。http1.1
起,默认使用长连接,使用长连接会有这一行Connection: keep-alive
,在长连接的情况下,当一个网页打开完成后,客户端和服务端之间用于传输http
的tcp连接
不会关闭,如果客户端再次访问这个服务器的页面,会继续使用这一条已经建立的连接。
注: keep-alive 不会永远保持,它有一个持续时间,一般在服务器中配置(如 apache),另外长连接需要客户端和服务器都支持时才有效。
HTTP 2.0
- 多路复用(代替了 HTTP1.x 的序列和阻塞机制,同域名下所有通信都在单个 TCP 连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗;并且单个连接上可以并行交错请求和响应,之间互不干扰。很多
http1.1
中的优化方案就无需用到了(譬如打包成精灵图,静态资源多域名拆分等)) - 首部压缩(
http
头部压缩(HPACK 算法),减少体积) - 二进制分帧(在应用层(HTTP/2)跟传输层(TCP or UDP)之间增加了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)
- 服务器端推送(也称为缓存推送 Push Cache,服务端可以对客户端的一个请求发出多个响应,可以主动通知客户端,当一个客户端请求资源 X,而服务器知道它很可能也需要资源 Z 的情况下,服务器可以在客户端发送请求前,主动将资源 Z 推送给客户端)
- 请求优先级(如果数据流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求。)
HTTP 3.0
http2
多路复用的关系,在出现丢包的情况下,整个 TCP
都要开始等待重传,也就导致了后面的所有数据都被阻塞,因此表现反而不如 http1
,所以 Google 就更起炉灶搞了一个基于 UDP
协议的 QUIC
协议,即 http3
。
QUIC
新功能包括:
- 0-RTT:类似 TCP 快速打开的技术,缓存当前会话的上下文,在下次恢复会话的时候,只需要将之前的缓存传递给服务端验证通过就可以进行传输了。0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势。
- 真·多路复用:QUIC 是基于 UDP 的,一个连接上的多个 stream 之间没有依赖,不存在 TCP 队头阻塞。另外 QUIC 在移动端的表现也会比 TCP 好。因为 TCP 是基于 IP 和端口去识别连接的,这种方式在多变的移动端网络环境下是很脆弱的。但是 QUIC 是通过 ID 的方式去识别一个连接,不管你网络环境如何变化,只要 ID 不变,就能迅速重连上。
- 加密认证的报文:除了个别报文比如 PUBLIC_RESET 和 CHLO,所有报文头部都是经过认证的,报文 Body 都是经过加密的。
- 向前纠错机制(FEC):每个数据包除了它本身的内容之外,还包括了部分其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。向前纠错牺牲了每个数据包可以发送数据的上限,但是减少了因为丢包导致的数据重传,因为数据重传将会消耗更多的时间。
HTTPS
https
与http
的区别就是:在请求前,会建立ssl链接
,确保接下来的通信都是加密的,无法被轻易截取分析。
HTTPS 原理(SSL 四次握手)
一般来说,主要关注的就是 SSL/TLS
的握手流程:
ClientHello
浏览器发起
https
请求建立SSL链接
(服务器的 443 端口),并向服务端发送以下信息(第一次HTTP
请求,即明文传输):- 随机数
Client random
,后面用于生成会话对称加密密钥
- 客户端支持的
加密方法
,比如 RSA 加密 - 客户端支持的
SSL/TLS
协议版本,如 TLS 1.2 版本
- 随机数
SeverHello
服务器收到客户端请求后,向客户端发出响应,向客户端发送以下信息(明文传输):
- 随机数
Server random
,后面用于生成会话对称加密密钥
- 确认的
加密算法
与Hash算法
- 确认
SSL/ TLS
协议版本,如果浏览器不支持,则关闭加密通信 - 服务器的
数字证书
(证书里包含了网站地址,非对称加密的公钥,以及证书颁发机构等信息)
- 随机数
浏览器回应
TLS
来验证证书的合法性(通过浏览器或者操作系统中的CA
公钥验证颁发机构是否合法,证书中包含的网址是否和正在访问的一样,过期时间等),如果证书信任则浏览器会显示一个小锁头,否则会弹出一个警告框提示证书存在问题。客户端接收证书后(不管信不信任),会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息(第二次
HTTP
请求):- 新的随机数
Premaster secret
,该随机数会被服务器公钥加密 - 加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
- 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
利用
Client random
、Server random
和Premaster secret
通过双方协商的加密算法各自生成本次通信的 会话对称加密密钥。- 新的随机数
服务器的最后回应
服务器收到客户端的第三个随机数
Premaster secret
之后,通过协商的加密算法计算出本次通信的 会话对称加密密钥。使用该密钥解密浏览器发来的握手消息,并验证Hash
是否与浏览器发来的一致。然后向客户端发生最后的信息:- 加密通信算法改变通知,表示随后的信息都将用会话对称加密密钥加密通信。
- 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
浏览器解密并计算握手消息的
HASH
,如果与服务端发来的HASH
一致,此时握手过程结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP
协议,只不过用会话对称加密密钥加密内容。
HTTPS 加密是在传输层
https
报文在被包装成 tcp
报文的时候完成加密的过程,无论是 https
的 header
域也好,body
域也罢都是会被加密的。
当使用 tcpdump
或者 wireshark
之类的 tcp
层工具抓包,获取是加密的内容,而如果用应用层抓包,使用 Charels(Mac)、Fildder(Windows)
抓包工具,那当然看到是明文的。
加密算法,信息摘要,数字签名,数字证书,CA
- 对称加密算法:
AES,RC4,3DES
, - 非对称加密算法:
RSA,DSA/DSS
, - 摘要算法(哈希
hash
算法、散列算法):MD5,SHA1,SHA256
,加盐salt
提升复杂度 - 信息摘要:通过信息摘要算法(HASH),将原信息摘要为一个固定长度的摘要
- 数字签名:信息摘要被私钥加密后的密文
- 数字证书:可以简单理解为 被 CA 承认且无法篡改的公钥,可用于验证网站是否可信(针对
HTTPS
)、验证某文件是否可信(是否被篡改)等,也可以用一个证书来证明另一个证书是真实可信,最顶级的证书称为根证书。除了根证书(自己证明自己是可靠),其它证书都要依靠上一级的证书,来证明自己。 CA
:Certificate Authority 签发证书的权威机构- 根
CA``:CA
的CA
,可以签发CA
的证书 - 根证书:根
CA
的自签名证书,内置在操作系统和浏览器中
HTTPS 和 HTTP 的区别
http
的信息是明文传输,连接简单无状态,https
则是安全性的加密传输。http
是直接与TCP
进行数据传输,https
是经过一层SSL
(OSI 会话层),端口前者是80
,后者是443
。https
协议握手阶段比较费时,需要到ca
申请证书或自制证书,会使页面的加载时间延长近 50%,增加 10% 到 20% 的耗电。https
连接缓存不如http
高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响。- 谷歌曾在 2014 年 8 月份调整搜索引擎算法,并称“比起同等
HTTP
网站,采用HTTPS
加密的网站在搜索结果中的排名将会更高”。
COOKIE
cookie
是浏览器的一种本地存储方式,一般用来帮助客户端和服务端通信的,常用来进行身份校验,结合服务端的 session
使用。
场景如下:
在登陆页面,用户登陆了,此时服务端会生成一个 session,session 中有对于用户的信息(如用户名、密码等),然后会有一个 sessionid(相当于是服务端的这个 session 对应的 key)。然后服务端在返回数据时,设置浏览器本地 cookie 值为:jsessionid=xxx。以后访问同域名下的页面时,自动带上 cookie 用于检验在有效时间内无需二次登陆。
GZIP 压缩
首先,明确gzip
是一种压缩格式,需要浏览器支持才有效(不过一般现在浏览器都支持), 而且gzip
压缩效率很好(高达 70%左右),一般是由apache、tomcat
等web
服务器开启,一般只需要在服务器上开启了gzip
压缩,然后之后的请求就都是基于gzip
压缩格式的,当然服务器除了gzip
外,也还会有其它压缩格式(如 deflate,没有 gzip 高效,且不流行)。
后台处理
负载均衡
用户发起的请求都指向调度服务器(反向代理服务器,譬如安装了 nginx 控制负载均衡),然后调度服务器根据实际的调度算法,分配不同的请求给对应集群中的服务器执行,然后调度器等待实际服务器的 HTTP 响应,并将它反馈给用户。
后台的处理
- 容器接受到请求(如 tomcat 容器),经过统一的验证如安全拦截,跨域验证。如不符合规则就直接返回了相应的 http 报文(如拒绝请求等)
- 验证通过后,进入实际的后台代码(如 java 程序),此时程序接收到请求,然后执行(譬如查询数据库,大量计算等等)
- 等程序执行完毕后,就会返回一个 http 响应包(一般这一步也会经过多层封装)发送到前端,完成交互
前端缓存
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应
三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
缓存位置优先级
Service Worker
Memory Cache
Disk Cache(HTTP cache)
Push Cache
都未取到则进行正式的网络请求请求最新数据。
Service Worker
Service Worker
是运行在浏览器背后的独立线程,传输协议必须为 HTTPS
。他与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
实现步骤:
- 注册
Service Worker
,然后监听到install
事件以后就可以缓存需要的文件 - 下次用户访问可以通过拦截请求的方式查询是否存在缓存,存在则直接读取缓存文件
- 如果没有命中缓存,则调用
fetch
函数按缓存优先级获取数据,无论此时从哪里来的数据netWork
里size
都将显示为Service Worker
Memory Cache
- 内存中的缓存,几乎所有的网络请求资源都会被浏览器自动加入到
memory cache
中,短期存储 (preload) - 浏览器的 TAB 关闭后该次浏览的
memory cache
便失效 - 无视 HTTP 头信息,从
memory cache
获取缓存内容时,浏览器会忽视例如 max-age=0, no-cache 等头部配置 - 但
Cache-control:no-store
是例外,可以让memory cache
失效
Disk Cache
Disk Cache (HTTP cache)
也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache
胜在容量和存储时效性上。
强缓存
强制缓存(200 from cache)
,当客户端请求后,会先访问缓存数据库看缓存是否存在。如果存在则直接返回;不存在则请求真的服务器,响应后再写入缓存数据库。强制缓存直接减少请求数,是提升最大的缓存策略。
- http1.0:
Expires
表示缓存到期时间(绝对时间),如:Expires: Thu, 10 Nov 2017 08:45:11 GMT
(缺点:写法复杂;本地时间不准确会出现错误) - http1.1:
Cache-control
表示缓存的最大有效时间(相对时间),如:Cache-control: max-age=2592000
(其他值:no-cache,no-store,public,private 等
)
注:http1.0 若想实现 no-cache
,使用 Pragma: no-cache
,或者 HTML
的 meta
标签:<meta http-equiv="Pragma" content="no-cache" />
频繁变动的资源: Cache-Control: no-cache
不常变化的资源: Cache-Control: max-age=31536000
弱缓存
协商缓存(304 Not Modified)、对比缓存
,强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。命中返回 304 Not Modified
, 不命中返回 200
,新资源和缓存标识存入浏览器缓存。在请求数上和没有缓存是一致的,只在响应体体积上有大幅度节省(304 时,Network size 指服务端通信报文的大小)。
- http1.0:
Last-Modified 和 If-Modified-Since
根据文件修改时间判断是否过期,精确到秒。如:Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
- http1.1:
ETag 和 If-None-Match
根据文件内容判断是否过期,如:etag: "FoWFkJxGtx5Edfyku7luLdv_wucA"
Push Cache
Push Cache
(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。
缓存过程
- 调用
Service Worker
的fetch
事件响应 - 查看
memory cache
- 查看
disk cache
- 如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是
200
- 如果有强制缓存但已失效,使用对比缓存,比较后确定
304
还是200
- 如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是
- 发送网络请求,等待网络响应
- 把响应内容存入
disk cache
(如果HTTP
头信息配置可以存的话) - 把响应内容的引用存入
memory cache
(无视HTTP
头信息的配置) - 把响应内容存入
Service Worker
的Cache Storage
(如果Service Worker
的脚本调用了cache.put()
)
浏览器行为
- 打开网页,地址栏输入地址: 查找
disk cache
中是否有匹配。如有则使用;如没有则发送网络请求。 - 普通刷新 (F5):因为 TAB 并没有关闭,因此
memory cache
是可用的,会被优先使用(如果匹配的话)。其次才是disk cache
。 - 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有
Cache-control: no-cache
(为了兼容,还带了Pragma: no-cache
),服务器直接返回 200 和最新内容。
页面渲染流程
当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。 在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。通过 chrome控制台->Performance->EventLog
可看到解析流程。
流程简述
浏览器内核
拿到内容后,渲染步骤分为以下几步:
- (DOM)解析
HTML
内容为DOM
树 - (Recalculate Style)将
CSS
转换为styleSheets
,并计算出DOM
节点的样式 - (Layout Tree)生成布居树(渲染树),并计算元素的布局信息
- (Layer Tree)对布局树进行分层(渲染层),生成图层树
- (Paint)为每个图层生成绘制列表,并将其提交到合成线程
- (Tiles/raster)将图层分成图块,并在光栅化线程池中将图块转换成位图
- (Composite Layers)合并图层形成图片,存入
GPU
内存中 - (DrawQuad)浏览器进程读取
GPU
内存,显示网页
前五步在渲染线程,6,7 步在合成线程,最后一步在浏览器进程。
HTML 解析,构建 DOM 树
即:字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模型
:
- 解码:浏览器将获得的 HTML 内容(Bytes)基于他的编码转换为单个字符
- 令牌化:浏览器按照 HTML 规范标准将这些字符转换为不同的标记 token,每个 token 都有自己独特的含义以及规则集
- 词法分析:生成的令牌转换成定义其属性和规则的对象(节点对象)
- DOM 构建:DOM 树构建完成,整个对象集合就像是一棵树形结构
DOM
树构建完成后,并且 html
所引用的内联外链 js
的同步代码都执行完毕后(不包括样式表,图片)触发 DomContentLoaded
事件。与 async
加载的脚本顺序无关,但一定加载在 defer
脚本执行完成之后。
jQuery $(document).ready(()=>{}); 即监听的
DOMContentLoaded
事件。
解析 CSS,进行样式计算
- 格式化样式表:类似 HTML 解析,即:
Bytes → characters → tokens → nodes → CSSOM
,得到styleSheets(CSSOM)
。可通过document.styleSheets
查看结果 - 标准化样式表:如
em->px,red->rgba(255,0,0,0),bold->700
- 计算每个
DOM
节点具体样式:通过继承
和层叠
,完成了 DOM 节点中每个元素的具体样式的计算。可通过window.getComputedStyle
查看结果
注意 第一步 DOM 树和 CSSOM 树一起建立的,第二步样式计算才算出带有样式的 dom 树;
生成布居树 Layout Tree(渲染树 Render Tree)
- 流览器布局系统需要额外去构建一棵只包含可见元素布局树 Layout Tree(在 DOM 树上不可见的、head、meta、display:none,最后都不会出现在布局树上)
- 计算布局树节点的坐标位置
分层
生成图层树(
Layer Tree
)在
DOM
树中每个节点都会对应一个LayoutObject
,当他们的LayoutObject
处于相同的坐标空间时,就会形成一个渲染层(Render Layers)
。Chrome -> More Tools -> Layer
查看层信息渲染引擎会为特定的节点创建单独的图层(
复合层/合成层
)某些特殊的渲染层会被认为是
合成层(Compositing Layers)
,合成层拥有单独的图形层(GraphicsLayer)
,会单独分配资源,所以又称为开启 GPU 硬件加速。而其他不是合成层的渲染层,则和其第一个拥有GraphicsLayer
父层公用一个。- (显式合成)拥有层叠上下文属性的元素会被提升为单独一层:
HTML 根元素;opacity;isolation;will-change;video,canvas,iframe;position:fixed;3D transforms:translate3d、translateZ 等
- (显式合成)需要裁剪(
clip
)的地方也会创建图层:比如一个标签很小,50*50
像素,你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁;如果出现了滚动条,那么滚动条也会被单独提升为一个图层 - (隐式合成)
z-index
比较低的节点会提升为一个单独的图层,那么层叠等级比它高的节点都会成为一个独立的图层
Chrome -> More Tools -> Rendering -> Layer borders
:黄色的就是复合图层信息。- (显式合成)拥有层叠上下文属性的元素会被提升为单独一层:
合成层的优劣及建议:
- 优:1.比
cpu
处理快(即GPU 硬件加速
) 2.repaint
不会影响其他层 3.可以直接合成 - 劣:大量的合成层会使传输到
GPU
变慢,并且占用GPU
和内存资源,还会出现层爆炸
- 建议:动画使用
transform
实现; 减少隐式合成; 减小合成层的尺寸
图层绘制
完成了图层的构建,渲染引擎会把一个复杂的图层拆分为很小的绘制指令,然后再按照这些指令的顺序组成一个待绘制列表。
在
Chrome -> More Tools -> Layer-profiler
可以看到绘制列表,右侧即为列表绘制过程。
栅格化
栅格化/光栅化(raster):图块生成位图。
当图层的绘制列表准备好之后,渲染线程
会把该绘制列表提交(commit)给合成线程
。合成线程会将图层划分为图块 tile,按照视口附近的图块来优先生成位图。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行(这指在没有启用硬件加速的浏览器,即CPU 软件渲染)。
快速栅格化、GPU 栅格化:通常,栅格化过程都会使用 GPU
来加速生成,渲染进程中通过 GraphicsContext
(GraphicsLayer
都有一个 GraphicsContext
)把生成位图的指令通过共享内存作为纹理上传到 GPU
交由 GPU
合成,生成的位图被保存在 GPU
内存中。属于GPU 硬件加速渲染.更多介绍
合成
什么是合成?合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - 合成线程(compositor thread)里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧发送给 GPU 来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。
当图层上面的图块都被栅格化后,合成线程会收集图块上面叫做绘画四边形(draw quads)
的信息来构建一个合成帧(compositor frame)
。
- 绘画四边形:包含图块在内存的位置以及图层合成后图块在页面的位置之类的信息
- 合成帧:代表页面一个帧的内容的绘制四边形集合(60fps 指的就是这个)
显示
合成线程通过 IPC 向浏览器进程提交一个渲染帧。浏览器进程里viz 组件用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,合成帧都会被发送给 GPU 进行聚合并生成最终完整的合成表面,最后再将内存发送给显卡,最后显示在屏幕上。
显卡:屏幕刷新频率为 60 帧,一张图片停留的时间约为 16.7 ms。而每次更新的图片都来自显卡的前缓冲区。而显卡接收到浏览器进程传来的页面后,会合成相应的图像,并将图像保存到后缓冲区,然后系统自动将前缓冲区和后缓冲区对换位置,如此循环更新。如果某个动画逻辑占用大量内存,浏览器生成图像的时候会变慢,图像传送给显卡就会不及时,而显示器还是以不变的频率刷新,因此会出现卡顿,也就是明显的掉帧现象。
更新视图
回流(Reflow)
也称为重排(Layout)
。更新了元素的几何属性。需要更新完整的渲染流水线,是影响浏览器性能的关键因素。
- 以下会触发回流:
- 盒子模型相关属性会触发重布局(
width,height,padding,margin,display,border-width,border,min-height
) - 定位属性及浮动也会触发重布局(
top,bottom,left,right,position,float,clear
) - 改变节点内部文字结构也会触发重布局(
text-align,overflow-y,font-weight,overflow,font-family,line-height,vertival-align,white-space,font-size
) - 获取某些属性也会引发回流:
offset、scroll、client(Top/Left/Width/Height)
width,height
- 调用了
getComputedStyle(),getBoundingClientRect()
或者 IE 的currentStyle
- 盒子模型相关属性会触发重布局(
现代浏览器大多会对做优化都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即 16.6ms)才会清空队列。当你获取布局信息的操作的时候,会强制队列刷新,因为队列中可能有会影响这些属性或方法返回值的操作,触发回流与重绘来确保返回正确的值。
重绘(Repaint)
更新了元素的绘制属性:color,border-style,border-radius,visibility,text-decoration,background,background-image,background-position,background-repeat,background-size,outline-color,outline,outline-style,outline-width,box-shadow
,渲染流水线直接从 Paint
阶段执行。
直接合成
如上面复合层已提到,使用 transform
实现动画效果,渲染流水线直接从 tiles/raster
开始,不占用渲染线程资源,大大提升绘制效率。这也是 CSS 动画比 JavaScript 动画高效的原因。(本质是利用 GPU
加速优先使用合成层合并属性)
减少重绘与回流
- 用使用直接合成属性或重绘属性代替回流属性,如使用
transform->top,left
,visibility->display:none
等 - 将动画或频繁重绘回流的节点设置为单独图层,使用
transform、opacity、filters、will-change
触发GPU
硬件加速直接合成 - 避免频繁操作
DOM
和样式,如将dom
离线:documentFragment
,display:none
,clone dom
等,设置class
类,一次更新 - 避免使用
table
布局,尽可能在DOM
树的最末端改变class
- 避免频繁读取会引发回流/重绘的属性
- 避免设置多层内联样式,
css
表达式
外链资源的下载
解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。
遇到 CSS 样式资源
css
下载和解析不会阻塞DOM
树解析(异步加载时DOM
照常构建),这是因为下载和解析 CSS 的工作是在预解析线程中进行的。- 但会阻塞布局树渲染(渲染时需等
css
加载完毕,因为布局树需要css
信息) - 会阻塞后面
js
脚本的执行 - 例外:
media query
声明的CSS
是不会阻塞渲染的
遇到 JS 脚本资源
- 阻塞浏览器的解析,当发现外链脚本时,需等待脚本下载并解析完成并执行后才会继续解析
HTML
,之前解析的 dom 不受影响,即 FP 可能会提前 - 如果是内联脚本,得等到页面所有脚本执行完后,才开始解析页面 dom
- 可以加上
defer(延迟执行)
或async(异步执行)
属性或document.createElement('script')
来避免阻塞
遇到 img 图片类资源
- 遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有 src 的地方。
- 当页面上所有的 DOM,样式表,脚本,图片都已经加载完成(包括异步),触发
load
事件。
JS 引擎解析过程
前面有提到遇到 JS
脚本时,会等到它的执行。
v8 引擎工作原理
V8 是用C++
编写的跨平台的 Google 开源高性能 JavaScript 和 WebAssembly 引擎,它用于 Chrome 和 Node.js 等,可以独立运行,也可以嵌入到任何C++
应用程序中。
js
源码通过词法分析(分词 tokenize)将代码分解成词元(token),再通过语法分析(解析 parse)根据语法规则转为AST,生成抽象语法树(AST
)和执行上下文。- 通过解释器 Ignition根据
AST
生成字节码(ByteCode)(不直接转成机器码是因为其内存消耗大) - 通过解释器 Ignition逐条解释执行执行字节码。如果有热点函数(函数被多次调用)就会经过编译器 TurboFan转换成优化的机器码,提高代码的执行性能。如果类型发生了变化,之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码。
最终计算机执行的就是CPU机器码
。
JIT
即时编译(JIT-Just In Time compiler)。解释器在解释执行字节码的同时将热点函数的字节码转换为机器码,并把转换后的机器码缓存起来。这样整个程序的运行速度能得到显著提升。
JS 的执行环境
即执行上下文(execution context)。js 解释器运行阶段还会维护一个环境栈(即执行栈/调用栈/栈空间 Call Stack)
,首次载入脚本,它将创建全局执行上下文
,并压入环境栈栈顶(不可被弹出)。当执行流进入一个函数时,就会创建一个执行上下文,并把它压入环境栈的顶部
,当函数执行完后会将其从环境栈弹出
,并将控制权返回前一个执行环境。环境栈的顶端始终是当前正在执行的环境。
如果程序执行完毕被弹出执行栈,然后没有被引用(没有形成闭包),那么这个函数中用到的内存就会被垃圾处理器自动回收。
在 js 中,执行环境可以抽象的理解为一个 object,它由以下几个属性构成:
- 作用域链(
Scope chain
) - 变量对象(
Variable object
-VO
) this
(上下文对象)
作用域链
作用域链,它在解释器进入到一个执行环境时初始化完成并将其分配给当前执行环境。每个执行环境的作用域链由当前环境的变量对象及父级环境的作用域链构成。
流程简述:在函数上下文中,查找一个变量foo
,如果函数的 VO
中找到了,就直接使用,否则去它的父级作用域链中(parent)找,如果父级中没找到,继续往上找,直到全局上下文中也没找到就报错。
作用域
作用域在于指定变量、函数的作用范围,即它们可以在什么范围内被访问到,也就是它们的可访问性。实际运行的时候,JS 内核通过一系列的 VO/AO
及其连接关系来解决变量、函数的作用域管理问题。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域,作用域是由代码函数声明的位置来决定。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
// 1
var scope = 'global scope';
function checkscope() {
var scope = 'local scope';
function f() {
return scope;
}
return f;
}
checkscope()();
//local scope
VO 与 AO
VO:每一个执行上下文都会分配一个变量对象(variable object),变量对象的属性由**变量(variable)和函数声明(function declaration)**构成。在函数上下文情况下,参数列表(parameter list)也会被加入到VO中作为属性。变量对象与当前作用域息息相关,不同作用域的变量对象互不相同,它保存了当前作用域的所有函数和变量。
AO:当函数被激活,那么一个活动对象(activation object)就会被创建并且分配给执行上下文。活动对象由特殊对象arguments初始化而成。随后他被当做VO用于变量初始化。
在函数上下文中:
VO === AO
在全局上下文中:
VO === this === global
总的来说,VO
中会存放一些变量信息(如声明的变量,函数,arguments 参数等等)
闭包
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
this 指针
this 是执行上下文环境的一个属性,Javascript 中神奇的 this
回收机制
CSS 相关
CSS 中规定每一个元素都有自己的盒子模型,然后按照规则摆放到页面上。
BFC
居中
flex & grid
一些属性
rem em
em
作为font-size
的单位时,其代表父元素的字体大小,em
作为其他属性单位时,代表自身字体大小。rem
作用于根元素,相对于原始大小(16px),作用于非根元素,相对于根元素字体大小。
line-height
行高。取值包括:
normal
取决于元素的 font-family,约为 1.2数字
行高则为数字 * font-size
长度
行高即为长度: 如 16px百分比
行高则为百分比 * font-size
padding/margin
内边距/外边距,取值除了普通长度(如 16px)之外,主要关注百分比:内边距/外边距即为 百分比*父元素的宽度
,可用做高度自适应。
position:fixed
fixed
属性会创建新的层叠上下文。当元素祖先的 transform
, perspective
或 filter
属性非 none
时,容器由视口改为该祖先。
box-sizing
box-sizing: content-box; /*默认值,对应标准盒子模型 */
box-sizing: border-box; /* 对应IE盒子模型,将 padding 和 border 被包含在定义的 width 和 height 之内 */
box-sizing: inherit; /* 规定应从父元素继承 box-sizing 属性 */
CSS3 多列
column-count
column-gap
column-rule