vue3的数据劫持跟vue2的有什么不一样

vue2-Object.defineProperty

vue2的响应式对象的核心是利用了ES5Object.defineProperty 给对象的属性添加了gettersetter

Vue会把propsdata等变成响应式对象,在创建过程中,发现子属性也为对象时则递归把该对象变成响应式。

触发getter的时候,触发依赖收集,那么依赖收集做了哪些事情?

getter-依赖收集

vue源码

  Object.defineProperty(obj, key, {
    
    
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    
    
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
    
    
        dep.depend() //依赖收集
        if (childOb) {
    
    
          childOb.dep.depend()
          if (Array.isArray(value)) {
    
    
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
    
    
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
    
    
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
    
    
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
    
    
        setter.call(obj, newVal)
      } else {
    
    
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify() // 派发更新
    }
  })

访问数据时,触发getter,调用当前watcheraddDep(),收集依赖存在this.subs(收集当前正在计算的watcher,然后把这些watcher作为订阅者),当数据发生改变的时候,会执行setter,派发更新。

注意点-延伸:在有v-ifv-else中视图渲染中,如果当前显示的是v-if的数据,那么不管v-else的依赖的数据怎么样变化,都不会触发重现渲染,因为在cleanupDeps函数中会比较this.newDepIdsdep中的watcher,如果没有,则remove掉,是一个性能上的提升。

总结:依赖收集就是订阅数据变化的watcher的收集,目的是当这些响应式数据发生变化,触发它们的 setter的时候,能知道应该通知哪一些订阅者去做相应的逻辑处理。

setter-派发更新

触发setter的时候,派发更新,那么派发更新做了哪些事情?

遍历之前的watcher订阅者,调用订阅者的subs[i].update();然后会执行queueWacther()这个方法,在这里方法里面会将watcher push 到 queue里面,这个过程是同步执行的,当执行完push操作之后,接着执行flushSchedulerQueue()这个方法;首先按照从父到子的顺序排序watcher,然后遍历watcher,执行watcher.run()

官网解释

链接:https://cn.vuejs.org/v2/guide/reactivity.html

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

对象

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

解决办法

// 添加一个属性
this.$set(this.obj,'b',1) // Vue.set(vm.obj,'b',1)

// 添加多个属性
this.obj = Object.assign({
    
    },this.obj,{
    
    a:1,b:2})
数组

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

解决办法

// 解决第一个 Vue.set, Array.prototype.splice
Vue.set(vm.items,indexOfItem,newValue) 
vm.items.splice(indexOfItem,1,newValue)

// 解决第二个 Array.prototype.splice
vm.items.splice(newLength)

Vue.set对应的是Vue.deleteVue.delete( target, propertyName/index

缺点

  1. 不能监听数组下标及长度变化、实例对象新增或删减属性
  2. 需要使用递归、闭包,消耗性能和内存
  3. 代码较复杂,需要做数据备份

vue3–proxy

vue3的响应式对象的核心是利用了ES6proxy 给对象的属性添加了gettersetter

proxy是在访问对象之前设置一层拦截,对整个对象进行代理(可以是任何类型的对象,包括原生数组、函数,或者是另一个代理)

proxy源码

let obj = new Proxy(ob,{
    
    
   //target就是第一个参数ob 
    get: function(target,key,value){
    
    
        // let result = Reflect.get(target,key)
        // return result
        return target[key]
    },
    set: function(target,key,value){
    
    
        // let result = Reflect.set(target,key,value)
        // return result
        target[key] = value
    }
})

缺点

  1. 因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11

相比Object.defineProperty()的优点

  1. 可以监听数组变化或对象属性的增减
  2. 不需要一次性遍历data属性,减少性能消耗
  3. 代码较简单

Guess you like

Origin blog.csdn.net/weixin_42060560/article/details/113182757