[译] 唯快不破:Web 运用的 13 个优化步骤

迎接关注知乎专栏 —— 前端的逆袭
迎接关注我的博客知乎GitHub

译文地点:【译】唯快不破:Web 运用的 13 个优化步骤 – 前端的逆袭 – 知乎专栏
原文地点:12 Steps to a Faster Web App — Auth0

时过境迁,Web 运用比以往任何时刻都更具交互性。搞定机能可以协助你极大地改良终端用户的体验。浏览以下的技能并学以致用,看看哪些可以用来改良耽误,衬着时刻以及团体机能吧!

更快的 Web 运用

优化 Web 运用是一项费力的事变。Web 运用不仅处于客户端和效劳器端的两部份组件当中,平常来讲也是由多种多样的手艺栈构建而成:数据库,后端组件(平常也是搭建在差别手艺架构之上的),以及前端(HTML + JavaScript + CSS + 转化器)。运转时也是变幻莫测的:iOS,Android,Chrome,Firefox,Edge。假如你曾事变在一个差别的单一巨大的平台之上,平常状况下机能优化只针关于单一目的(以至只是目的的单一版本罢了),然则现在的话你就可以会意想到使命庞杂度要远超于此。这就对了。但这儿也有一些通用的优化指南可以大大优化一个运用。我们将会在接下来的章节中议论这些指南的内容。

一份 Bing 的研讨表明,页面加载时刻每增添 10ms,网站的年收入就会削减 25 万美元。 —— Rob Trace 和 David Walp,微软高等顺序司理

过早优化?

优化最难的处所就是如安在开辟生命周期中最恰当的时刻去做优化。Donald Knuth 有一句名言:_「过早优化乃万恶之源」_。这句话背地的缘由异常简朴:因为一不小心就会糟蹋时刻去优化某个 1% 的处所,然则结果却并不会对机能形成什么严重影响。与此同时,一些优化还阻碍了可读性或许是可保护性,以至还会引入新的 Bug。换句话说,优化不该该被认为是「意味着取得运用顺序的最好机能」,而是「探究优化运用的_准确的体式格局_,并取得_最大的效益_」。再换句话说,自觉的优化可以会致使效力的丧失,而收益却很小。在你运用以下技能的时刻请将此铭记在心。你最好的朋侪就是剖析东西:找到你可以举行经由历程优化取得最大水平改良的机能点,而不必损伤运用开辟的历程或许可保护性。

顺序员们糟蹋了大批时刻来思索,或许说是担心,他们的顺序中非症结部份的运转速率。而且他们关于机能的这些尝试,实际上却对代码的调试和保护有着异常悲观的影响。我们应当遗忘那些不主要的机能影响,在 97% 的时刻里都可以这么说:过早优化乃万恶之源。固然我们也不该该在那症结的 3% 上摒弃我们的时机。—— Donald Knuth

1. JavaScript 紧缩和模块打包

JavaScript 运用是以源码情势举行分发的,而源码剖析的效力是要比字节码低的。关于一小段剧本来讲,区分可以忽略不计。然则关于更大型的运用,剧本的大小会对运用启动时刻有着负面的影响。事实上,寄希冀于运用 WebAssembly 而取得最大水平的改良,个中之一就是可以取得更快的启动时刻。

另一方面,模块打包则用于将差别剧本打包在一起并放进统一文件。更少的 HTTP 请乞降单个文件剖析都可以削减加载时刻。平常状况下,零丁一种东西就可以处置惩罚打包和紧缩。Webpack 就是个中之一。

示例代码:

function insert(i) {
    document.write("Sample " + i);
}

for(var i = 0; i < 30; ++i) {
    insert(i);
}

结果以下:

