乐趣区

关于vue.js:从函数式组件引发的性能思考

简介

vue函数式组件大部分人在开发过程中用到的不多,就连官网文档地位搁置的也比拟费解,然而在咱们对我的项目做性能优化时,却是一个不错的抉择。本文将对函数式组件初始化过程做一个系统性的论述,通过本文,你将理解到以下内容:

  • 什么是函数式组件
  • 函数式组件与一般组件间的差别
  • vue类似性能优化点

什么是函数式组件

函数式组件即 无状态组件 ,没有datacomputedwatch,也没有生命周期办法,组件中也没有this 上下文,只有 props 传参。在开发中,有很多组件仅仅只用到了 props 和插槽,这部分组件就能够提炼为函数式组件。借用官网demo,最简略的函数式组件如下:

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

函数式组件与一般组件间的差别

组件实例化过程大抵分为四步,状态初始化 –> 模板编译 –> 生成VNode –> 转换为实在DOM。接下来比照一般组件与函数式组件罕用配置项,比拟下差别。

性能点名称 一般组件 函数式组件 形容
vm Y N 组件作用域
hooks Y N 生命周期钩子
data Y N 数据对象申明
computed Y N 计算属性
watch Y N 侦听器
props Y Y 属性
children Y Y VNode 子节点的数组
slots Y Y 一个函数,返回了蕴含所有插槽的对象
scopedSlots Y Y 作用域插槽的对象
injections Y Y 依赖注入
listeners Y Y 事件监听
parent Y Y 对父组件的援用

从上表中能够看出,一般组件与函数式组件最大的差异在于函数式组件 没有独立作用域,没有响应式数据申明。没有独立作用域,会有以下长处:

  1. 没有组件实例化 (new vnode.componentOptions.Ctor(options)),函数式组件获取VNode 仅仅是一般函数调用

    • 无公共属性、办法拷贝
    • 无生命周期钩子调用
  2. 函数式组件间接挂载到父组件中,缩短首次渲染、diff更新门路

    • 函数式组件在父组件生成 VNode 时,函数式组件 render 办法会被调用,生成 VNode 挂载到父组件 children 中,patch阶段可间接转换成真是 DOM, 一般组件则在createElm 时,走组件初始化流程。
    • diff更新时,函数式组件调用 render,间接创立一般VNode,而一般组件创立的VNode 的是蕴含组件作用域的,diff操作时,还有额定调用 updateChildComponent 更新属性、自定义事件等,调用链路会比拟长。

vue 性能优化点

函数式组件带来的性能晋升次要体现在 缩短渲染门路 缩小组件嵌套层级,前者与浏览器重绘回流有殊途同归之处,后者能够升高工夫复杂度。

无论何种性能优化,能从代码层面做优化的,无疑是代价最小的,尽管有时成果不是很显著,然而千里之行; 始于足下。在 vue 中,有不少与上述类似的点,能够晋升代码执行效率。

正当申明 data 中数据,确保 data 中申明数据都是必须的

很多时候有一些数据没必要申明在 data 中,比方须要组件内共享,但不须要响应式解决的数据。data中的数据,对象都会对其深度优先用 Object.defineProperty 申明,数组也会拦挡基本操作办法。不必要的申明会造成无意义的数据劫持

正当应用 computedwatch

computedwatch 最大的区别在于 computed 是惰性加载的。惰性加载次要体现在两个方面:

  1. 依赖状态产生扭转时,不会立刻触发,只是扭转以后 Watcher 实例的 dirty 属性值为true
  2. 当对计算属性值取操作时,当且仅当 watcher.dirty === true 时,才会触发计算

以上两点个性,可能防止一些不必要的代码执行,具体代码如下所示:

// src\core\instance\state.js
function createComputedGetter (key) {return function computedGetter () {
    // 获取实例上的 computed 属性的 watcher 实例
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 当且仅当 computed 依赖属性发生变化 && 对计算属性进行取操作,才会调用 Watcher 的 update 办法,将 dirty 置为 true
      if (watcher.dirty) {
        // 调用 get 办法,获取到 computed 的值
        watcher.evaluate()}
      if (Dep.target) {watcher.depend()
      }
      return watcher.value
    }
  }
}
// src\core\observer\watcher.js
update () {
  // computed watcher lazy === true
  if (this.lazy) {
    // 标记懒执行是否可执行状态,false 不执行计算属性计算
    this.dirty = true
  } else if (this.sync) { // 同步执行
    this.run()} else {
    // 将以后 watcher 放入到 watcher 队列
    queueWatcher(this)
  }
}

v-for 绑定 key 值

v-for循环定义 key 值目标是便于精准找到 diff 比对节点,防止一些无意义的比对。

一般 diff: 从头尾开始,新旧节点头尾别离比拟,游标向两头聚拢,当且仅当一个节点遍历完结后,diff流程完结

带有 key 值 diff: 依据 key 值保护一个 hash 表,每次循环精准定位到更新指标节点,当且仅当一个节点遍历完结后,diff流程完结

思考

vue 中,很多性能优化点都是缩短代码执行门路,尤其在存在大量计算逻辑中,性能的晋升会有肉眼可见的成果。理论开发中,也有不少场景能够用到此类优化办法,举个最简略的例子,关键词高亮匹配。实现这个操作,须要以下几步:

  1. 获取匹配关键词,将关键词进行格式化(对正则表达式中有意义的字符串进行本义)
  2. 动静生成匹配的正则表达式
  3. 依据正则表达式进行 replace 操作

有些时候,第一,二步咱们能够省略,间接执行第三步即可,因为输出关键字可能存在雷同的,因而咱们能够将字符串与正则表达式缓存在 Map 中,下次匹配时,如果存在缓存,间接从缓存中拿即可。

vue模板编译用到的就是这个个性,每次会把编译的模板字符串作为 key 值,render办法作为value,缓存起来,如果遇到一样的模板,能够省去编译流程,带来肯定的性能晋升。

小结

养成良好的编码习惯,对于集体能力,也是一个不错的晋升。

退出移动版