CSS和JS文件的位置会影响页面效率。js脚本应放在底部,如果放在首部,当下载执行js时,会影响渲染行程绘制页面;而CSS应放在顶部,如果放在底部,页面可以逐步呈现,但在CSS下载并解析完毕后,已经呈现的文字和图片就要需要根据新的样式重绘。
js脚本文件的位置
js脚本应该放在底部,原因在于js线程与GUI渲染线程是互斥的关系,如果js放在首部,当下载执行js的时候,会影响渲染行程绘制页面,js的作用主要是处理交互,而交互必须得先让页面呈现才能进行,所以为了保证用户体验,尽量让页面先绘制出来。
CSS文件的位置
CSS 是页面渲染的关键因素之一,(当页面存在外链 CSS 时,)浏览器会等待全部的 CSS 下载及解析完成后再渲染页面。关键路径上的任何延迟都会影响首屏时间,因而我们需要尽快地将 CSS 传输到用户的设备,否则,(在页面渲染之前,)用户只能看到一个空白的屏幕。
CSS文件放在顶部一方面是因为放置顺序决定了下载的优先级,更关键的是浏览器的渲染机制。
css在加载过程中不会影响到DOM树的生成,但是会影响到Render树的生成,进而影响到layout,所以一般来说,style的link标签需要尽量放在head里面,因为在解析DOM树的时候是自上而下的,而css样式又是通过异步加载的,这样的话,解析DOM树下的body节点和加载css样式能尽可能的并行,加快Render树的生成的速度。
将CSS放在底部,页面可以逐步呈现,但在CSS下载并解析完毕后,已经呈现的文字和图片就要需要根据新的样式重绘,这是一种不好的用户体验。
js、css等脚本位置对性能的影响
用一句话概括就是: JS 全阻塞,CSS 半阻塞。(词是我发明的,方便记忆而已)
JS 会阻塞后续 DOM 解析以及其它资源(如 CSS,JS 或图片资源)的加载。
CSS 不会阻塞后续 DOM 结构的解析,不会阻塞其它资源(如图片)的加载,但是会阻塞 JS 文件的加载。
现代浏览器很聪明,会进行 prefetch 优化,浏览器在获得 html 文档之后会对页面上引用的资源进行提前下载。(注意仅仅只是提前下载)
下面开始我就一边测试,一边解释上述测试的结果:
测试的浏览器是 Chrome,版本号为 55.0.2883.95 (64-bit)
先用 Nodejs 搭建一个简单 http 服务器:
//test.jsconst http = require('http');const fs = require('fs');const hostname = '127.0.0.1';const port = 9000;http.createServer((req, res) => { if(req.url === "/") { fs.readFile("index.html", "utf-8", function(err, data) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(data); res.end(); }) }else if(req.url === "/yellow.js") { //延迟 5s fs.readFile("yellow.js", "utf-8", function(err, data) { res.writeHead(200, { 'Content-Type': 'text/plain' }); setTimeout(function () { res.write(data); res.end(); }, 5000); }) }else if(req.url === "/blue.js") { //延迟 10s fs.readFile("blue.js", "utf-8", function(err, data) { res.writeHead(200, { 'Content-Type': 'text/plain' }); setTimeout(function () { res.write(data); res.end(); }, 10000); }) }else if(req.url === "/red.css") { //延迟 15s fs.readFile("red.css", "utf-8", function(err, data) { res.writeHead(200, { 'Content-Type': 'text/css' }); setTimeout(function () { res.write(data); res.end(); }, 15000); }) }else if(req.url === "/green.css") { //延迟 20s fs.readFile("green.css", "utf-8", function(err, data) { res.writeHead(200, { 'Content-Type': 'text/css' }); setTimeout(function () { res.write(data); res.end(); }, 20000); }) }}).listen(port, hostname, () => { console.log('Server running at ' + hostname);});
首页的代码结构:
//index.html测试浏览器渲染 First Line
Second Line
Third Line
Fourth Line
Fifth Line
以及其它 CSS 和 JS 文件:
//yellow.js document.body.style.cssText = "background: yellow !important"; //blue.js document.body.style.cssText = "background: blue !important";
//red.css body { background:red !important; } //green.css body { background: green !important; }
说明下:yellow.js 和 blue.js 下载时间分别为 5s 和 10s,red.css 和 green.css 下载时间分别为 15s 和 20s。
之后将所有文件放到同个目录下,在控制台输入 node test.js,打开浏览器访问 127.0.0.1:9000 就可以访问。
先来看第三点结论:现代浏览器很聪明,会进行 prefetch 优化,浏览器在获得 html 文档之后会对页面上引用的资源进行提前下载。(注意仅仅只是提前下载)
很好理解,从图中可以看出:CSS、JS、图片在浏览器在拿到 html 文档之后会将页面上引用资源几乎同时下载,但具体什么时候执行要看 html 的结构,注意我这里使用的是 Chrome 浏览器,其它浏览器可能会有差别。
还有就是一个奇怪的现象,Chrome 浏览器有时会对 img 进行 prefetch,有时则不会。
接着是第一点规则:
JS 会阻塞后续 DOM 解析以及其它资源(如 CSS,JS 或图片资源)的加载。
从上图可以看出,当浏览器解析到 yellow.js 这行时候会等待 yellow.js 加载,阻塞后续 DOM 结构的解析(包括 DOM 结构,其他所有资源(CSS, JS, 图片))。
这个很好理解:
JS 运行在浏览器中,是单线程的,每个 window 一个 JS 线程,所以当然会阻塞后续 DOM 树的解析咯。
JS 有可能会修改 DOM 结构,给 DOM 添加样式等等,所以这就意味着在当前 JS 加载执行完成前,后续资源的加载可能是没有意义的。
其次第二点:
CSS 不会阻塞后续 DOM 结构的解析,不会阻塞其它资源(如图片)的加载,但是会阻塞 JS 文件的加载。
这个就相对比较复杂点,让我先上测试结果的图:
从图中可以得出以下总结:
在加载完 yellow.js 后,当在下载 red.css 时候并不会阻塞 DOM 解析,并且由于第一点规则,当解析到 blue.js 这行的时候,同样会阻塞后续 DOM 解析。
由于我们设置的 red.css 下载时间为 15s 而 blue.js 为 10s,而从前面第三条规则的图中也可以看到,blue.js 在 10s 左右下载完而 red.css 在 15s 左右下载完毕。
最后在 15s 时候页面变为了蓝色,这说明了 CSS 阻塞了 JS 的加载,后续的 JS 文件虽然提前下载完毕了,但还是要等前面 CSS 文件加载完后才能执行。
后续当 blue.js 加载完之后可以看到,green.css 的下载并不会影响到后续 img 的加载,所以说明 CSS 文件下载并不会影响后续图片等其它资源以及 DOM 的加载。
这个也好理解:JS 代码在执行前,浏览器必须保证在 JS 之前的所有 CSS 样式都解析完成,不然不就乱套了,前面的 CSS 样式可能会覆盖 JS 文件中定义的元素样式,这是 CSS 阻塞后续 JS 执行的根本原因。
最后这里说明下为什么最后 body 的背景色没有变成绿色:因为 js 定义的样式在内联样式,优先级高于在 CSS 文件中定义的样式,所以不是 green.css 没有加载,而是没有生效。看下图就知道了:(green 和 red 样式都被划掉了)
所以知道了上述的结论之后,我们在开发的时候应该尽可能地:
将样式或 CSS 文件定义在 head 中,并且在处理此类请求的时候应该尽快能够响应(CDN 什么的),如果像上面请求一个 CSS 文件都要 10s 的话,那你这页面估计没多少人有耐心等下去。
将 JS 脚本文件放在 body 底部,让 DOM 结构能优先渲染出来,避免 DOM 被阻塞。
当编写比较耗时的 JS 代码时候尽可能使用异步的方式进行加载,比如 setTimeout, ajax 等等,同样也是为了避免页面渲染耗时过长,影响用户体验。
上面介绍了 JS 会阻塞后续 DOM 解析以及其它资源(如 CSS,JS 或图片资源)的加载,这是在没有考虑到 defer, async 的情况下。
当浏览器碰到 script 脚本的时候:(不考虑浏览器的 prefetch)
从使用的角度来看,首先把脚本丢到 body 底部是比较好的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。
上述的三点用图可表示为:
蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。
总结:
由于现代浏览器都存在 prefetch,所以 defer, async 可能并没有太多的用途,可以作为了解扩展知识,仅仅将脚本文件放到 body 底部就可以起到很不错的优化效果。
defer 和 async 都是异步加载脚本文件。
慎用 async,因为它完全不考虑依赖关系,只要下载完后就加载,不考虑此时页面样式先后的加载顺序,不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics。
耗时较长的脚本代码可以使用 defer 来推迟执行。
更多编程相关知识,请访问:编程视频!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!