为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

哈士奇WWW 2019-12-06 16:01:32

在网上的无数地方,我看到了建议在JavaScript之前包含CSS。推理通常采用以下形式:


在订购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

上面的微型服务器允许我为JavaScript文件(服务器和客户端)设置任意延迟,以及CSS延迟。例如,http://10.0.0.50:8081/test.css?delay=500给我500毫秒的CSS传输延迟。

当我首先包含CSS时,页面需要1.5秒的时间来呈现:


CSS优先


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


JavaScript优先


我在Chrome,Firefox和Internet Explorer中得到了类似的结果。但是,在Opera中,排序根本没有关系。


似乎正在发生的事情是JavaScript解释器拒绝启动,直到所有CSS被下载为止。因此,随着JavaScript线程获得更多的运行时间,似乎首先包含JavaScript会更有效。


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


显然,我们可以添加异步或使用setTimeout释放渲染线程或将JavaScript代码放在页脚中,或使用JavaScript加载器。这里的重点是关于头部中必需的JavaScript位和CSS位的排序。


查看完整描述

3 回答

?
叮当猫咪

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


我在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设置为延迟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 都设置为延迟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>。


在现代的DESKTOP浏览器中,似乎首先链接到CSS 绝对不会提高性能。将CSS放在脚本之后可以在延迟CSS和脚本时获得微不足道的收益,但是在延迟CSS时可以带来很多收益。(由last第一组结果中的列显示。)


鉴于最后一次链接到CSS似乎并不影响性能,但是在某些情况下可以带来收益,因此如果您不关心旧浏览器的性能,则应该在仅在桌面浏览器上链接到外部脚本之后再链接到外部样式表。 阅读有关移动情况的信息。


为什么?

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


因此,广为人知的建议是将样式表放在首位–首先下载样式表,然后并行加载第一个下载的脚本。


但是,现代浏览器(包括我上面测试过的所有浏览器)已经实现了推测性解析,即浏览器以HTML形式“向前看”,并在脚本下载和执行之前开始下载资源。


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


浏览器支持

推测性解析首先在以下位置实现:(以及截至2012年1月使用该版本或更高版本的全球桌面浏览器用户所占的百分比)


Chrome 1(WebKit 525)(100%)

IE 8(75%)

Firefox 3.5(96%)

Safari 4(99%)

歌剧11.60(85%)

总计目前使用的台式机浏览器中大约有85%支持推测性加载。将脚本放到CSS上将对全球 15%的用户造成性能损失;YMMV根据您网站的特定受众群体。(请记住,这个数字正在减少。)


在移动浏览器上,仅由于移动浏览器和OS的异构性不同,很难获得确定的数字。由于投机渲染是在WebKit 525(2008年3月发布)中实现的,并且几乎所有有价值的移动浏览器都基于WebKit,因此可以得出结论,“大多数”移动浏览器应支持它。根据quirksmode,iOS 2.2 / Android 1.0使用WebKit525。我不知道Windows Phone的外观。


但是,我在Android 4设备上运行了该测试,尽管看到的数字与桌面结果相似,但还是将其连接到了Android版Chrome浏览器中出色的新型远程调试器上,并且“网络”标签显示该浏览器实际上正在等待下载CSS,直到JavaScript完全加载为止-换句话说,即使最新版本的WebKit for Android也似乎不支持推测性解析。 我怀疑可能由于移动设备固有的CPU,内存和/或网络限制而将其关闭。


原谅草率–这是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


查看完整回答
反对 回复 2019-12-06
?
繁星coding

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


旧的浏览器(Internet Explorer 6-7,Firefox 2等)在开始下载脚本时会阻止所有后续下载。因此,如果您a.js之后依次b.css下载它们,请先下载:a然后b。如果您b.css关注a.js它们,则可以并行下载它们,从而使页面加载更快。


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


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


查看完整回答
反对 回复 2019-12-06
?
潇湘沐

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


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


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


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


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


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


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


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


查看完整回答
反对 回复 2019-12-06

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信