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

如何基于ES6 Proxy实现保留原型的不可变类实例?

Hey there! Let's work through creating an immutable class instance with ES6 Proxy—your core idea is solid, but there are a few key fixes and improvements needed to make it behave as expected, especially around preserving the class prototype and handling property updates correctly.

First, let's break down the issues with your current code:

  • The set trap in a Proxy must return a boolean (to signal if the assignment succeeded), not the modified object. Returning an object here will throw a runtime error.
  • JavaScript's assignment expression (obj.prop = value) evaluates to value, not the modified object. So your line let newJesonProxy = (jesonProxy.schema = {}) would set newJesonProxy to the new schema object, not the updated Proxy instance.
  • We need to ensure deep cloning preserves the original class's prototype chain (so the cloned instance is still a Men object).

This approach uses a custom set method on the Proxy to handle updates, ensuring we get a new immutable Proxy instance every time we modify a property. It's clear, avoids JavaScript syntax limitations, and preserves the class prototype.

// First, a deep clone function that preserves prototype chains
// (or use lodash's _.cloneDeep which does this automatically)
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  
  // Create a new instance of the original object's constructor
  const cloned = new obj.constructor();
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloned[key] = deepClone(obj[key]);
    }
  }
  return cloned;
}

function createImmutableProxy(instance) {
  const handler = {
    get(target, key) {
      // Expose a custom set method to generate updated instances
      if (key === 'set') {
        return (prop, value) => {
          // Deep clone the original instance (preserves prototype)
          const clonedInstance = deepClone(target);
          // Apply the update (works with both direct properties and setters)
          clonedInstance[prop] = value;
          // Return a new immutable Proxy for the updated instance
          return createImmutableProxy(clonedInstance);
        };
      }
      // Return regular properties as-is
      return Reflect.get(target, key);
    },
    // Block direct assignment to enforce using the set method
    set(target, key) {
      throw new Error(`Cannot mutate immutable instance directly. Use .set('${key}', value) instead.`);
    }
  };

  return new Proxy(instance, handler);
}

// Your original Men class
class Men {
  constructor(name) {
    this.schema = { prop: { innerProp: {} } };
    this.name = name;
  }
  set newName(name) {
    this.name = name;
  }
}

// Usage example
const jeson = new Men("jeson");
const jesonProxy = createImmutableProxy(jeson);

// Update schema to get a new immutable instance
const newJesonProxy = jesonProxy.set('schema', { newProp: { newInnerProp: {} } });
console.log(newJesonProxy.schema); // { newProp: { newInnerProp: {} } }
console.log(jesonProxy.schema); // Original value remains unchanged: { prop: { innerProp: {} } }

// Test the class setter
const updatedNameJeson = jesonProxy.set('newName', 'Jeson Updated');
console.log(updatedNameJeson.name); // "Jeson Updated"

// Verify prototype is preserved
console.log(newJesonProxy instanceof Men); // true

Key Features:

  • Immutable Updates: Every modification returns a new Proxy instance, leaving the original untouched.
  • Prototype Preservation: The deep clone function creates a new instance of the original class, so instanceof checks still work.
  • Supports Setters: The custom set method works with class setters (like newName) just like regular properties.
  • Prevents Accidental Mutation: Direct assignment throws an error, so you can't accidentally modify the original instance.

If You Prefer Assignment Syntax (Workaround)

JavaScript's assignment syntax doesn't let us return the modified object as the result of the expression, but we can use a Proxy trap to create a new instance and expose it via a separate property. Note this is less intuitive than the functional approach:

function createImmutableProxy(instance) {
  let latestInstance = instance;
  const handler = {
    set(target, key, value) {
      const cloned = deepClone(target);
      cloned[key] = value;
      latestInstance = cloned;
      return true;
    },
    get(target, key) {
      if (key === 'getLatest') {
        return () => createImmutableProxy(latestInstance);
      }
      return Reflect.get(target, key);
    }
  };
  return new Proxy(instance, handler);
}

// Usage
const jesonProxy = createImmutableProxy(new Men("jeson"));
jesonProxy.schema = { newProp: {} };
const newJesonProxy = jesonProxy.getLatest();

But this approach has downsides: it relies on a side effect (updating latestInstance) and can lead to confusion if multiple updates are made in quick succession. The functional method is far more predictable.

内容的提问来源于stack exchange,提问作者Goor Lavi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:57:26