惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

Oragekk's Blog

一个 waline 评论系统bug引发的思考 Jenkins 远程触发构建踩坑记 Git SSH 密钥配置 初识Rust vuepress-plugin-meting2 谷歌发布多平台应用开发神器Project IDX!PaLM 2加持 Vue2响应式原理解析 Flutter 工作原理 Dart 中的并发 如何利用GitHub Action提交URL到搜索引擎 提交URL到搜索引擎(百度、Bing、Google) GitHub Actions 使用介绍 素材设计 浏览器的事件循环 前端-Q&A 你不知道的 CSS 之包含块 CSS 属性计算过程 Vercel deploy忽略指定分支 评论插件 Waline 之邮件通知配置 公开API 终端究极美化iTerm2+Pure 使用Bing API提交网站URL Flutter 基础大集合 关于本站 关于我 Markdown 展示 使用n命令管理node版本 幻灯片页 ReactNative State(状态) ReactNative开发环境配置,ES6语法介绍 ReactNative介绍 更优雅强大的终端ZSH 神经网络模型训练 YYCache优秀的缓存设计 WebViewJavascriptBridge NSError WCDB漫谈 优雅的实现TableViewCell单选 初探机器学习框架CoreML 深入理解swift中闭包的捕捉语义 ijkPlayer 集成 iOS 配置https iOS timelineLogistics iOS Cookie的配置及使用 WKWebView拦截URL WKWebView使用及自适应高度 textfield限制输入字符 评论系统从多说迁移到disqus指南 利用Runtime进行快速归档 iOS 10.3 keychain 重大更新 Cell的accessoryType属性标记单元格之后,出现的重用问题 通过UserAgent判断设备 AFNetworking A memory leak 一人一句宋词 OC 中的枚举类型 iOS - Image compression algorithm iOS程序启动原理(下) iOS程序启动原理(上) TableView性能优化 NSOperatioin Runloop Test Three ways to call Update Cocoapods 1.1.1 减小iOS-App或者静态库体积 Jekyll旧站回忆 JavaScript ES6 CommonJS,RequireJS,SeaJS 归纳笔记 Unix/Linux 扫盲笔记
Vue常见优化手段
2023-05-17 · via Oragekk's Blog

相关信息

永远不要过早优化,优化也有相应的代价

  • 开发时间变长
  • 开发成本增加
  • 代码难以阅读
  • 增加维护成本

何时优化,因地制宜,是一门艺术,尽量把优化的思想带入写代码的过程中

本文章的优化手段基于vue2

服务端渲染 SSR or 预渲染

客户端渲染:使用 JavaScript 框架进行页面渲染
服务端渲染:服务端将HTML文本组装好,并返回给浏览器,这个HTML文本被浏览器解析之后,不需要经过 JavaScript 脚本的执行,即可直接构建出希望的 DOM 树并展示到页面中,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

优点:

更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。对客户端渲染的页面来说,简直无能为力,因为返回的HTML是一个空壳,它需要执行 JavaScript 脚本之后才会渲染真正的页面。
用户将会更快速地看到完整渲染的页面

缺点:

为了实现服务端渲染,应用代码中需要兼容服务端和客户端两种运行情况
由于服务器增加了渲染HTML的需求,使得原本只需要输出静态资源文件的nodejs服务,新增了数据获取的IO和渲染HTML的CPU占用,
服务器渲染应用程序,需要处于 Node.js server 运行环境。

如何实现?

想要在服务器端渲染,我们需要做什么呢?那就是同构我们的项目,Vue.js 是构建客户端应用程序的框架,服务器渲染的 Vue.js 应用程序也可以被认为是"同构"或"通用",因为应用程序的大部分代码都可以在服务器和客户端上运行

当运行在不同环境中时,我们的代码将不会完全相同,同构就是让一份代码,既可以在服务端中执行,也可以在客户端中执行,并且执行的效果都是一样的,都是完成这个html的组装,正确的显示页面。
对于同构应用来说,我们必须实现客户端与服务端的路由、模型组件、数据模型的共享。

服务器端渲染注意事项

为避免造成交叉请求状态污染,每个请求应该都是全新的、独立的应用程序实例。
由于没有动态更新,所有的生命周期钩子函数中,只有 beforeCreate 和 created 会在服务器端渲染(SSR)过程中被调用。
通用代码不可接受像 window 或 document,这种仅浏览器可用的全局变量
浏览器可能会更改的一些特殊的 HTML 结构,例如,浏览器会在

内部自动注入 ,然而,由于 Vue 生成的虚拟 DOM(virtual DOM) 不包含 ,所以会导致无法匹配。

使用key

对于通过循环生成的列表,应给每个列表项一个稳定且唯一的key,这有利于在列表变动时,尽量少的删除、新增、改动元素

使用冻结的对象

冻结的对象不会被响应化,如果对象很多,嵌套结构很深,遍历过程需要花费很多时间,如果对象不需要动态更改,可以使用冻结对象,如:商品列表等纯展示页面,并不会通过用户交互来更改

var obj = {a:1,b:2}
// 冻结对象
Object.freeze(obj)
// 尝试更改
obj.a = 3
console.log(obj)
// 打印
{a:1,b:2}
// 验证
Object.isFrozen(obj)
// 结果
<.true

vue在处理过程中,如果发现对象是冻结对象,就不会去遍历对象,不会变成响应式

下面是1000000个对象的加载过程

vue

冻结对象

可见vue把对象深度遍历成为响应式,对于大量结构复杂的数据来说,是很耗时间的

使用函数式组件

