CSS 与 JS 是这样阻塞 DOM 解析和渲染的

估计大家都听过,尽量将 CSS 放头部,JS 放底部,这样可以提高页面的性能。然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂。因此洗(wang)心(yang)革(bu)面(lao),小结一下最近玩出来的成果。

友情提示,本文也是小白向为主,如果直接想看结论可以拉到最下面看的~



node 端唯一需要解释一下的是这个函数:

function?sleep(time)?{??return?new?Promise(function(res)?{????setTimeout(()?=>?{??????res()????},?time);??})}

嗯!其实就延时啦。如果 CSS 或者 JS 文件名有 sleep3000 之类的前缀时,意思就是延迟 3000 毫秒才会返回这文件。

下文使用的 HTML 文件是长这样的:

????<!DOCTYPE?html>????<html?lang="en">????<head>????????<meta?charset="UTF-8">????????<title>Title</title>????????<style>????????????div?{????????????????width:?100px;????????????????height:?100px;????????????????background:?lightgreen;????????????}????????</style>????</head>????<body>????????<div></div>????</body>????</html>

我会在其中插入不同的 JS 和 CSS。

而使用的 common.css,不论有没有前缀,内容都是这样的:

div?{??background:?red;}

好了,话不多数,开始正文!

CSS

关于 CSS,大家肯定都知道的是<link>标签放在头部性能会高一点,少一点人知道如果<script>与<link>同时在头部的话,<script>在上可能会更好。这是为什么呢?下面我们一起来看一下 CSS 对 DOM 的影响是什么。

CSS 不会阻塞 DOM 的解析

注意哦!这里说的是 DOM 解析,证明的例子如下,首先在头部插入<script defer src="/js/logDiv.js"></script>,JS 文件的内容是:

const?div?=?document.querySelecot(‘div‘);console.log(div);

defer 属性相信大家也很熟悉了,MDN 对此的描述是用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。设置这个属性,能保证 DOM 解析后马上打印出 div。

之后将&lt;link rel="stylesheet" href="/css/sleep3000-common.css"&gt;插入 HTML 文件的任一位置,打开浏览器,可以看到是首先打印出 div 这个 DOM 节点,过 3s 左右之后才渲染出一个浅蓝色的 div。这就证明了 CSS 是不会阻塞 DOM 的解析的,尽管 CSS 下载需要 3s,但这个过程中,浏览器不会傻等着 CSS 下载完,而是会解析 DOM 的。

这里简单说一下,浏览器是解析 DOM 生成 DOM Tree,结合 CSS 生成的 CSS Tree,最终组成 render tree,再渲染页面。由此可见,在此过程中 CSS 完全无法影响 DOM Tree,因而无需阻塞 DOM 解析。然而,DOM Tree 和 CSS Tree 会组合成 render tree,那 CSS 会不会页面阻塞渲染呢?

CSS 阻塞页面渲染

其实这一点,刚才的例子已经说明了,如果 CSS 不会阻塞页面阻塞渲染,那么 CSS 文件下载之前,浏览器就会渲染出一个浅绿色的 div,之后再变成浅蓝色。浏览器的这个策略其实很明智的,想象一下,如果没有这个策略,页面首先会呈现出一个原始的模样,待 CSS 下载完之后又突然变了一个模样。用户体验可谓极差,而且渲染是有成本的。

因此,基于性能与用户体验的考虑,浏览器会尽量减少渲染的次数,CSS 顺理成章地阻塞页面渲染。

然而,事情总有奇怪的,请看这例子,HTML 头部结构如下:

<header>????<link?rel="stylesheet"?href="/css/sleep3000-common.css">????<script?src="/js/logDiv.js"></script></header>

但思考一下这会产生什么结果呢?

答案是浏览器会转圈圈三秒,但此过程中不会打印任何东西,之后呈现出一个浅蓝色的 div,再打印出 null。结果好像是 CSS 不单阻塞了页面渲染,还阻塞了 DOM 的解析啊!稍等,在你打算掀桌子疯狂吐槽我之前,请先思考一下是什么阻塞了 DOM 的解析,刚才已经证明了 CSS 是不会阻塞的,那么阻塞了页面解析其实是 JS!但明明 JS 的代码如此简单,肯定不会阻塞这么久,那就是 JS 在等待 CSS 的下载,这是为什么呢?

