在网上无数的地方,我都看到过在JavaScript之前使用CSS的建议。推理一般是这样的:

当涉及到CSS和JavaScript的排序时,你需要你的CSS 先来。原因是呈现线程拥有所有的 样式显示页面所需的信息。如果JavaScript include首先出现,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毫秒延迟传输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&amp;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和ie浏览器上得到了类似的结果。然而,在Opera中,顺序并不重要。

似乎发生的情况是,JavaScript解释器在下载所有CSS之前拒绝启动。因此,首先使用JavaScript包含似乎更有效,因为JavaScript线程获得了更多的运行时间。

我遗漏了什么吗?把CSS include放在JavaScript include之前的建议是不正确的吗?

显然,我们可以添加async或使用setTimeout来释放呈现线程,或将JavaScript代码放在页脚中,或使用JavaScript加载器。这里的重点是关于基本JavaScript位和CSS位在头部的排序。


当前回答

在JavaScript之前包含CSS的建议是无效的吗?

如果你只是把它当作一个建议就不会了。但如果你把它当做一个硬性规定呢?是的,是无效的。

从窗口:DOMContentLoaded事件:

样式表加载块脚本执行,因此如果您有一个<script> 在<link rel="stylesheet"…>页面将不会完成解析 和DOMContentLoaded将不会触发—直到样式表被加载。

似乎您需要知道每个脚本依赖于什么,并确保脚本的执行延迟到正确的完成事件之后。如果脚本只依赖于DOM,它可以在ondomready/domcontentloaded中恢复。如果它依赖于要加载的图像或要应用的样式表,那么如果我正确地阅读了上面的引用,那么该代码必须延迟到onload事件。

我不认为一个尺码的袜子适合所有人,尽管这是他们销售的方式,我知道一个尺码的鞋子不适合所有人。我不认为有一个明确的答案,首先加载样式或脚本。它更多的是一个个案决定,什么必须以什么顺序加载,什么可以推迟到以后,因为不是在“关键路径”上。

To speak to the observer that commented that it is better to delay the users ability to interact until the sheet is pretty. There are many of you out there and you annoy your counterparts that feel the opposite. They came to a site to accomplish a purpose and delays to their ability to interact with a site while waiting for things that don't matter to finish loading are very frustrating. I am not saying that you are wrong, only that you should be aware that there is another faction that exists that does not share your priority.

这个问题尤其适用于所有放在网站上的广告。如果网站作者只渲染广告内容的占位符div,并确保他们的网站在注入广告在onload事件之前是加载和交互式的,我将会很喜欢。即使这样,我也希望看到广告是连续加载的,而不是一次性加载,因为它们会影响我在加载臃肿的广告时甚至滚动网站内容的能力。但这只是一个人的观点。

Know your users and what they value. Know your users and what browsing environment they use. Know what each file does, and what its prerequisites are. Making everything work will take precedence over both speed and pretty. Use tools that show you the network time line when developing. Test in each of the environments that your users use. It may be needed to dynamically (server side, when creating the page) alter the order of loading based on the users environment. When in doubt, alter the order and measure again. It is possible that intermixing styles and scripts in the load order will be optimal; not all of one then all of the other. Experiment not just what order to load the files but where. head? In body? After body? DOM Ready/Loaded? Loaded? Consider async and defer options when appropriate to reduce the net delay the user will experience before being able to interact with the page. Test to determine if they help or hurt. There will always be trade-offs to consider when evaluating the optimal load order. Pretty vs. responsive being just one.

其他回答

在JavaScript之前包含CSS的建议是无效的吗?

如果你只是把它当作一个建议就不会了。但如果你把它当做一个硬性规定呢?是的,是无效的。

从窗口:DOMContentLoaded事件:

样式表加载块脚本执行,因此如果您有一个<script> 在<link rel="stylesheet"…>页面将不会完成解析 和DOMContentLoaded将不会触发—直到样式表被加载。

似乎您需要知道每个脚本依赖于什么,并确保脚本的执行延迟到正确的完成事件之后。如果脚本只依赖于DOM,它可以在ondomready/domcontentloaded中恢复。如果它依赖于要加载的图像或要应用的样式表,那么如果我正确地阅读了上面的引用,那么该代码必须延迟到onload事件。

