分享一下 vue 项目中的数据操作

223 阅读3分钟

全文字数约 1000 字

为什么要写这篇文章

锻炼写作能力、总结能力。输出更好更优质的技术文章。


数据操作

对象

问题1

<div id="demo">
  <button v-on:click="update">
    {{obj.name}}  <!-- 'res'-->
  </button>
    <button v-on:click="add">
    {{obj}}  <!-- {name: 'res'}-->
  </button>
</div>
export default {
  data:{
    return {
      obj: {name: 'res'}
    }
  },
  methods:{
    update(){
         delete this.obj.name;// {}  页面无法响应更新
    },
    add(){
         this.obj.age = 33;// {name:'res',age:'33'} 页面无法响应更新
    } 
  }
}

为什么?

我们都知道 vue 的核心响应式采用了 object.defineProperty.通过属性函数getter 和 setter ,vue 在组件初始化中,通过递归对数据模版中现有的数据对象进行了深层次读写劫持,实现了数据和视图之间的双向交互。但也因此导致 vue 对数据的新增删除是无感的。

怎么解决?

  • 手动刷新 -- 不建议这么操作
this.obj.__ob__.dep.notify() //notify即是dep通知更新的函数
  • API 删除
this.$delete(this.obj, 'name');//$delete => 如果对象是响应式的,确保删除能触发更新视图
this.$set(this.obj, 'age', 2222);//$set => 将数据添加为响应式

所以

  1. 在项目中我们应该避免直接 delete obj key
  2. 保证 对象中常用的 key 在原始模版中被初始化,以免带来不必要的困惑

数组

问题2

<div id="demo">
    <button v-on:click="clear">
    {{arr}}
  </button>
  <button v-on:click="update">
    {{arr}}
  </button>
    <button v-on:click="add">
    {{arr}}
  </button>
</div>

export default {
  data:{
    return {
      arr: ['age', { name: '111' }]
    }
  },
  methods:{
    clear(){
        this.arr.length = 0;//页面无法响应更新?
    },
    update(){
      this.arr[1].name = 222;//页面无法响应更新?
      this.arr[0] = '111111';//页面响应更新?
    },
    add(){
      this.arr[1] = 'fdfsdf';//页面无法响应更新?
    },
  }
}

为什么?

上文我们知道 vue 使用了object.defineProperty 对数据对象进行了响应式处理,那么对于数组 vue 是怎么做的呢?让我们来看一段源码

// Array原型
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 会改变原数组的操作
const methodsToPatch = [
  'push',//末尾添加
  'pop',//末尾删除
  'shift',//首位删除
  'unshift',//首位添加
  'splice',//截取删除替换
  'sort',//排序
  'reverse'//翻转
]
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  //def => defineProperty方法的封装
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    //通知 dep 更新
    ob.dep.notify()
    return result
  })
})

是的,vue 总结了会改变数组的所有方法;对这些方法进行了重写;最终通过__proto__引入到数组的原型中。这也是为什么 我们在执行 clearupdate 第一步操作 的时候无法触发更新;那又是为什么 update 第二步操作会触发更新呢,让我们再看一段源码。

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; 
  constructor (value: any) {
    if (Array.isArray(value)) {
      // 数组的 响应处理
      this.observeArray(value)
    }
  }
  //遍历数组中所有的元素
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
//响应处理函数
function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  // __ob__ 代表被响应式处理过
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    //重点在这里 对数组中的value 进行深层的递归 响应处理
    ob = new Observer(value)
  }
  return ob
}

通过上面的代码我们可以得知,vue 对数组中元素的进行了深层响应处理。这也是为什么update 第二步中对数组对象的操作会触发更新。但是对于相对浅层的数据对象,递归的效率会很高,但是随着嵌套层级的递增,数组的响应初始化效率也在递减。

怎么解决?

  • 手动刷新(和上文对象的操作一样) -- 不建议这么操作
this.arr.__ob__.dep.notify() //notify即是dep通知更新的函数
  • API
this.$delete(this.arr, 0);//$delete => 如果对象是响应式的,确保删除能触发更新

所以

  1. 避免直接操作key改变数组的行为。尽量使用push pop等操作。
  2. 减少嵌套过深数组对象的使用。

小结

对象和数组是项目中最常见的两种数据类型,优化对两者的操作方法会在很大程度上影响程序的健壮性。vue 框架为我们提供了很多开箱即用的方法和api,合理的使用则会事半功倍。

另外您在阅览文章的时候发现问题,请您在评论区不吝赐教~🤝