仔细思考一下,其实这样做是有道理的,如果脚本的内容是获取元素的样式,宽高等 CSS 控制的属性,浏览器是需要计算的,也就是依赖于 CSS。浏览器也无法感知脚本内容到底是什么,为避免样式获取,因而只好等前面所有的样式下载完后,再执行 JS。因而造成了之前例子的情况。

所以,看官大人明白为何&lt;script&gt;&lt;link&gt;同时在头部的话,&lt;script&gt;在上可能会更好了么?之所以是可能,是因为如果&lt;link&gt;的内容下载更快的话,是没影响的,但反过来的话,JS 就要等待了,然而这些等待的时间是完全不必要的。

JS

JS,也就是<script>标签,估计大家都很熟悉了,不就是阻塞 DOM 解析和渲染么。然而,其中其实还是有一点细节可以考究一下的,我们一起来好好看看。
JS 阻塞 DOM 解析

首先我们需要一个新的 JS 文件名为 blok.js,内容如下:

const?arr?=?[];for?(let?i?=?0;?i?<?10000000;?i++)?{??arr.push(i);??arr.splice(i?%?3,?i?%?7,?i?%?5);}const?div?=?document.querySelector(‘div‘);console.log(div);复制代码

其实那个数组操作时没意义的,只是为了让这个 JS 文件多花执行时间而已。之后把这个文件插入头部,浏览器跑一下。

结果估计大家也能想象得到,浏览器转圈圈一会,这过程中不会有任何东西出现。之后打印出 null,再出现一个浅绿色的 div。现象就足以说明 JS 阻塞 DOM 解析了。其实原因也很好理解,浏览器并不知道脚本的内容是什么,如果先行解析下面的 DOM,万一脚本内全删了后面的 DOM,浏览器就白干活了。更别谈丧心病狂的 document.write。浏览器无法预估里面的内容,那就干脆全部停住,等脚本执行完再干活就好了。

对此的优化其实也很显而易见,具体分为两类。如果 JS 文件体积太大,同时你确定没必要阻塞 DOM 解析的话,不妨按需要加上 defer 或者 async 属性,此时脚本下载的过程中是不会阻塞 DOM 解析的。

而如果是文件执行时间太长,不妨分拆一下代码,不用立即执行的代码,可以使用一下以前的黑科技:setTimeout()。当然,现代的浏览器很聪明,它会“偷看”之后的 DOM 内容,碰到如&lt;link&gt;&lt;script&gt;&lt;img&gt;等标签时,它会帮助我们先行下载里面的资源,不会傻等到解析到那里时才下载。

浏览器遇到?&lt;script&gt;?标签时,会触发页面渲染

这个细节可能不少看官大人并不清楚,其实这才是解释上面为何 JS 执行会等待 CSS 下载的原因。先上例子,HTML 内 body 的结构如下:

<body>????<div></div>????<script?src="/js/sleep3000-logDiv.js"></script>????<style>????????div?{????????????background:?lightgrey;????????}????</style>????<script?src="/js/sleep5000-logDiv.js"></script>????<link?rel="stylesheet"?href="/css/common.css"></body>

这个例子也是很极端的例子,但不妨碍它透露给我们很多重要的信息。想象一下,页面会怎样呢?

答案是先浅绿色,再浅灰色,最后浅蓝色。由此可见,每次碰到<script>标签时,浏览器都会渲染一次页面。这是基于同样的理由,浏览器不知道脚本的内容,因而碰到脚本时,只好先渲染页面,确保脚本能获取到最新的 DOM 元素信息,尽管脚本可能不需要这些信息。

小结

综上所述,我们得出这样的结论:

  • CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。
  • JS 阻塞 DOM 解析,但浏览器会"偷看"DOM,预先下载相关资源。
  • 浏览器遇到?<script>且没有 defer 或 async 属性的 标签时,会触发页面渲染,因而如果前面 CSS 资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。

所以,你现在明白为何&lt;script&gt;最好放底部,&lt;link&gt;最好放头部,如果头部同时有&lt;script&gt;&lt;link&gt;的情况下,最好将&lt;script&gt;放在&lt;link&gt;上面了吗?

原文地址:https://blog.51cto.com/14284898/2380395

时间: 2024-11-13 01:36:18

CSS 与 JS 是这样阻塞 DOM 解析和渲染的的相关文章

原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

