Object.defineProperty 监听数组
时间: 2024-12-27 08:30:22 浏览: 3
### 使用 `Object.defineProperty` 实现监听数组变化
由于 JavaScript 中的对象属性可以通过 `Object.defineProperty()` 方法进行拦截,但对于数组来说,默认情况下其长度和内部元素的变化不会触发这些拦截器。为了实现对数组的监听,可以重写数组原型上的变更方法。
#### 重写数组变异方法
JavaScript 数组有多种改变自身状态的方法,比如 `push`, `pop`, `shift`, `unshift`, `splice`, `sort`, 和 `reverse` 等。要使这些操作能够被侦测到并作出反应,可以在创建新数组之前先保存原始方法,再覆盖它们以便在调用时通知观察者[^1]。
下面是一个简单的例子展示如何利用 `Object.defineProperty` 对象定义来监控数组的变化:
```javascript
function observeArray(arr) {
const methodsToOverride = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
// 创建一个函数用来替换原有的数组方法
function makeObservable(methodName) {
let originalMethod = Array.prototype[methodName];
return function(...args) {
console.log(`Calling ${methodName} with arguments `, args);
// 调用原来的数组方法
let result = originalMethod.apply(this, args);
// 当 push 或 unshift 添加了新的项,则需要遍历新增加的内容继续观测
if (['push', 'unshift'].includes(methodName)) {
for(let i=0; i<args.length;i++){
observe(args[i]);
}
}
// 如果是 splice 操作,那么可能既会删除也会增加项目
if ('splice' === methodName){
for(let i=0; i<args.length && i%2===1 ;i+=2){
observe(args[i]);
}
}
// 发布更新事件给订阅者或其他逻辑处理...
notifyChange();
return result;
};
}
// 遍历所有需要替代的方法名列表,并将其替换成我们自定义版本
methodsToOverride.forEach(function(methodName) {
arr[methodName] = makeObservable(methodName).bind(arr);
});
}
// 定义一个辅助函数用于递归地监测复杂结构内的简单对象或数组
function observe(value) {
if (!value || typeof value !== 'object') {return;}
Object.keys(value).forEach(key => {
defineReactiveProperty(value, key, value[key]);
});
if(Array.isArray(value)){
observeArray(value);
}
}
// 定义响应式的 getter/setter 属性描述符
function defineReactiveProperty(obj, key, val) {
observe(val); // 递归观测嵌套的对象
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log(`${key} was accessed`);
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) return;
console.log(`Setting new value to ${key}:`, newVal);
val = newVal;
observe(newVal); // 继续监视新设置的值
notifyChange(); // 告知视图或者其他组件发生了更改
}
});
}
```
上述代码片段展示了怎样通过 `Object.defineProperty` 来追踪数组及其成员的变化。每当执行像 `push` 这样的动作时,不仅会记录下这个行为本身,还会进一步检查是否有任何新加入的数据也需要受到同样的监听机制的影响[^4]。
请注意,在 Vue.js 版本升级至 Vue 3 后,官方推荐使用更强大的 Proxy API 替代 `Object.defineProperty`,因为后者存在一些局限性,特别是在处理动态添加属性以及深层路径绑定方面表现不佳[^3]。
阅读全文