前端性能优化


来自:http://www.oschina.net/translate/front-end-performance-for-web-designers-and-front-end-developers#section:maximising-parallelisation


根蒂根基知识

关于性能,有一些知识在所有的设计师和前端开发者中广为传播。例如,尽可能少的请求,优化图片,把样式表(stylesheets)放在<head>, 把JS放在</body>之前, 最小化(minifying) JS 和 CSS 等等。这些根蒂根基知识已被用来加快用户响应了,但还有更多更多需要学习。

延伸阅读

如果每个引用的新域名具有DNS查询的前端代价,你必须确保这个代价的确是值得的。如果是一个小网站(例如像CSS魔法),那么由子域名提供资源可能并不值得;相比执行多个域名的DNS查询并将其并行化来说,从一个域名非并行的获取若干资源,浏览器可能更快。

顶部的Styles, 底部的scripts

这真的是一条基本规则,每个人都能非常轻易的在大多数时间遵守,但为什么它主要?简短的说:

  • CSS 块渲染, 因此你需要立即处理它(即在文档的顶部,在你的<head>之中)。
  • JS 块下载, 因此你需要最后处理它们,以确保它们没有耽误页面中任何其它东西。

CSS块渲染是因为浏览器总是试图渐进式的渲染页面;它们想在元素到达的时候按次的渲染它。如果style在间隔很远的页面下部,浏览器在获得它之前没有办法渲染那个CSS。因为这个原因,如果浏览器在渲染文档过程中,改变了之前渲染的东西,它们可以躲免style的重绘。浏览器在它获得所有需要的style信息之前不会渲染页面,如果你将style放在文档底部,你就是在使浏览器等待,阻塞了渲染。

这全部都很好,但后面我们将接洽在特定环境下,怎样从子域名提供服务却会实际上对性能有害。

JavaScript块下载是由于好几个原因(这又是浏览器聪明之处),但首先我们需要知道浏览器里的资源下载是如何实际产生的;简单的说,浏览器会从一个单一的域名并行的尽可能多的下载资源。它从越多的域名下载,就可以在一瞬间并行的获得更多的资源。

JavaScript中断了这个过程,阻塞了从任何一个域名的并行的下载,因为:

所以,要使页面被尽可能快的渲染,将styles放在顶部。为了阻遏JS的阻塞影响到渲染,将scripts放在底部。

但是显然,现代浏览器还是变得聪明了。我将给你一个Andy Davies寄给我的电子邮件的摘录,因为他解释的比我清楚:

现代浏览器将并行下载JS,只有在脚本被执行的时候阻塞渲染(显然脚本必须也被下载了)。

脚本下载常常被浏览器的预加载器所完成。

当浏览器页面渲染被阻塞,即等待CSS,或JS被执行,预阐发器将扫描页面剩余部分,寻找它能下载的资源。

有些浏览器如 Chrome, 将分先后下载资源,例如,如果脚本与图片同时在等待下载,它将先下载脚本。

漂亮的内容!

<link rel="stylesheet" href="https://si0.twimg.com/a/1358386289/t1/css/t1_core.bundle.css" type="text/css" media="screen">

Facebook 用fbstatic-a.akamaihd.net:

我在前面说过,字体和图片体现非常相似,上面所说的规则同样也适用于字体文件,但你无法使用隐藏的<div>载入字体文件(你需要使用预取link)。

再谈到浏览器和并行;大多数浏览器一次只从每个引用的域下载一些资源,而JS会阻塞这些下载。所以,你做的每个HTTP请求都应该仔细斟酌,而不是随便随便做的。

尽可能并行


为了让浏览器能并行的下载更多资源,你可以由不同的域名提供服务。如果说,浏览器只能一次从一个域名获取两个资源,那么由两个域名提供服务意味着它可以一次性获取四个资源;三个域名意味着六个并行下载。

很主要的需要记得的是,比方说一旦HTML被请求于foo.com,对那个主机的DNS查询就立即产生了,所以后绝的任何对foo.com的请求不再受制于DNS查询。

  • 被调用的脚本可能改变页面,即浏览器在继绝别的事情以前,将不得不处理它。因此为了处理那个意外事件,浏览器截至了任何其它东西的下载,以便集中精力存眷于它。
  • 脚本正常工作经常需要依照肯定的按次加载,例如,要在加载一个插件之前加载jQuery。浏览器阻遏了JavaScript的并行下载,因此它不会同时下载jQuery和你的插件;很显然如果你同时并行下载二者,你的插件会在jQuery之前到达。

