惟快不破:Web 应用的 13 个优化步骤

时过境迁,Web 应用比以往任什么时候候都更具交互性。搞定性能能够帮助你极大地改善终端用户的体验。阅读如下的技巧并学以至用,看看哪些能够用来改善延迟,渲染时间以及总体性能吧!javascript

更快的 Web 应用css

优化 Web 应用是一项费劲的工做。Web 应用不只处于客户端和服务器端的两部分组件当中,一般来讲也是由多种多样的技术栈构建而成:数据库,后端组件(通常也是搭建在不一样技术架构之上的),以及前端(HTML + JavaScript + CSS + 转译器)。运行时也是变化无穷的:iOS,Android,Chrome,Firefox,Edge。若是你曾经工做在一个不一样的单一庞大的平台之上,一般状况下性能优化只针对于单一目标(甚至只是目标的单一版本而已),可是如今的话你就可能会意识到任务复杂度要远超于此。这就对了。但这儿也有一些通用的优化指南能够大大优化一个应用。咱们将会在接下来的章节中探讨这些指南的内容。html

一份 Bing 的研究代表,页面加载时间每增长 10ms,网站的年收入就会减小 25 万美圆。 —— Rob Trace 和 David Walp,微软高级程序经理
过早优化?前端

优化最难的地方就是如何在开发生命周期中最适当的时候去作优化。Donald Knuth 有一句名言:「过早优化乃万恶之源」。这句话背后的缘由很是简单:由于一不当心就会浪费时间去优化某个 1% 的地方,可是结果却并不会对性能形成什么重大影响。与此同时,一些优化还妨碍了可读性或者是可维护性,甚至还会引入新的 Bug。换句话说,优化不该当被认为是「意味着获得应用程序的最佳性能」,而是「探索优化应用的正确的方式,并获得最大的效益」。再换句话说,盲目的优化可能会致使效率的丢失,而收益却很小。在你应用如下技巧的时候请将此铭记在心。你最好的朋友就是分析工具:找到你能够进行经过优化得到最大程度改善的性能点,而不用损害应用开发的进程或者可维护性。java

程序员们浪费了大量时间来思考,或者说是担心,他们的程序中非关键部分的运行速度。而且他们对于性能的这些尝试,实际上却对代码的调试和维护有着很是消极的影响。咱们应当忘记那些不重要的性能影响,在 97% 的时间里均可以这么说:过早优化乃万恶之源。固然咱们也不该当在那关键的 3% 上放弃咱们的机会。—— Donald Knuth
1. JavaScript 压缩和模块打包react

javascript 应用是以源码形式进行分发的,而源码解析的效率是要比字节码低的。对于一小段脚原本说,区别能够忽略不计。可是对于更大型的应用,脚本的大小会对应用启动时间有着负面的影响。事实上,寄指望于使用 WebAssembly 而得到最大程度的改善,其中之一就是能够获得更快的启动时间。linux

另外一方面,模块打包则用于将不一样脚本打包在一块儿并放进同一文件。更少的 HTTP 请求和单个文件解析均可以减小加载时间。一般状况下,单独一种工具就能够处理打包和压缩。Webpack 就是其中之一。nginx

示例代码:程序员

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};returnr[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
1
2
1
2
进一步打包

你也可使用 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>
(...)
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
一个很是好的实践范例就像 Goggle Images 的搜索工具同样。点击前面的连接而且滑动页面滚动条就能够看到效果了。

3. 在使用 DOM 操做库时用上 array-ids

若是你正在使用 React,Ember,Angular 或者其余 DOM 操做库,使用 array-ids(或者 Angular 1.x 中的 track-by 特性)很是有助于实现高性能,对于动态网页尤为如此。
这里写图片描述
此特性背后的主要概念就是尽量多地重用已有的节点。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,能够在最新的浏览器中看到区别。

这里写图片描述

6. 应用性能分析

性能分析是优化任何应用程序时的重要一步。就像介绍中所提到的那样,盲目尝试优化应用常常会致使效率的浪费,微不足道的收益和更差的可维护性。执行性能分析是识别你的应用问题所在的一个重要步骤。

对于 Web 应用来讲,延迟时间是最大的抱怨之一,因此你须要确保数据的加载和显示都尽量得快。Chrome 提供了很是棒的性能分析工具。特别是 Chrome Dev Tools 中的时间线和网络视图都对于定位延迟问题有着很大的帮助:

这里写图片描述

时间线视图能够帮忙找到运行时间较长的操做。

这里写图片描述

网络视图能够帮助识别出额外的由缓慢请求致使的延迟或对于某一端点的串行访问。

正确分析的话,内存则是另外一块可能得到收益的部分。若是你正在运行着一个拥有不少虚拟元素的页面(庞大的动态表格)或者可交互式的元素(好比游戏),内存优化能够得到更少的卡顿和更高的帧率。

找到性能损耗的中心可让你有效率地达到优化的目标。

对后端的性能分析会更加困难。一般状况下,确认一个耗费较多时间的请求可让你明确应该优先分析哪个服务。对于后端的分析工具来讲,则取决于所构建的技术栈。

一个关于算法的注意事项

在大多数状况下,选择一个更优的算法,比围绕着小成本中心所实现的具体优化策略可以得到更大的收益。在某种程度上,CPU 和内存分析应该能够帮你找到大的性能瓶颈。当这些瓶颈跟编码问题并不相关时,则是时候考虑考虑不一样的算法了。

7. 使用负载均衡方案

咱们在以前讨论缓存的时候简要提到了内容分发网络(CDNs)。把负载分配到不一样的服务器(甚至于不一样的地理区域)能够给你的用户提供更好的延迟时间,可是这条路还很漫长,特别是在处理不少的并发链接的时候。

负载均衡就跟使用某个 round-robin(循环)解决方案同样简单,能够基于一个 nginx 反向代理 ,或者基于一个成熟的分布式网络,好比 Cloudflare 或者 Amazon CloudFront。

这里写图片描述

以上的图来自于 Citrix。 为了使负载均衡真正有效,动态内容和静态内容都应该被拆分红易于并发访问的。换句话说,元素的串形访问会削弱负载均衡器以最佳形式进行分流的能力。与此同时,对于资源的并发访问能够改善启动时间。
虽然负载均衡可能会很复杂。对最终一致性算法不友好的数据模型,或者缓存都会让事情更加困难。幸运的是,大多数应用对于已简化的数据集都只须要保证高层次的一致性便可。若是你的应用程序没有这样设计的话,就有必要重构一下了。

8. 为了更快的启动时间考虑一下同构 JavaScript

改善 Web 应用程序观感的方式之一,就是减小启动时间或者减小首页渲染时间。这对于新兴的单页面应用尤其重要,其须要在客户端执行大量任务。在客户端作更多事情一般就意味着,在第一次渲染被执行以前就须要下载更多的信息。同构 JavaScript 能够解决这个问题:自从 JavaScript 能够同时运行在客户端和服务器端,这就让在服务器端来执行页面的首次渲染成为可能,先把已渲染的页面发送出去而后再由客户端的脚本接管。这限制了所使用的后端(必须使用支持该特性的 JavaScript 框架),但却能得到更好的用户体验。举例来讲,React 就很适合于作这个,就像如下代码所示:

var React = require('react/addons');
var ReactApp = React.createFactory(require('http://www.22yigouyule.cn/../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 (http://www.yule8766.com/) {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
});
}

