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

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..."
}
<!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>

答案

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
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
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
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>
<!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>
var jsload = Date.now();


$(function() {
    $.post('/result' + location.pathname.replace('.html','') + '/' + (jsload - start) + '/' + (bodyexec - start));
});

将 CSS 放在 JavaScript 之前有两个主要原因。

  1. 旧的浏览器(Internet Explorer 6-7,Firefox 2 等)在开始下载脚本时会阻止所有后续下载。因此,如果您在a.js之后是b.cssb.css按顺序下载它们:首先是 a,然后是 b。如果您在b.css后接a.jsa.js并行下载它们,因此页面加载速度更快。

  2. 在下载所有样式表之前,不会呈现任何内容 - 在所有浏览器中都是如此。脚本是不同的 - 它们阻止呈现页面中script 标记下方的所有 DOM 元素。如果将脚本放在 HEAD 中,则意味着整个页面都无法渲染,直到下载所有样式表和所有脚本为止。虽然可以阻止样式表的所有呈现(这样您就可以第一次获得正确的样式并避免出现未样式化的内容 FOUC),但是阻止脚本呈现整个页面是没有意义的。脚本通常不影响任何 DOM 元素或仅影响 DOM 元素的一部分。 最好将脚本加载到尽可能低的页面中,甚至更好地异步加载。

Cuzillion创建示例很有趣。例如, 此页面的 HEAD 中有一个脚本,因此整个页面空白,直到完成下载为止。但是,如果我们将脚本移到 BODY 块的末尾,则将显示页面标题,因为这些 DOM 元素位于 SCRIPT 标记上方,如您在此页面上所见。

我不会在您获得的结果上强调太多,我相信这是主观的,但是我有理由向您解释,最好将 CSS 放在 js 之前。

在加载网站期间,您会看到两种情况:

案例 1:白屏 > 无样式的网站 > 带样式的网站 > 交互 > 带样式的交互式网站

案例 2:白屏 > 无样式的网站 > 交互 > 有样式的网站 > 有样式和互动的网站


老实说,我无法想象有人选择案例 2。这意味着使用慢速 Internet 连接的访问者将面临一个未样式化的网站,该网站使他们可以使用 Javascript 与该网站进行交互(因为该网站已经加载)。此外,通过这种方式可以最大程度地增加花在浏览无样式网站上的时间。为什么有人要那个?

正如jQuery 所述,它也可以更好地工作

“当使用依赖 CSS 样式属性值的脚本时,在引用脚本之前引用外部样式表或嵌入样式元素很重要”。

当文件以错误的顺序加载(首先是 JS,然后是 CSS)时,依赖于 CSS 文件中设置的属性(例如 div 的宽度或高度)的任何 Javascript 代码都将无法正确加载。似乎使用错误的加载顺序,正确的属性有时是 Javascript 已知的(也许这是由竞争条件引起的吗?)。根据使用的浏览器,此效果似乎更大或更小。

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

通常,“在顶部放置样式表,在底部放置脚本” 规则的目的是,这是实现最佳渐进式渲染的最佳方法,这对于用户体验至关重要。

除了其他因素:假设您的测试是有效的,并且您确实在产生与流行规则相反的结果,那么这就不足为奇了。每个网站(使整个内容显示在用户的屏幕上所需的一切)都不尽相同,互联网也在不断发展。

由于其他原因,我在 Javascript 之前加入了 CSS 文件。

如果我的 Javascript 需要动态调整某些页面元素的大小(对于那些在某些情况下 CSS 确实是后面的主要情况),则在 JS 崩溃后加载 CSS 可能会导致竞争状况,其中元素的尺寸会在 CSS 样式之前进行调整被应用,因此样式最终插入时看起来很奇怪。如果我预先加载 CSS,则可以保证一切按预期顺序运行,并且最终布局是我想要的样子。

<head>
<script src="//alias-0.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=1"></script>
<script src="//alias-1.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=2"></script>
<script src="//alias-2.redacted.com/payload.php?type=js&amp;delay=333&amp;rand=3"></script>
</head>
<link href="//alias-0.redacted.com/payload.php?type=css&amp;delay=666" rel="stylesheet">
<script src="//alias-1.redacted.com/payload.php?type=js&amp;delay=333&amp;block=1000"></script>
CSS Download  500ms:<------------------------------------------------>
JS Download   400ms:<-------------------------------------->
JS Execution 1000ms:                                                  <-------------------------------------------------------------------------------------------------->
DOM Ready   @1500ms:                                                                                                                                                      ◆
JS Download   400ms:<-------------------------------------->
CSS Download  500ms:<------------------------------------------------>
JS Execution 1000ms:                                        <-------------------------------------------------------------------------------------------------->
DOM Ready   @1400ms:                                                                                                                                            ◆

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

如果您仅将其视为推荐,则不会。但是,如果您将其视为一成不变的规则?,是的,它无效。

来自https://developer.mozilla.org/zh-CN/docs/Web/Reference/Events/DOMContentLoaded