所以,由于浏览器在获取JavaScript的时候截至了所有其他下载,将你的JavaScript脚本放在文档中尽可能晚加载的地方是一个好主意。我相信你们都看到过页面中的空白片段,在那里第三方的JS脚本被花时间加载,并且它还阻遏了页面其他资源的获取和渲染;这就是JavaScript的阻塞在作用了。

<link rel="stylesheet" href="https://fbstatic-a.akamaihd.net/rsrc.php/v2/yi/r/76f893pcD3j.css">

经由这些花儿与少年静态的资源域名, Twitter与Facebook能提供更多的并行资源服务;来自twitter.com和si0.twimg.com的资源可以协作方式下载。这真的是使你的页面上获得更多并发下载的简单方法,如果再加上实际的CDN技术就会更好,CDN技术经由从一个更加适合的物理位置提供资源服务的方法来裁汰延迟。

所以,只要你将CSS放在页面的顶部,那么浏览器就可以连忙开始渲染。

因此,现在有了我们关于性能的根蒂根基知识:

  • 经由预取加速你的网站

资源预取

和DNS预取一样,也可以顺便对你的站点需要的其它资源进行预取。为了弄清楚我们想要预取哪些资源, 首先我们需要了解浏览器通常会在什么时候以什么方式对资源发出请求。

然而关于这还有个问题,DNS查询。每次(从一个空缓存)一个新的域名被引用,HTTP请求会受制于一个耗时的DNS查询(某个介于20到120毫秒之间的值),在DNS查询中,发出的请求会查询资源实际存在的地址;互联网经由IP地址被绑定在一起,这些地址由DNS治理的主机名引用。

<link rel="prefetch" href="webfont.woff">

所以,基本可以这么说,我们这里所作的一切,只能算是让浏览器提前下载资源的“小把戏”而已,耍了小把戏之后,在浏览器碰到要使用CSS的时候,其中所引用的资源就早已下载好了(或者至少已在下载中了)。 漂亮极了!

如果你或许有一打资源,你可能会斟酌从一个子域名提供它们的资源服务;为了更好的并行化那许多资源,额外的DNS查询可能是值得的。如果说你有40个资源,可能将那些资源切分到两个子域名是值得的;为了由总数为三个的域名提供你的网站服务,两个额外的DNS查询会是值得的。

DNS查询代价很高,因此你需要决定什么才是对你的网站更适合的;承担查询的消耗或者只是由一个域名提供所有服务。

许多网站有静态/资源 域名;你可以发现, Twitter, 用 si0.twimg.com 来做静态资源:


DNS 预取

如果你像我一样想在网站上有一个Twitter小程序,还有网站阐发,再或许一些网页字体,那么你必须要链接到一些其它域名,这意味着你将不得不引发DNS查询。我的建议通常是,不要还没有先适当的斟酌性能影响就使用某个或任何一个小程序,但对于你以为的确需要的,下面的将很有用……

因为这些东西都存在于其它域名,比方说这就意味着你的网站字体CSS将会同你自己的CSS并行下载,从某种意义上说是一种好处,但是脚本将仍会阻塞(除非它们是异步的)

事实上,这里的问题是DNS查询牵涉到了第三方域名。幸运的是,有一个相称快又简单的办法来加速这个过程:DNS预取。

DNS预取所做的恰恰就是凭证领餐(on the tin),它不能被简单实现。比方说,如果你需要请求来自widget.foo.com的资源,那么你可以经由简单的在页面的<head>里先增加下面这个来预取那个主机的DNS:

虽然在我们每天的工作生活中,浏览器给我们制造麻烦,使我们头疼,但请记住,他们也是很聪明的; 它们为我们做了许多性能优化工作, 所以大量的性能调优知识不仅要知道浏览器在哪里给我们做了优化,还要知道怎么更好的挖掘它们。大量性能调优诀窍只是理解,利用和操纵浏览器已替我们做好的优化工作。

这种简单的链接元素(就是我在CSS魔法上用到的)完全后向兼容,而且不会忽略性能影响。将它看做是性能提升增强吧!

<head>
    ...
    <link rel="dns-prefetch" href="//widget.foo.com">
    ...
</head>

