草庐IT

javascript - CSS 应该总是在 Javascript 之前吗?

coder 2023-04-23 原文

在网上无数地方,我看到了在 JavaScript 之前包含 CSS 的建议。推理一般是,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.


我的实际测试揭示了一些完全不同的东西:
我的测试线束
我使用以下 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 秒来呈现:

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

我在 Chrome、Firefox 和 Internet Explorer 中得到了类似的结果。然而,在 Opera 中,顺序根本无关紧要。
似乎正在发生的事情是 JavaScript 解释器拒绝启动,直到所有 CSS 下载完毕。因此,随着 JavaScript 线程获得更多运行时间,首先包含 JavaScript 似乎更有效。
我是否遗漏了什么,建议在 JavaScript 包含之前放置 CSS 包含不正确?
很明显,我们可以添加异步或使用 setTimeout 来释放渲染线程或将 JavaScript 代码放在页脚中,或者使用 JavaScript 加载器。这里的重点是关于头部中基本 JavaScript 位和 CSS 位的排序。

最佳答案

这是一个非常有趣的问题。我一直把我的 CSS <link href="...">在我的 JS 之前 <script src="..."> s 因为“我读过一次它更好。”所以,你是对的;现在是我们进行一些实际研究的时候了!

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

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

  • 结果

    首先,将 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 设置为延迟 500 毫秒而不是 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 延迟 500 毫秒:
         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> s 应该在 <link> 之前s 在 <head> .

    在现代桌面浏览器中,看起来像是先链接到 CSS 从不 提供性能增益。当 CSS 和脚本都被延迟时,将 CSS 放在脚本之后会给你带来微不足道的 yield ,但当 CSS 被延迟时会给你带来很大的 yield 。 (由第一组结果中的 last 列显示。)

    鉴于链接到 CSS last 似乎不会损害性能,但在某些情况下可以提供 yield ,仅在桌面浏览器上链接到外部脚本后,您应该链接到外部样式表 如果旧浏览器的性能不是问题。 请继续阅读移动情况。

    为什么?

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

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

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

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

    浏览器支持

    推测性解析首先在:(以及截至 2012 年 1 月使用此版本或更高版本的全局桌面浏览器用户的百分比)
  • Chrome 1 (WebKit 525) (100%)
  • IE 8 (75%)
  • 火狐 3.5 (96%)
  • Safari 4 (99%)
  • 歌剧 11.60 (85%)

  • 总的来说,目前使用的桌面浏览器中大约有 85% 支持推测加载。将脚本放在 CSS 之前会对全局 15% 的用户造成性能损失; YMMV 基于您网站的特定受众。 (请记住,这个数字正在缩小。)

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

    然而,我在我的 Android 4 设备上运行了测试,虽然我看到的数字与桌面结果相似,但我将它连接到了很棒的新 remote debugger在 Chrome for Android 中,网络选项卡显示浏览器实际上正在等待下载 CSS,直到 JavaScript 完全加载 - 换句话说,即使是适用于 Android 的最新版本的 WebKit 似乎也不支持推测解析。 我怀疑它可能由于移动设备固有的 CPU、内存和/或网络限制而被关闭。

    代码

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

    测试.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 应该总是在 Javascript 之前吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9271276/

    有关javascript - CSS 应该总是在 Javascript 之前吗?的更多相关文章

    1. ruby - capybara field.has_css?匹配器 - 2

      我在MiniTest::Spec和Capybara中使用以下规范:find_field('Email').must_have_css('[autofocus]')检查名为“电子邮件”的字段是否具有autofocus属性。doc说如下:has_css?(path,options={})ChecksifagivenCSSselectorisonthepageorcurrentnode.据我了解,字段“Email”是一个节点,因此调用must_have_css绝对有效!我做错了什么? 最佳答案 通过JonasNicklas得到了答案:No

    2. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

      为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

    3. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

      我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

    4. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

      我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

    5. java - 我的模型类或其他类中应该有逻辑吗 - 2

      我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

    6. css - 用 watir 检查标签类? - 2

      我有一个div,它根据表单是否正确提交而改变。我想知道是否可以检查类的特定元素?开始元素看起来像这样。如果输入不正确,添加错误类。 最佳答案 试试这个:browser.div(:id=>"myerrortest").class_name更多信息:http://watir.github.com/watir-webdriver/doc/Watir/HTMLElement.html#class_name-instance_method另一种选择是只查看具有您期望的类的div是否存在browser.div((:id=>"myerrortes

    7. ruby-on-rails - 带有 Zeus 的 RSpec 3.1,我应该在 spec_helper 中要求 'rspec/rails' 吗? - 2

      使用rspec-rails3.0+,测试设置分为spec_helper和rails_helper我注意到生成的spec_helper不需要'rspec/rails'。这会导致zeus崩溃:spec_helper.rb:5:in`':undefinedmethod`configure'forRSpec:Module(NoMethodError)对thisissue最常见的回应是需要'rspec/rails'。但这是否会破坏仅使用spec_helper拆分rails规范和PORO规范的全部目的?或者这无关紧要,因为Zeus无论如何都会预加载Rails?我应该在我的spec_helper中做

    8. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

      我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

    9. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

      在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

    10. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

      我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

    随机推荐