Vue的响应式原理-数组处理

22 篇文章 0 订阅
9 篇文章 0 订阅

需要您明白大致的Vue响应式原理相关知识ObserverDepWatcher

开始

官方文档

Vue 不能检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength

导致上边的原因是因为对象通过Object.defineproperty代理,但是数组无法通过这种方式代理,Vue采用的方法是代理数组的方法(push,pop,shift,unshift,splice,sort,reverse)来达到响应式的目的

代码

下面将整体流程按函数区分来说下

和对象代理部分的区别

数组和对象处理的区别是在class Observer{}中开始区分的,对象的话是调用了walk方法进行处理,数组的话是首先执行protoAugment/copyAugment方法来“劫持”数组七种方法,然后调用observeArray(value)将数组中的属性循环执行observe(item)

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    // 这里的dep:
    // 创建一个多数用于数组使用的dep
    // 详情可以看作者的其他文章
    this.dep = new Dep()
    this.vmCount = 0
    // 为value添加"__ob__"属性,值为this
    def(value, '__ob__', this)
    // -------------这里是处理数组的逻辑-------------
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 调用observeArray去代理数组内部的属性
      this.observeArray(value)
    } else {
      // 处理对象操作,这里不管他
      this.walk(value)
    }
  }
  // 处理数组中子集的方法,因为数组中除了属性,也可能会有数组|对象等数据,在这里对子属性重新执行observe函数进行校验与代理
  observeArray (items: Array<any>) {
    // 就是递归了一下,循环执行observe
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

劫持数组的方法

劫持数组的方法实际上都已经生成完毕,包装在arrayMethods这个对象中,Vue调用了两个不同的函数(protoAugmentcopyAugment)去使得数组调用arrayMethods中的那些方法,实际执行的内容是相似的,只是做了兼容性的处理,__proto__是一个非标准属性,else中的copyAugment函数就是兼容了不支持__proto__的浏览器

const hasProto = '__proto__' in {} // 判断__proto__能不能用
......
if (hasProto) {
  protoAugment(value, arrayMethods) // 存在__proto__这个非标准属性
} else {
  copyAugment(value, arrayMethods, arrayKeys) // 不存在时做的兼容处理
}

__proto__早已被大多数浏览器兼容)

劫持数组的方法

我们首先说下arrayMethods对象

在引入Vue后,arrayMethods对象中的方法就已经创建完成了

  1. 首先以Array.prototype为原型创建arrayMethods空对象,这样做的目的是就算我们调用Vue没有代理过的数组方法,也可以通过原型链找到原生的方法
  2. 创建策略名称数组methodsToPatch,里边是Vue要代理的内容
  3. 循环methodsToPatch数组,在arrayMethods中创建数组的新方法的方法

首先执行原生的方法
判断执行的方法会不会插入新的数据,如果插入的话,执行class Observer类中的observeArray()方法将新的数据进行响应式代理
调用ob.dep.notify(),通知组件更新视图
返回原生方法执行后的结果(返回返回值)

// 将原生的数组方法取到存起来
const arrayProto = Array.prototype
// 用原生的数组方法当做原型,创建arrayMethods空对象
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * 循环methodsToPatch以在arrayMethods里创建对应methodsToPatch数组中的事件
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method] // 缓存原始的方法
  // def: 用Object.defineProperty将该函数定义为arrayMethods[method]属性
  // 也就是说,def中的参数function mutator 就是我们要创建的7个新方法
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args) // 执行原生的方法
    const ob = this.__ob__ // 获取当前属性的Observer实例
    // 查看执行的方法中是不是有插入的新数据,如果有,把插入的新数据以数组的格式存到inserted中
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 对插入的新数据进行响应式绑定(observeArray方法在上边有写)
    if (inserted) ob.observeArray(inserted)
    // 最后调用notify,通知watcher更新视图
    ob.dep.notify()
    return result // 返回原生方法执行后的结果
  })
})

用新方法替换老方法
protoAugment

__proto__的时候,没啥可说的,直接替换__proto__

function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
copyAugment

没有__proto__的时候,将上边创建的方法对象(arrayMethods ==== src)便利,用Object.defineProperty放到target(数组)上,当数组.push()或.其他方法时,首先调用的就是我们改造后的方法了

// 稍微改造了一下,使其更易懂
const arrayKeys = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
function copyAugment (target: Object, src: Object) {
  for (let i = 0, l = arrayKeys.length; i < l; i++) {
    const key = arrayKeys[i]
    def(target, key, src[key])
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值