我不认为一个尺码的袜子适合所有人,尽管这是他们销售的方式,我知道一个尺码的鞋子不适合所有人。我不认为有一个明确的答案,首先加载样式或脚本。它更多的是一个个案决定,什么必须以什么顺序加载,什么可以推迟到以后,因为不是在“关键路径”上。

To speak to the observer that commented that it is better to delay the users ability to interact until the sheet is pretty. There are many of you out there and you annoy your counterparts that feel the opposite. They came to a site to accomplish a purpose and delays to their ability to interact with a site while waiting for things that don't matter to finish loading are very frustrating. I am not saying that you are wrong, only that you should be aware that there is another faction that exists that does not share your priority.

这个问题尤其适用于所有放在网站上的广告。如果网站作者只渲染广告内容的占位符div,并确保他们的网站在注入广告在onload事件之前是加载和交互式的,我将会很喜欢。即使这样,我也希望看到广告是连续加载的,而不是一次性加载,因为它们会影响我在加载臃肿的广告时甚至滚动网站内容的能力。但这只是一个人的观点。

Know your users and what they value. Know your users and what browsing environment they use. Know what each file does, and what its prerequisites are. Making everything work will take precedence over both speed and pretty. Use tools that show you the network time line when developing. Test in each of the environments that your users use. It may be needed to dynamically (server side, when creating the page) alter the order of loading based on the users environment. When in doubt, alter the order and measure again. It is possible that intermixing styles and scripts in the load order will be optimal; not all of one then all of the other. Experiment not just what order to load the files but where. head? In body? After body? DOM Ready/Loaded? Loaded? Consider async and defer options when appropriate to reduce the net delay the user will experience before being able to interact with the page. Test to determine if they help or hurt. There will always be trade-offs to consider when evaluating the optimal load order. Pretty vs. responsive being just one.

史蒂夫·苏德斯已经给出了明确的答案,但是……

我想知道Sam的原始测试和Josh的重复测试是否都有问题。

这两个测试似乎都是在低延迟连接上执行的,其中建立TCP连接的开销很小。

我不确定这会如何影响测试结果,我想在“正常”延迟连接上查看测试的瀑布,但是……

下载的第一个文件将获得用于HTML页面的连接,下载的第二个文件将获得新的连接。(提前刷新<head>会改变动态,但这里没有这样做。)

在较新的浏览器中,第二个TCP连接是投机性地打开的,因此连接开销减少/消失了。在旧的浏览器中,这是不正确的,并且第二个连接将有被打开的开销。

我不确定这会如何/是否会影响测试结果。

以下是之前所有主要答案的总结:

对于现代浏览器,可以将CSS内容放在您喜欢的任何地方。他们会分析您的HTML文件(他们称之为推测性解析),并在HTML解析的同时开始下载CSS。

对于旧的浏览器,继续将CSS放在最上面(如果您不想首先显示一个裸露但交互式的页面)。

对于所有浏览器,请将JavaScript内容放在页面上尽可能靠后的位置,因为这会暂停HTML的解析。最好是异步下载(即Ajax调用)。

也有一些实验结果表明,JavaScript优先(与传统的CSS优先相反)可以提供更好的性能,但没有给出任何逻辑推理,也缺乏广泛适用性的验证,所以现在可以忽略它。

所以,回答这个问题:是的。在JavaScript之前包含CSS的建议对于现代浏览器是无效的。把CSS放在你喜欢的任何地方,尽可能把JavaScript放在最后。

您的测试是在您的个人计算机上执行的,还是在web服务器上执行的?它是一个空白的页面,还是一个包含图像、数据库等的复杂在线系统?你的脚本是执行一个简单的悬停事件动作,还是你的网站呈现和与用户交互的核心组件?这里有几件事需要考虑,当你冒险进行高水平的web开发时,这些建议的相关性几乎总是成为规则。

“将样式表放在顶部,脚本放在底部”规则的目的是,在一般情况下,这是实现最佳渐进式呈现的最佳方法,这对用户体验至关重要。

撇开其他因素不谈:假设您的测试是有效的,并且您的测试结果确实与流行规则相反,这并不令人惊讶,真的。每个网站(以及让整个网站出现在用户屏幕上的所有内容)都是不同的,互联网也在不断发展。

这是一个非常有趣的问题。我总是把我的CSS <link href="…">s before my JavaScript <script src="…"因为“我读过一次,它更好。”所以,你是对的;是时候做些实际研究了!

