在网上无数的地方,我看到了在 JavaScript 之前包含 CSS 的建议。推理通常是,of this form:
在订购 CSS 和 JavaScript 时,您希望 CSS 排在第一位。原因是渲染线程拥有渲染页面所需的所有样式信息。如果 JavaScript 包含首先出现,则 JavaScript 引擎必须先解析所有内容,然后再继续处理下一组资源。这意味着渲染线程不能完全显示页面,因为它没有它需要的所有样式。
我的实际测试揭示了一些完全不同的东西:
我的测试工具
我使用以下 Ruby 脚本为各种资源生成特定的延迟:
require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
require 'date'
class Handler < EventMachine::Connection
include EventMachine::HttpServer
def process_http_request
resp = EventMachine::DelegatedHttpResponse.new( self )
return unless @http_query_string
path = @http_path_info
array = @http_query_string.split("&").map{|s| s.split("=")}.flatten
parsed = Hash[*array]
delay = parsed["delay"].to_i / 1000.0
jsdelay = parsed["jsdelay"].to_i
delay = 5 if (delay > 5)
jsdelay = 5000 if (jsdelay > 5000)
delay = 0 if (delay < 0)
jsdelay = 0 if (jsdelay < 0)
# Block which fulfills the request
operation = proc do
sleep delay
if path.match(/.js$/)
resp.status = 200
resp.headers["Content-Type"] = "text/javascript"
resp.content = "(function(){
var start = new Date();
while(new Date() - start < #{jsdelay}){}
})();"
end
if path.match(/.css$/)
resp.status = 200
resp.headers["Content-Type"] = "text/css"
resp.content = "body {font-size: 50px;}"
end
end
# Callback block to execute once the request is fulfilled
callback = proc do |res|
resp.send_response
end
# Let the thread pool (20 Ruby threads) handle request
EM.defer(operation, callback)
end
end
EventMachine::run {
EventMachine::start_server("0.0.0.0", 8081, Handler)
puts "Listening..."
}
上面的迷你服务器允许我为 JavaScript 文件(服务器和客户端)设置任意延迟和任意 CSS 延迟。例如,http://10.0.0.50:8081/test.css?delay=500
给了我 500 ms 延迟传输 CSS。
我使用以下页面进行测试。
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script type='text/javascript'>
var startTime = new Date();
</script>
<link href="http://10.0.0.50:8081/test.css?delay=500" type="text/css" rel="stylesheet">
<script type="text/javascript" src="http://10.0.0.50:8081/test2.js?delay=400&jsdelay=1000"></script>
</head>
<body>
<p>
Elapsed time is:
<script type='text/javascript'>
document.write(new Date() - startTime);
</script>
</p>
</body>
</html>
当我首先包含 CSS 时,页面需要 1.5 秒才能呈现:
https://i.stack.imgur.com/ZDgrs.png
当我首先包含 JavaScript 时,页面需要 1.4 秒才能呈现:
https://i.stack.imgur.com/5o5SG.png
我在 Chrome、Firefox 和 Internet Explorer 中得到了类似的结果。然而,在 Opera 中,顺序并不重要。
似乎正在发生的事情是 JavaScript 解释器在所有 CSS 下载完成之前拒绝启动。因此,似乎首先包含 JavaScript 会更有效,因为 JavaScript 线程会获得更多的运行时间。
我是否遗漏了什么,将 CSS 包含在 JavaScript 包含之前的建议是否不正确?
很明显,我们可以添加 async 或使用 setTimeout 来释放渲染线程或将 JavaScript 代码放在页脚中,或者使用 JavaScript 加载器。这里的重点是关于头部中基本 JavaScript 位和 CSS 位的排序。
delay=400&jsdelay=1000
和 delay=500
,这与 100 毫秒或 89 毫秒相去甚远。我想我不清楚你指的是哪些数字。
这是一个非常有趣的问题。我总是将我的 CSS <link href="...">
放在我的 JS <script src="...">
之前,因为“我曾经读过它更好”。所以,你是对的;现在是我们做一些实际研究的时候了!
我在 Node 中设置了自己的测试工具(代码如下)。基本上,我:
确保没有 HTTP 缓存,因此每次加载页面时浏览器都必须进行完整下载。
为了模拟现实,我加入了 jQuery 和 H5BP CSS(因此有相当数量的脚本/CSS 需要解析)
设置两个页面 - 一个在脚本前使用 CSS,一个在脚本后使用 CSS。
记录了
中的外部脚本执行的时间记录
中的内联脚本执行的时间,类似于DOMReady。延迟向浏览器发送 CSS 和/或脚本 500 毫秒。
在 3 大浏览器中运行了 20 次测试。
结果
首先,将 CSS 文件延迟 500 毫秒:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 583ms 36ms | 559ms 42ms | 565ms 49ms
St Dev | 15ms 12ms | 9ms 7ms | 13ms 6ms
------------|--------------|--------------|------------
Body Exec | | |
Average | 584ms 521ms | 559ms 513ms | 565ms 519ms
St Dev | 15ms 9ms | 9ms 5ms | 13ms 7ms
接下来,我将 jQuery 设置为延迟 500 毫秒而不是 CSS:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 597ms 556ms | 562ms 559ms | 564ms 564ms
St Dev | 14ms 12ms | 11ms 7ms | 8ms 8ms
------------|--------------|--------------|------------
Body Exec | | |
Average | 598ms 557ms | 563ms 560ms | 564ms 565ms
St Dev | 14ms 12ms | 10ms 7ms | 8ms 8ms
最后,我将 jQuery 和 CSS 都设置为延迟 500 毫秒:
Browser: Chrome 18 | IE 9 | Firefox 9
CSS: first last | first last | first last
=======================================================
Header Exec | | |
Average | 620ms 560ms | 577ms 577ms | 571ms 567ms
St Dev | 16ms 11ms | 19ms 9ms | 9ms 10ms
------------|--------------|--------------|------------
Body Exec | | |
Average | 623ms 561ms | 578ms 580ms | 571ms 568ms
St Dev | 18ms 11ms | 19ms 9ms | 9ms 10ms
结论
首先,请务必注意,我在假设您的文档的 <head>
中(而不是 <body>
的末尾)中有脚本的情况下进行操作。关于为什么您可能会链接到 <head>
中的脚本而不是文档的末尾,存在各种争论,但这超出了此答案的范围。这严格来说是关于 <script>
是否应该在 <head>
中的 <link>
之前。
在现代 DESKTOP 浏览器中,似乎首先链接到 CSS 从不提供了性能提升。当 CSS 和脚本都被延迟时,将 CSS 放在脚本之后会给您带来微不足道的收益,但在 CSS 延迟时会给您带来很大的收益。 (由第一组结果中的 last
列显示。)
鉴于最后链接到 CSS 似乎不会损害性能,但在某些情况下可以提供收益,如果旧浏览器的性能不是问题,您应该在仅在桌面浏览器上链接到外部脚本后链接到外部样式表。继续阅读移动情况。
为什么?
从历史上看,当浏览器遇到指向外部资源的 <script>
标记时,浏览器会停止解析 HTML,检索脚本,执行它,然后继续解析 HTML。相反,如果浏览器遇到外部样式表的 <link>
,它会继续在获取 CSS 文件的同时(并行)解析 HTML。
因此,广泛重复的将样式表放在首位的建议——它们会先下载,而要下载的第一个脚本可以并行加载。
但是,现代浏览器(包括我上面测试过的所有浏览器)已经实现了 speculative parsing,浏览器在 HTML 中“向前看”并开始下载资源在脚本下载和执行之前。
在没有推测解析的旧浏览器中,将脚本放在首位会影响性能,因为它们不会并行下载。
浏览器支持
推测解析首先实现于:(以及截至 2012 年 1 月使用此版本或更高版本的全球桌面浏览器用户的百分比)
Chrome 1 (WebKit 525) (100%)
即 8 (75%)
火狐 3.5 (96%)
野生动物园 4 (99%)
歌剧 11.60 (85%)
总的来说,目前使用的桌面浏览器中大约有 85% 支持推测加载。将脚本放在 CSS 之前将对全球 15% 的用户造成性能损失; YMMV 基于您网站的特定受众。 (请记住,这个数字正在减少。)
在移动浏览器上,仅仅由于移动浏览器和操作系统环境的异构性,获得明确的数字有点困难。由于推测性渲染是在 WebKit 525(2008 年 3 月发布)中实现的,并且几乎所有有价值的移动浏览器都基于 WebKit,我们可以得出结论,“大多数”移动浏览器应该支持它。根据quirksmode,iOS 2.2/Android 1.0 使用 WebKit 525。我不知道 Windows Phone 长什么样。
但是,我在我的 Android 4 设备上运行了测试,虽然我看到了与桌面结果相似的数字,但我将它连接到 Chrome for Android 中出色的新 remote debugger,并且网络标签显示浏览器实际上一直在等待下载 CSS,直到 JavaScript 完全加载——换句话说,即使是最新版本的 Android 版 WebKit 似乎也不支持推测解析。我怀疑它可能被关闭了由于移动设备固有的 CPU、内存和/或网络限制。
代码
原谅马虎——这是问答环节。
应用程序.js
var express = require('express')
, app = express.createServer()
, fs = require('fs');
app.listen(90);
var file={};
fs.readdirSync('.').forEach(function(f) {
console.log(f)
file[f] = fs.readFileSync(f);
if (f != 'jquery.js' && f != 'style.css') app.get('/' + f, function(req,res) {
res.contentType(f);
res.send(file[f]);
});
});
app.get('/jquery.js', function(req,res) {
setTimeout(function() {
res.contentType('text/javascript');
res.send(file['jquery.js']);
}, 500);
});
app.get('/style.css', function(req,res) {
setTimeout(function() {
res.contentType('text/css');
res.send(file['style.css']);
}, 500);
});
var headresults={
css: [],
js: []
}, bodyresults={
css: [],
js: []
}
app.post('/result/:type/:time/:exec', function(req,res) {
headresults[req.params.type].push(parseInt(req.params.time, 10));
bodyresults[req.params.type].push(parseInt(req.params.exec, 10));
res.end();
});
app.get('/result/:type', function(req,res) {
var o = '';
headresults[req.params.type].forEach(function(i) {
o+='\n' + i;
});
o+='\n';
bodyresults[req.params.type].forEach(function(i) {
o+='\n' + i;
});
res.send(o);
});
css.html
<!DOCTYPE html>
<html>
<head>
<title>CSS first</title>
<script>var start = Date.now();</script>
<link rel="stylesheet" href="style.css">
<script src="jquery.js"></script>
<script src="test.js"></script>
</head>
<body>
<script>document.write(jsload - start);bodyexec=Date.now()</script>
</body>
</html>
js.html
<!DOCTYPE html>
<html>
<head>
<title>CSS first</title>
<script>var start = Date.now();</script>
<script src="jquery.js"></script>
<script src="test.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script>document.write(jsload - start);bodyexec=Date.now()</script>
</body>
</html>
测试.js
var jsload = Date.now();
$(function() {
$.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));
});
jquery.js 是 jquery-1.7.1.min.js
将 CSS 放在 JavaScript 之前有两个主要原因。
旧浏览器(Internet Explorer 6-7、Firefox 2 等)在开始下载脚本时会阻止所有后续下载。因此,如果您有 a.js,然后是 b.css,它们会按顺序下载:首先是 a,然后是 b。如果您有 b.css 后跟 a.js,它们会并行下载,因此页面加载速度更快。在下载所有样式表之前不会呈现任何内容 - 在所有浏览器中都是如此。脚本是不同的——它们会阻止页面中脚本标签下方的所有 DOM 元素的呈现。如果将脚本放在 HEAD 中,则意味着在下载所有样式表和所有脚本之前,整个页面将被阻止呈现。虽然阻止样式表的所有呈现是有意义的(因此您可以在第一时间获得正确的样式并避免无样式内容 FOUC 的闪烁),但阻止脚本的整个页面呈现是没有意义的。通常脚本不会影响任何 DOM 元素或仅影响 DOM 元素的一部分。最好在页面中尽可能低地加载脚本,或者更好地异步加载它们。
使用 Cuzillion 创建示例很有趣。例如,this page 在 HEAD 中有一个脚本,因此整个页面在下载完成之前都是空白的。但是,如果我们将脚本移动到 BODY 块的末尾,则页面标题会呈现,因为这些 DOM 元素出现在 SCRIPT 标记上方,正如您在 this page 中看到的那样。
async
属性,Steve 在此处推荐“更好地异步加载它们”- stackoverflow.com/questions/1834077/…
@import
指令链接 CSS 文件吗?
jQuery
+ jQuery UI
+ $(document).ready(function () { });
放在页面末尾吗?它会始终按预期工作吗?
我不会过分强调你得到的结果,我认为这是主观的,但我有理由向你解释,最好在 js 之前放入 CSS。
在加载您的网站期间,您会看到两种情况:
案例 1:白屏 > 无风格网站 > 风格网站 > 交互 > 风格和互动网站 案例 2: 白屏 > 无风格网站 > 交互 > 风格网站 > 风格和互动网站 老实说,我无法想象有人会选择案例 2。这意味着使用慢速互联网连接的访问者将面临一个无样式的网站,该网站允许他们使用 Javascript 与它进行交互(因为它已经加载)。此外,通过这种方式可以最大限度地增加查看无样式网站的时间。为什么会有人想要那个?
它还可以更好地用作 jQuery states
“使用依赖 CSS 样式属性值的脚本时,在引用脚本之前引用外部样式表或嵌入样式元素非常重要”。
当文件以错误的顺序加载时(首先是 JS,然后是 CSS),任何依赖于 CSS 文件中设置的属性(例如 div 的宽度或高度)的 Javascript 代码都将无法正确加载。似乎在加载顺序错误的情况下,Javascript 有时会知道正确的属性(也许这是由竞争条件引起的?)。根据所使用的浏览器,这种效果似乎更大或更小。
您的测试是在您的个人计算机上还是在 Web 服务器上进行的?它是一个空白页面,还是一个包含图像、数据库等的复杂在线系统?您的脚本是执行简单的悬停事件操作,还是您的网站如何呈现和与用户交互的核心组件?这里有几件事需要考虑,当您冒险进行高质量的 Web 开发时,这些建议的相关性几乎总是成为规则。
“将样式表放在顶部,脚本放在底部”规则的目的是,一般来说,这是实现最佳渐进式渲染的最佳方式,这对用户体验至关重要。
抛开其他一切不谈:假设您的测试是有效的,并且您确实产生了与流行规则相反的结果,这并不奇怪,真的。每个网站(以及使整个内容出现在用户屏幕上所需的一切)都是不同的,互联网也在不断发展。
出于不同的原因,我在 Javascript 之前包含 CSS 文件。
如果我的 Javascript 需要对某些页面元素进行动态调整大小(对于那些 CSS 确实是后面主要内容的极端情况),那么在 JS russing 之后加载 CSS 可能会导致竞争条件,其中元素在 CSS 样式之前调整大小被应用,因此当样式最终启动时看起来很奇怪。如果我预先加载 CSS,我可以保证事情按照预期的顺序运行,并且最终的布局是我想要的。
在 JavaScript 之前包含 CSS 的建议是否无效?
如果您将其仅视为建议,则不会。但是,如果您将其视为硬性规定?是的,它是无效的。
来自https://developer.mozilla.org/en-US/docs/Web/Reference/Events/DOMContentLoaded
样式表加载块脚本执行,因此如果在 之后有一个
<head>
中(而不是<body>
的末尾)中有脚本的情况下进行操作。” I会在答案的前面很多突出显示,就像在顶部一样。从几乎任何角度来看(当然不是性能角度),在head
中包含引用外部文件的script
几乎都是不正确的。我不记得在现实生活中必须这样做。 inline 脚本中可能有一两行奇数,但仅此而已。默认情况下,没有 非常 很好的相反原因,应该是正文的结尾。