hello~各位亲爱的看官老爷们大家好.估计大家都听过,尽量将CSS放头部,JS放底部,这样可以提高页面的性能.然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂.因此洗(wang)心(yang)革(bu)面(lao),小结一下最近玩出来的成果. 友情提示,本文也是小白向为主,如果直接想看结论可以拉到最下面看的~ 由于关系到文件的读取,那是肯定需要服务器的,我会把全部的文件放在github上,给我点个 star 我会开心

css、js的相互阻塞

先决条件:脚本前面存在外部样式 以下试验虽然是在chrome下,但是对于IE8+以及其他浏览器也适用. 1.内联脚本(http://jsbin.com/mudab/1) <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS Bin</title> <script>var start = +new Date;</script&g

CSS规则树和HTML的DOM树合成渲染树时渲染结点与选择器链的匹配

在浏览器内核(排版引擎)CSS规则树和HTML的DOM树合成渲染树的时候,会涉及到渲染树的位置属性的问题,因为其位置属性将通过CSS选择器链的优先级来决定,而渲染树的某个结点可能会同时满足多个选择器链,这时候就要通过选择器的优先级来完成属性的赋值. 在这个地方,我仅仅处理了几个简单的选择器情况:{(.class)     (#id)       (element)      (#id,.class,elememt)      (#id>.class)        (#id element)  

异步执行js脚本——防止阻塞

JS允许我们修改页面中的所有方面:内容,样式和用户进行交互时的行为. 但是js同样可以阻塞DOM树的形成并且延迟页面的渲染. 让你的js变成异步执行,并且减少不必要的js文件从而提高性能. JavaScript可以查询和修改DOM和CSSOM JavaScript的执行阻塞了CSSOM的执行 JavaScript 阻塞了DOM的形成,除非特殊声明js异步执行 js是一个同步语言可以修改网页的任何方面: <html> <head> <meta name="viewpo

css加载会造成阻塞吗?

之前面试今日头条的时候,今日头条面试官问我,js执行会阻塞DOM树的解析和渲染,那么css加载会阻塞DOM树的解析和渲染吗?所以,接下来我就来对css加载对DOM树的解析和渲染做一个测试.为了完成本次测试,先来科普一下,如何利用chrome来设置下载速度 打开chrome控制台(按下F12),可以看到下图,重点在我画红圈的地方 点击我画红圈的地方(No throttling),会看到下图,我们选择GPRS这个选项 这样,我们对资源的下载速度上限就会被限制成20kb/s,好,那接下来就进入我们的正

SpringMVC配置了拦截器(interceptors)却显示不出css、js样式的解决办法

首先因为在web.xml里面配置了 <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 导致所有的连接都会经过DispatcherServlet,会过滤掉css.js等样式,导致页面无法渲染成功 因此需要在springmvc配置文件中放行静态资源 <!

前端性能优化知识,包括css和js

作者:野次链接:http://www.zhihu.com/question/33032042/answer/95948831来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 1. 减少HTTP请求次数 尽量合并图片.CSS.JS.比如加载一个页面,如果有5个css文件的话,那么会发出5次http请求,这样会让用户第一次访问你的页面的时候会长时间等待.而如果把这个5个文件合成一个的话,就只需要发出一次http请求,节省网络请求时间,加快页面的加载. 2. 使用CDN

前端性能优化 css和js的加载与执行

一个网站在浏览器端是如何进行渲染的? html本身首先会被渲染成 DOM 树,实际上 html 是最先通过网址请求过来的,请求过来之后,html 本身会由一个字节流转化成一个字符流,浏览器端拿的就是字符流,然后通过词法分析之后,将相应的语法分析成相应的 token ,比如说 header token, 转化不同的 token tag ,然后通过 token 类型 append 到 dom 树. 遇到 link token tag,然后去请求 css ,请求过来之后再去对 css 进行解析,生成

html、css、js三者的加载顺序

页面的加载顺序时从上往下加载的: 1.DOM树加载到llink标签 css文件的加载和DOM树的加载是可以同时进行的,也就是说,css在加载时DOM树还在继续构建. 这个过程中如果遇到css样式或者img,会向服务器发送一个请求,待资源返回后,将其添加到DOM中的相应位置. 2.DOM树加载到script标签 由于JS是单线程模式,因此不会与DOM并行加载,因此需要等到整个js文件加载完后再继续加载DOM.倘若JS文件过大,则可能导致浏览器页面显示滞后,出现"阻塞效应". .JS阻塞其