You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何订阅JavaScript对象的变更?求非废弃实现方案

嘿,我完全懂你想要实现类似Redux那种订阅JavaScript对象变更的需求——毕竟Object.prototype.watch()和Object.observe()都废弃了,老帖子的方案又可能过时,确实让人头疼。下面给你几个当前完全可用的现代JS方案,结合你给出的store示例来讲解:

方案1:使用Proxy(最推荐的现代方案)

ES6引入的Proxy是专门用来拦截对象各种操作的API,完美替代废弃的监听方案,而且能处理数组、嵌套对象的变更,灵活性拉满。我们可以给目标对象套一层Proxy,同时维护一个订阅者队列,当对象的属性(包括数组元素)发生变化时,自动通知所有订阅者。

示例代码:

// 创建一个带订阅功能的状态容器
function createStore(initialState) {
  // 存储订阅者的数组
  const subscribers = [];
  
  // 创建Proxy来拦截状态变更
  const state = new Proxy(initialState, {
    set(target, property, value) {
      // 先执行原始赋值操作
      target[property] = value;
      // 通知所有订阅者(返回状态副本避免外部直接修改)
      subscribers.forEach(callback => callback({ ...state }));
      return true;
    },
    deleteProperty(target, property) {
      // 处理删除操作
      const result = delete target[property];
      subscribers.forEach(callback => callback({ ...state }));
      return result;
    }
  });

  // 订阅方法:添加回调到队列,返回取消订阅函数
  function subscribe(callback) {
    subscribers.push(callback);
    return () => {
      const index = subscribers.indexOf(callback);
      if (index !== -1) subscribers.splice(index, 1);
    };
  }

  return { state, subscribe };
}

// 用你的示例初始化store
const { state: store, subscribe } = createStore({ anArray: ['Hi', 'my', '...'] });

// 订阅变更:每次store变化时打印最新状态
const unsubscribe = subscribe((newState) => {
  console.log('Store更新了:', newState);
});

// 测试变更
store.anArray.push('name'); // 触发订阅回调
store.anArray[0] = 'Hello'; // 触发订阅回调
delete store.anArray[1]; // 删除元素同样触发

// 取消订阅(如果需要的话)
// unsubscribe();

这个方案能监听数组的push、splice、直接修改索引等操作,完全覆盖你的需求。

方案2:手动实现极简Redux风格的状态管理

如果你想要更贴近Redux的核心思路(单一数据源、通过action修改状态、订阅变更),可以手动实现一个极简版的状态管理类,所有状态变更都通过dispatch触发,这样更可控,也更容易调试。

示例代码:

class ReduxLikeStore {
  constructor(initialState) {
    this.state = { ...initialState };
    this.subscribers = [];
  }

  // 获取当前状态(返回副本,避免外部直接修改原状态)
  getState() {
    return { ...this.state };
  }

  // 订阅变更
  subscribe(callback) {
    this.subscribers.push(callback);
    return () => {
      const index = this.subscribers.indexOf(callback);
      if (index !== -1) this.subscribers.splice(index, 1);
    };
  }

  // 触发状态变更(类似Redux的dispatch)
  dispatch(action) {
    switch (action.type) {
      case 'UPDATE_ARRAY':
        this.state.anArray = action.payload;
        break;
      case 'PUSH_TO_ARRAY':
        this.state.anArray.push(action.payload);
        break;
      case 'MODIFY_ARRAY_ITEM':
        this.state.anArray[action.index] = action.payload;
        break;
      // 可以根据需求扩展更多action类型
      default:
        return;
    }
    // 通知所有订阅者
    this.subscribers.forEach(callback => callback(this.getState()));
  }
}

// 初始化你的store
const store = new ReduxLikeStore({ anArray: ['Hi', 'my', '...'] });

// 订阅变更
const unsubscribe = store.subscribe((newState) => {
  console.log('Store更新了:', newState);
});

// 通过dispatch修改状态
store.dispatch({ type: 'PUSH_TO_ARRAY', payload: 'is' });
store.dispatch({ type: 'MODIFY_ARRAY_ITEM', index: 0, payload: 'Hello' });

// 取消订阅
// unsubscribe();

这种方式的好处是所有状态变更都有迹可循,和Redux的思路完全一致,适合复杂的应用场景。

方案3:使用Getter/Setter(适合简单固定结构)

如果你的对象结构非常固定(比如只有anArray这一个属性),可以直接用ES5的getter和setter来拦截属性的读写操作,实现简单的监听。不过这个方案灵活性差,不适合嵌套对象或动态属性的场景。

示例代码:

const initialState = { anArray: ['Hi', 'my', '...'] };
const subscribers = [];

// 创建带getter/setter的store
const store = {
  get anArray() {
    return [...initialState.anArray]; // 返回副本,避免外部直接修改原数组
  },
  set anArray(newValue) {
    initialState.anArray = newValue;
    // 通知订阅者
    subscribers.forEach(callback => callback({ ...store }));
  }
};

// 订阅方法
function subscribe(callback) {
  subscribers.push(callback);
  return () => {
    const index = subscribers.indexOf(callback);
    if (index !== -1) subscribers.splice(index, 1);
  };
}

// 测试
const unsubscribe = subscribe((newState) => {
  console.log('Store更新了:', newState);
});

store.anArray = [...store.anArray, 'name']; // 触发订阅
// 注意:直接修改返回的副本不会触发setter,必须重新赋值整个数组
// store.anArray[0] = 'Hello'; // 这行不会触发订阅

这个方案适合非常简单的场景,但局限性比较大,比如无法监听数组内部的push操作,必须重新赋值整个数组才会触发setter。

以上三个方案都是当前非废弃、完全可用的,你可以根据自己的场景选择:如果需要最大的灵活性,选Proxy;如果想要贴近Redux的可控性,选第二个方案;如果只是简单固定属性,选第三个。

内容的提问来源于stack exchange,提问作者Eyk Rehbein

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:24:20