整理本身的笔记,构建知识体系(前端篇 二)

导言

上一篇咱们整理了从浏览器接收到url到开启网络请求线程,有兴趣的同窗能够看看整理本身的笔记,构建知识体系(前端篇 一),闲话很少说,咱们继续。javascript

2 开启网络请求线程发出完整的http请求

DNS域名解析

DNS(Domain Name System):域名解析服务器css

DNS根域html

根域:目前有13个根集群服务器,美国10台,日本1台,荷兰1台,瑞典1台前端

一级域名:Top Level Domain: tld com, edu, mil, gov, net, org, int,arpa 组织域、国家域(.cn, .ca, .hk, .tw)、反向域 等java

二级域名node

三级域名nginx

最多127级域名es6

如图:web

域名结构

ICANN(The Internet Corporation for Assigned Names and Numbers)互联网名称与数字地址分配机构,负责在全球范围内对互联网通用顶级域名(gTLD)以及国家和地区顶级域名(ccTLD)系统的管理、以及根服务器系统的管理算法

DNS工做原理

客户端向离它最近的DNS服务器发起了查询请求,通常是由运营商提供 若是代理DNS服务器有记录则直接能够返回给客户端;若是没有记录则去根DNS服务器请求,根DNS并不会存储因此的主机名对应IP的记录,它只会记录它的子域的IP,例如.com等后缀的域,代理DNS服务器会拿到.com域的DNS服务器IP 而后再将请求发往.com.域的DNS服务器,若是仍是没有找到主机,则再往它的下一级找,直到找到具体的主机,把IP返回给客户端,同时代理DNS服务器也会缓存一份到本地 一次完整的查询请求通过的流程:Client -->hosts文件 -->DNS Service Local Cache --> DNS Server (recursion) --> Server Cache --> iteration(迭代) --> 根--> 顶级域名DNS-->二级域名DNS…

NDS工做原理

TCP/IP请求

http的本质就是tcp/ip请求

这里咱们须要了解3次握手规则创建链接以及断开链接时的四次挥手

tcp将http长报文划分为短报文,经过三次握手与服务端创建链接,进行可靠传输

三次握手的步骤:(抽象派)

客户端:hello,你是server么?

服务端:hello,我是server,你是client么?

客户端:yes,我是client

创建链接成功后,接下来就正式传输数据

四次挥手的步骤:(抽象派)

主动方:我已经关闭了向你那边的主动通道了,只能被动接收了

被动方:收到通道关闭的信息

被动方:那我也告诉你,我这边向你的主动通道也关闭了

主动方:最后收到数据,以后双方没法通讯

为何链接的时候是三次握手,关闭的时候倒是四次握手?

由于当Server端收到Client端的SYN链接请求报文后,能够直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。可是关闭链接时,当Server端收到FIN报文时,极可能并不会当即关闭SOCKET,因此只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端全部的报文都发送完了,我才能发送FIN报文,所以不能一块儿发送。故须要四步握手。

五层因特网协议栈

协议

在计算机网络与信息通信领域里,人们常常说起 “协议” 一词。互联网中经常使用的协议有HTTP、TCP、IP等。

协议的必要性

简单来讲,协议就是计算机与计算机之间经过网络通讯时,事先达成的一种 “约定”。这种“约定”使不一样厂商的设备、不一样的CPU以及不一样操做系统组成的计算机之间,只要遵循相同的协议就可以实现通讯。这就比如一个中国人说汉语一个外国人说英语使用不一样的国家语言进行沟通,怎么也没法理解。若是两我的约定好 都说中文或英文,就能够互相沟统统信。协议分为不少种,每一种协议都明确界定了它的行为规范。两台计算机必须可以支持相同的协议,并遵循相同协议进行处理,这样才能实现相互通讯。

协议分层

网络协议一般分不一样层次进行开发,每一层分别负责不一样的通讯功能

协议层

分层的做用

这两部分我推荐一篇我的以为写得比较详细并且易懂的文章 TCP/IP详解

3 服务器接收到请求

服务端在接收到请求时,内部会进行不少的处理,可是本身也只作过一点nodejs后端,也没有大型高并发项目经验,因此只是简单介绍一下,留个概念