函数式组件,设置functional:true,函数式组件没有data,这以为它无状态(没有响应式数据

,也没用实例(没有this上下文),所以组件树中不存在函数式组件,一个函数式组件就像这样

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。

当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。

在 2.5.0 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明:

<template functional>
</template>

组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:

  • props:提供所有 prop 的对象
  • children:VNode 子节点的数组
  • slots:一个函数,返回了包含所有插槽的对象
  • scopedSlots:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
  • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
  • parent:对父组件的引用
  • listeners:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
  • injections:(2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的 property。

在添加 functional: true 之后,需要更新我们的锚点标题组件的渲染函数,为其增加 context 参数,并将 this.$slots.default 更新为 context.children,然后将 this.level 更新为 context.props.level

因为函数式组件只是函数,所以渲染开销(时间内存)也低很多。

在作为包装组件时它们也同样非常有用。比如,当你需要做这些时:

  • 程序化地在多个组件中选择一个来代为渲染;
  • 在将 childrenpropsdata 传递给子组件之前操作它们。

下面是一个 smart-list 组件的例子,它能根据传入 prop 的值来代为渲染更具体的组件:

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true,
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  render: function (createElement, context) {
    function appropriateListComponent () {
      var items = context.props.items

      if (items.length === 0)           return EmptyList
      if (typeof items[0] === 'object') return TableList
      if (context.props.isOrdered)      return OrderedList

      return UnorderedList
    }

    return createElement(
      appropriateListComponent(),
      context.data,
      context.children
    )
  }
})

使用计算属性

如果模版中某个数据会使用多次,并且该数据是通过计算得到的,使用计算属性以缓存它们

非实时绑定的表单项

当使用v-model绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致vue发生重新渲染(rerender),这会带来一些性能的开销。

我们可以通过使用lazy或不使用v-model的方式解决该问题,但要注意,这样可能会导致在某一个时间段内数据和表单项的值是不一致的。

vue设计思想是关注的是数据而不是界面,代码的可维护性和可阅读性也很重要,js执行线程和浏览器渲染线程是互斥的,所以运行动画时执行jS线程动画会卡顿

如双向绑定的文本框输入的内容改变,输入abcd,会进行4次重新渲染,可以使用v-model.lazy,监听@change,不使用监听的是@input

保持对象引用稳定

在绝大部分情况下,vue出发rerender的时机是其依赖的数据发生变化

若数据没有发生变化,哪怕给数据重新赋值了,vue也不会做出任何处理的

下面是vue判断数据没有变化的源码

function hasChanged(x,y) {
  if (x === y) {
    return x === 0 && 1 / x !== 1/y
  }else {
    return x === x || y === y
  }
}

因此,如果需要,只要能保证组件的依赖数据不发生变化,组件就不会重新渲染

对于原始数据类型,保持其值不变即可

对于对象类型,保持其引用不变即可

使用v-show替代v-if

对于频繁切换显示状态的元素,使用v-show可以保证虚拟的dom树的稳定,避免频繁的新增和删除元素,特别是对于那些内部包含大量dom元素的节点,这一点及其重要

使用延迟装载defer

首页白屏时间主要受到两个因素的影响:

  • 打包体积过大

    巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个<div>,没有可显示的内容

  • 需要立即渲染的内容太多

JS传输完成后,浏览器开始执行JS构造页面

但可能一开始要渲染的组件太多,不仅JS的事件很长,而且执行完后浏览器要渲染的元素过多,从而导致页面白屏loading过久

一个可行的办法就是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来

提示

延迟装载是一个思路,本质上就是利用requestAnimationFrame事件分批渲染内容,它的具体实现多种多样

告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

callback: 下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。

思路:浏览器渲染1s渲染60次,第一次渲染一部分,第二次一部分,隔开渲染,分批绘制

// defer.js
export default function(maxFrameCount) {
  return {
    data(){
      return{
        // 浏览器每重绘一次,计数
        frameCount:0,
      };
    },
    mounted() {
      const refreshFrameCount = () => {
        requestAnimationFrame(() => {
        	this.frameCount++;
          if(this.frameCount < maxFrameCount) {
            refreshFrameCount();
          }
        });
      };
      refreshFrameCount();
    },
    methods: {
      defer(showInFrameCount) {
        // 用于v-if 的判断条件,渲染次数大于showInFrameCount后继续下一次渲染
        return this.frameCount >= showInFrameCount
      },
    },
  };
}
<template>
  <div class="container">
    <!--vue3.0 v-if 优先级高 vue2.x v-for优先级高-->
    <div class="block" v-for="n in 20" v-if="defer(n)">
      <heavy-comp></heavy-comp>
    </div>
  </div>
</template>
<script>
import defer from "./mixin/defer";
export default {
  mixins: [defer(300)],
  components: { HeavyComp },
};
</script>

使用keep-alive

用于缓存内部组件实例,里面有include和exclude属性,max设置最大缓存数,超过后,自动删除最久没用的。

受到keep-alive影响,其内部的组件都具有两个生命周期,activateddeactivated ,分别再组件激活和失活时触发,第一次activated是在mounted之后。

一般用在需要多个页面频繁操作的场景(导航条)

长列表优化

一般用在app端下拉的时候,或者列表很长的时候,通过一个固定大小的渲染池来解决。通过滚动条等一些操作,减少页面渲染市场,有现成的库,vue-virtual-scroller

https://github.com/Akryum/vue-virtual-scroller

通过v-once创建低开销的静态组件,渲染一次后就缓存起来了,除非你非常留意渲染速度,不然最好不要用,因为有的开发者不知道这个属性或者看漏了,然后花费好几个小时来找为什么模板无法正确更新。

打包体积优化

  • Webpack 对图片进行压缩
  • 静态资源的优化使用对象存储加CDN
  • 减少 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue 项目的编译优化

基础优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