Vue2.0实现数组劫持的骚操作

457 阅读1分钟

明确问题: Vue2.0中劫持使用的Object.defineProperty,只能劫持到对象每个属性的获取get和修改set,而数组有很多方法可以修改自身,那么Vue是怎样劫持到它的改变?

重点: 必须理解原型链的原理。

对照代码,逐步分析

使用Object.create创建一个对象,它的原型链上有Array的原型,是为了避免我们在修改被劫持的数组原型链时,影响到其他数组。

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

下面就是es6之前会改变数组自身的7个方法:

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

这是def方法:把val绑定在obj下

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

给arrayMethods对象加上这7个方法

const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
})

Vue会把arrayMethods放在需要劫持的对象的__proto__下

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

push、unshift、splice三个方法会新增元素,新增的元素也需要被劫持

 switch (method) {
  case 'push':
  case 'unshift':
    inserted = args
    break
  case 'splice':
    inserted = args.slice(2)
    break
}
if (inserted) ob.observeArray(inserted)

触发watch

ob.dep.notify()

下面是Array的全部源码

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

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

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    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)
    // notify change
    ob.dep.notify()
    return result
  })
})