负载均衡

对于大型的项目,因为并发访问量很大,因此每每一台服务器是吃不消的,因此通常会有若干台服务器组成一个集群,而后配合反向代理实现负载均衡

固然了,负载均衡不止这一种实现方式,这里不深刻...

简单的说:

用户发起的请求都指向调度服务器(反向代理服务器,譬如安装了nginx控制负载均衡),而后调度服务器根据实际的调度算法,分配不一样的请求给对应集群中的服务器执行,而后调度器等待实际服务器的HTTP响应,并将它反馈给用户

想全面一点了解的同窗请看这里

后台处理

通常后台都是部署到容器中的,因此通常为:

先是容器接受到请求(如tomcat容器) 而后对应容器中的后台程序接收到请求(如java程序) 而后就是后台会有本身的统一处理,处理完后响应响应结果 归纳下:

通常有的后端是有统一的验证的,如安全拦截,跨域验证 若是这一步不符合规则,就直接返回了相应的http报文(如拒绝请求等) 而后当验证经过后,才会进入实际的后台代码,此时是程序接收到请求,而后执行(譬如查询数据库,大量计算等等) 等程序执行完毕后,就会返回一个http响应包(通常这一步也会通过多层封装) 而后就是将这个包从后端发送到前端,完成交互

4 先后台http交互

http报文

用于HTTP协议交互的信息被称为报文。

请求端(客户端)的HTTP报文叫作请求报文,响应端(服务器端)的叫作响应报文。

HTTP报文自己是由多行数据构成的字符串文本。

HTTP报文大体上可分为报文首部和报文主体两块,二者由最初出现的空行来划分。

一般,并不必定要有报文主体,报文通常包括了:通用头部,请求/响应头部,请求/响应体。

通用头部

Request Url: 请求的web服务器地址

Request Method: 请求方式 (Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)

Status Code: 请求的返回状态码,如200表明成功

Remote Address: 请求的远程服务器地址(会转为IP)

譬如,在跨域拒绝时,多是method为options,状态码为404/405等(固然,实际上可能的组合有不少)

其中,Method的话通常分为两批次:

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。

HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。 这里面最经常使用到的就是状态码,不少时候都是经过状态码来判断 状态码大体范围意思:

请求/响应头

经常使用请求头:

Accept: 接收类型,表示浏览器支持的MIME类型
(对标服务端返回的Content-Type)

Accept-Encoding:浏览器支持的压缩类型,如gzip等,超出类型不能接收

Content-Type:客户端发送出去实体内容的类型

Cache-Control: 指定请求和响应遵循的缓存机制,如no-cache

If-Modified-Since:对应服务端的Last-Modified,用来匹配看文件是否变更,只能精确到1s以内,http1.0中

Expires:缓存控制,在这个时间内不会请求,直接使用缓存,http1.0,并且是服务端时间

Max-age:表明资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存,http1.1中

If-None-Match:对应服务端的ETag,用来匹配文件内容是否改变(很是精确),http1.1中

Cookie: 有cookie而且同域访问时会自动带上

Connection: 当浏览器与服务器通讯时对于长链接如何进行处理,如keep-alive

Host:请求的服务器URL

Origin:最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私

Referer:该页面的来源URL(适用于全部类型的请求,会精确到详细页面地址,csrf拦截经常使用到这个字段)

User-Agent:用户客户端的一些必要信息,如UA头部等
复制代码

经常使用响应头:

Access-Control-Allow-Headers: 服务器端容许的请求Headers

Access-Control-Allow-Methods: 服务器端容许的请求方法

Access-Control-Allow-Origin: 服务器端容许的请求Origin头部(譬如为*)

Content-Type:服务端返回的实体内容的类型

Date:数据从服务器发送的时间

Cache-Control:告诉浏览器或其余客户,什么环境能够安全的缓存文档

Last-Modified:请求资源的最后修改时间

Expires:应该在何时认为文档已通过期,从而再也不缓存它

Max-age:客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效

ETag:请求变量的实体标签的当前值

Set-Cookie:设置和页面关联的cookie,服务器经过这个头部把cookie传给客户端