if (Meteor.isServer) {
Meteor.startup(function () {

可是,为了支持服务器端渲染,须要像 meteor-ssr 这样的插件 . 若是你有复杂的或者中等大小的应用须要支持同构部署,试试这个,你可能会感到惊讶的。

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 rel="stylesheet" type="text/css" media="only screen and (max-device-width: 480px)" href="mobile-device.css" />
1
1
轮到 JavaScript 了,关键就在于遵循某些用于内联 JavaScript 的规则(好比内联在 HTML 文件当中的代码)。内联 JavaScript 应该尽量短,并将其放在不会阻塞页面剩余部分解析的地方。换句话说,被放在 HTML 树中间的内联 JavaScript 将会在这个地方阻塞解析器,并强制其等待直到脚本被执行完毕。若是在 HTML 文件中随意放了一些大的代码块或者不少小的代码块,对于性能来讲这会成为性能杀手。内联能够有效减小额外对于某些特定脚本的网络请求。可是对于重复使用的脚本或者大的代码块来讲,这个好处就能够忽略不计了。

防止 JavaScript 阻塞解析器和渲染器的一种方法就是将 <script> 标签标记为异步的。这限制了咱们对于 DOM 的访问可是可让浏览器无论脚本的执行状态而继续解析和渲染页面。换句话说,为了得到最佳的启动时间,确保那些对于渲染不重要的脚本已经经过异步属性的方式标记成异步的了。

<script src="async.js" async></script>
1
1
12. 用于将来的一个建议:使用 service workers + 流

Jake Archibald 最近的一篇博文详细描述了一种有趣的技术,能够用于加速渲染时间:将 service workers 和流结合起来。结果很是使人叹服:

不幸的是这个技术所须要的 APIs 都还不稳定,这也是为何这是一种有趣的概念但如今尚未真正被应用的缘由。这个想法的主旨就是在网站和客户端之间放置一个 service worker。这个 service worker 能够在获取缺失信息的同时缓存某些数据(好比 header 和一些不会常常改变的东西)。缺失的内容就能够尽量快速地流向被渲染的页面。
https://www.youtube.com/watch?v=Cjo9i www.meiinylpt.com q8k-bc

13. 图片编码优化

图片编码优化。PNGs 和 JPGs 在 Web 发布时都会使用次优的设置进行编码。经过改变编码器和它的设置,对于须要大量图片的网站来讲能够得到有效的改善。流行的解决方案包括 OptiPNG 和jpegtran。

A guide to PNG optimization(http://optipng.sourceforge.net/pngtech/optipng.html) 详细描述了 OptiPNG 能够如何用于优化 PNGs。

The man page for jpegtran(http://linux.die.net/man/1/jpegtran) 对它的一些特性提供了很好的介绍。

若是你发现这些指南相对于你的要求来讲都太复杂了的话,这儿有一些在线网站能够提供优化服务。也有一些像 RIOT 同样的图形化界面,很是有助于批量操做和结果检查。

结论

因为应用程序变得愈来愈大和愈来愈复杂,性能优化对于 Web 开发来讲正在变得愈来愈重要。在作出任何值得的时间和潜在的将来成本的优化尝试时,有针对性的改进都是必不可少的。Web 应用程序早已突破了大多数静态内容的边界,学习常见模式进行优化则是使人愉悦的应用和彻底不可用的应用之间最大的区别(这是让你的访客留下来的长远之计!)。没有什么规则是绝对的,可是:性能分析和研究特定软件技术栈的错综复杂之处,是找出如何优化它的惟一方式。

相关文章
相关标签/搜索