样式表会加载块脚本执行,因此,如果在<link rel="stylesheet" ...> <script>之后有一个<script>在加载样式表之前,页面将无法完成解析 - 并且 DOMContentLoaded 不会触发。

看来您需要了解每个脚本所依赖的内容,并确保脚本的执行被延迟到正确的完成事件之后。如果脚本仅依赖 DOM,则可以在 ondomready / domcontentloaded 中恢复,如果脚本依赖于要加载的图像或要应用的样式表,则如果我正确地阅读了以上引用,则必须将该代码推迟到 onload 事件之前。

我不认为一种袜子尺寸能适合所有人,尽管那是他们出售的方式,而且我知道一种鞋号不能完全适合所有人。我认为没有一个确定的答案可以首先加载样式或脚本。还要根据具体情况来决定必须以什么顺序加载什么内容,以及哪些内容可以推迟到以后才在 “关键路径” 上进行处理。

与评论员交谈的观察者说,最好将用户的交互能力延迟到表格漂亮为止。你们当中有很多人,并且让与您相反的人感到烦恼。他们来到一个站点来实现目标,而在等待无关紧要的事情来完成加载时,他们与站点交互的能力的延迟非常令人沮丧。我并不是说您错了,只是您应该知道,存在另一个不具有优先权的派系。

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

  • 了解您的用户及其价值。
  • 了解您的用户以及他们使用的浏览环境。
  • 了解每个文件的功能以及先决条件。使一切正常运行将优先于速度和美观。
  • 开发时使用显示网络时间轴的工具。
  • 在用户使用的每种环境中进行测试。可能需要根据用户环境动态地(在服务器端,在创建页面时)更改加载顺序。
  • 如有疑问,请更改顺序并再次测量。
  • 加载顺序中的混合样式和脚本可能是最佳的。不是所有的一个,而是所有的另一个。
  • 不仅要尝试按什么顺序加载文件,还要尝试在哪里进行实验。头?在体内?后身体? DOM 准备 / 已加载?装了吗
  • 在适当时考虑使用异步和延迟选项,以减少用户在能够与页面进行交互之前所经历的净延迟。测试以确定它们是帮助还是伤害。
  • 在评估最佳负载顺序时,总会有折衷考虑。漂亮与响应式合二为一。

我不确定使用 Java 脚本时如何测试 “渲染” 时间。但是考虑一下

您网站上的一页是 50k,这不是不合理的。用户在东部海岸,而服务器在西部。 MTU 绝对不是 10k,所以会有一些来回旅行。可能需要 1/2 秒才能收到您的页面和样式表。通常(对我而言)(通过 jquery 插件等)javascript 远远超过 CSS。当您的互联网连接在页面中途阻塞时,也发生了什么,但您可以忽略它(它偶尔发生在我身上,我相信 CSS 会渲染,但我不是 100%确信)。

由于 css 在头,可能会有其他连接来获取它,这意味着它可能可以在页面之前完成。无论如何,在键入期间,页面的其余部分都会被占用,而 javascript 文件(还有更多字节)是未设置样式的,这会使站点 / 连接显得很慢。

即使 JS 解释器拒绝启动,直到 CSS 完成之后,下载 javascript 代码所花费的时间也是如此,尤其是在离服务器很远的地方切入 CSS 的时间时,这会使网站看起来不太漂亮。

它是一个小的优化,但这就是其原因。

这是上面所有主要答案的摘要 (或稍后可能在下面总结 :)

对于现代浏览器,请将 CSS 放在您喜欢的位置。他们将分析您的 html 文件(称为推测性解析 ),并开始与 html 解析并行下载 css。

对于旧的浏览器,请始终将 css 放在顶部(如果您不想首先显示一个裸露但交互式的页面)。

对于所有浏览器,请将 javascript 放在页面上尽可能远的位置,因为它将停止 html 的解析。最好异步下载(即,ajax 调用)

对于特定案例,也有一些实验结果,这些案例声称将 javascript 放在首位(而不是将 CSS 放在首位的传统观点)可提供更好的性能,但没有给出逻辑推理,并且缺乏关于广泛适用性的验证,因此您可以现在忽略它。

因此,回答这个问题:是的。对于现代浏览器,建议在 JS 之前包含 CSS 的建议无效。将 CSS 放置在任意位置,并尽可能将 JS 放置在末尾。

史蒂夫 · 索德斯(Steve Souders)已经给出了明确的答案,但...

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

这两个测试似乎都是在低等待时间的连接上执行的,而建立 TCP 连接的成本却很小。

这不确定如何影响测试结果,我不确定,我想看看通过 “正常” 延迟连接进行测试的瀑布,但是...

下载的第一个文件获取用于 html 页面的连接,下载的第二个文件应获取新的连接。 (刷新早期会更改该动态,但此处未完成)

在较新的浏览器中,第二个 TCP 连接以推测方式打开,因此减少了连接 / 消除了连接的开销,在较旧的浏览器中,情况并非如此,第二个连接将具有打开的开销。

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