一、代码优化vfor中使用key 使用vfor更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素; 使用key的注意事项:不要使用可能重复的或者可能变化key值(控制台也会给出提醒)如果数组中的数据有状态需要维持时(例如输入框),不要使用数组的index作为key值,因为如果在数组中插入或者移除一个元素时,其后面的元素index将会变化,这回让vue进行原地复用时错误的绑定状态。如果数组中没有唯一的key值可用,且数组更新时不是全量更新而是采用类似push,splice来插入或者移除数据时,可以考虑对其添加一个key字段,值为Symbol()即可保证唯一。 何时使用何种key? 这是一个非常有考究的问题,首先你要知道vue中的原地复用(大概就是虚拟dom变化时,两个虚拟dom节点的key如果一样就不会重新创建节点,而是修改原来的节点) 当我们渲染的数据不需要保持状态时,例如常见的单纯的表格分页渲染(不包含输入,只是展示)、下拉加载更多等场景,那么使用index作为key再好不过,因为进入下一页或者上一页时就会原地复用之前的节点,而不是重新创建,如果使用唯一的id作为key反而会重新创建dom,性能相对较低。 此外使用index作为key我还应该要尽量避免对数组的中间进行增加删除等会影响后面元素key变化的操作。这会让vue认为后面所有元素都发生了变化,导致多余的对比和原地复用。 所以使用index作为key需要满足:数据没有独立的状态数据不会进行增加删除等会影响后面元素key变化的操作 哪何时使用id作为key呢? 对于大多数数据的id都是唯一的,这无疑的一个key的优选答案。对于任何大多数情况使用id作为key都不会出现上面bug。但是如果你需要考虑性能问题,那就就要思考是否应该使用原地复用了。 同样是上面的分页数据展示,如果使用id作为key,可想而知每一页的每一条数据id都是不一样的,所以当换页时两颗虚拟DOM树的节点的key完全不一致,vue就会移除原来的节点然后创建新的节点。可想而知效率会更加低下。但是他也有它的优点。唯一的key可以帮助diff更加精确的为我们绑定状态,这尤其适合数据有独立的状态的场景,例如带输入框或者单选框的列表数据。 所以何时使用id作为key?只有一点:无法使用index作为key的时候vifvelseifvelse中使用key 可能很多人都会忽略这个点 原因:默认情况下,Vue会尽可能高效的更新DOM。这意味着其在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除然后在同一位置添加一个新元素。如果本不相同的元素被识别为相同,则会出现意料之外的副作用。 如果只有一个vif,没有velse或者vifelse的话,就没有必要加key了 相对于vfor的key,vifvelseifvelse中的key相对简单,我们可以直接写入固定的字符串或者数组即可transitionbuttonvifisEditingvon:clickisEditingfalseSavebuttonbuttonvelsevon:clickisEditingtrueEditbuttontransition。venteractive,。vleaveactive{transition:all1s;}。venter,。vleaveto{opacity:0;transform:translateY(30px);}。vleaveactive{position:} 例如对于上面的代码,你会发现虽然对button添加了过渡效果,但是如果不添加key切换时是无法触发过渡的vfor和vif不要一起使用(Vue2) 此优化技巧仅限于Vue2,Vue3中对vfor和vif的优先级做了调整 这个大家都知道 永远不要把vif和vfor同时用在同一个元素上。引至Vue2。x风格指南〔1〕 原因是vfor的优先级高于vif,所以当它们使用再同一个标签上是,每一个渲染都会先循环再进行条件判断 注意:Vue3中vif优先级高于vfor,所以当vfor和vif一起使用时效果类似于Vue2中把vif上提的效果 例如下面这段代码在Vue2中是不被推荐的,Vue也会给出对应的警告ullivforuserinusersvifuser。active{{user。name}}liul 我们应该尽量将vif移动到上级或者使用计算属性来处理数据ulvifactivelivforuserinusers{{user。name}}liul 如果你不想让循环的内容多出一个无需有的上级容器,那么你可以选择使用template来作为其父元素,template不会被浏览器渲染为DOM节点 如果我想要判断遍历对象里面每一项的内容来选择渲染的数据的话,可以使用computed来对遍历对象进行过滤jsletusersActivecomputed(()users。filter(useruser。active))templateullivforuserinusersActive{{user。name}}liul合理的选择vif和vshow vif和vshow的区别相比大家都非常熟悉了;vif通过直接操作DOM的删除和添加来控制元素的显示和隐藏;vshow是通过控制DOM的displayCSS熟悉来控制元素的显示和隐藏 由于对DOM的添加删除操作性能远远低于操作DOM的CSS属性 所以当元素需要频繁的显示隐藏变化时,我们使用vshow来提高性能。 当元素不需要频繁的显示隐藏变化时,我们通过vif来移除DOM可以节约掉浏览器渲染这个的一部分DOM需要的资源使用简单的计算属性应该把复杂计算属性分割为尽可能多的更简单的property。 易于测试当每个计算属性都包含一个非常简单且很少依赖的表达式时,撰写测试以确保其正确工作就会更加容易。 易于阅读简化计算属性要求你为每一个值都起一个描述性的名称,即便它不可复用。这使得其他开发者(以及未来的你)更容易专注在他们关心的代码上并搞清楚发生了什么。 更好的拥抱变化任何能够命名的值都可能用在视图上。举个例子,我们可能打算展示一个信息,告诉用户他们存了多少钱;也可能打算计算税费,但是可能会分开展现,而不是作为总价的一部分。小的、专注的计算属性减少了信息使用时的假设性限制,所以需求变更时也用不着那么多重构了。 引至Vue2风格指南〔2〕 computed大家后很熟悉,它会在其表达式中依赖的响应式数据发送变化时重新计算。如果我们在一个计算属性中书写了比较复杂的表达式,那么其依赖的响应式数据也任意变得更多。当其中任何一个依赖项变化时整个表达式都需要重新计算letpricecomputed((){letbasePricemanufactureCost(1profitMargin)return(basePricebasePrice(discountPercent0))}) 当manufactureCost、profitMargin、discountPercent中任何一个变化时都会重新计算整个price。 但是如果我们改成下面这样letbasePricecomputed(()manufactureCost(1profitMargin))letdiscountcomputed(()basePrice(discountPercent0))letfinalPricecomputed(()basePricediscount) 如果当discountPercent变化时,只会重新计算discount和finalPrice,由于computed的缓存特性,不会重新计算basePricefunctional函数式组件(Vue2) 注意,这仅仅在Vue2中被作为一种优化手段,在3。x中,有状态组件和函数式组件之间的性能差异已经大大减少,并且在大多数用例中是微不足道的。因此,在SFCs上使用functional的开发人员的迁移路径是删除该attribute,并将props的所有引用重命名为props,将attrs重命名为attrs。 优化前templatesectionvelseclassoffsectiontemplate 优化后templatefunctionalsectionvelseclassoffsectiontemplate没有this(没有实例)没有响应式数据拆分组件 什么?你写的一个vue文件有一千多行代码? 合理的拆分组件不仅仅可以优化性能,还能够让代码更清晰可读。单一功能原则嘛 源自slides。comakryumvuec〔3〕 优化前template{{heavy()}}template 优化后templateChildComptemplate 由于Vue的更新是组件粒度的,虽然每一帧都通过数据修改导致了父组件的重新渲染,但是ChildComp却不会重新渲染,因为它的内部也没有任何响应式数据的变化。所以优化后的组件不会在每次渲染都执行耗时任务使用局部变量 优化前template{{result}}template 优化后template{{result}}template这里主要是优化前后的组件的计算属性result的实现差异,优化前的组件多次在计算过程中访问this。base,而优化后的组件会在计算前先用局部变量base,缓存this。base,后面直接访问base。 那么为啥这个差异会造成性能上的差异呢,原因是你每次访问this。base的时候,由于this。base是一个响应式对象,所以会触发它的getter,进而会执行依赖收集相关逻辑代码。类似的逻辑执行多了,像示例这样,几百次循环更新几百个组件,每个组件触发computed重新计算,然后又多次执行依赖收集相关逻辑,性能自然就下降了。 从需求上来说,this。base执行一次依赖收集就够了,把它的getter求值结果返回给局部变量base,后续再次访问base的时候就不会触发getter,也不会走依赖收集的逻辑了,性能自然就得到了提升。 引至揭秘Vue。js九个性能优化技巧〔4〕使用KeepAlive 在一些渲染成本比较高的组件需要被经常切换时,可以使用keepalive来缓存这个组件 而在使用keepalive后,被keepalive包裹的组件在经过第一次渲染后,的vnode以及DOM都会被缓存起来,然后再下一次再次渲染该组件的时候,直接从缓存中拿到对应的vnode和DOM,然后渲染,并不需要再走一次组件初始化,render和patch等一系列流程,减少了script的执行时间,性能更好。 注意:滥用keepalive只会让你的应用变得更加卡顿,因为他会长期占用较大的内存事件的销毁 当一个组件被销毁时,我们应该清除组件中添加的全局事件和定时器等来防止内存泄漏 Vue3的HOOK可以让我们将事件的声明和销毁写在一起,更加可读functionscrollFun(){。。。}document。addEventListener(scroll,scrollFun)onBeforeUnmount((){document。removeEventListener(scroll,scrollFun)}) Vue2依然可以通过once来做到这样的效果,当然你也可以在optionsAPIbeforeDestroy中销毁事件,但是我更加推荐前者的写法,因为后者会让相同功能的代码更分散functionscrollFun(){。。。}document。addEventListener(scroll,scrollFun)this。once(hook:beforeDestroy,(){document。removeEventListener(scroll,scrollFun)})functionscrollFun(){。。。}exportdefault{created(){document。addEventListener(scroll,scrollFun)},beforeDestroy(){document。removeEventListener(scroll,scrollFun)}}图片加载 图片懒加载:适用于页面上有较多图片且并不是所有图片都在一屏中展示的情况,vuelazyload插件给我们提供了一个很方便的图片懒加载指令vlazy 但是并不是所有图片都适合使用懒加载,例如banner、相册等更加推荐使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。采用合理的数据处理算法 这个相对比较考验数据结构和算法的功底 例如一个将数组转化为多级结构的方法数组转树形结构,时间复杂度O(n)paramlist数组paramidKey元素id键paramparIdKey元素父id键paramparId第一级根节点的父id值return{〔〕}functionlistToTree(list,idKey,parIdKey,parId){letmap{};letresult〔〕;letlenlist。构建mapfor(leti0;i){将数组中数据转为键值对结构(这里的数组和obj会相互引用,这是算法实现的重点)map〔list〔i〕〔idKey〕〕list〔i〕;}构建树形数组for(leti0;i){letitemParIdlist〔i〕〔parIdKey〕;顶级节点if(itemParIdparId){result。push(list〔i〕);}孤儿节点,舍弃(不存在其父节点)if(!map〔itemParId〕){}将当前节点插入到父节点的children中(由于是引用数据类型,obj中对于节点变化,result中对应节点会跟着变化)if(map〔itemParId〕。children){map〔itemParId〕。children。push(list〔i〕);}else{map〔itemParId〕。children〔list〔i〕〕;}}}其他 除了上面说的方法以外还有很多优化技巧,只是我在项目并不是太常用冻结对象(避免不需要响应式的数据变成响应式)长列表渲染分批渲染长列表渲染动态渲染(vuevirtualscroller〔5〕)。。。二、首屏体积优化 我在项目中关于首屏优化主要有以下几个优化方向体积代码分割网络体积优化压缩打包代码:webpack和vite的生产环境打包默认就会压缩你的代码,这个一般不需要特殊处理,webpack也可以通过对应的压缩插件手动实现取消sourcemap:可以查看你的打包产物中是否有。map文件,如果有你可以将sourcemap的值设置为false或者空来关闭代码映射(这个占用的体积是真的大)打包启用gizp压缩:这个需要服务器也开启允许gizp传输,不然启用了也没啥用(webpack有对应的gzip压缩插件,不太版本的webpack压缩插件可能不同,建议先到官网查询)代码分割 代码分割的作用的将打包产物分割为一个一个的小产物,其依赖esModule。所以当你使用import()函数来导入一个文件或者依赖,那么这个文件或者依赖就会被单独打包为一个小产物。路由懒加载和异步组件都是使用这个原理。路由懒加载异步组件 对于UI库我一般不会使用按需加载组件,而是比较喜欢CDN引入的方式来优化。网络 CDN:首先就是上面的说的CDN引入把,开发阶段使用本地库,通过配置外部扩展(Externals)打包时来排除这些依赖。然后在html文件中通过CDN的方式来引入它们 ServerPush:HTTP2已经相对成熟了;经过上面的CDN引入,我们可以对网站使用HTTP2的ServerPush功能来让浏览器提前加载这些CDN和其他文件。 开启gzip:这个上面已经说过了,其原理就是当客户端和服务端都支持gzip传输时,服务端会优先发送经过gzip压缩过的文件,然后客户端接收到在进行解压。 开启缓存:一般我使用的是协商缓存,但是这并不适用于所有情况,例如对于使用了ServerPush的文件,就不能随意的修改其文件名。所以我一般还会将生产的主要文件固定文件名