# Vue 中对数组变量的拦截监听
# 数组方法拦截
举个例子,在不改变readBook的方法前提下,想实现个拦截功能,添加埋点上报等功能,一般思路是这样的:
function readBook() {
}
function readBookProxy() {
readBook();
console.log('You read this book.');
}
在平常开发中,由于全局变量较为分散,不知道在哪出修改了数组,如给数组添加了一项,这时我们就需要拦截下数组的Array方法,参考上述的思路:
const arrMethod= 'push';
const originProto = Array.prototype;
const midProto = Object.create(originProto);
midProto[arrMethod] = function(...args) {
const result = originProto[arrMethod].apply(this, args);
console.log('U called array push method', args);
return result;
}
// example
const list = [];
list.__proto__ = midProto;
list.push(1);
有些对象上不存在__proto__
属性,这时就需要更改下思路
const arr = [];
const methods = Object.getOwnPropertyNames(midProto);
methods.forEach(methods => {
arr[method] = midProto[method];
});
实例上添加的方法是可以呗枚举的,这时我们就要设置成不可枚举,隐藏其该方法
const arr = [];
const methods = Object.getOwnPropertyNames(midProto);
methods.forEach(methods => {
Object.defineProperty(arr, method, {
enumerable: false,
writable: true,
configurale: true,
value: midProto[method]
})
arr[method] = midProto[method];
});
我们再执行下上面的例子,可以看到已经实例方法已经不能枚举出来了。
# 源码相关代码片段
import {def} from '../util/index'
// 缓存原始数组原型
const arrayProto = Array.prototype
// 基于数组原型创建新的数组原型
export const arrayMethods = Object.create(arrayProto)
// 需要拦截的方法
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
// 拦截方法并进行监听
methodsToPatch.forEach(function(method) {
// 缓存原始方法
const original = arrayProto[method]
// 实例上定义的方法是可以被枚举的,这里把 enumerable 设置成false
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)
// 通知订阅者更新
ob.dep.notify()
// 返回结果
return result
})
})
// 是否有__proto__属性
const hasProto = '__proto__' in {}
// 获取拦截的数组方法名
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
// 链接数组对象的原型到新数组对象原型上
function protoAugment(target, src: Object) {
target.__proto__ = src
}
// 兼容那些对象上没有__proto__的数组,给他们添加拦截方法
function copyAugment(target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
// def 是 Object.defineProperty()的方法封装
def(target, key, src[key])
}
}
// 判断是否为数组
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
}