明确问题: 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
})
})