build议在JavaScript无效之前包含CSS吗?
在网上无数的地方,我已经看到了在JavaScript之前包含CSS的build议。 推理一般是这种forms :
在订购CSS和JavaScript时,首先需要CSS。 原因在于呈现线程具有呈现页面所需的所有样式信息。 如果JavaScript包括第一,JavaScript引擎必须先parsing它,然后继续下一组资源。 这意味着渲染线程不能完全显示页面,因为它没有所需的所有样式。
我的实际testing揭示了非常不同的东西
我的testing线束
我使用下面的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
delay=500给我一个500毫秒的延迟传输的CSS。
我使用下面的页面来testing。
<!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秒的时间来渲染:
当我首先包含JavaScript时,页面需要1.4秒来渲染:
Chrome,Firefox和Internet Explorer中的结果类似。 但在Opera中,sorting并不重要。
看来正在发生的事情是JavaScript解释器拒绝启动,直到所有的CSS被下载。 所以,当JavaScript线程获得更多的运行时间时,似乎首先包含JavaScript是更高效的。
我是否缺less一些东西,build议放置CSS包括之前的JavaScript包含不正确的东西?
很明显,我们可以添加asynchronous或使用setTimeout释放渲染线程或将JavaScript代码放在页脚中,或使用JavaScript加载器。 这里的重点是关于头部的基本JavaScript位和CSS位的sorting。
这是一个非常有趣的问题。 我总是把我的CSS <link href="...">
s放在我的JS <script src="...">
s之前,因为“我读过一次更好。 所以,你是对的。 现在是我们做一些实际研究的时候了!
我在Node中设置了我自己的testing工具(代码如下)。 基本上,我:
- 确保没有HTTPcaching,因此浏览器每次加载页面时都需要进行完整的下载。
- 为了模拟现实,我包含了jQuery和H5BP CSS(所以有足够的脚本/ CSSparsing)
- 设置两个页面 – 一个用脚本之前的CSS,一个用脚本之后的CSS。
- logging执行
<head>
的外部脚本需要多长时间 - logging
<body>
的内联脚本执行所花费的时间,类似于DOMReady
。 - 延迟发送CSS和/或脚本到浏览器500毫秒。
- 在三大浏览器中进行了20次testing。
结果
首先,将CSS文件延迟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
接下来,我设置jQuery延迟500ms,而不是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 都设置为延迟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
结论
首先,需要注意的是,我假设脚本位于文档的<head>
(而不是<body>
的末尾)。 关于为什么你可能链接到<head>
中的脚本和文档的末尾有各种各样的争论,但这不在本答案的范围之内。 这严格的是<script>
是否应该在<head>
<link>
之前。
在现代DESKTOP浏览器中,看起来像链接到CSS首先从来没有提供性能增益。 在脚本之后放置CSS可以在CSS和脚本都延迟的情况下获得微不足道的增益,但是当CSS延迟时会给您带来巨大的收益。 (由第一组结果的last
列所示)
鉴于最后一次链接到CSS似乎不会损害性能,但在某些情况下可以提供收益,如果不考虑旧浏览器的性能,则只能在桌面浏览器上链接到外部脚本后, 才能链接到外部样式表 。 阅读移动的情况。
为什么?
历史上,当浏览器遇到指向外部资源的<script>
标记时,浏览器将停止parsingHTML,检索脚本,执行脚本,然后继续parsingHTML。 相反,如果浏览器遇到一个外部样式表的<link>
,它将继续parsingHTML,同时获取CSS文件(并行)。
因此,广泛重复的build议首先放置样式表 – 他们会首先下载,第一个下载的脚本可以并行加载。
然而,现代浏览器(包括我上面testing的所有浏览器)都实现了推测性parsing ,浏览器在HTML中“向前看”,并在脚本下载和执行之前开始下载资源。
在没有推测性分析的旧浏览器中,首先放置脚本会影响性能,因为它们不会并行下载。
浏览器支持
推测parsing首先实现在:(以及2012年1月以来使用此版本或更高版本的全球桌面浏览器用户的百分比)
- Chrome 1(WebKit 525)(100%)
- IE 8(75%)
- Firefox 3.5(96%)
- Safari 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设备上运行了testing,虽然我看到类似于桌面结果的数字,但是我把它连接到Chrome浏览器中新的远程debugging器上,Network选项卡显示浏览器实际上正在等待下载CSS直到JavaScript被完全加载 – 换句话说, 即使是Android的最新版本的WebKit似乎也不支持推测性分析。 我怀疑它可能是由于移动设备固有的CPU,内存和/或networking限制而被closures的。
码
原谅sl – – 这是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+='\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>
test.js
var jsload = Date.now(); $(function() { $.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start)); });
jquery.js是jquery-1.7.1.min.js
在JavaScript之前放置CSS有两个主要原因。
-
旧版浏览器(Internet Explorer 6-7,Firefox 2等)会在开始下载脚本时阻止所有后续的下载。 所以如果你有
a.js
后跟b.css
他们会顺序下载:先a然后b。 如果你有b.css
后跟a.js
他们会并行下载,所以页面加载速度更快。 -
所有样式表都下载之前,不会显示任何东西 – 所有浏览器都是如此。 脚本是不同的 – 它们阻止页面中脚本标记下的所有DOM元素的呈现。 如果将脚本放在HEAD中,则意味着整个页面都将被阻止呈现,直到下载了所有样式表和所有脚本。 虽然阻止所有的样式表渲染是合理的(所以你第一次得到正确的样式,并避免无格式内容FOUC的闪光),但是阻止渲染整个页面的脚本是没有意义的。 通常,脚本不会影响任何DOM元素或DOM元素的一部分。 最好在页面中尽可能低地加载脚本,或者更好地将脚本加载到asynchronous。
用Cuzillion创造例子很有趣。 例如, 这个页面在HEAD中有一个脚本,因此整个页面都是空白的,直到完成下载。 但是,如果我们将脚本移动到BODY块的末尾,则页面标题将呈现,因为这些DOM元素出现在SCRIPT标记的上方,如您在此页面上所看到的那样。
我不会过多地强调你所得到的结果,我相信这是主观的,但我有理由向你解释,在js之前放置CSS更好。
在加载您的网站期间,您会看到两种情况:
案例1:白屏>无风格网站>风格网站>互动>风格互动的网站
案例2:白屏>无风格网站>互动>风格网站>风格互动的网站
我真的不能想象任何人select案例2.这意味着访问者使用缓慢的互联网连接将面临一个无风格的网站,使他们能够使用Javascript(因为已经加载)与它进行交互。 此外,用这种方式可以最大限度地节省查看无版式网站的时间。 为什么会有人想要这个?
它也工作得更好,因为jQuery的状态
“使用依赖CSS样式属性值的脚本时,在引用脚本之前引用外部样式表或embedded样式元素是很重要的”。
当文件以错误顺序(第一个JS,然后是CSS)加载时,任何依靠CSS文件中设置的属性的Javascript代码(例如div的宽度或高度)将无法正确加载。 这似乎是错误的加载顺序,正确的属性是“有时”已知的Javascript(也许这是由竞争条件引起的)。 这种效果似乎更大或更小取决于使用的浏览器。
你的testing是在个人电脑上还是在networking服务器上进行的? 这是一个空白页面,还是一个复杂的在线系统与图像,数据库等? 您的脚本是否执行简单的hover事件操作,还是您的网站呈现和与用户交互的核心组件? 这里有几件事情需要考虑,当你冒险进入高质量的网站开发时,这些build议的相关性几乎总是成为规则。
“把样式表放在最上面,脚本放在最下面”的目的是,一般来说,这是实现对用户体验至关重要的最佳渐进渲染的最佳方式 。
除此之外,假设你的testing是有效的,而且你真的正在产生与stream行的规则相反的结果,那真是一点也不奇怪。 每个网站(以及使整个事情出现在用户屏幕上的一切)都不同,互联网也在不断发展。
由于其他原因,我在Javascript之前包含了CSS文件。
如果我的Javascript需要做一些页面元素的dynamic调整(对于那些CSS是真正的主要背景的情况),那么在JS之后加载CSS会导致竞争条件,在这种情况下元素在CSS样式之前resize应用程序,因此看起来很奇怪,当样式最后踢。如果我事先加载CSS我可以保证事情运行在预定的顺序,最终的布局是我想要的。
build议在JavaScript无效之前包含CSS吗?
不是,如果你把它当作一个简单的build议。 但是,如果你把它看作是一个硬性规定,是的,这是无效的。
从https://developer.mozilla.org/en-US/docs/Web/Reference/Events/DOMContentLoaded
样式表加载阻止脚本执行,所以如果在
<link rel="stylesheet" ...>
<script>
之后有一个<script>
,那么页面将不会完成parsing – 而DOMContentLoaded将不会触发 – 直到样式表加载完毕。
看来您需要知道每个脚本所依赖的内容,并确保脚本的执行延迟到正确的完成事件之后。 如果脚本仅依赖于DOM,那么它可以在ondomready / domcontentloaded中恢复,如果它依赖于要加载的图像或样式表来应用,那么如果我正确读取上述引用,则必须将该代码推迟到onload事件。
我不认为一只袜子适合所有人,尽pipe这是他们的销售方式,我知道一个鞋子的尺寸不适合所有人。 我不认为有一个明确的答案来加载首先,样式或脚本。 对于什么样的东西必须按什么样的顺序加载,什么样的东西可以推迟到什么时候才会被放在“关键path”上呢?
对观察者说,最好是延迟用户互动的能力,直到表单漂亮为止。 你们中有很多人在那里,而你们却感到相反。 他们来到一个网站来完成一个目的,并拖延他们与网站互动的能力,同时等待无关紧要的事情完成加载是非常令人沮丧的。 我并不是说你错了,只是你应该知道有另外一个派别存在,并不能分享你的优先权。
这个问题特别适用于所有放在网站上的广告。 如果网站作者只是为广告内容呈现占位符div,并确保在加载事件中注入广告之前,他们的网站已被加载和交互,我就会喜欢它。 即使这样,我也希望看到这些广告是连续加载的,而不是一次加载,因为这会影响我在加载臃肿广告的同时滚动网站内容的能力。 但这只是一个人的观点。
- 了解你的用户和他们的价值。
- 了解你的用户和他们使用的浏览环境。
- 知道每个文件的作用,以及它的前提条件是什么。 一切工作都将优先于速度和美丽。
- 使用开发时显示networking时间线的工具。
- 在用户使用的每个环境中进行testing。 可能需要dynamic地(服务器端,创build页面时)根据用户环境改变加载的顺序。
- 如有疑问,请改变顺序并再次测量。
- 加载顺序中的混合风格和脚本可能是最佳的; 不是所有其他的一个。
- 不仅要试验加载文件的顺序,还要考虑到哪里。 头? 在身体? 身体后? DOM就绪/加载? 装?
- 在适当的时候考虑asynchronous和推迟选项,以减less用户在能够与页面交互之前所经历的净延迟。 testing以确定他们是否帮助或伤害。
- 在评估最佳加载顺序时总会考虑折衷。 漂亮与响应只是一个。
我不完全确定你的testing“渲染”你的使用Java脚本的时间。 不过考虑一下
你网站上的一个页面是50K,这不是不合理的。 用户位于东海岸,而您的服务器位于西部。 MTU绝对不是10K,所以会有一些往返。 接收您的页面和样式表可能需要1/2秒的时间。 通常(对我来说)javascript(通过jquery插件等)远远超过CSS。 这也是当你的互联网连接在页面中途窒息,但让我们忽略(偶尔发生在我身上,我相信CSS呈现,但我不是100%确定)会发生什么。
由于css头脑中可能有其他连接来获取它,这意味着它可能会在页面之前完成。 反正在types的页面的其余部分需要和JavaScript文件(这是更多的字节)的页面是无风格的,这使得站点/连接显得缓慢。
即使JS解释器拒绝启动,直到CSS完成下载JavaScript代码所用的时间,特别是当远离服务器切入CSS时间,这将使网站看起来不漂亮。
它是一个小的优化,但这是它的原因。
史蒂夫Souders已经给出了一个明确的答案,但…
我想知道Sam的原始testing和Josh的重复testing是否存在问题。
这两个testing看起来都是在低延迟的连接上进行的,在那里build立TCP连接的成本是微不足道的。
这如何影响testing的结果我不知道,我想看看瀑布的testing通过一个“正常”的延迟连接,但…
下载的第一个文件应该得到用于html页面的连接,并且下载的第二个文件将得到新的连接。 (冲洗早期dynamic变化,但这不是在这里完成)
在较新的浏览器中,第二个TCP连接是以推测方式打开的,所以连接开销会减less/消失,而在旧版本的浏览器中,这是不正确的,第二个连接将会被打开。
相当多/如果这影响testing的结果,我不确定。
build议在JavaScript之前包含一个CSS文件(或者也许是另一个好的理由):
JavaScript阻止浏览器执行任何操作,直到下载,parsing和执行。 为什么? 由于JavaScript文件可能包含诸如document.write("<!--")
,执行时会更改页面的标记。
如果你把HTML文件中的CSS文件放在你的HTML源文件中,事情会变得更好。 浏览器可以并行加载两个文件(CSS和JavaScript)。 当然,它会阻止下载,parsing和执行JavaScript文件,但这段时间将被用来并行下载其他文件。
我认为这不会是所有的情况下都是如此。 因为CSS将平行下载,但JS不能。 考虑同样的情况,
而不是单一的CSS,拿2或3个CSS文件,并尝试这些方法,
1)css..css..js 2)css..js..css 3)js..css..css
我相信css..css..js会比其他所有人都有更好的结果。
我们必须记住,新的浏览器已经在他们的Javascript引擎,他们的parsing器等等上工作,优化了常见的代码和标记问题,使古代的浏览器(如<= IE8)中遇到的问题不再相关,关于标记,还包括使用JavaScriptvariables,元素select器等。在不久的将来,我可以看到技术达到了性能不再是真正的问题的地步。
这里是上述所有主要答案的摘要 (或者可能在下面:)
对于现代浏览器,把CSS放在任何你喜欢的地方。 他们会分析你的HTML文件(他们称之为推测性parsing ),并开始下载与HTMLparsing并行的CSS。
对于旧的浏览器不断放在顶部的CSS(如果你不想显示一个裸体,但首先交互式页面)。
对于所有的浏览器,尽可能把JavaScript放在页面上,因为它会停止你的html的parsing。 最好是asynchronous下载(即ajax调用)
还有一些关于某个特定情况的实验结果,声称把JavaScript放在第一位(相对于传统的把CSS放在第一位的方式),但是没有给出合理的逻辑推理,而且缺乏对广泛适用性的validation,所以你可以现在忽略它。
所以,要回答这个问题:是的。 在JS之前包含CSS的build议对于现代浏览器是无效的。 把CSS放在任何你喜欢的地方,尽可能的把JS放到最后。
就个人而言,我不会过分强调这种“民间智慧”。 过去可能是真的,现在可能并非如此。 我认为所有与网页解释和渲染相关的操作都是完全asynchronous的(“提取”某些东西,“按照它”行事是两个完全不同的东西,可能是由不同的线程处理的) 等等。在任何情况下完全超出了你的控制或你的关注。
我将CSS引用放在文档的“head”部分,以及对外部脚本的引用。 (有些脚本可能要求放置在身体中,如果是这样,则强制它们)。
除此之外……如果你观察到“这个看起来比这个/那个浏览器更快/更慢,”把这个观察看作是一个有趣但不相干的好奇心 ,不要让它影响你的devise决定。 太多事情变化太快。 (Anyone want to lay any bets on how many minutes it will be before the Firefox team comes out with yet another interim-release of their product? Yup, me neither.)