当前位置: 首页 > 科技观察

Web应用性能优化解决方案总结

时间:2023-03-13 09:04:31 科技观察

在开发Web应用时,性能是一个必不可少的话题。大部分前端优化机制已经集成到前端打包工具webpack中。当然,仍然有一些有趣的机制可以帮助Web应用程序提高性能。这里我们会讲到如何优化web应用程序的一些机制,也会讲到这些机制背后的原理。ChromeCorverage分析代码覆盖率在讲解这些机制之前,先说一个Chrome工具Corverage。此工具可帮助查找在当前页面上使用或未使用的JavaScript和CSS代码。打开工具的过程是:打开浏览器控制台ctrl+shift+p打开命令窗口在命令窗口输入showCoverage并显示webpackjs选项卡如果要查询页面加载时使用的代码,请点击reloadbutton如果想查看与页面交互后使用的代码,请点击record按钮这里以淘宝为例,分别介绍点击reload和record后如何使用上面的两个解析。其中,从左到右分别是需要的资源URL资源中包含的js和css资源总大小,以及当前未使用的资源大小。左下角有总结。指示当前页面上加载的资源大小和未使用的百分比。可以看出,淘宝首页代码的未使用率仅为36%。引入这个特性的目的并不是要你重构你的代码库,让每个页面只包含需要的js和css。这是困难的,甚至是不可能的。但是这种指标可以提高我们对当前项目的了解,从而提高绩效。提高代码覆盖率的好处是所有性能优化机制中最高的,这意味着可以加载更少的代码,执行更少的代码,消耗更少的资源,缓存更少的资源。webpackexternals获取外部CDN资源一般来说,我们构建SPA单页项目基本都是使用Vue、React以及相应的组件库。但是在构建的时候,将这些框架代码直接打包到项目中并不是一个非常明智的选择。我们可以直接在项目的index.html中加入如下代码然后你可以像这样在webpack.config.js中配置module.exports={//...externals:{'vue':'Vue','vue-router':'VueRouter',}};webpackexternals的作用并不是Vue会在构建时打包到最终的项目中,而是这些外部依赖会在运行时被拾取。这对于项目初期没有实力自建,但又需要使用CDN服务的团队有很好的效果。原理这些项目在打包成第三方库的时候,也会以全局变量的形式导出。因此,可以直接在浏览器的window对象上获取和使用。就是window.Vue//?bn(t){this._init(t)},这就是为什么我们可以在html页面Vue中直接使用{{message}}

//挂载在window上,所以可以直接在页面上使用varapp=newVue({el:'#app',data:{message:'HelloVue!'}})。至此,我们就可以学习如何使用webpackAuthoringLibraries使用webpack开发第三方包了。优缺点Advantages对于这种既不能进行codesplitting也不能进行TreeShaking的依赖库,将这些需要的依赖库放在一个公共的CDN中是非常有好处的。Bugs对于像VueReact这样的库,CDN服务出现问题意味着项目根本无法使用。需要经常浏览所使用的CDN服务商的公告(比如不再提供服务的公告),在代码中加入类似的错误补偿方案。webpack动态导入提高代码覆盖率我们可以使用webpack动态导入,在需要使用代码的时候调用getComponent。在此之前,需要配置webpack。有关详细信息,请参阅webpack动态导入。配置完成后,我们就可以编写如下代码了。asyncfunctiongetComponent(){constelement=document.createElement('div');/**webpackChunkName,同名会打包成一个chunk*/const{default:_}=awaitimport(/*webpackChunkName:"lodash"*/'lodash');element.innerHTML=_.join(['Hello','webpack'],'');returnelement;}getComponent().then(component=>{document.body.appendChild(component);});优点和缺陷优点通过动态导入配置,可以处理多个chunk,在需要的时候加载执行。用户不会使用的资源(路由控制、权限控制)不会被加载,直接提高了代码的覆盖率。DefectTreeShaking可以理解为deadcodeelimination,即不构建和打包不需要的代码。但是当我们使用动态导入的时候,就不能使用TreeShaking优化了,因为这两者之间存在兼容性问题。因为webpack无法假设用户如何使用动态导入。BasicCodeXModuleAModuleB--------------------------------BusinessCodeABusinessCodeBBusinessCode...当业务中使用多个异步块时,业务代码A需要模块A,业务代码B需要模块B,但是webpack不能假设用户代码中的A和B两个模块在交互的时候同时排斥或互补。因此,不可避免地假设模块A和B可以同时加载。此时基本代码X有两个出口状态。这是不可能的!从这个角度看,dynamicimport和TreeShaking很难兼容。详情请参考文档whytreeshakingisnotperformedonasyncchunks。当然,使用动态导入也会在一定程度上降低性能。毕竟一个是本地函数调用,一个涉及到网络请求和编译。但这更像是一个决定而不是缺陷。哪个对自己的项目更有帮助?使用loadjs辅助加载第三方CDN资源。我们可以在普通的业务代码中使用动态导入。在如今的前端项目中,总有一些我们需要但使用率很低的库。比如只在统计模块中出现的ECharts数据图表库,或者只有在编辑文档或网页时才会出现的富文本编辑器库。对于这些苦涩的库,我们其实可以在页面或者组件挂载的时候使用loadjs来加载。因为使用动态导入这些第三方库并没有通过Treeshaking增强,效果差不多,但是loadjs可以拉取公共CDN资源。具体可以参考githubloadjs使用。由于该库比较简单,这里不做深入探讨。使用output.publicPath托管代码,因为将公共cdn与webpack外部或loadjs一起使用是一种权衡。如果公司可以花钱购买oss+cdn服务,可以直接托管打包好的资源。module.exports={//...output:{//每个块的前缀publicPath:'https://xx/',chunkFilename:'[id].chunk.js'}};//在此打包时间输出数据的前缀会变成,此时业务服务器只需要加载index.html即可。如果您不需要在浏览器的第一个屏幕中使用脚本,请在空闲时使用预取来加载资产。您可以使用浏览器的新预取来延迟获取脚本。下面这段代码告诉浏览器,以后某个导航或者功能会用到echarts,但是资源的下载顺序有比较低的权重。也就是说,prefetch通常是用来加速下一次导航的。标记为预取的资源将在空闲时由浏览器加载。该功能同样适用于html和css资源预先请求。使用instant.page提前加载资源instant.page是一个比较新的小而美的特征库。并且非侵入性。只需在项目的之前添加以下代码,您就会得到好处。此方案不适用于单页应用程序,但是这个库很好地使用了预取。当鼠标悬停在链接上超过65ms时,已放置的头部的最后一个链接将更改为悬停链接的href。以下代码为主要代码//加载prefetcherconstprefetcher=document.createElement('link')//检查是否prefetcherconstisSupported=prefetcher.relList&&prefetcher.relList.supports&&prefetcher.relList.supports('prefetch')//悬停时间65msletdelayOnHover=65//读取脚本上设置的instantIntensity,如果悬停时间有修改constmilliseconds=parseInt(document.body.dataset.instantIntensity)if(!isNaN(milliseconds)){delayOnHover=milliseconds}//支持prefetch,不支持打开数据保护模式if(isSupported&&!isDataSaverEnabled){prefetcher.rel='prefetch'document.head.appendChild(prefetcher)...//鼠标悬停在instantIntensitms||65ms上将href更改为预取htmlmouseoverTimer=setTimeout(()=>{preload(linkElement.href)mouseoverTimer=undefined},delayOnHover)...functionpreload(url){prefetcher.href=url}延迟预取?或者在鼠标停留时加载。不得不说,该库利用了许多新的浏览器机制。包括使用type=module拒绝旧的浏览器执行,使用dataset读取instantIntensity来控制延迟时间。optimize-jsskipsv8pre-Parse优化代码性能认识到这个库在v8关于新版本的文章中,在github中标记为UNMAINTAINED,不再维护,但是了解和学习这个库还是有它的价值和意义的.该库的使用非常简单粗暴。其实只是把函数改成了IIFE(立即执行的函数表达式)。用法如下:optimize-jsinput.js>output.jsExampleinput:!function(){}()functionrunIt(fun){fun()}runIt(function(){})Exampleoutput:!(function(){})()functionrunIt(fun){fun()}runIt((function(){}))的原理在v8引擎内部(不仅仅是V8,这里以v8为例),而Parse位于每个编译器前面都分为ForPre-Parse和Full-Parse,Pre-Parse会检查整个Js代码。通过检查,可以直接判断有语法错误,直接中断后面的解析。在此阶段,Parse不会生成源代码的AST结构。//Thisisthetop-levelscope.functionouter(){//这里preparsed会预解析functioninner(){//这里preparsed会预解析但不会全解析编译}}outer();//全解析编译`outer`,但不是`内部`。但如果使用IIFE,v8引擎不会直接进行Pre-Parsing操作,而是立即完整解析并编译函数。可以参考Blazinglyfastparsing,part2:lazyparsingadvantagesanddisadvantagesAdvantagesarefast!即使在较新的v8引擎上,我们也可以看到optimize-js仍然是最快的。且不说国内浏览器的版本比现在的v8.0版本小很多。与后端节点不同,前端页面的生命周期很短,执行得越快越好。缺陷但同样的,任何技术都不是灵丹妙药,直接全量解析编译也会造成内存压力,js引擎不推荐使用该库。相信在不久的将来,这个图书馆的收入会逐渐减少,但是对于一些特殊的需求,这个图书馆确实会有所帮助。再谈代码覆盖率这里我们又要谈代码覆盖率了。如果我们能够在第一屏就开始录制,我们就可以实现非常高的代码覆盖率。直接执行是更好的方法。项目中的代码覆盖率越高,绕过Pre-Parsing并让代码尽快执行的好处就越大。Polyfill.io根据不同的浏览器建立不同的polyfill。如果你写过前端,不可能不知道polyfills。不同的浏览器版本需要不同的polyfill。Polyfill.io是一项服务,通过选择性地填充浏览器所需的内容来减少Web开发的烦恼。Polyfill.io读取每个请求的User-Agent标头并返回适合请求浏览器的polyfill。如果最新的浏览器带有Array.prototype.filterhttps://polyfill.io/v3/polyfill.min.js?features=Array.prototype.filter/*Disableminification(remove`.min`fromURLpath)fororeinfo*/如果没有,相关的polyfill将添加到文本下方。国内阿里巴巴也建了服务,可以考虑。URL为https://polyfill.alicdn.com/polyfill.min.jstype='module'协助打包部署es2015+代码。使用新的DOMAPI,它可以有条件地加载polyfill,因为它可以在运行时检测到。然而,使用新的JavaScript语法,这可能会很棘手,因为任何未知的语法都会导致解析错误,然后所有代码都不会运行。这个问题的解决方案是。早在2017年,我就知道type=module可以直接在浏览器中原生支持模块功能。详情请参考JavaScriptmodules模块。但是当时觉得这个功能很强大,并没有对这个功能进行解读。但是没想到会用这个功能来识别你的浏览器是否支持ES2015。每个支持type="module"的浏览器都支持你熟悉的大部分ES2015+语法!!!!例如,asyncawait函数原生支持箭头函数,原生支持PromisesMapSet等。您可以进行优雅降级。支持type=module时提供属于的js,不支持时提供另一个js。详情请参考PhillipWalton的精彩博文,这里也有翻译版https://jdc.jd.com/archives/4911。VueCLI现代模式如果当前项目已经开始从webpack阵营转为VueCLI阵营,恭喜,以上方案已经内置到VueCLI中。只需使用以下指令,项目将生成两个版本的包。vue-cli-servicebuild--modern的详细介绍可以参考VueCLImodern模式的优缺点提高代码覆盖率,直接使用nativeawait等语法直接减少很多代码。提高代码性能。Crankshaft编译器之前在v8中使用过。随着时间的推移,编译器因为无法优化现代语言特性而被废弃。之后,v8引入了新的Turbofan编译器来支持和优化新的语言特性。之前在社区中讨论的trycatch、await、jsonregularization的性能有了很大的提升。详情可以不定时浏览v8博客查看功能优化。编写ES2015代码是开发人员的胜利,部署ES2015代码是用户的胜利。没有缺点,实在想不出有什么不对的地方。鼓励如果您觉得这篇文章不错,希望您能给我一些鼓励,帮我在我的github博客下star。