vue2的响应式数据变化

一、Vue2是如何实现数据响应式的

Vue实现响应式变化的方式是通过数据劫持和发布-订阅模式。

  1. 数据劫持:Vue通过使用Object.defineProperty()方法对数据对象的属性进行劫持,使其能够在属性值发生变化时触发相应的操作。

  2. 发布-订阅模式:Vue使用发布-订阅模式来监听数据变化,并在变化时通知相关的订阅者更新视图。当数据发生变化时,会触发相应的setter方法,然后通知所有订阅者进行更新。

具体实现步骤如下:

  1. 在初始化Vue实例时,通过遍历数据对象,利用Object.defineProperty()方法将每个属性转化为gettersetter

  2. getter方法中,将订阅者Watcher对象添加到当前属性的依赖列表中。

  3. setter方法中,当数据发生改变时,会触发该属性的所有订阅者的更新操作,即将Watcher对象的update方法加入到异步更新队列中。

  4. 当所有同步任务执行完毕后,异步更新队列会依次执行各个Watcher对象的update方法,更新视图。

通过数据劫持和发布-订阅模式的结合,Vue能够在数据变化时及时更新视图,实现了响应式变化。

export function defineReactive ( // 定义响应式数据

  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean

) {
    
    
  const dep = new Dep()
  // 如果不可以配置直接return

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    
    
    return

 }
  // cater for pre-defined getter/setters

  const getter = property && property.get

  const setter = property && property.set

  if ((!getter || setter) && arguments.length === 2) {
    
    
    val = obj[key]
 }
  // 对数据进行观测

  let childOb = !shallow && observe(val)
  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()  // {a:1} => {} 外层对象

          if (Array.isArray(value)) {
    
     // 如果是数组 {arr:[[],[]]} 
vm.arr取值只会让arr属性和外层数组进行收集   

            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()
   }
 })
}

二、vue2中如何检测数组的变化

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择重写
数组( push,shift,pop,splice,unshift,sort,reverse )方法。
数组中如果是对象数据类型也会进行递归劫持

数组的索引和长度变化是无法监控到的

检测数组的变化是通过重写数组原型上的相关方法来实现的。具体步骤如下:

  1. 首先,Vue会判断当前浏览器是否支持原生的数组方法。如果支持,则直接重写数组原型上的方法,并在重写的方法中添加对应的变异方法。如果不支持,则创建一个新的对象,使用Object.defineProperty来拦截数组变异方法。

  2. 在重写数组方法时,Vue会先缓存原生的数组方法,比如Array.prototype.pushArray.prototype.pop等。然后,在重写的方法中,先调用缓存的原生方法,再根据不同的变异方法类型执行不同的操作,比如添加响应式元素、触发依赖更新等。

  3. Vue还会判断是否支持__proto__属性,如果支持,则直接将数组的原型指向重写的原型对象,以便数组的原型链能够正常查找到重写的方法。如果不支持,则遍历重写的原型对象,将其中的方法拷贝到数组的实例上。

示例代码如下:

// 是否支持原生数组方法
const hasProto = '__proto__' in {
    
    };
// 缓存原生数组方法
const arrayProto = Array.prototype;
// 创建重写的原型对象
const arrayMethods = Object.create(arrayProto);

// 定义重写的原型方法
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
    
    
  // 缓存原生数组方法
  const original = arrayProto[method];
  // 重写原型方法
  def(arrayMethods, method, function mutator(...args) {
    
    
    // 调用原生数组方法
    const result = original.apply(this, args);
    // 获取当前数组的__ob__属性,即Observer实例
    const ob = this.__ob__;
    // 数组变异操作的类型
    let inserted;
    switch (method) {
    
    
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    // 如果有新增的元素,将其转换为响应式对象
    if (inserted) ob.observeArray(inserted);
    // 触发依赖更新
    ob.dep.notify();
    return result;
  });
});

// 将重写的原型对象设置到数组的原型上
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
if (hasProto) {
    
    
  protoAugment(target, arrayMethods);
} else {
    
    
  copyAugment(target, arrayMethods, arrayKeys);
}

通过以上代码,Vue实现了对数组变化的检测,并能够自动追踪数组的操作,以实现响应式更新。

猜你喜欢

转载自blog.csdn.net/jieyucx/article/details/134534625