浏览器基础是前端知识网中的一个小分支,也是前端开发人员必须掌握的基础知识点。他贯穿着前端的整个网络体系,项目优化也是围绕着浏览器进行的。css
开发人员在面试的时候或许会被问到:html
从你在浏览器输入一个网址到网页内容彻底被展现的这段时间内,都发生了什么事情?前端
确实是个老生常谈的问题,但问题的答案并非惟一的,或许在三五年前,这个问题还会有一个「相对」标准的答案。程序员
- 浏览器在接收到这个指令时,会开启一个单独的线程来处理这个指令,首先要判断用户输入的是否为合法或合理的 URL 地址,是否为 HTTP 协议请求,若是是那就进入下一步
- 浏览器的浏览器引擎将对此 URL 进行分析,若是存在缓存「cache-control」且未过时,则会从本地缓存提取文件(From Memory Cache,200返回码),若是缓存「cache-control」不存在或过时,浏览器将发起远程请求
- 经过 DNS 解析域名获取该网站地址对应的 IP 地址,连同浏览器的 Cookie、 userAgent 等信息向此 IP 发出 GET 请求。
- 接下来就是经典的「三次握手」,HTTP 协议会话,浏览器客户端向 Web 服务器发送报文,进行通信和数据传输。
- 进入网站的后端服务,如 Tomcat、Apache 等,还有近几年流行的 Node.js 服务器,这些服务器上部署着应用代码,语言有不少,如 Java、 PHP、 C++、 C# 和 Javascript 等。
- 服务器根据 URL 执行相应的后端应用逻辑,期间会使用到「服务器缓存」或「数据库」。
- 服务器处理请求并返回响应报文,若是浏览器访问过该页面,缓存上有对应资源,与服务器最后修改记录对比,一致则返回 304,不然返回 200 和对应的内容。
- 浏览器接收到返回信息并开始下载该 HTML文件(无缓存、200返回码)或从本地缓存提取文件(有缓存、304返回码)
- 浏览器的渲染引擎在拿到 HTML 文件后,便开始解析构建 DOM 树,并根据 HTML 中的标记请求下载指定的 MIME 类型文件(如 CSS、 JavaScript 脚本等),同时使用&设置缓存等内容。
- 渲染引擎根据 CSS 样式规则将 DOM 树扩充为渲染树,而后进行重排、重绘。
- 若是含有 JS 文件将会执行,进行 Dom 操做、缓存读存、事件绑定等操做。最终页面将被展现在浏览器上。
此答案精简的归纳了「后端为主的 MVC 模式」及早期 Web 应用的浏览器响应的全过程。前端技术发展到如今,「先后端分离」「中间件直出」和「MNV*模式」也已问世,再谈及此问题,答案会有所不一样。web
就以「先后端分离」为例,在上方答案的第4步后,紧接着就不会直接进入后端服务器了。而会被 HTTP 和反向代理服务器,如 Ngnix,拦截。面试
- 前置步骤一、二、三、4
- Ngnix 在监听到 HTTP(80端口)或 HTTPS(443端口)请求,根据 URL 作服务分发,分发(rewrite)到后端服务器或静态资源服务器,首页请求基本是分发到静态服务器,返回一个 HTML 文件
- 步骤七、八、九、10
- 执行 JS 脚本,异步 ajax、 fetch 发起 POST、 GET 请求,从新进入 Ngnix 分发,这次分发到后端服务器,步骤五、六、7,而后返回一个 xml 或 json 格式的信息,通常含有 code(返回码)和 result(依赖信息)
- js 回调根据返回码执行不一样的逻辑,增删改页面元素,此时可能会发生重排或重绘。首页加载结束。
从以上步骤能够发现,浏览器可能会触发两次重绘,极易产生「白屏」或「页面抖动」现象,为了解决这个问题「中间件直出」的模式应运而生。另外为了扩充大前端的阵营,吸纳 IOS 和 Android,Google 设计了「MNV*模式」,典型表明就是 ReactNative,但此模式已经脱离了浏览器的范畴,此处就再也不作扩展。ajax
以上讨论的渲染过程当中使用到了较多的浏览器功能,如用户地址栏输入框、网络请求、浏览器文档解析、渲染引擎渲染网页、 JavaScript 引擎执行 js 脚本、客户端存储等。 接下来咱们介绍下浏览器的基本结构组成。数据库
浏览器通常由七个模块组成,User Interface(用户界面)、Browser engine(浏览器引擎)、Rendering engine(渲染引擎)、Networking(网络)、JavaScript Interpreter(js解释器)、UI Backend(UI 后端)、Date Persistence(数据持久化存储) 以下图:json
做为前端开发人员,咱们须要重点理解渲染引擎的工做原理,灵活应用数据存储技术,在实际项目开发中会常常涉及到这两个部分,尤为是在作项目性能优化时,理解浏览器渲染引擎的工做原理尤其重要。而其余部分则是由浏览器自行管理的,开发者能控制的地方较少。今天咱们就围绕这两个重点其中的一个部分「浏览器渲染引擎」进行展开小程序
浏览器渲染引擎是由各大浏览器厂商依照 W3C 标准自行研发的,也被称之为「浏览器内核」。
目前,市面上使用的主流浏览器内核有5类:Trident、Gecko、Presto、Webkit、Blink。
Trident:俗称 IE 内核,也被叫作 MSHTML 引擎,目前在使用的浏览器有 IE11 -,以及各类国产多核浏览器中的IE兼容模块。另外微软的 Edge 浏览器再也不使用 MSHTML 引擎,而是使用类全新的引擎 EdgeHTML。
Gecko:俗称 Firefox 内核,Netscape6 开始采用的内核,后来的 Mozilla FireFox(火狐浏览器)也采用了该内核,Gecko 的特色是代码彻底公开,所以,其可开发程度很高,全世界的程序员均可觉得其编写代码,增长功能。由于这是个开源内核,所以受到许多人的青睐,Gecko 内核的浏览器也不少,这也是 Gecko 内核虽然年轻但市场占有率可以迅速提升的重要缘由。
Presto:Opera 前内核,为啥说是前内核呢?由于 Opera12.17 之后便拥抱了 Google Chrome 的 Blink 内核,此内核就没了寄托
Webkit:Safari 内核,也是 Chrome 内核原型,主要是 Safari 浏览器在使用的内核,也是特性上表现较好的浏览器内核。也被大量使用在移动端浏览器上。
Blink: 由 Google 和 Opera Software 开发,在Chrome(28及日后版本)、Opera(15及日后版本)和Yandex浏览器中使用。Blink 实际上是 Webkit 的一个分支,添加了一些优化的新特性,例如跨进程的 iframe,将 DOM 移入 JavaScript 中来提升 JavaScript 对 DOM 的访问速度等,目前较多的移动端应用内嵌的浏览器内核也渐渐开始采用 Blink。
浏览器渲染引擎最重要的工做就是将 HTML 和 CSS 文档解析组合最终渲染到浏览器窗口上。以下图所示,渲染引擎在接受到 HTML 文件后主要进行了如下操做:解析 HTML 构建 DOM 树 -> 构建渲染树 -> 渲染树布局 -> 渲染树绘制。
解析 HTML 构建 DOM 树时渲染引擎会将 HTML 文件的便签元素解析成多个 DOM 元素对象节点,而且将这些节点根据父子关系组成一个树结构。同时 CSS 文件被解析成 CSS 规则表,而后将每条 CSS 规则按照「从右向左」的方式在 DOM 树上进行逆向匹配,生成一个具备样式规则描述的 DOM 渲染树。接下来就是将渲染树进行布局、绘制的过程。首先根据 DOM 渲染树上的样式规则,对 DOM 元素进行大小和位置的定位,关键属性如position;width;margin;padding;top;border;...
,接下来再根据元素样式规则中的color;background;shadow;...
规则进行绘制。
另外,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽量早的将内容呈现到屏幕上,并不会等到全部的 html 都解析完成以后再去构建和布局 render 树。它是解析完一部份内容就显示一部份内容,同时,可能还在经过网络下载其他内容。
再者,须要注意的是,在浏览器渲染完首屏页面后,若是对 DOM 进行操做会引发浏览器引擎对 DOM 渲染树的从新布局和从新绘制,咱们叫作「重排」和「重绘」,因为重排和重绘是先后依赖的关系,重绘发生时未必会触发渲染引擎的重排,可是若是发生了重排就必然会触发重绘操做,这样带来的性能损害就是巨大的。所以咱们在作性能优化的时候应该遵循「避免重排;减小重绘」的原则。
在不一样的浏览器内核下, 浏览器页面渲染的流程略有不一样
上面两幅图分别是 Webkit 和 Geoko 内核渲染 DOM 的工做流程,对比能够看出,二者的区别主要在于 CSS 样式表的解析时机,Webkit 内核下,HTML 和 CSS 文件的解析是同步的,而 Geoko 内核下,CSS 文件须要等到 HTML 文件解析成内容 Sink 后才进行解析。
另外描述术语也有不一样,除此以外二者的流程就基本相同了,其中最重要的三个部分就是 「HTML 的解析」「CSS 的解析」「渲染树的生成」。这三个部分的原理比较深,会涉及到「词法分析」「语法分析」「转换」「解释」等数据结构的知识,比较枯燥,通常咱们了解到这里就够了,想深刻了解的同窗能够阅读此篇译文,浏览器的工做原理,里面详细的解释了以上三个部分的流程和原理。此处就再也不多作赘述了。
上面咱们提到过, CSS 规则是按照「从右向左」的方式在 DOM 树上进行逆向匹配的,最终生成一个具备样式规则描述的 DOM 渲染树。
可是你知道为何要「从右向左」作逆向匹配吗?
咱们从新回看【webkit 内核工做流程图】
CSS 规则匹配是发生在webkit引擎的「Attachment」过程当中,浏览器要为每一个 DOM Tree 中的元素扩充 CSS 样式规则(匹配 Style Rules)。对于每一个 DOM 元素,必须在全部 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的,在遍历 DOM Tree 时,从 Style Rules 中去寻找对应的 selector。
咱们来举一个最简单的栗子:
<template>
<div>
<div class="t">
<span>test</span>
<p>test</p>
<div>
</div>
</template>
<style>
div{ color: #000; }
div .t span{ color: red; }
div .t p{color: blue; }
</style>复制代码
此处咱们有一个 html 元素 和一个 style 元素,二者须要作遍历匹配
此处会有 4*3 个匹配项,若是作正向匹配,在遇到 <span>
标签匹配 div .t p{ color: red; }
到匹配项时,计算机首先要找到<span>
标签的父标签和祖父标签,判断他们是否知足div .t
的规则,而后再匹配<span>
是否为p
标签,此处匹配不成功,产生了三次浪费。
若是时逆向匹配,那么第一次对比<span>
是否为p
标签即可排除此规则,效率更高。
若是将 HTML 结构变复杂,CSS 规则表变庞大,那么,「逆向匹配」的优点就远大于「正向匹配」了,由于匹配的状况远远低于不匹配的状况。另外,若是在选择器结尾加上通配符「*」,那么「逆向匹配」的优点就大打折扣了,这也就是不少优化原则提到的「尽可能避免在选择器末尾添加通配符」的缘由。
极限了想,若是咱们的样式表不存在嵌套关系,以下:
<template>
<div class="t">
<span class="div_t_span">test</span>
<p class="div_t_p">test</p>
<div>
</template>
<style>
div{ color: #000; }
.div_t_span{ color: red; }
.div_t_p{color: blue; }
</style复制代码
那么引擎的「Attachment」过程将获得极大的精简,效率也是可想而知的,这就是为何「微信小程序」样式表不建议使用关系行写法的缘由。
咱们大体能够在以上案例中看到同浏览器渲染引擎相关的可行优化点。
大体为如下几种
将 JS 文件放在 HTML 文档后加载,或者使用异步的方式加载 JS 代码
在作 css 动画的时候减小使用 width、 margin、 padding 等影响 CSS 布局对规则,可使用 CSS3 的 transform 代替。另外值得注意的是,在加载大量的图片元素时,尽可能预先限定图片的尺寸大小,不然在图片加载过程当中会更新图片的排版信息,产生大量的重排。
直接使用惟一的类名便可最大限度的提高渲染效率,另外尽可能避免在选择器末尾添加通配符
减小无心义的 dom 层级能够减小 渲染引擎 Attachment 过程当中的匹配计算量
「前端那些事儿系列」二 关于前端优化策略