!function(r){function t(o){if(e[o])return e[o].exports;var n=e[o]={exports:{},id:o,loaded:!1};return r[o].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var e={};return t.m=r,t.c=e,t.p="",t(0)}([function(r,t){function e(r){document.write("Sample "+r)}for(var o=0;30>o;++o)e(o)}]);
//# sourceMappingURL=bundle.min.js.map

进一步打包

你也可以运用 Webpack 打包 CSS 文件以及兼并图片。这些特征都可以有助于改良启动时刻。研讨一下 Webpack 文档来做些测试吧!

2. 按需加载资本

资本(迥殊是图片)的按需加载或许说_惰性加载_,可以有助于你的 Web 运用在团体上取得更好的机能。关于运用大批图片的页面来讲惰性加载有着明显的三个优点:

  • 削减向效劳器发出的并发请求数目(这就使得页面的其他部份取得更快的加载时刻)

  • 削减浏览器的内存运用率(更少的图片,更少的内存)

  • 削减效劳器端的负载

大体上的理念就是只在必要的时刻才去加载图片或资本(如视频),比方在第一次被显现的时刻,或许是在将要显现的时刻对其举行加载。因为这类体式格局跟你建站的体式格局密切相干,惰性加载的处理计划平常须要借助其他库的插件或许扩大来完成。举个例子,react-lazy-load 就是一个用于处置惩罚 React 惰性加载图片的插件:

const MyComponent = () => (
  <div>
    Scroll to load images.
    <div className="filler" />
    <LazyLoad height={762} offsetVertical={300}>
      <img src='http://apod.nasa.gov/apod/image/1502/HDR_MVMQ20Feb2015ouellet1024.jpg' />
    </LazyLoad>
    (...)

一个异常好的实践范例就像 Goggle Images 的搜刮东西一样。点击前面的链接而且滑动页面滚动条就可以看到结果了。

3. 在运用 DOM 操纵库时用上 array-ids

假如你正在运用 ReactEmberAngular 或许其他 DOM 操纵库,运用 array-ids(或许 Angular 1.x 中的 track-by 特征)异常有助于完成高机能,关于动态网页迥殊云云。我们已在上一篇顺序衡量标准的文章中看到这个特征的结果了: More Benchmarks: Virtual DOM vs Angular 1 & 2 vs Mithril.js vs cito.js vs The Rest (Updated and Improved!)《[译] 唯快不破:Web 运用的 13 个优化步骤》

此特征背地的主要观点就是尽量多地重用已有的节点。Array ids 使得 DOM 操纵引擎可以「晓得」在什么时刻某个节点可以被映射到数组当中的某个元素。没有 array-ids 或许 track-by 的话,大部份库都邑举行从新排序而摧毁已有的节点并从新建立新的。这就异常消耗机能了。

4. 缓存

Caches 是用于存储那些被频仍存取的静态数据的组件,便于随后关于这个数据的请求可以更快地被相应,或许说请求体式格局越发高效。因为 Web 运用是由许多可拆卸的部件组合而成,缓存就可以存在于架构中的许多部份。举例来讲,缓存可以被放在动态内容效劳器和客户端之间,就可以防备大众请求以削减效劳器的负载,与此同时改良相应时刻。其他缓存可以被安排在代码里,以优化某些用于剧本存取的通用形式,还有些缓存可以被安排在数据库或许是长运转历程之前。

简而言之,在 Web 运用中运用缓存是一种改良相应时刻和削减 CPU 运用的绝佳体式格局。难点就在于搞清楚那里才是在架构中寄存缓存的处所。再一次,答案就是机能剖析:罕见的瓶颈在那里?数据或许结果可缓存吗?他们都太轻易失效吗?这都是一些辣手的题目,须要从道理上来一点一点回复。

缓存的运用在 Web 环境中富有创造性。比方,basket.js 就是一个运用_Local Storage_ 来缓存运用剧本的库。所以你的 Web 运用在第二次运转剧本的时刻就可以险些霎时加载了。

现在一个广受迎接的缓存效劳就是亚马逊的 CloudFront。CloudFront 就跟平常的内容分发收集(CDN)用处一样,可以被设置作为动态内容的缓存。

5. 启用 HTTP/2

愈来愈多的浏览器都最先支撑 HTTP/2。这可以听起来没有必要,然则 HTTP/2 为统一效劳器的并发衔接题目带来了许多优点。换句话说,假如有许多小型资本须要加载(假如你打包过的话就没有必要了),在耽误和机能方面 HTTP/2 秒杀 HTTP/1。尝尝 Akamai 的 HTTP/2 demo,可以在最新的浏览器中看到区分。《[译] 唯快不破:Web 运用的 13 个优化步骤》

6. 运用机能剖析

机能剖析是优化任何运用顺序时的主要一步。就像引见中所提到的那样,自觉尝试优化运用常常会致使效力的糟蹋,眇乎小哉的收益和更差的可保护性。实行机能剖析是辨认你的运用题目所在的一个主要步骤。

关于 Web 运用来讲,耽误时刻是最大的埋怨之一,所以你须要确保数据的加载和显现都尽量得快。Chrome 供应了异常棒的机能剖析东西。迥殊是 Chrome Dev Tools 中的时刻线和收集视图都关于定位耽误题目有着很大的协助:《[译] 唯快不破:Web 运用的 13 个优化步骤》

时刻线视图可以帮助找到运转时刻较长的操纵。《[译] 唯快不破:Web 运用的 13 个优化步骤》

收集视图可以协助辨认出分外的由迟缓请求致使的耽误或关于某一端点的串行接见。

准确剖析的话,内存则是另一块可以取得收益的部份。假如你正在运转着一个具有许多假造元素的页面(巨大的动态表格)或许可交互式的元素(比方游戏),内存优化可以取得更少的卡顿和更高的帧率。从我们近来的文章 4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them 中,关于怎样运用 Chrome 的开辟东西有着进一步的深度明白。

CPU 机能剖析也可以在 Chrome Dev Tools 中找到。看看这篇来自 Google 官方文档中的文章 Profiling JavaScript Performance《[译] 唯快不破:Web 运用的 13 个优化步骤》

找到机能消耗的中心可以让你有用力地到达优化的目的。

对后端的机能剖析会越发难题。平常状况下,确认一个消耗较多时刻的请求可以让你明白应当优先剖析哪一个效劳。关于后端的剖析东西来讲,则取决于所构建的手艺栈。

一个关于算法的注意事项

在大多数状况下,挑选一个更优的算法,比围绕着小本钱中心所完成的详细优化战略可以取得更大的收益。在某种水平上,CPU 和内存剖析应当可以帮你找到大的机能瓶颈。当这些瓶颈跟编码题目并不相干时,则是时刻斟酌斟酌差别的算法了。

7. 运用负载平衡计划

我们在之前议论缓存的时刻扼要提到了内容分发收集(CDNs)。把负载分配到差别的效劳器(以至于差别的地舆地区)可以给你的用户供应更好的耽误时刻,然则这条路还很冗长,迥殊是在处置惩罚许多的并发衔接的时刻。

负载平衡就跟运用某个 round-robin(轮回)处理计划一样简朴,可以基于一个 nginx 反向代办 ,或许基于一个成熟的分布式收集,比方 Cloudflare 或许 Amazon CloudFront《[译] 唯快不破:Web 运用的 13 个优化步骤》

以上的图来自于 Citrix。 为了使负载平衡真正有用,动态内容和静态内容都应当被拆分红易于并发接见的。换句话说,元素的串形接见会减弱负载平衡器以最好情势举行分流的才能。与此同时,关于资本的并发接见可以改良启动时刻。

虽然负载平衡可以会很庞杂。对终究一致性算法不友好的数据模型,或许缓存都邑让事变越发难题。荣幸的是,大多数运用关于已简化的数据集都只须要保证高层次的一致性即可。假如你的运用顺序没有如许设想的话,就有必要重构一下了。

8. 为了更快的启动时刻斟酌一下同构 JavaScript

改良 Web 运用顺序观感的体式格局之一,就是削减启动时刻或许削减首页衬着时刻。这关于新兴的单页面运用尤为主要,其须要在客户端实行大批使命。在客户端做更多事变平常就意味着,在第一次衬着被实行之前就须要下载更多的信息。同构 JavaScript 可以处理这个题目:自从 JavaScript 可以同时运转在客户端和效劳器端,这就让在效劳器端来实行页面的初次衬着成为可以,先把已衬着的页面发送出去然后再由客户端的剧本接受。这限定了所运用的后端(必需运用支撑该特征的 JavaScript 框架),但却能取得更好的用户体验。举例来讲,React 就很适合于做这个,就像以下代码所示:

var React = require('react/addons');
var ReactApp = React.createFactory(require('../components/ReactApp').ReactApp);

module.exports = function(app) {

    app.get('/', function(req, res){
        // React.renderToString takes your component
        // and generates the markup
        var reactHtml = React.renderToString(ReactApp({}));
        // Output html rendered by react
        // console.log(myAppHtml);
        res.render('index.ejs', {reactOutput: reactHtml});
    });

};

Meteor.js 关于客户端和效劳器端的 JavaScript 混用有着异常棒的支撑。

if (Meteor.isClient) {
  Template.hello.greeting = function () {
    return "Welcome to myapp.";
  };

  Template.hello.events({
    'click input': function () {
      // template data, if any, is available in 'this'
      if (typeof console !== 'undefined')
        console.log("You pressed the button");
    }
  });
}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}

然则,为了支撑效劳器端衬着,须要像 meteor-ssr 如许的插件。

感谢 gabrielpoca 在批评中指出这一点。假如你有庞杂的或许中等大小的运用须要支撑同构布置,尝尝这个,你可以会觉得惊奇的。

9. 运用索引加快数据库查询

假如你须要处理数据库查询消耗大批时刻的题目(剖析你的运用看看是不是是这类状况!),是时刻找出加快数据库的要领了。每一个数据库和数据模型都有自身的衡量。数据库优化在每一方面都是一个主题:数据模型,数据库范例,详细完成计划,等等。提速可以不是那末的简朴。然则这儿有个发起,可以可以对某些数据库有所协助:索引。索引是一个历程,即数据库所建立的疾速接见数据构造,从内部映射到键(在关联数据库中的列),可以进步检索相干数据的速率。大多数当代数据库都支撑索引。索引并非文档型数据库(比方 MongoDB)所独占的,也包含关联型数据库(比方PostgreSQL)。

为了运用索引来优化你的查询,你将须要研讨一下运用顺序的接见形式:什么是最罕见的查询,在哪一个键或列中实行搜刮,等等。

10. 运用更快的转译计划

JavaScript 软件手艺栈自始自终的庞杂。而改良言语自身的需求则又增添了庞杂度。不幸地是,JavaScript 作为目的平台又会被用户的运转时所限定。只管许多革新已以 ECMAScript 2015(2016正在举行)的情势完成了,然则平常状况下,对客户端代码来讲又不可以依赖于这个版本。这类趋向促使了一系列的_转译器_:用于处置惩罚 ECMAScript 2015 代码的东西和只运用 ECMAScript 5 构造完成个中所缺失的特征。与此同时,模块绑定和紧缩处置惩罚也已被集成到这个临盆历程当中,被称为_为宣布而构建_的代码版本。这些东西可以转化代码,而且可以以有限的体式格局影响到终究代码的机能。Google 开辟者 Paul Irish 花了一些时刻来寻觅这些转译计划会怎样影响机能和终究代码的大小。只管大多数状况下收益会很小,但也值得在正式采纳某个东西栈之前看看这些数据。关于大型运用顺序来讲,这类区分可以会影响严重。

11. 防备或最小化 JavaScript 和 CSS 的运用而壅塞衬着

JavaScript 和 CSS 资本都邑壅塞页面的衬着。经由历程采用某些的划定规矩,你可以保证你的剧本和 CSS 被尽量疾速地处置惩罚,以便于浏览器可以显现你的网站内容。

在 CSS 的状况下这是异常主要的,一切的 CSS 划定规矩都不能与特定媒体直接相干,划定规矩只用于处置惩罚你预备在页面上所显现内容的优先级。这可以经由历程运用 CSS 媒体查询来完成。媒体查询通知浏览器,哪些 CSS 样式表运用在某个特定的显现媒体上。举个例子,用于打印的某些划定规矩可以被给予比用于屏幕显现更低的优先级。

媒体查询可以被设置成 <link> 标签属性:

<link rel="stylesheet" type="text/css" media="only screen and (max-device-width: 480px)" href="mobile-device.css" />

轮到 JavaScript 了,症结就在于遵照某些用于内联 JavaScript 的划定规矩(比方内联在 HTML 文件当中的代码)。内联 JavaScript 应当尽量短,并将其放在不会壅塞页面盈余部份剖析的处所。换句话说,被放在 HTML 树中心的内联 JavaScript 将会在这个处所壅塞剖析器,并强迫其守候直到剧本被实行终了。假如在 HTML 文件中随便放了一些大的代码块或许许多小的代码块,关于机能来讲这会成为机能杀手。内联可以有用削减分外关于某些特定剧本的收集请求。然则关于重复运用的剧本或许大的代码块来讲,这个优点就可以忽略不计了。

防备 JavaScript 壅塞剖析器和衬着器的一种要领就是将 <script> 标签标记为_异步的_。这限定了我们关于 DOM 的接见然则可以让浏览器不论剧本的实行状况而继承剖析和衬着页面。换句话说,为了取得最好的启动时刻,确保那些关于衬着不主要的剧本已经由历程异步属性的体式格局标记成异步的了。

<script src="async.js" async></script>

12. 用于将来的一个发起:运用 service workers + 流

Jake Archibald 近来的一篇博文详细描述了一种风趣的手艺,可以用于加快衬着时刻:将 service workers 和流结合起来。结果异常使人叹服:

不幸的是这个手艺所须要的 APIs 都还不稳定,这也是为何这是一种风趣的观点但现在还没有真正被运用的缘由。这个主意的宗旨就是在网站和客户端之间安排一个 service worker。这个 service worker 可以在猎取缺失信息的同时缓存某些数据(比方 header 和一些不会常常转变的东西)。缺失的内容就可以尽量疾速地流向被衬着的页面。

https://www.youtube.com/watch?v=Cjo9iq8k-bc

13. 更新:图片编码优化

我们的一个读者指出了一个异常主要的脱漏:图片编码优化。PNGs 和 JPGs 在 Web 宣布时都邑运用次优的设置举行编码。经由历程转变编码器和它的设置,关于须要大批图片的网站来讲可以取得有用的改良。盛行的处理计划包含 OptiPNGjpegtran

A guide to PNG optimization 详细描述了 OptiPNG 可以怎样用于优化 PNGs。

The man page for jpegtran 对它的一些特征供应了很好的引见。

假如你发明这些指南相干于你的请求来讲都太庞杂了的话,这儿有一些在线网站可以供应优化效劳。也有一些像 RIOT 一样的图形化界面,异常有助于批量操纵和结果搜检。

扩大浏览

你可以鄙人面的链接中浏览更多信息,以及找到有助于优化网站的东西:

悄悄话:Auth0 中罕见的优化

我们是一个 Web 公司。就以这类身份来讲,我们为我们的基础设施的某些部份布置了一些特定的优化。举例来讲,在登录页面你可以发明,在我们域名的 /learn 途径下(比方,登录页面的单点登录),我们采纳了一种迥殊的优化:为了轻易我们运用 CMS 来建立每篇文章。因为文章都没有中心索引,然则为了可以被搜刮引擎发明,运用了 webtask 的爬虫来预衬着每一个页面并天生了一个静态版本然后上传到我们 CDN。这削减了我们在效劳器端上的压力,因为不必为每一个访客都天生动态的效劳器端内容。与此同时还改良了耽误(而且隔离了我们发明与 CMS 相干的安全题目)。

关于文档部份,我们正在运用_同构 JavaScript_,这让我们取得了异常棒的启动时刻,而且使我们的后端和前端团队可以轻松集成。

结论

因为运用顺序变得愈来愈大和愈来愈庞杂,机能优化关于 Web 开辟来讲正在变得愈来愈主要。在做出任何值得的时刻和潜伏的将来本钱的优化尝试时,有针对性的革新都是必不可少的。Web 运用顺序早已突破了大多数静态内容的边境,进修罕见形式举行优化则是使人愉悦的运用和完整不可用的运用之间最大的区分(这是让你的访客留下来的久远之计!)。没有什么划定规矩是相对的,然则:机能剖析和研讨特定软件手艺栈的扑朔迷离的地方,是找出怎样优化它的唯一体式格局。你曾发明过对你的运用发生巨大影响的其他发起吗?请留言让我们晓得。Hack on!

迎接关注知乎专栏 —— 前端的逆袭
迎接关注我的博客知乎GitHub

    原文作者:吕立青
    原文地址: https://segmentfault.com/a/1190000005798306
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