Keep-Alive:若是客户端有keep-alive,服务端也会有响应(如timeout=38)

Server:服务器的一些相关信息
复制代码

http缓存

先后端的http交互中,使用缓存能很大程度上的提高效率,并且基本上对性能有要求的前端项目都是必用缓存的

强缓存与弱缓存

缓存能够简单的划分红两种类型:强缓存(200 from cache)与协商缓存(304)(这里推荐大佬的好文前端进阶必备的网络基础

区别简述以下:

强缓存(200 from cache)时,浏览器若是判断本地缓存未过时,就直接使用,无需发起http请求 协商缓存(304)时,浏览器会向服务端发起http请求,而后服务端告诉浏览器文件未改变,让浏览器使用本地缓存 对于协商缓存,使用Ctrl + F5强制刷新可使得缓存无效

可是对于强缓存,在未过时时,必须更新资源路径才能发起新的请求(更改了路径至关因而另外一个资源了,这也是前端工程化中经常使用到的技巧)

缓存头部简述 上述提到了强缓存和协商缓存,那它们是怎么区分的呢?

答案是经过不一样的http头部控制

先看下这几个头部:

If-None-Match/E-tag、If-Modified-Since/Last-Modified、Cache-Control/Max-Age、Pragma/Expires 这些就是缓存中经常使用到的头部,这里不展开。仅列举下大体使用。

属于强缓存控制的:

(http1.1)Cache-Control/Max-Age (http1.0)Pragma/Expires 注意:Max-Age不是一个头部,它是Cache-Control头部的值

属于协商缓存控制的:

(http1.1)If-None-Match/E-tag (http1.0)If-Modified-Since/Last-Modified 能够看到,上述有提到http1.1和http1.0,这些不一样的头部是属于不一样http时期的

再提一点,其实HTML页面中也有一个meta标签能够控制缓存方案-Pragma

不过,这种方案仍是比较少用到,由于支持状况不佳,譬如缓存代理服务器确定不支持,因此不推荐

头部的区别 首先明确,http的发展是从http1.0到http1.1

而在http1.1中,出了一些新内容,弥补了http1.0的不足。

http1.0中的缓存控制:

Pragma:严格来讲,它不属于专门的缓存控制头部,可是它设置no-cache时可让本地强缓存失效(属于编译控制,来实现特定的指令,主要是由于兼容http1.0,因此之前又被大量应用)

Expires:服务端配置的,属于强缓存,用来控制在规定的时间以前,浏览器不会发出请求,而是直接使用本地缓存,注意,Expires通常对应服务器端时间,如Expires:Fri, 30 Oct 1998 14:19:41 If-Modified-Since/Last-Modified:这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是If-Modified-Since,而服务端的是Last-Modified,它的做用是,在发起请求时,若是If-Modified-Since和Last-Modified匹配,那么表明服务器资源并未改变,所以服务端不会返回资源实体,而是只返回头部,通知浏览器可使用本地缓存。Last-Modified,顾名思义,指的是文件最后的修改时间,并且只能精确到1s之内

http1.1中的缓存控制:

Cache-Control:缓存控制头部,有no-cache、max-age等多种取值

Max-Age:服务端配置的,用来控制强缓存,在规定的时间以内,浏览器无需发出请求,直接使用本地缓存,注意,Max-Age是Cache-Control头部的值,不是独立的头部,譬如Cache-Control: max-age=3600,并且它值得是绝对时间,由浏览器本身计算

If-None-Match/E-tag:这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是If-None-Match,而服务端的是E-tag,一样,发出请求后,若是If-None-Match和E-tag匹配,则表明内容未变,通知浏览器使用本地缓存,和Last-Modified不一样,E-tag更精确,它是相似于指纹同样的东西,基于FileEtag INode Mtime Size生成,也就是说,只要文件变,指纹就会变,并且没有1s精确度的限制。

Max-Age相比Expires?

Expires使用的是服务器端的时间

可是有时候会有这样一种状况-客户端时间和服务端不一样步

那这样,可能就会出问题了,形成了浏览器本地的缓存无用或者一直没法过时

因此通常http1.1后不推荐使用Expires

而Max-Age使用的是客户端本地时间的计算,所以不会有这个问题

所以推荐使用Max-Age。

注意,若是同时启用了Cache-Control与Expires,Cache-Control优先级高。

E-tag相比Last-Modified?

Last-Modified:

代表服务端的文件最后什么时候改变的 它有一个缺陷就是只能精确到1s, 而后还有一个问题就是有的服务端的文件会周期性的改变,致使缓存失效 而E-tag:

是一种指纹机制,表明文件相关指纹 只有文件变才会变,也只要文件变就会变, 也没有精确时间的限制,只要文件一遍,立马E-tag就不同了 若是同时带有E-tag和Last-Modified,服务端会优先检查E-tag

各大缓存头部的总体关系以下图

cookie

Cookie是什么? Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序均可以读取的信息。

为何须要Cookie? 由于HTTP协议是无状态的,对于一个浏览器发出的屡次请求,WEB服务器没法区分 是否是来源于同一个浏览器。因此,须要额外的数据用于维护会话。 Cookie 正是这样的一段随HTTP请求一块儿被传递的额外数据。

Cookie能作什么? Cookie只是一段文本,因此它只能保存字符串。并且浏览器对它有大小限制以及 它会随着每次请求被发送到服务器,因此应该保证它不要太大。 Cookie的内容也是明文保存的,有些浏览器提供界面修改,因此, 不适合保存重要的或者涉及隐私的内容。

Cookie 的限制。 大多数浏览器支持最大为 4096 字节的 Cookie。因为这限制了 Cookie 的大小,最好用 Cookie 来存储少许数据,或者存储用户 ID 之类的标识符。用户 ID 随后即可用于标识用户,以及从数据库或其余数据源中读取用户信息。 浏览器还限制站点能够在用户计算机上存储的 Cookie 的数量。大多数浏览器只容许每一个站点存储 20 个 Cookie;若是试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自全部站点的 Cookie 总数做出绝对限制,一般为 300 个。

经过前面的内容,咱们了解到Cookie是用于维持服务端会话状态的,一般由服务端写入,在后续请求中,供服务端读取。

经常使用的使用场景:

在登录页面,用户登录了

此时,服务端会生成一个session,session中有对于用户的信息(如用户名、密码等)

而后会有一个sessionid(至关因而服务端的这个session对应的key)

而后服务端在登陆页面中写入cookie,值就是:jsessionid=xxx

而后浏览器本地就有这个cookie了,之后访问同域名下的页面时,自动带上cookie,自动检验,在有效时间内无需二次登录。
复制代码

上述就是cookie的经常使用场景简述(固然了,实际状况下得考虑更多因素)

通常来讲,cookie是不容许存放敏感信息的(千万不要明文存储用户名、密码),由于很是不安全,若是必定要强行存储,首先,必定要在cookie中设置httponly(这样就没法经过js操做了),另外能够考虑rsa等非对称加密(由于实际上,浏览器本地也是容易被攻克的,并不安全)

另外,因为在同域名的资源请求时,浏览器会默认带上本地的cookie,针对这种状况,在某些场景下是须要优化的。

譬如如下场景:

客户端在域名A下有cookie(这个能够是登录时由服务端写入的)

而后在域名A下有一个页面,页面中有不少依赖的静态资源(都是域名A的,譬若有20个静态资源)

此时就有一个问题,页面加载,请求这些静态资源时,浏览器会默认带上cookie

也就是说,这20个静态资源的http请求,每个都得带上cookie,而实际上静态资源并不须要cookie验证

此时就形成了较为严重的浪费,并且也下降了访问速度(由于内容更多了)
复制代码

固然了,针对这种场景,是有优化方案的(多域名拆分)。具体作法就是:

将静态资源分组,分别放到不一样的域名下(如static.base.com) 而page.base.com(页面所在域名)下请求时,是不会带上static.base.com域名的cookie的,因此就避免了浪费 说到了多域名拆分,这里再提一个问题,那就是:

在移动端,若是请求的域名数过多,会下降请求速度(由于域名整套解析流程是很耗费时间的,并且移动端通常带宽都比不上pc) 此时就须要用到一种优化方案:dns-prefetch(让浏览器空闲时提早解析dns域名,不过也请合理使用,勿滥用) 关于cookie的交互,能够看下图总结:

长链接短连接

长链接:client方与server方先创建链接,链接创建后不断开,而后再进行报文发送和接收。这种方式下因为通信链接一直存在。此种方式经常使用于P2P通讯。

短链接:Client方与server每进行一次报文收发交易时才进行通信链接,交易完毕后当即断开链接。此方式经常使用于一点对多点通信。C/S通讯。

长链接与短链接的操做过程:

短链接的操做步骤是:创建链接——数据传输——关闭链接...创建链接——数据传输——关闭链接;

长链接的操做步骤是:创建链接——数据传输...(保持链接)...数据传输——关闭链接

长链接与短链接的使用时机:

长链接

链接多用于操做频繁,点对点的通信,并且链接数不能太多的状况。每一个TCP链接的创建都须要三次握手,每一个TCP链接的断开要四次握手。若是每次操做都要 创建链接而后再操做的话处理速度会下降,因此每次操做下次操做时直接发送数据就能够了,不用再创建TCP链接。例如:数据库的链接用长链接,若是用短链接 频繁的通讯会形成socket错误,频繁的socket建立也是对资源的浪费。

短链接

链接:web网站的http服务通常都用短链接。由于长链接对于服务器来讲要耗费必定的资源。像web网站这么频繁的成千上万甚至上亿客户端的链接用短连 接更省一些资源。试想若是都用长链接,并且同时用成千上万的用户,每一个用户都占有一个链接的话,可想而知服务器的压力有多大。因此并发量大,可是每一个用户 又不需频繁操做的状况下须要短链接。

总之:长链接和短链接的选择要视需求而定。

http1.0/1.1/2.0

http1.0

早先1.0的HTTP版本,是一种无状态、无链接的应用层协议。

HTTP1.0规定浏览器和服务器保持短暂的链接,浏览器的每次请求都须要与服务器创建一个TCP链接,服务器处理完成后当即断开TCP链接(无链接),服务器不跟踪每一个客户端也不记录过去的请求(无状态)。

这种无状态性能够借助cookie/session机制来作身份认证和状态记录。而下面两个问题就比较麻烦了。

首先,无链接的特性致使最大的性能缺陷就是没法复用链接。每次发送请求的时候,都须要进行一次TCP的链接,而TCP的链接释放过程又是比较费事的。这种无链接的特性会使得网络的利用率很是低。

其次就是队头阻塞(head of line blocking)。因为HTTP1.0规定下一个请求必须在前一个请求响应到达以前才能发送。假设前一个请求响应一直不到达,那么下一个请求就不发送,一样的后面的请求也给阻塞了。

为了解决这些问题,HTTP1.1出现了。

http1.1

对于HTTP1.1,不只继承了HTTP1.0简单的特色,还克服了诸多HTTP1.0性能上的问题。

首先是长链接,HTTP1.1增长了一个Connection字段,经过设置Keep-Alive能够保持HTTP链接不断开,避免了每次客户端与服务器请求都要重复创建释放创建TCP链接,提升了网络的利用率。若是客户端想关闭HTTP链接,能够在请求头中携带Connection: false来告知服务器关闭请求。

注意: keep-alive不会永远保持,它有一个持续时间,通常在服务器中配置(如apache),另外长链接须要客户端和服务器都支持时才有效

其次,是HTTP1.1支持请求管道化(pipelining)。基于HTTP1.1的长链接,使得请求管线化成为可能。管线化使得请求可以“并行”传输。举个例子来讲,假如响应的主体是一个html页面,页面中包含了不少img,这个时候keep-alive就起了很大的做用,可以进行“并行”发送多个请求。(注意这里的“并行”并非真正意义上的并行传输,具体解释以下。)

须要注意的是,服务器必须按照客户端请求的前后顺序依次回送相应的结果,以保证客户端可以区分出每次请求的响应内容。

也就是说,HTTP管道化可让咱们把先进先出队列从客户端(请求队列)迁移到服务端(响应队列)。

http 2.0

http2.0不是https,它至关因而http的下一代规范(譬如https的请求能够是http2.0规范的)

而后简述下http2.0与http1.1的显著不一样点:

http1.1中,每请求一个资源,都是须要开启一个tcp/ip链接的,因此对应的结果是,每个资源对应一个tcp/ip请求,因为tcp/ip自己有并发数限制,因此当资源一多,速度就显著慢下来 http2.0中,一个tcp/ip请求能够请求多个资源,也就是说,只要一次tcp/ip请求,就能够请求若干个资源,分割成更小的帧请求,速度明显提高。 因此,若是http2.0全面应用,不少http1.1中的优化方案就无需用到了(譬如打包成精灵图,静态资源多域名拆分等)

而后简述下http2.0的一些特性:

多路复用(即一个tcp/ip链接能够请求多个资源)

首部压缩(http头部压缩,减小体积)

二进制分帧(在应用层跟传送层之间增长了一个二进制分帧层,改进传输性能,实现低延迟和高吞吐量)

服务器端推送(服务端能够对客户端的一个请求发出多个响应,能够主动通知客户端)

请求优先级(若是流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定须要多少资源来处理该请求。) 详细在这里

https

https中的s表示SSL或者TLS,就是在原http的基础上加上一层用于数据加密、解密、身份认证的安全层。

通常来讲,主要关注的就是SSL/TLS的握手流程,以下(简述):

1. 浏览器请求创建SSL连接,并向服务端发送一个随机数–Client random和客户端支持的加密方法,好比RSA加密,此时是明文传输。 

2. 服务端从中选出一组加密算法与Hash算法,回复一个随机数–Server random,并将本身的身份信息以证书的形式发回给浏览器
(证书里包含了网站地址,非对称加密的公钥,以及证书颁发机构等信息)

3. 浏览器收到服务端的证书后
    
    - 验证证书的合法性(颁发机构是否合法,证书中包含的网址是否和正在访问的同样),若是证书信任,则浏览器会显示一个小锁头,不然会有提示
    
    - 用户接收证书后(无论信不信任),浏览会生产新的随机数–Premaster secret,而后证书中的公钥以及指定的加密方法加密`Premaster secret`,发送给服务器。
    
    - 利用Client random、Server random和Premaster secret经过必定的算法生成HTTP连接数据传输的对称加密key-`session key`
    
    - 使用约定好的HASH算法计算握手消息,并使用生成的`session key`对消息进行加密,最后将以前生成的全部信息发送给服务端。 
    
4. 服务端收到浏览器的回复

    - 利用已知的加解密方式与本身的私钥进行解密,获取`Premaster secret`
    
    - 和浏览器相同规则生成`session key`
    
    - 使用`session key`解密浏览器发来的握手消息,并验证Hash是否与浏览器发来的一致
    
    - 使用`session key`加密一段握手消息,发送给浏览器
    
5. 浏览器解密并计算握手消息的HASH,若是与服务端发来的HASH一致,此时握手过程结束,
复制代码

详细这里

5 解析页面

前面有提到http交互,那么接下来就是浏览器获取到html,而后解析,渲染

流程简述

浏览器内核拿到内容后,渲染步骤大体能够分为如下几步:

1. 解析HTML,构建DOM树

2. 解析CSS,生成CSS规则树

3. 合并DOM树和CSS规则,生成render树

4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算

5. 绘制render树(paint),绘制页面像素信息

6. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上
复制代码

如图:

html解析生成dom树

整个渲染步骤中,HTML解析是第一步。

简单的理解,这一步的流程是这样的:浏览器解析HTML,构建DOM树。

但实际上,在分析总体构建时,却不能一笔带过,得稍微展开。

解析HTML到构建出DOM固然过程能够简述以下:

Bytes → characters → tokens → nodes → DOM
譬如假设有这样一个HTML页面:
复制代码
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>
复制代码

浏览器的处理以下:

重点过程:

1. Conversion转换:浏览器将得到的HTML内容(Bytes)基于他的编码转换为单个字符

2. Tokenizing分词:浏览器按照HTML规范标准将这些字符转换为不一样的标记token。每一个token都有本身独特的含义以及规则集

3. Lexing词法分析:分词的结果是获得一堆的token,此时把他们转换为对象,这些对象分别定义他们的属性和规则

4. DOM构建:由于HTML标记定义的就是不一样标签之间的关系,这个关系就像是一个树形结构同样
例如:body对象的父节点就是HTML对象,而后段略p对象的父节点就是body对象
复制代码

css解析生成样式树

同理,CSS规则树的生成也是相似。简述为:

Bytes → characters → tokens → nodes → CSSOM
譬如style.css内容以下:

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
复制代码

那么最终的CSSOM树就是:

渲染

渲染树

当DOM树和CSSOM都有了后,就要开始构建渲染树了

通常来讲,渲染树和DOM树相对应的,但不是严格意义上的一一对应

由于有一些不可见的DOM元素不会插入到渲染树中,如head这种不可见的标签或者display: none等

总体来讲能够看图:

渲染流程

图中重要的四个步骤就是:

1. 计算CSS样式

2. 构建渲染树

3. 布局,主要定位坐标和大小,是否换行,各类position overflow z-index属性

4. 绘制,将图像绘制出来
复制代码

而后,图中的线与箭头表明经过js动态修改了DOM或CSS,致使了从新布局(Layout)或渲染(Repaint)

这里Layout和Repaint的概念是有区别的:

Layout,也称为Reflow,即回流。通常意味着元素的内容、结构、位置或尺寸发生了变化,须要从新计算样式和渲染树 Repaint,即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只须要应用新样式绘制这个元素就能够了 回流的成本开销要高于重绘,并且一个节点的回流每每回致使子节点以及同级节点的回流, 因此优化方案中通常都包括,尽可能避免回流。

什么会引发回流?

1.页面渲染初始化

2.DOM结构改变,好比删除了某个节点

3.render树变化,好比减小了padding

4.窗口resize

5.最复杂的一种:获取某些属性,引起回流,
不少浏览器会对回流作优化,会等到数量足够时作一次批处理回流,
可是除了render树的直接变化,当获取一些属性时,浏览器为了得到正确的值也会触发回流,这样使得浏览器优化无效,包括
    (1)offset(Top/Left/Width/Height)
     (2) scroll(Top/Left/Width/Height)
     (3) cilent(Top/Left/Width/Height)
     (4) width,height
     (5) 调用了getComputedStyle()或者IE的currentStyle
复制代码

回流必定伴随着重绘,重绘却能够单独出现

因此通常会有一些优化方案,如:

减小逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新 避免循环操做dom,建立一个documentFragment或div,在它上面应用全部DOM操做,最后再把它添加到window.document 避免屡次读取offset等属性。没法避免则将它们缓存到变量 将复杂的元素绝对定位或固定定位,使得它脱离文档流,不然回流代价会很高

简单层与复合层

简单介绍下:

能够认为默认只有一个复合图层,全部的DOM节点都是在这个复合图层下的 若是开启了硬件加速功能,能够将某个节点变成复合图层 复合图层之间的绘制互不干扰,由GPU直接控制 而简单图层中,就算是absolute等布局,变化时不影响总体的回流,可是因为在同一个图层中,仍然是会影响绘制的,所以作动画时性能仍然很低。而复合层是独立的,因此通常作动画推荐使用硬件加速

详细简单层与复合层

js引擎解析

JS的解释阶段 首先得明确: JS是解释型语音,因此它无需提早编译,而是由解释器实时运行

引擎对JS的处理过程能够简述以下:

1. 读取代码,进行词法分析(Lexical analysis),而后将代码分解成词元(token)

2. 对词元进行语法分析(parsing),而后将代码整理成语法树(syntax tree)

3. 使用翻译器(translator),将代码转为字节码(bytecode)

4. 使用字节码解释器(bytecode interpreter),将字节码转为机器码
最终计算机执行的就是机器码。
复制代码

为了提升运行速度,现代浏览器通常采用即时编译(JIT-Just In Time compiler)

即字节码只在运行时编译,用到哪一行就编译哪一行,而且把编译结果缓存(inline cache)

这样整个程序的运行速度能获得显著提高。

并且,不一样浏览器策略可能还不一样,有的浏览器就省略了字节码的翻译步骤,直接转为机器码(如chrome的v8)

总结起来能够认为是: 核心的JIT编译器将源码编译成机器码运行

JS的预处理阶段 上述将的是解释器的总体过程,这里再提下在正式执行JS前,还会有一个预处理阶段 (譬如变量提高,分号补全等)

预处理阶段会作一些事情,确保JS能够正确执行,这里仅提部分:

分号补全

JS执行是须要分号的,但为何如下语句却能够正常运行呢?

console.log('a')

console.log('b')
复制代码

缘由就是JS解释器有一个Semicolon Insertion规则,它会按照必定规则,在适当的位置补充分号

譬如列举几条自动加分号的规则:

当有换行符(包括含有换行符的多行注释),而且下一个token无法跟前面的语法匹配时,会自动补分号。 当有}时,若是缺乏分号,会补分号。 程序源代码结束时,若是缺乏分号,会补分号。 因而,上述的代码就变成了

