# 监听一个变量的变化,需要怎么做
(1)方法1:ES5 的 Object.defineProperty
基础版本实现:
// 监视对象
function observe(obj) {
// 遍历对象,使用 get/set 重新定义对象的每个属性值
Object.keys(obj).map(key => {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, k, v) {
// 递归子属性
if (typeof(v) === 'object') observe(v)
// 重定义 get/set
Object.defineProperty(obj, k, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get: ' + v)
return v
},
// 重新设置值时,触发收集器的通知机制
set: function reactiveSetter(newV) {
console.log('set: ' + newV)
v = newV
},
})
}
let data = {a: 1}
// 监视对象
observe(data)
data.a // get: 1
data.a = 2 // set: 2
但是以上方法存在几个缺陷:
- IE8 及更低版本 IE 是不支持的(如果不考虑IE8以下兼容可忽略)
- 无法检测到对象属性的新增或删除
- 如果修改数组的 length ( Object.defineProperty 不能监听数组的长度),以及数组的 push 等变异方法是无法触发 setter
而vue中使用了函数劫持的方式,重写了数组的方法,Vue 将 data 中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组 api 时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。实现方法如下所示:
// 获得数组原型
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]
// 重写函数
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
})
})
(2)ES6 的 Proxy
使用proxy会带来几个问题:
- Proxy只会代理对象的第一层,那么又是怎样处理这个问题的呢?——我们可以判断当前 Reflect.get 的返回值是否为 Object ,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。
- 监测数组的时候可能触发多次get/set,那么如何防止触发多次呢(因为获取push和修改length的时候也会触发)——我们可以判断是否是 hasOwProperty
const toProxy = new WeakMap(); // 存放被代理过的对象
const toRaw = new WeakMap(); // 存放已经代理过的对象
function reactive(target) {
return createReactiveObject(target); // // 创建响应式对象
}
function isObject(target) {
return typeof target === "object" && target !== null;
}
function hasOwn(target, key) {
return target.hasOwnProperty(key);
}
// 创建响应式对象
function createReactiveObject(target) {
// 如果不是对象,则直接返回
if (!isObject(target)) {
return target;
}
// toProxy用来存放被代理过的对象
let observed = toProxy.get(target);
// 判断是否被代理过,如果被代理过则直接返回
if (observed) {
return observed;
}
// 判断是否要重复代理
if (toRaw.has(target)) {
return target;
}
const handlers = {
get(target, key, receiver) {
let res = Reflect.get(target, key, receiver);
track(target, 'get', key); // 依赖收集==
return isObject(res) ? reactive(res) : res; // 如果是对象,则继续代理
},
set(target, key, value, receiver) {
let oldValue = target[key];
let hadKey = hasOwn(target, key);
let result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
trigger(target, 'add', key); // 触发添加
} else if (oldValue !== value) {
trigger(target, 'set', key); // 触发修改
}
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
return result;
}
};
// 开始代理
observed = new Proxy(target, handlers);
toProxy.set(target, observed);
toRaw.set(observed, target); // 做映射表
return observed;
}