在网上的无数地方,我都看到过在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让我在传输CSS时延迟500毫秒.

我使用下面的页面进行测试.

<!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秒来呈现:

CSS first

当我首先包含JavaScript时,页面需要1.4秒来呈现:

JavaScript first

我在Chrome、Firefox和Internet Explorer上也得到了类似的结果.然而,在歌剧中,顺序根本不重要.

似乎正在发生的是,JavaScript解释器拒绝启动,直到所有CSS都下载完毕.因此,随着JavaScript线程获得更多的运行时间,让JavaScript includes first更有效.

我是否遗漏了什么,将CSS包含放在JavaScript包含之前的建议是否不正确?

很明显,我们可以添加async,或者使用setTimeout释放渲染线程,或者将JavaScript代码放入页脚,或者使用JavaScript加载程序.这里的重点是关于基本JavaScript位和CSS位在头部的顺序

推荐答案

这是一个非常有趣的问题.我总是把CSS <link href="...">放在JS <script src="...">之前,因为"我读过一次,它更好."所以,你是对的;我们该做些实际的研究了!

我在 node 中设置了自己的测试线束(代码如下).基本上,我:

  • 确保没有HTTP缓存,以便浏览器在每次加载页面时都必须进行完整下载.
  • 为了模拟现实,我加入了jQuery和H5BP个CSS(因此有相当多的脚本/CSS需要解析)
  • 设置两个页面——一个在脚本之前使用CSS,一个在脚本之后使用CSS.
  • 记录执行100中的外部脚本所需的时间
  • 记录100中的内联脚本执行所需的时间,类似于DOMReady.
  • 将CSS和/或脚本发送到浏览器的时间延迟了500ms.
  • 在三大浏览器中运行了20次测试.

Results

首先,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设置为延迟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

最后,我将bothjQuery和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

Conclusions

首先,重要的是要注意,我是在假设您的脚本位于文档的第<head>位(而不是第<body>位)的情况下进行操作的.关于为什么您可能会链接到文档末尾的<head>个脚本,有各种不同的论点,但这超出了本答案的范围.严格地说,这是关于<head><script>是否应该在<link>之前的问题.

In modern DESKTOP browsers,看起来链接到CSS First never可以提供性能提升.当CSS和脚本都延迟时,将CSS放在脚本后面会给您带来很小的yield ,但当CSS延迟时,会给您带来很大的yield .(由第一组结果中的last列显示.)

考虑到链接到CSS last似乎不会影响性能,但can在某些情况下会带来好处,如果旧浏览器的性能不是问题的话,you should link to external stylesheets after you link to external scripts only on desktop browsers.Read on for the mobile situation.

Why?

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

Hence, the widely-repeated advice to put stylesheets first – they would download first, and the first script to download could be loaded in parallel.

然而,现代浏览器(包括我在上面测试过的所有浏览器)已经实现了speculative parsing,浏览器在HTML中"向前看",并开始下载资源before脚本下载和执行.

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

浏览器支持

推测性解析首先在以下位置实施:(截至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,因此我们可以得出结论,"大多数"移动浏览器should都支持它.根据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 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.

应用程序.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相关问答推荐

在shiny 模块中实现JavaScript

序列查找器功能应用默认值而不是读取响应

如何分配类型脚本中具有不同/额外参数的函数类型

窗口.getComputedStyle()在MutationObserver中不起作用

我开始使用/url?q=使用Cheerio

XSLT处理器未运行

提交链接到AJAX数据结果的表单

更新动态数据中对象或数组中的所有值字符串

使用Nuxt Apollo在Piniastore 中获取产品细节

如何使用JS创建一个明暗功能按钮?

从另一个数组中的对应行/键值对更新数组中的键值对对象

向数组中的对象添加键而不改变原始变量

如何利用CSS中的隐藏元素实现平滑扩展和防止网格行间隙

在Odoo中如何以编程方式在POS中添加产品

为什么延迟在我的laravel项目中不起作用?

React:防止useContext重新渲染整个应用程序或在组件之间共享数据而不重新渲染所有组件

用另一个带有类名的div包装元素

我们是否可以在reactjs中创建多个同名的路由

有角粘桌盒阴影

如何检测当前是否没有按下键盘上的键?