那行简单的内容将会告诉支持的浏览器去开始预取那个域名的DNS,这要稍稍早于它实际需要的时刻。它意味着DNS查询过程,在浏览器<script>元素真正请求小程序的时候就已在进行中了。这仅仅给浏览器增加了一个很小的开头。

  • 将样式表放在文档的顶部
  • 将JavaScript放在底部(可能的地方)
  • 尽可能裁汰HTTP请求
  • 从多个域名提供资源服务能增加浏览器并行下载的资源数目。

HTTP 请求与 DNS 查询

每当你从任何域名请求一个资源,会发出一个带有相关头部,被访问资源的 HTTP请求,并且会返回一个响应。这是对该过程的一个极端简化,但它基本就是事实上你需要知道的。这是一个HTTP请求,而且所有涉及的资源都从属于这个往返的旅行。当提到前端性能,这些请求正是次要的瓶颈所在,因为如我们谈到的,浏览器受限于有几请求可以并行产生。这也是为什么我们经常要使用子域名;以便容许这些请求在数个域名上产生,容许同时产生多得多数目的请求。

CSS中引用的Web字体和图片体现基原形同;浏览器在碰到需要它们的HTML时开始对它们进行下载。就和我在前面提到那样,浏览器非常聪明,这又是一个例证。想象一下,浏览器一看到下面的CSS声明就开始下载其中所引用的图片:

.page--home         { background-image:url(home.jpg); }
.page--about        { background-image:url(about.jpg); }
.page--portfolio    { background-image:url(portfolio.jpg); }
.page--contact      { background-image:url(contact.jpg); }

如果浏览器不是等碰到需要这些图片的HTML再下载它们,那么访问主页就会立即下载所有这四个图片。这会造成浪费,所以浏览器肯定会确保在需要这些图片时才会开始下载它们。所以,这里有个问题在于,图片下载直到很晚才会开始。

如果我们可以完全确认某个CSS图片肯定会在每个页面都市用到的话,我们就可以用个小把戏让浏览器早早下载好这个图片,无需比及让浏览器碰到需要使用该图片的HTML才开始下载。想做到这一点也非常简单,但所用的方法可能会有点糙,就看你怎么弄了。

比较糙的方法和大多数笨拙的万全之法类似,就是在每个页面放置一个隐藏的<div>,在该div中使用带有空的alt属性的<img>标签。我在CSS Wizardry项目中的精灵中就是这么干的;因为我知道,每个页面都要使用该精灵,所以我就经由在HTML中对其进行引用对它进行预取。浏览器处理内联(inline)<img>的方式非常好,浏览器会早早地对它们进行预取,所以经由让浏览器将我的精灵作为HTML中的<img>进行载入,浏览器就可以在使用需要精灵的CSS之前将其下载好。经由首先在我的HTML中引用该精灵(隐藏起来的),我就可以够抢先把精灵下载好。

还有第二种方法比较“优雅”,但会让人有些困惑。它和DNS预取的例子非常相似

<link rel="prefetch" href="sprite.png">

这会显式地告诉浏览器,马上开始预取我的精灵图片,而不要斟酌在它处理CSS时可能会做的任何决定。

令人感到困惑之处在于有两篇文章似乎有不同的观点;基于来自MDN的这篇文章,貌似这种预取指令只是示意浏览器在它余暇时有可能会对href所指的资源进行预取。然而,与此矛盾的是,来自Planet Performance的这篇文章貌似在说,如果浏览器支持rel="prefetch"的话,它就肯定会预取href中所指的资源,并没有说起是否要在浏览器余暇时才进行预取。我在WebKit的Inpsector中的瀑布图中所看到的状况是后者说得是对的,但是在打开Developer Tools的状况下(薛定谔测不准。。。)WebKit的体现及其怪异,我就观察不到预取动作的状况了,这也就是我说,我无法100%包管我说的是对的。要是谁能解释清楚这方面的状况,我将不胜感激。

更少的请求

另一个明显而基本的性能优化方法是少下载。页面需要的每个资源就是一次额外的HTTP请求;浏览器不得不停下来去获取每个用于渲染页面所需的资源。每次HTTP请求都可能引发DNS查询,重定向,404,等等。每次HTTP请求,无论为了样式表,图片,web字体,JS文件还是其它你能想到的,都可能是一次非常昂贵的操作。尽量裁汰这些请求是你可以做的最快的优化方法中的一种.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值