美文网首页我爱编程
网页的渲染机制

网页的渲染机制

作者: DHFE | 来源:发表于2018-01-17 00:32 被阅读30次

    网页的渲染机制

    参考文章:
    ScriptJava——了解HTML页面的渲染过程
    浏览器加载,解析,渲染的过程
    HTML渲染过程详解
    Web页面加载,解析,渲染过程
    浏览器的渲染机制
    还有最经典的
    How browsers work
    前端必读:浏览器内部工作原理

    既然谈到了网页的渲染机制,一般而言指的就是浏览器下网页的渲染过程了,也就是客户端的浏览器行为。为什么我这里要提到客户端,因为按照知识点延伸,知识的枝丫还可以“生长”到HTTP的知识点,当然这里就不展开啦。

    浏览器构成
    浏览器主要组件
    • 用户界面(User Interface)
      包括地址栏、后退/前进按钮、书签目录等,也就是除了显示你请求的页面以外的所有部分
    • 浏览器引擎(Browser engine)
      用来查询及操作渲染引擎的接口,如我们常提到的Webkit(Safari,Chrome),Gecko(Firefox),Trident(IE)等。

    • 渲染引擎(Rendering engine)
      用来显示请求的内容,例如:请求内容为HTML,它负责解析HTML及CSS,并将解析后的结果显示出来。

    • 网络层(Networking)
      用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同的平台上工作。

    • UI后端(UI Backend)
      用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通过接口,底层使用操作系统的用户接口。
      P.S:调用的是操作系统的底层GUI接口?

    • JS解释器(JS编译器)
      用来解释JS代码。

    • 数据存储(Data Persistence)
      于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

    需要注意的是,不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。

    难道这是Chrome耗内存的原因之一?类比了一下JVM......


    Webkit主流程 (firefox)Geoko主流程

    渲染主流程(The main flow)

    • 当向service请求到资源后,交由渲染引擎以解析html,并将标签转化为DOM树(Document Object Model Tree),什么是DOM树
      DOM树的构建过程是一个深度遍历的过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

    • 将CSS解析成CSSOM树(Cascading Style Sheets Object Model Tree)

    • 根据DOM树和CSSOM树去构造Rendering Tree(渲染树)。

    渲染树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。构建好渲染树后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。在下一步就是绘制,即遍历渲染树,并使用UI后端层绘制每个节点。
    值得注意的事,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局渲染树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。


    这里基本上就是粗略的梳理了一遍浏览器解析过程,DOM、CSSOM、Rendering等,但是还有一些point需要知道的:

    脚本

    web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的(src),则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。

    这里又会有新的知识点,浏览器渲染中的进程阻塞,后面再说。

    预解析(Speculative parsing)

    Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。

    样式表(Style sheets)

    样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而Chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。


    进程阻塞

    要实现一个阻塞,最简单的方法,就是在HTML中head内出插入一段<script>alert("hello world")</script>

    这就是一个阻塞案例。

    我们回顾一下浏览器渲染过程,从URL输入地址栏,DNS解析,服务器获取请求.......到最后浏览器拿到HTTP响应和响应内容,开始解析。
    这时候,渲染引擎就会开始解析HTML标签,此时,会有CSS的link,图片的src等等资源加载请求被发出,但这些,都不会影响“主线程”HTML文档的解析,是异步请求

    但是,当遇到了js文件,不管是href还是内嵌式的脚本,HTML都会挂起渲染(加载解析渲染同步)的线程,不仅要等待js文件加载完毕,还要等待解析,执行完毕,才可以恢复HTML文档的渲染过程。

    那一般来说,我们希望JS什么时候执行呢?首先我们得想JS对HTML和CSS有什么作用,没错,经常出现的就是通过JS增删查改HTML和CSS,添加DOM节点,改变式样等等,那么我们就得保证JS执行之前,浏览器中已经解析完毕所有的HTML和CSS,这样JS才能“找”到它们,so,一般来说,都是将JS放在最后执行(只是标准情况)。

    原因:
    JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。

    办法:可以将外部引用的js文件放在</body>前。

    这里也有一个新问题:CSS和JS在网页中的放置顺序是怎样的?
    CSS虽然不影响JS文件的加载,但却影响JS的执行,如脚本中需要用到CSS,但CSS又没有解析完成时......

    原因:
    可能会有 var width = $('#id').width(),这意味着,js代码执行前,浏览器必须保证css文件已下载和解析完成。这也是css阻塞后续js的根本原因。

    办法:当js文件不需要依赖css文件时,可以将js文件放在头部css的前面。


    图片来自——ScriptJava的Blog

    知识点挺多,引用几篇文章。

    浏览器的工作机制
    让我们再聊聊浏览器资源加载优化
    浏览器渲染阻塞与优化-详解推迟加载、异步加载
    仙女不需要ID——回流(reflow)与重绘(repaint)
    魔王小瑾回流——(reflow)与重绘(repaint)
    其中需要用到Chrome Devloper Tools的Network,这里也上几篇文章吧。
    Chrome DevTools 之 Network,网络加载分析利器
    Chrome开发者工具详解(2)-Network面板
    Chrome浏览器Network面板http请求时间分析

    defer 和 async

    script标签存在两个属性,defer和async。

    • <script src="example.js"></script>
      没有属性时,浏览器会立即记载并执行指定的脚本,“立即”指的是在渲染该script标签之下的文档元素之前就执行。
    • <script async src="example.js"></script>
      属性:async,加载和渲染后续文档元素的过程将和example.js的加载执行并行进行(异步)
    • <script defer src="example.js"></script>
      defer,和async类似,加载和渲染后续文档元素的过程将和example.js的加载执行并行进行,但是,example.js的执行(仅是执行)要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
    • 当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。
    • 当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash。
    区别

    蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。

    1. defer和async在网络加载过程是一致的,都是异步执行的;
    2. 两者的区别在于脚本加载完成之后何时执行,可以看出defer更符合大多数场景对应用脚本加载和执行的要求;
    3. 如果存在多个有defer属性的脚本,那么它们是按照加载顺序执行脚本的;而对于async,它的加载和执行是紧紧挨着的,无论声明顺序如何,只要加载完成就立刻执行,它对于应用脚本用处不大,因为它完全不考虑依赖。
    详解defer和async的原理及应用
    javascript在html中的加载顺序
    HTML页面加载和解析流程详细介绍

    CSS和JS在网页中的放置顺序是怎样的?

    遵守关注点分离原则,CSS由link标签来外链引入,不推荐使用内联CSS。

    重点是JS,JS代码的存放引入也有几种方法可使用。

    • <script src="example.js"></script>
      src引入,一般放置在head标签内,特征是,如果把javascript放在head里的话,则先被解析,但这时候body还没有解析,所以会返回空值。一般都会绑定一个监听(windows.onload),当全部的html文档解析完之后,再执行代码。

    需调用才执行的脚本或事件触发执行的脚本放在HTML的head部分中。当你把脚本放在head部分中时,可以保证脚本在任何调用之前被加载。

    有时候你可能想在几个页面中运行同样的脚本程序, 而不需在各个页面中重复的写这些代码。这时你就要用到外部脚本。你可以把脚本写在一个外部文件中,保存在扩展名为 .js的文件中。

    • 放置在body内(末尾),<body><script type="text/javascript">........</script></body>
      页面加载完成后,才会加载执行JavaScript代码。

    将JavaScript标识放置<Head>... </Head>在头部之间,使之在主页和其余部分代码之前预先装载,从而可使代码的功能更强大; 比如对*.js文件的提前调用。 也就是说把代码放在<head>区在页面载入的时候,就同时载入了代码,你在<body>区调用时就不需要再载入代码了,速度就提高了,这种区别在小程序上是看不出的,当运行很大很复杂的程序时,就可以看出了。
    当然也可以将JavaScript标识放置在<Body>... </Body>主体之间以实现某些部分动态地创建文档。 这里比如制作鼠标跟随事件,肯定只有当页面加载后再进行对鼠标坐标的计算。或者是filter滤镜与javascript的联合使用产生的图片淡入淡出效果。


    白屏和FOUC是什么?

    白屏
    不同的浏览器对于CSS和HTML的处理方式不同,有的是等待CSS加载完成之后,在对HTML元素进行渲染和展示(白屏问题)。
    有的是先对HTML元素进行展示,然后等待CSS加载完成之后重新对样式进行渲染(回流),这是FOUC(Flash Of Unstyled Content)。

    对于白屏问题,常出现于IE浏览器,在某些场景下(新窗口、刷新)会出现此情况,使用@import标签,CSS放入link,嵌入head内,也有可能出现白屏。

    解决:使用link标签将其放入顶部。

    FOUC
    样式放入底部时,在某些特定场景下(点击链接,输入URL,使用书签进入等),会出现FOUC现象(逐步加载无样式的内容,等CSS加载完毕后突然展现样式),对于Firefox会一直表现出FOUC。

    解决:同样的,将CSS置入head内,良好的顺应了浏览器引擎解析HTML文档顺序的自然规则。

    相关文章

      网友评论

        本文标题:网页的渲染机制

        本文链接:https://www.haomeiwen.com/subject/hjvsoxtx.html