console.log('a');

console.log('b');
复制代码

因此能够正常运行

固然了,这里有一个经典的例子:

function b() {
    return
    {
        a: 'a'
    };
}
复制代码

因为分号补全机制,因此它变成了:

function b() {
    return;
    {
        a: 'a'
    };
}
复制代码

因此运行后是undefined

变量提高

通常包括函数提高和变量提高

譬如:

a = 1;
b();
function b() {
    console.log('b');
}
var a;
复制代码

通过变量提高后,就变成:

function b() {
    console.log('b');
}
var a;
a = 1;
b();
复制代码

这里没有展开,其实展开也能够牵涉到不少内容的

譬如能够提下变量声明,函数声明,形参,实参的优先级顺序,以及es6中let有关的临时死区等

JS的执行阶段

这里能够参考深刻理解javascript系列(10) 大叔讲解的十分到位

回收机制

JS有垃圾处理器,因此无需手动回收内存,而是由垃圾处理器自动处理。

通常来讲,垃圾处理器有本身的回收策略。

譬如对于那些执行完毕的函数,若是没有外部引用(被引用的话会造成闭包),则会回收。(固然通常会把回收动做切割到不一样的时间段执行,防止影响性能)

经常使用的两种垃圾回收规则是:

标记清除, 引用计数

Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),简单解释以下:

