CSS 应该总是在 Javascript 之前吗? [英] Should CSS always precede Javascript?
问题描述
在网上无数地方,我看到了在 JavaScript 之前包含 CSS 的建议.推理通常是, CSS(因此有大量的脚本/CSS 需要解析)
中记录了外部脚本执行的时间
中的内联脚本执行需要多长时间,类似于DOMReady
.莉>结果
首先,将 CSS 文件延迟 500 毫秒:
浏览器:Chrome 18 |IE 9 |火狐 9CSS: 先后 |第一个 最后 |先后========================================================头执行 |||平均 |583ms 36ms |559ms 42ms |565 毫秒 49 毫秒圣德夫 |15ms 12ms |9ms 7ms |13ms 6ms------------|--------------|--------------|------------身体执行 |||平均 |584 毫秒 521 毫秒 |559 毫秒 513 毫秒 |565 毫秒 519 毫秒圣德夫 |15ms 9ms |9ms 5ms |13ms 7ms
接下来,我将 jQuery 设置为延迟 500 毫秒而不是 CSS:
浏览器:Chrome 18 |IE 9 |火狐 9CSS: 先后 |第一个 最后 |先后========================================================头执行 |||平均 |597ms 556ms |562ms 559ms |564 毫秒 564 毫秒圣德夫 |14ms 12ms |11ms 7ms |8ms 8ms------------|--------------|--------------|------------身体执行 |||平均 |598ms 557ms |563 毫秒 560 毫秒 |564 毫秒 565 毫秒圣德夫 |14ms 12ms |10ms 7ms |8ms 8ms
最后,我将 jQuery 和 CSS 都设置为延迟 500 毫秒:
浏览器:Chrome 18 |IE 9 |火狐 9CSS: 先后 |第一个 最后 |先后========================================================头执行 |||平均 |620ms 560ms |577 毫秒 577 毫秒 |571 毫秒 567 毫秒圣德夫 |16ms 11ms |19ms 9ms |9 毫秒 10 毫秒------------|--------------|--------------|------------身体执行 |||平均 |623ms 561ms |578ms 580ms |571 毫秒 568 毫秒圣德夫 |18ms 11ms |19ms 9ms |9 毫秒 10 毫秒
结论
首先,需要注意的是,我假设您的文档位于文档的 <head>
(而不是 <正文>
).关于为什么您可能会链接到 <head>
中的脚本而不是文档末尾,存在各种争论,但这超出了本答案的范围.这严格来说是关于 s 是否应该在
中的
s 之前.
在现代桌面浏览器中,看起来首先链接到 CSS 从来没有提供性能提升.当 CSS 和脚本都被延迟时,将 CSS 放在脚本之后会给你带来微不足道的收益,但是当 CSS 被延迟时会给你带来很大的收益.(由第一组结果中的 last
列显示.)
鉴于最后链接到 CSS 似乎不会影响性能,但可以在某些情况下提供收益,您应该链接到外部样式表 链接到外部脚本仅在桌面浏览器上,如果旧浏览器的性能不是问题.继续阅读移动情况.
为什么?
过去,当浏览器遇到指向外部资源的 标记时,浏览器会停止解析 HTML,检索脚本,执行它,然后继续解析 HTML.相比之下,如果浏览器遇到外部样式表的
,它会继续在获取 CSS 文件的同时解析 HTML(并行).>
因此,广泛重复的建议是将样式表放在首位 –他们会先下载,第一个下载的脚本可以并行加载.
然而,现代浏览器(包括我在上面测试过的所有浏览器)已经实现了推测性解析,浏览器在 HTML 中向前看"并开始下载资源在脚本下载和执行之前.
在没有推测性解析的旧浏览器中,将脚本放在首位会影响性能,因为它们不会并行下载.
浏览器支持
推测性解析首先在:(以及截至 2012 年 1 月使用此版本或更高版本的全球桌面浏览器用户的百分比)
- Chrome 1 (WebKit 525) (100%)
- IE 8 (75%)
- Firefox 3.5 (96%)
- Safari 4 (99%)
- Opera 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 设备上运行了测试,虽然我看到的数字与桌面结果相似,但我将它连接到了很棒的新 远程调试器,网络选项卡显示浏览器实际上正在等待下载 CSS,直到 JavaScripts完全加载–换句话说,即使是适用于 Android 的最新版本的 WebKit 似乎也不支持推测性解析.我怀疑它可能由于移动设备固有的 CPU、内存和/或网络限制而被关闭.
代码
请原谅我的草率–这是问答环节.
app.js
var express = require('express'), app = express.createServer(), fs = require('fs');app.listen(90);变量文件={};fs.readdirSync('.').forEach(function(f) {控制台.log(f)文件[f] = fs.readFileSync(f);if (f != 'jquery.js' && f != 'style.css') app.get('/' + f, function(req,res) {res.contentType(f);res.send(文件[f]);});});app.get('/jquery.js', function(req,res) {设置超时(功能(){res.contentType('text/javascript');res.send(file['jquery.js']);}, 500);});app.get('/style.css', function(req,res) {设置超时(功能(){res.contentType('text/css');res.send(file['style.css']);}, 500);});变量头结果={css: [],js: []}, 正文结果={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));重发();});app.get('/result/:type', function(req,res) {var o = '';headresults[req.params.type].forEach(function(i) {o+='
' + i;});o+='
';bodyresults[req.params.type].forEach(function(i) {o+='
' + i;});res.send(o);});
css.html
<头><title>CSS优先</title><script>var start = Date.now();</script><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="test.js"></script>头部><身体><script>document.write(jsload - start);bodyexec=Date.now()</script>
js.html
<头><title>CSS优先</title><script>var start = Date.now();</script><script src="jquery.js"></script><script src="test.js"></script><link rel="stylesheet" href="style.css">头部><身体><script>document.write(jsload - start);bodyexec=Date.now()</script>
test.js
var jsload = Date.now();$(函数(){$.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));});
jquery.js 是 jquery-1.7.1.min.js
In countless places online I have seen the recommendation to include CSS prior to JavaScript. The reasoning is generally, of this form:
When it comes to ordering your CSS and JavaScript, you want your CSS to come first. The reason is that the rendering thread has all the style information it needs to render the page. If the JavaScript includes come first, the JavaScript engine has to parse it all before continuing on to the next set of resources. This means the rendering thread can't completely show the page, since it doesn't have all the styles it needs.
My actual testing reveals something quite different:
My test harness
I use the following Ruby script to generate specific delays for various resources:
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..."
}
The above mini server allows me to set arbitrary delays for JavaScript files (both server and client) and arbitrary CSS delays. For example, http://10.0.0.50:8081/test.css?delay=500
gives me a 500 ms delay transferring the CSS.
I use the following page to test.
<!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>
When I include the CSS first, the page takes 1.5 seconds to render:
When I include the JavaScript first, the page takes 1.4 seconds to render:
I get similar results in Chrome, Firefox and Internet Explorer. In Opera however, the ordering simply does not matter.
What appears to be happening is that the JavaScript interpreter refuses to start until all the CSS is downloaded. So, it seems that having JavaScript includes first is more efficient as the JavaScript thread gets more run time.
Am I missing something, is the recommendation to place CSS includes prior to JavaScript includes not correct?
It is clear that we could add async or use setTimeout to free up the render thread or put the JavaScript code in the footer, or use a JavaScript loader. The point here is about ordering of essential JavaScript bits and CSS bits in the head.
This is a very interesting question. I've always put my CSS <link href="...">
s before my JS <script src="...">
s because "I read one time that it's better." So, you're right; it's high time we do some actual research!
I set up my own test harness in Node (code below). Basically, I:
- Made sure there was no HTTP caching so the browser would have to do a full download each time a page is loaded.
- To simulate reality, I included jQuery and the H5BP CSS (so there's a decent amount of script/CSS to parse)
- Set up two pages - one with CSS before script, one with CSS after script.
- Recorded how long it took for the external script in the
<head>
to execute - Recorded how long it took for the inline script in the
<body>
to execute, which is analogous toDOMReady
. - Delayed sending CSS and/or script to the browser by 500ms.
- Ran the test 20 times in the 3 major browsers.
Results
First, with the CSS file delayed by 500ms:
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
Next, I set jQuery to delay by 500ms instead of the 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
Finally, I set both jQuery and the CSS to delay by 500ms:
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
Conclusions
First, it's important to note that I'm operating under the assumption that you have scripts located in the <head>
of your document (as opposed to the end of the <body>
). There are various arguments regarding why you might link to your scripts in the <head>
versus the end of the document, but that's outside the scope of this answer. This is strictly about whether <script>
s should go before <link>
s in the <head>
.
In modern DESKTOP browsers, it looks like linking to CSS first never provides a performance gain. Putting CSS after script gets you a trivial amount of gain when both CSS and script are delayed, but gives you large gains when CSS is delayed. (Shown by the last
columns in the first set of results.)
Given that linking to CSS last does not seem to hurt performance but can provide gains under certain circumstances, you should link to external stylesheets after you link to external scripts only on desktop browsers if the performance of old browsers is not a concern. Read on for the mobile situation.
Why?
Historically, when a browser encountered a <script>
tag pointing to an external resource, the browser would stop parsing the HTML, retrieve the script, execute it, then continue parsing the HTML. In contrast, if the browser encountered a <link>
for an external stylesheet, it would continue parsing the HTML while it fetched the CSS file (in parallel).
Hence, the widely-repeated advice to put stylesheets first – they would download first, and the first script to download could be loaded in parallel.
However, modern browsers (including all of the browsers I tested with above) have implemented speculative parsing, where the browser "looks ahead" in the HTML and begins downloading resources before scripts download and execute.
In old browsers without speculative parsing, putting scripts first will affect performance since they will not download in parallel.
Browser Support
Speculative parsing was first implemented in: (along with the percentage of worldwide desktop browser users using this version or greater as of Jan 2012)
- Chrome 1 (WebKit 525) (100%)
- IE 8 (75%)
- Firefox 3.5 (96%)
- Safari 4 (99%)
- Opera 11.60 (85%)
In total, roughly 85% of desktop browsers in use today support speculative loading. Putting scripts before CSS will have a performance penalty on 15% of users globally; YMMV based on your site's specific audience. (And remember that number is shrinking.)
On mobile browsers, it's a little harder to get definitive numbers simply due to how heterogeneous the mobile browser and OS landscape is. Since speculative rendering was implemented in WebKit 525 (released Mar 2008), and just about every worthwhile mobile browser is based on WebKit, we can conclude that "most" mobile browsers should support it. According to quirksmode, iOS 2.2/Android 1.0 use WebKit 525. I have no idea what Windows Phone looks like.
However, I ran the test on my Android 4 device, and while I saw numbers similar to the desktop results, I hooked it up to the fantastic new remote debugger in Chrome for Android, and Network tab showed that the browser was actually waiting to download the CSS until the JavaScripts completely loaded – in other words, even the newest version of WebKit for Android does not appear to support speculative parsing. I suspect it might be turned off due to the CPU, memory, and/or network constraints inherent to mobile devices.
Code
Forgive the sloppiness – this was Q&D.
app.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+='
' + i;
});
o+='
';
bodyresults[req.params.type].forEach(function(i) {
o+='
' + 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>
test.js
var jsload = Date.now();
$(function() {
$.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));
});
jquery.js was jquery-1.7.1.min.js
这篇关于CSS 应该总是在 Javascript 之前吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!