关于Object.defineProperty的介绍看这里,今天又看到《数据劫持 OR 数据代理》一文,我就转载收藏记录一下关于Object.defineProperty的问题。
不能监听数组的变化
let arr = [1,2,3]
let obj = {}
Object.defineProperty(obj, 'arr', {
get () {
console.log('get arr')
return arr
},
set (newVal) {
console.log('set', newVal)
arr = newVal
}
})
obj.arr.push(4) // 只会打印 get arr, 不会打印 set
obj.arr = [1,2,3,4] // 这个能正常 set
数组的以下几个方法不会触发set:
push
pop
shift
unshift
splice
sort
reverse
Vue 把这些方法定义为变异方法 (mutation method),指的是会修改原来数组的方法。与之对应则是非变异方法 (non-mutating method),例如 filter, concat, slice 等,它们都不会修改原始数组,而会返回一个新的数组。Vue官网有相关文档讲述这个问题。
Vue的做法是把这些方法重写来实现数组的劫持。一个极简的实现如下:
const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {
// 这里是原生Array的原型方法
let original = Array.prototype[method];
// 将 push, pop 等封装好的方法定义在对象 arrayAugmentations 的属性上
// 注意:是实例属性而非原型属性
arrayAugmentations[method] = function () {
console.log('我被改变啦!');
// 调用对应的原生方法并返回结果
return original.apply(this, arguments);
};
});
let list = ['a', 'b', 'c'];
// 将我们要监听的数组的原型指针指向上面定义的arrayAugmentations空数组对象
// 这样就能在调用 push, pop 这些方法时走进我们刚定义的方法,多了一句 console.log
list.__proto__ = arrayAugmentations;
list.push('d'); // 我被改变啦!
// 这个 list2 是个普通的数组,所以调用 push 不会走到我们的方法里面。
let list2 = ['a', 'b', 'c'];
list2.push('d'); // 不输出内容
必须遍历对象的每个属性
使用 Object.defineProperty() 多数要配合 Object.keys() 和遍历,于是多了一层嵌套。如:
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
// ...
})
})
Object.keys(obj)
参数:obj
要返回其枚举自身属性的对象。
返回值
一个表示给定对象的所有可枚举属性的字符串数组。数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。
必须深层遍历嵌套的对象
所谓的嵌套对象,是指类似
let obj = {
info: {
name: 'eason'
}
}
如果是这一类嵌套对象,那就必须逐层遍历,直到把每个对象的每个属性都调用 Object.defineProperty() 为止。 Vue 的源码中就能找到这样的逻辑 (叫做 walk 方法)。