遍历全部可访问的对象。 回收已不可访问的对象。 譬如:(出自javascript高程)

当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到它们。 而当变量离开环境时,则将其标记为“离开环境”。 垃圾回收器在运行的时候会给存储在内存中的全部变量都加上标记(固然,可使用任何标记方式)。 而后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包,也就是说在环境中的以及相关引用的变量会被去除标记)。 而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了。 最后,垃圾回收器完成内存清除工做,销毁那些带标记的值并回收它们所占用的内存空间。 关于引用计数,简单点理解:

跟踪记录每一个值被引用的次数,当一个值被引用时,次数+1,减持时-1,下次垃圾回收器会回收次数为0的值的内存(固然了,容易出循环引用的bug)

GC的缺陷

和其余语言同样,javascript的GC策略也没法避免一个问题: GC时,中止响应其余操做

这是为了安全考虑。

而Javascript的GC在100ms甚至以上

对通常的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。

这就是引擎须要优化的点: 避免GC形成的长时间中止响应。

GC优化策略

这里介绍经常使用到的:分代回收(Generation GC)

目的是经过区分“临时”与“持久”对象:

多回收“临时对象”区(young generation)

少回收“持久对象”区(tenured generation)

减小每次需遍历的对象,从而减小每次GC的耗时。

像node v8引擎就是采用的分代回收(和java同样,做者是java虚拟机做者。)

结语

本文仅是梳理下知识体系骨架,便于你们记忆,造成本身的知识树,开支阔叶。学海无涯,砥砺前行,不负初心!

再次感谢大佬们的分享,顺便提一下上篇的小问题 我给的答案是 8 只,不知道是否是最少的,请大佬们指教。

相关文章
相关标签/搜索