时代变了,Web应用程序比以往任何时候都更具交互性。获得正确的性能可以帮助您显着改善最终用户体验。继续阅读下面的提示并应用您学到的知识,看看您可以使用哪些提示来改善延迟、渲染时间和整体性能!更快的Web应用程序优化Web应用程序是一项艰巨的工作。Web应用不仅存在于客户端和服务器这两个组件中,通常还由多种技术栈构建:数据库、后端组件(通常构建在不同的技术架构上)、前端(HTML+JavaScript+CSS+转译器)。运行时也多种多样:iOS、Android、Chrome、Firefox、Edge。如果您曾经在不同的整体平台上工作过,通常性能优化只针对单个目标(甚至只是目标的单个版本),但现在您可能意识到任务的复杂性远远超出这里。现在是对的。但这里有一些通用的优化指南,可以极大地优化应用程序。我们将在下一章探讨这些指南的内容。Bing的一项研究表明,页面加载时间每增加10毫秒,网站的年收入就会损失250,000美元。-MicrosoftPrematureOptimization高级项目经理RobTrace和DavidWalp?优化最困难的部分是如何在开发生命周期中最合适的时间进行优化。DonaldKnuth有句名言:“过早的优化是万恶之源”。这个说法背后的原因很简单:因为如果你不小心,你会浪费时间优化某个1%,但结果不会对性能产生重大影响。同时,一些优化阻碍了可读性或可维护性,甚至引入了新的错误。换句话说,优化不应被认为是“意味着获得应用程序的最佳性能”,而是“探索优化应用程序并获得最佳收益的正确方法”。换句话说,盲目优化可能会导致效率损失而收效甚微。在应用以下技术时请记住这一点。你最好的朋友是分析工具:找到你可以优化的性能点,在不影响应用程序开发或可维护性的情况下获得最大的改进。程序员浪费大量时间思考或担心他们程序的非关键部分运行得有多快。而他们在性能上的尝试其实对代码的调试和维护有着非常负面的影响。我们应该忘记在97%的时间里可以说的不重要的性能命中:过早的优化是万恶之源。当然,我们不应该放弃那关键的3%的机会。—唐纳德·克努特1.JavaScript压缩和模块打包JavaScript应用程序分布在源代码中,源代码解析效率不如字节码。对于一小段脚本,差异可以忽略不计。但对于较大的应用程序,脚本的大小会对应用程序启动时间产生负面影响。事实上,您可以期望使用WebAssembly带来的最大改进之一是更快的启动时间。另一方面,模块捆绑用于将不同的脚本打包在一起并放入同一个文件中。更少的HTTP请求和单个文件解析都减少了加载时间。通常,一个工具就可以处理打包和压缩。Webpack就是其中之一。示例代码:functioninsert(i){document.write("Sample"+i);}for(vari=0;i<30;++i){insert(i);}结果如下:!function(r){functiont(o){if(e[o])return[o].exports;varn=e[o]={exports:{},id:o,loaded:!1};returnr[o].调用(n.exports,n,n.exports,t),n.loaded=!0,n.exports}vare={};returnt.m=r,t.c=e,t.p="",t(0)}([function(r,t){functione(r){document.write("Sample"+r)}for(varo=0;30>o;++o)e(o)}]);//#sourceMappingURL=bundle.min.js.map进一步打包你也可以使用Webpack来打包CSS文件和合并图片。这两个功能都有助于缩短启动时间。研究Webpack文档来做一些测试!2.按需加载资源按需或延迟加载资源(尤其是图像)可以帮助您的Web应用程序获得更好的整体性能。延迟加载对图片较多的页面有三个明显的好处:减少对服务器的并发请求数量(这会导致页面其他部分的加载时间更快)减少浏览器内存使用(更少的图像,更少的内存)以减少服务器-sideload一般的思路是只在需要的时候才加载图片或者资源(比如视频),比如第一次展示的时候,或者即将展示的时候加载它。由于这种方法与您构建网站的方式密切相关,因此延迟加载解决方案通常需要借助插件或其他库的扩展来实现。例如,react-lazy-load是一个用于处理React延迟加载图像的插件:constMyComponent=()=>(
Scrolltoloadimages.
(...)一个很好的实践例子就像在GoggleImages上搜索工具是同理,点击上一个链接,滚动页面查看效果。3、使用DOM操作库时使用array-ids,如果你使用的是React、Ember、Angular等DOM操作库,使用array-idsids(或Angular1.x中的track-by特性)对高性能非常有帮助,尤其是对于动态网页。我们在之前关于程序指标的文章中看到了这个特性的效果:更多基准:VirtualDOMvsAngular1&2vsMithril.jsvscito.jsvs其余(更新和改进!)。此功能背后的主要概念是尽可能多地重用现有节点。数组id使DOM操作引擎“知道”何时可以将节点映射到数组中的元素。如果没有array-ids或track-by,大多数库将进行重新排序,破坏现有节点并重新创建新节点。这是非常损失性能的。4.缓存缓存是用来存储被频繁访问的静态数据的组件,以便后续对这些数据的请求响应更快,或者说请求方式更高效。由于Web应用程序由许多可分离的部分组成,因此架构的许多部分都可以存在缓存。例如,可以在动态内容服务器和客户端之间放置缓存,避免常见请求并减少服务器负载,同时缩短响应时间。其他缓存可能放置在代码中以优化脚本访问的某些常见模式,而其他缓存可能放置在数据库或长时间运行的进程之前。简而言之,在Web应用程序中使用缓存是提高响应时间和降低CPU使用率的好方法。困难的部分是弄清楚将缓存放在架构中的什么位置。同样,答案是分析:常见的瓶颈在哪里?数据或结果是否可缓存?他们都太容易失败了吗?这是一些需要从原理上一点一点回答的难题。缓存在Web环境中被创造性地使用。例如,basket.js是一个使用LocalStorage来缓存应用程序脚本的库。因此,您的网络应用程序在您第二次运行脚本时几乎立即加载。当今流行的缓存服务是Amazon的CloudFront。CloudFront可以设置为动态内容的缓存,就像普通的内容分发网络(CDN)一样。5、启用HTTP/2越来越多的浏览器开始支持HTTP/2。这听起来可能没有必要,但HTTP/2为同一台服务器的并发连接问题带来了很多好处。换句话说,如果有很多小资源要加载(如果你hack就没有必要),HTTP/2在延迟和性能方面会杀死HTTP/1。试试Akamai的HTTP/2演示,看看现代浏览器的不同之处。6.应用程序性能分析性能分析是优化任何应用程序的重要步骤。正如引言中提到的,盲目尝试优化应用程序通常会导致效率浪费、收益微不足道和可维护性差。执行性能分析是确定应用程序问题所在的重要步骤。延迟是对Web应用程序最大的抱怨之一,因此您需要确保尽可能快地加载和显示数据。Chrome有很棒的性能分析工具。特别是ChromeDevTools中的时间线和网络视图都非常有助于定位延迟问题:时间线视图可以帮助找到长时间运行的操作。网络视图可以帮助识别由缓慢请求或对端点的串行访问引起的额外延迟。如果分析得当,内存是另一个潜在收益领域。如果您正在运行一个包含大量虚拟元素(巨大的动态表)或交互元素(例如游戏)的页面,内存优化可以获得更少的卡顿和更高的帧率。从我们最近的文章4TypesofMemoryLeaksinJavaScriptandHowtoGetRidOfThem中,对如何使用Chrome的开发者工具有了更深入的了解。CPU分析也可在Chrome开发工具中使用。从Google的官方文档中查看这篇文章ProfilingJavaScriptPerformance。找到性能损失的中心可以让您有效地达到优化目标。后端的性能分析会比较困难。通常,识别出耗时较长的请求会让您清楚地知道首先分析哪个服务。对于后端分析工具,则取决于构建的技术栈。关于算法的注意事项在大多数情况下,选择一个更优的算法比围绕小成本中心实施特定的优化策略可以产生更大的收益。在某种程度上,CPU和内存分析应该可以帮助您找到较大的性能瓶颈。当这些瓶颈与编码问题无关时,就该考虑不同的算法了。7.使用负载平衡解决方案前面讨论缓存时,我们简要提到了内容分发网络(CDN)。将负载分配到不同的服务器(甚至不同的地理区域)可以为您的用户提供更好的延迟,但这还有很长的路要走,尤其是在处理许多并发连接时。负载平衡就像使用一些循环解决方案一样简单,可以基于nginx反向代理,也可以基于成熟的分布式网络,如Cloudflare或AmazonCloudFront。上图来自Citrix。为了使负载平衡真正有效,动态和静态内容都应该被拆分以便于并发访问。换句话说,对元素的序列化访问会削弱负载均衡器以有效方式分配流量的能力。同时,并发访问资源可以缩短启动时间。尽管负载平衡可能很复杂。对最终一致性算法或缓存不友好的数据模型会使事情变得更加困难。幸运的是,大多数应用程序只需要减少数据集的高度一致性。如果你的应用程序不是这样设计的,那么重构它是很有必要的。8.考虑使用同构JavaScript来缩短启动时间同构JavaScript改进Web应用程序外观的方法之一是减少启动时间或减少呈现第一个页面所需的时间。这对于需要在客户端执行大量任务的新兴单页应用程序尤其重要。在客户端做更多的事情通常意味着在执行第一次渲染之前需要下载更多的信息。同构JavaScript可以解决这个问题:由于JavaScript可以同时运行在客户端和服务端,所以可以在服务端进行页面的完整渲染,先发送渲染后的页面,再发送给客户端。脚本接管。这限制了使用的后端(必须使用支持此功能的JavaScript框架),但会带来更好的用户体验。例如,React对此非常有用,如以下代码所示:varReact=require('react/addons');varReactApp=React.createFactory(require('../components/ReactApp').ReactApp);module。exports=function(app){app.get('/',function(req,res){//React.renderToString获取你的组件//并生成标记varreactHtml=React.renderToString(ReactApp({}));//Outputhtmlrenderedbyreact//console.log(myAppHtml);res.render('index.ejs',{reactOutput:reactHtml});});};Meteor.js非常支持混合客户端和服务器端JavaScript。if(Meteor.isClient){Template.hello.greeting=function(){return"Welcometomyapp.";};Template.hello.events({'clickinput':function(){//templatedata,ifany,isavailablein'this'if(typeofconsole!=='undefined')console.log("Youpressedthebutton");}});}if(Meteor.isServer){Meteor.startup(function(){//codetorunonserveratstartup});}然而,为了支持服务器端渲染需要像meteor-ssr这样的插件。感谢gabrielpoca在评论中指出这一点。如果您有需要支持同构部署的复杂或中型应用程序,试试这个,您可能会感到惊讶。9.使用索引来加速数据库查询如果你需要解决数据库查询花费大量时间的问题(分析你的应用程序看看是否是这种情况!),是时候找出如何加速数据库了。每个数据库和数据模型都有自己的权衡。数据库优化的每个方面都是一个主题:数据模型、数据库类型、具体实现等等。加速可能不是那么简单。但这里有一个可能对某些数据库有帮助的建议:索引。索引是数据库创建快速访问数据结构的过程,这些数据结构在内部映射到键(关系数据库中的列),从而提高相关数据的检索速度。大多数现代数据库都支持索引。索引不是文档数据库(如MongoDB)独有的,关系型数据库(如PostgreSQL)也是如此。为了使用索引来优化查询,您需要研究应用程序的访问模式:最常见的查询是什么,对哪些键或列执行搜索等。10.使用更快的翻译解决方案JavaScript软件堆栈一如既往的复杂。改进语言本身的需要增加了复杂性。不幸的是,作为目标平台的JavaScript受到用户运行时间的限制。虽然以ECMAScript2015的形式实现了很多改进(2016正在进行中),但客户端代码一般不可能依赖这个版本。这种趋势导致了一系列转译器:处理ECMAScript2015代码和仅使用ECMAScript5结构实现缺失功能的工具。同时,模块绑定和缩小过程也被集成到这个生产过程中,称为为发布构建的代码版本。这些工具可以转换代码并以有限的方式影响最终代码的性能。Google开发人员PaulIrish花了一些时间来了解这些转译方案如何影响性能和最终代码大小。虽然大部分收益都很小,但在投入使用工具堆栈之前查看这些数字是值得的。对于大型应用程序,这种区别可能很重要。11.避免或尽量减少使用JavaScript和CSS来阻塞渲染JavaScript和CSS资源会阻塞页面的渲染。通过采用某些规则,您可以确保尽快处理您的脚本和CSS,以便浏览器可以显示您的网站内容。在CSS的情况下,这是非常重要的,没有任何CSS规则可以直接与特定媒体相关,这些规则仅用于处理您要在页面上显示的内容的优先级。这可以通过使用CSS媒体查询来实现。媒体查询告诉浏览器将哪些CSS样式表应用于特定的显示媒体。例如,某些打印规则的优先级低于屏幕显示的优先级。媒体查询可以设置为标签属性:轮到JavaScript了,诀窍是遵循内联JavaScript的某些规则(就像HTML文件中内联的代码)。内联JavaScript应尽可能短,并放置在不会阻止页面其余部分解析的位置。换句话说,位于HTML树中间的内联JavaScript会在此时阻塞解析器并强制它等待脚本执行完毕。如果在HTML文件中随便放几个大代码块或者很多小代码块,对于性能来说都是性能杀手。内联可以有效减少某些特定脚本的额外网络请求。但对于可重用的脚本或大型代码块,这种好处可以忽略不计。防止JavaScript阻塞解析器和渲染器的一种方法是将12.未来的一个建议:使用serviceworkers+streamsJakeArchibald最近的一篇博文详细介绍了一种可用于加快渲染时间的有趣技术:合并服务工人和溪流。结果令人印象深刻:不幸的是,这项技术所需的API还不稳定,这就是为什么它是一个有趣的概念,但尚未真正实现。这个想法的要点是在网站和客户端之间放置一个服务工作者。这个serviceworker可以在获取缺失信息的同时缓存一些数据(例如标头和不经常更改的内容)。丢失的内容可以尽快流向呈现的页面。13.更新:图像编码优化我们的一位读者指出了一个非常重要的遗漏:图像编码优化。PNG和JPG都使用网络发布的次优设置进行编码。通过更改编码器及其设置,可以显着改善图像密集型站点。流行的解决方案包括OptiPNG和jpegtran。PNG优化指南(http://optipng.sourceforge.net/pngtech/optipng.html)详细描述了如何使用OptiPNG来优化PNG。jpegtran的手册页(http://linux.die.net/man/1/jpegtran)很好地介绍了它的一些功能。如果您发现这些指南对于您的要求来说过于复杂,这里有一些提供优化服务的在线站点。还有一些像RIOT这样的图形化界面,对批量操作和结果检查很有帮助。耳语:Auth0中的常见优化我们是一家网络公司。在这种能力下,我们已经为基础设施的某些部分部署了一些特定的优化。比如在登录页面你可以看到在我们域的/learn路径下(比如登录页面的单点登录),我们采用了特殊的优化:为了方便每篇文章的创作我们使用CMS。因为文章没有中央索引,但是为了被搜索引擎找到,所以使用webtask爬虫来预渲染每个页面并生成静态版本并上传到我们的CDN。这减少了我们的服务器端负载,因为不必为每个访问者生成动态服务器端内容。它还同时改善了延迟(并隔离了我们发现的与CMS相关的安全问题)。对于文档部分,我们使用同构JavaScript,这为我们提供了极好的启动时间,并允许我们的后端和前端团队之间轻松集成。结束语随着应用程序变得越来越大和越来越复杂,性能优化对于Web开发变得越来越重要。在进行任何值得花费时间和潜在的未来成本的优化尝试时,有针对性的改进是必不可少的。Web应用程序早已突破了大多数静态内容的界限,学习常见的优化模式可能是一个令人愉悦的应用程序和一个完全无法使用的应用程序之间的最大区别(从长远来看,这是让您的访问者留住的原因。计数!)。没有绝对的规则,但是:分析和研究特定软件堆栈的复杂性是找出如何优化它的唯一方法。