我在Node.js中设置了自己的测试工具(代码如下)。基本上,我:

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 to DOMReady. Delayed sending CSS and/or script to the browser by 500 ms. Ran the test 20 times in the three major browsers.

结果

首先,CSS文件延迟500毫秒(单位为毫秒):

     Browser: Chrome 18    | IE 9         | Firefox 9
         CSS: first  last  | first  last  | first last
=======================================================
Header Exec |              |              |
Average     | 583    36    | 559    42    | 565   49
St Dev      |  15    12    |   9     7    |  13    6
------------|--------------|--------------|------------
Body Exec   |              |              |
Average     | 584    521   | 559    513   | 565   519
St Dev      |  15      9   |   9      5   |  13     7

接下来,我设置jQuery延迟500毫秒,而不是CSS:

     Browser: Chrome 18    | IE 9         | Firefox 9
         CSS: first  last  | first  last  | first last
=======================================================
Header Exec |              |              |
Average     | 597    556   | 562    559   | 564   564
St Dev      |  14     12   |  11      7   |   8     8
------------|--------------|--------------|------------
Body Exec   |              |              |
Average     | 598    557   | 563    560   | 564   565
St Dev      |  14     12   |  10      7   |   8     8

最后,我将jQuery和CSS都设置为延迟500毫秒:

     Browser: Chrome 18    | IE 9         | Firefox 9
         CSS: first  last  | first  last  | first last
=======================================================
Header Exec |              |              |
Average     | 620    560   | 577    577   | 571   567
St Dev      |  16     11   |  19      9   |   9    10
------------|--------------|--------------|------------
Body Exec   |              |              |
Average     | 623    561   | 578    580   | 571   568
St Dev      |  18     11   |  19      9   |   9    10

结论

首先,需要注意的是,我的操作假设脚本位于文档的<head>(而不是<body>的末尾)。关于为什么要将脚本链接到<head>而不是文档的末尾,有各种各样的争论,但这超出了本回答的范围。这严格是关于<script>s是否应该在<head>中的<link>s之前。

在现代的DESKTOP浏览器中,首先链接到CSS似乎永远不会带来性能提升。当CSS和脚本都延迟时,将CSS放在脚本之后会获得少量的收益,但当CSS延迟时,将获得大量收益。(由第一组结果的最后一列显示。)

考虑到最后链接到CSS似乎不会损害性能,但在某些情况下可以提供收益,如果不考虑旧浏览器的性能,则应该在链接到桌面浏览器上的外部脚本之后再链接到外部样式表。继续阅读手机的情况。

Why?

过去,当浏览器遇到指向外部资源的<script>标记时,浏览器将停止解析HTML,检索脚本,执行它,然后继续解析HTML。相反,如果浏览器遇到外部样式表的<link>,它将在获取CSS文件的同时继续解析HTML(并行地)。

因此,广泛重复的建议是将样式表放在第一位——它们将首先下载,而第一个要下载的脚本可以并行加载。

然而,现代浏览器(包括我上面测试的所有浏览器)都实现了推测性解析,即浏览器在HTML中“向前看”,并在脚本下载和执行之前开始下载资源。

在没有推测性解析的旧浏览器中,将脚本放在前面会影响性能,因为它们不会并行下载。

浏览器支持

推测解析首次实现于:(以及截至2012年1月全球使用此版本或更高版本的桌面浏览器用户的百分比)

Chrome 1 (WebKit 525) (100%) Internet Explorer 8 (75%) Firefox 3.5 (96%) Safari 4 (99%) Opera 11.60 (85%)

总的来说,目前大约85%的桌面浏览器支持推测加载。把脚本放在CSS之前会对全球15%的用户造成性能损失;您的里程可能会根据您网站的特定受众而有所不同。(请记住,这个数字正在缩小。)

在移动浏览器上,由于移动浏览器和操作系统的多样性,要获得确切的数据有点困难。既然推测渲染是在WebKit 525(2008年3月发布)中实现的,而且几乎每一个有价值的移动浏览器都是基于WebKit的,我们可以得出结论,“大多数”移动浏览器应该支持它。根据quirksmode, iOS 2.2/Android 1.0使用WebKit 525。我不知道Windows Phone长什么样。

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 JavaScript code 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

原谅我的草率——这是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);
});

文件就是说

<!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是jQuery -1.7.1.min.js