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实现了对数组变化的检测,并能够自动追踪数组的操作,以实现响应式更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieyucx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值