浏览器渲染css和script

转自浏览器渲染

参考渲染内部原理

参考渲染原理内部剖析

参考浏览器工作原理(一)

浏览器渲染过程,涉及的知识体系甚广。了解浏览器渲染过程和原理,对理解前端优化原则

浏览器渲染页面的过程包含以下五个耗时过程:

1. DNC查找

2. TCP连接过程

3. HTTP请求响应

4. 服务器响应

5. 客户端渲染

我们在此主要研究浏览器渲染。也就是获取渲染树、布局、绘制页面,这个过程又可以分为以下五个步骤:

1. HTML解析成DOM树

2. CSS解析成CSSOM树

3. DOM和CSSOM合并成一个渲染树

4. 根据渲染树来布局,计算每个节点的几何信息

5. 将各个节点绘制到页面


但是在浏览器渲染过程中如果DOM和CSSOM改变,以上步骤就会重新执行,才能计算出哪些像素需要在屏幕上重新渲染。

阻塞渲染

浏览器总是并行加载资源的,html解析器被脚本阻塞时,解析器会停止html解析,但是仍会识别脚本后面的资源,并进行预加载。

同时,由于下面两点:

  1. 默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。
  2. JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性。

存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。另外:

  1. 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
  2. JavaScript 可以查询和修改 DOM 与 CSSOM。
  3. CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。

所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:

  1. CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
  2. JavaScript 应尽量少影响 DOM 的构建。

浏览器的发展日益加快(目前的 Chrome 官方稳定版是 61),具体的渲染策略会不断进化,但了解这些原理后,就能想通它进化的逻辑。下面来看看 CSS 与 JavaScript 具体会怎样阻塞资源。

 reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 <html> 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

  repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

注意:(1)display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。

     (2)display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

   (3)有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。


1、简单地说,页面渲染就是浏览器将html代码根据CSS定义的规则显示在浏览器窗口中的这个过程。先来大致了解一下浏览器都是怎么工作的:
  1. 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
  2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;
  3. 浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
  4. 浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;
  5. 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
  6. 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
  7. 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;
  8. Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码;
  9. 终于等到了</html>的到来,浏览器泪流满面……
  10. 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;
  11. 浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请

  求了新的CSS文件,重新渲染页面。


CSS

<style> p { color: red; }</style>
<link rel="stylesheet" href="index.css">

这样的 link 标签(无论是否 inline)会被视为阻塞渲染的资源,浏览器会优先处理这些 CSS 资源,直至 CSSOM 构建完毕。

渲染树(Render-Tree)的关键渲染路径中,要求同时具有 DOM 和 CSSOM,之后才会构建渲染树。即,HTML 和 CSS 都是阻塞渲染的资源。HTML 显然是必需的,因为包括我们希望显示的文本在内的内容,都在 DOM 中存放,那么可以从 CSS 上想办法。

最容易想到的当然是精简 CSS 并尽快提供它。除此之外,还可以用媒体类型(media type)和媒体查询(media query)来解除对渲染的阻塞。

<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">

第一个资源会加载并阻塞。
第二个资源设置了媒体类型,会加载但不会阻塞,print 声明只在打印网页时使用。
第三个资源提供了媒体查询,会在符合条件时阻塞渲染。

JavaScript

JavaScript 的情况比 CSS 要更复杂一些。观察下面的代码:

<p>Do not go gentle into that good night,</p>
<script>console.log("inline")</script>
<p>Old age should burn and rave at close of day;</p>
<script src="app.js"></script>
<p>Rage, rage against the dying of the light.</p>

<p>Do not go gentle into that good night,</p>
<script src="app.js"></script>
<p>Old age should burn and rave at close of day;</p>
<script>console.log("inline")</script>
<p>Rage, rage against the dying of the light.</p>

这样的 script 标签会阻塞 HTML 解析,无论是不是 inline-script。上面的 P 标签会从上到下解析,这个过程会被两段 JavaScript 分别打断一次(加载并且执行的时间段内)。

所以实际工程中,我们常常将资源放到文档底部。

defer

<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>

defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。

defer 不会改变 script 中代码的执行顺序,示例代码会按照 1、2、3 的顺序执行。所以,defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。

async

<script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>

async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

从上一段也能推出,多个 async-script 的执行顺序是不确定的。值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true,下一节会继续这个话题。

document.createElement

使用 document.createElement 创建的 script 默认是异步的,示例如下。

console.log(document.createElement("script").async); // true

所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。

如果使用 document.createElement 创建 link 标签会怎样呢?

const style = document.createElement("link");
style.rel = "stylesheet";
style.href = "index.css";
document.head.appendChild(style); // 阻塞?

其实这只能通过试验确定,已知的是,Chrome 中已经不会阻塞渲染,Firefox、IE 在以前是阻塞的,现在会怎样我没有试验。

document.write 与 innerHTML

通过 document.write 添加的 link 或 script 标签都相当于添加在 document 中的标签,因为它操作的是 document stream(所以对于 loaded 状态的页面使用 document.write 会自动调用 document.open,这会覆盖原有文档内容)。即正常情况下, link 会阻塞渲染,script 会同步执行。不过这是不推荐的方式,Chrome 已经会显示警告,提示未来有可能禁止这样引入。如果给这种方式引入的 script 添加 async 属性,Chrome 会检查是否同源,对于非同源的 async-script 是不允许这么引入的。

如果使用 innerHTML 引入 script 标签,其中的 JavaScript 不会执行。当然,可以通过 eval() 来手工处理,不过不推荐。如果引入 link 标签,我试验过在 Chrome 中是可以起作用的。另外,outerHTML、insertAdjacentHTML() 应该也是相同的行为,我并没有试验。这三者应该用于文本的操作,即只使用它们添加 text 或普通 HTML Element。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值