javascript数组本身不支持观察者模式,要实现需通过封装或proxy拦截操作并通知订阅者。1. 使用自定义类可封装数组,重写push、pop、splice等方法,在操作后调用\_notify通知订阅者;2. 直接索引赋值无法用setter捕获,需借助es6 proxy的set陷阱实现;3. proxy通过get和set陷阱统一拦截数组读写操作,能全面监控方法调用和索引修改,自动触发回调;4. 深层对象属性变化仍需递归观察,proxy虽无法完全解决但大幅简化实现;5. 需注意性能开销与内存泄漏风险,确保提供unsubscribe机制。该方案有效解耦数据变化与响应逻辑,适用于ui更新、数据同步等场景。

JavaScript数组本身并没有内置的观察者模式,这意味着当你直接修改一个数组(比如
push
pop
splice
arr[0] = 'newValue'

要让JavaScript数组拥有观察能力,核心思路是创建一个中间层,这个层负责管理数组的实际操作以及通知机制。最直接的方法是创建一个自定义的类或对象来封装数组,并暴露订阅/取消订阅的方法。
这个封装器需要:
立即学习“Java免费学习笔记(深入)”;

subscribe
unsubscribe
push
pop
splice
shift
unshift
sort
reverse
Proxy
举个例子,一个基础的封装器可能长这样:
class ObservableArray {
constructor(initialArray = []) {
this._array = initialArray;
this._subscribers = [];
}
subscribe(callback) {
this._subscribers.push(callback);
// 返回一个取消订阅的函数,方便管理
return () => this.unsubscribe(callback);
}
unsubscribe(callback) {
this._subscribers = this._subscribers.filter(sub => sub !== callback);
}
_notify(changeType, payload) {
this._subscribers.forEach(callback => callback(changeType, payload));
}
// 拦截 push
push(...items) {
const oldLength = this._array.length;
const result = this._array.push(...items);
this._notify('push', { items, newLength: this._array.length, oldLength });
return result;
}
// 拦截 pop
pop() {
const item = this._array.pop();
this._notify('pop', { item, newLength: this._array.length });
return item;
}
// 拦截 splice
splice(start, deleteCount, ...items) {
const removed = this._array.splice(start, deleteCount, ...items);
this._notify('splice', { start, deleteCount, items, removed, newLength: this._array.length });
return removed;
}
// 访问器,确保外部不能直接修改 _array
get array() {
return [...this._array]; // 返回副本,防止直接修改
}
// 尝试处理直接索引赋值,但这很复杂,通常需要Proxy
set(index, value) {
if (index >= 0 && index < this._array.length) {
const oldValue = this._array[index];
this._array[index] = value;
this._notify('set', { index, oldValue, newValue: value });
return true;
} else if (index === this._array.length) { // 类似push
this._array[index] = value;
this._notify('push', { items: [value], newLength: this._array.length, oldLength: this._array.length - 1 });
return true;
}
return false;
}
}
// 实际使用
// const myObservableArray = new ObservableArray([1, 2, 3]);
// myObservableArray.subscribe((type, payload) => {
// console.log(`Array changed: ${type}`, payload, 'Current array:', myObservableArray.array);
// });
// myObservableArray.push(4); // 会触发通知
// myObservableArray.set(0, 100); // 会触发通知这种手动拦截的方式虽然可行,但对于所有数组方法和直接索引赋值的处理会变得非常冗长和容易出错。这也是为什么ES6的
Proxy

说实话,我个人在项目里遇到需要观察数组变动的情况,大多都和UI渲染、数据同步以及某些业务逻辑的自动化触发有关。这不仅仅是为了“酷”,而是实实在在解决了许多痛点。
一个很典型的场景就是前端框架中的数据绑定。想象一下,你有一个用户列表数组,当用户添加、删除或者修改了列表中的某一项时,你希望页面能立刻更新,而不需要手动去重新渲染整个列表。观察者模式在这里就发挥了关键作用:数组变动时,它会通知订阅者(比如UI组件),然后组件根据变化的数据进行局部更新。这大大提高了开发效率,也让代码逻辑更清晰。
再比如,在一些复杂的数据处理流程中,你可能需要根据数组的状态变化来触发后续的计算或API请求。比如,一个购物车商品列表,每当商品数量或种类发生变化时,你可能需要重新计算总价,或者自动保存到用户的会话中。如果能直接“监听”数组,这些操作就能做到自动化和解耦,而不是在每次修改数组的地方都手动调用一次更新函数,那样代码会变得非常冗余且难以维护。它本质上是在解决“数据变化如何驱动行为变化”的问题,让数据和行为之间的耦合度降到最低。
在尝试让数组变得“可观察”时,你很快就会发现一些让人头疼的地方,这不像观察一个普通对象那么直接。
首先,也是最让人困惑的,是JavaScript数组的“原生”修改方式太多样了。
push
pop
splice
shift
unshift
sort
reverse
myArray[0] = 'newValue'
Object.defineProperty
$set
Vue.set
其次,是深层观察的复杂性。如果你的数组里存储的不是基本类型(字符串、数字),而是对象,那么当这些对象内部的属性发生变化时,数组本身并没有改变。比如
[{id: 1, name: 'A'}][{id: 1, name: 'B'}]再来,是性能考量和内存泄漏风险。如果你有大量的数组实例需要被观察,或者一个数组被大量的订阅者监听,那么每次数组变动时的通知开销可能会变得很大。特别是当通知逻辑本身比较复杂时,这会影响应用的响应速度。同时,如果订阅者没有正确地取消订阅,就可能导致内存泄漏,因为被订阅的对象会一直持有对订阅者回调函数的引用,阻止垃圾回收。这要求你在设计订阅/取消订阅机制时要非常严谨,提供便捷的取消订阅方式(比如返回一个取消函数)。
这些挑战都促使开发者去寻找更优雅、更底层的解决方案,而ES6的
Proxy
说实话,当我第一次深入了解ES6的
Proxy
Proxy
最关键的几个陷阱是:
set(target, property, value, receiver)
arr[0] = 'newValue'
arr.length = 0
get(target, property, receiver)
arr.push()
get
push
get
push
push
通过
Proxy
这是一个简化版的
Proxy
function createObservableArray(initialArray = []) {
const subscribers = [];
const array = initialArray;
const notify = (changeType, payload) => {
subscribers.forEach(callback => callback(changeType, payload));
};
const handler = {
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver); // 执行原始赋值
// 仅在实际值发生变化时通知,或处理新元素添加
if (property !== 'length' && oldValue !== value) {
notify('set', { index: property, oldValue, newValue: value });
} else if (property === 'length' && oldValue !== value) {
// 长度变化可能意味着元素被移除或添加
if (value < oldValue) { // 长度变短,可能是pop或splice导致
notify('remove', { oldLength: oldValue, newLength: value });
} else { // 长度变长
notify('add', { oldLength: oldValue, newLength: value });
}
}
return result;
},
get(target, property, receiver) {
// 拦截数组的修改方法
if (['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].includes(property)) {
return function(...args) {
const oldArray = [...target]; // 记录修改前的状态
const result = Reflect.apply(target[property], target, args); // 调用原始方法
const newArray = target; // 修改后的数组
// 根据不同的方法类型发送通知
switch (property) {
case 'push':
notify('push', { added: args, newArray, oldArray });
break;
case 'pop':
notify('pop', { removed: result, newArray, oldArray });
break;
case 'shift':
notify('shift', { removed: result, newArray, oldArray });
break;
case 'unshift':
notify('unshift', { added: args, newArray, oldArray });
break;
case 'splice':
notify('splice', { args, removed: result, newArray, oldArray });
break;
case 'sort':
case 'reverse':
notify(property, { newArray, oldArray }); // 排序或反转
break;
}
return result;
};
}
// 对于其他属性(如length,或普通元素访问),直接返回
return Reflect.get(target, property, receiver);
}
};
const proxy = new Proxy(array, handler);
// 暴露订阅和取消订阅的方法
proxy.subscribe = (callback) => {
subscribers.push(callback);
return () => proxy.unsubscribe(callback);
};
proxy.unsubscribe = (callback) => {
subscribers.splice(subscribers.indexOf(callback), 1);
};
return proxy;
}
// 实际使用
// const myProxyArray = createObservableArray([10, 20, 30]);
// myProxyArray.subscribe((type, payload) => {
// console.log(`Proxy Array changed: ${type}`, payload, 'Current array:', myProxyArray);
// });
// myProxyArray.push(40); // 触发 push 通知
// myProxyArray[0] = 100; // 触发 set 通知
// myProxyArray.pop(); // 触发 pop 通知
// myProxyArray.splice(0, 1); // 触发 splice 通知
// console.log(myProxyArray[0]); // 不会触发通知,只是读取Proxy
Proxy
Proxy
以上就是javascript数组如何实现观察者模式的详细内容,更多请关注php中文网其它相关文章!
java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号