Skip to content

Proxy

不可变辅助工具

js
class MultiTreeNode {
  value = undefined;
  parent = undefined;
  key = undefined;
  constructor(value, parent, key) {
    this.value = value;
    this.parent = parent;
    this.key = key;
  }
}

function isJSONObject(value) {
  return typeof value === 'object' && value !== null;
}

function shallowCopy(obj) {
  return Array.isArray(obj) ? [...obj] : { ...obj };
}

class ImmutableHelper {
  obj = undefined;
  proxy = undefined;
  nodeMap = new Map();
  oldToNew = undefined;
  newToOld = undefined;

  constructor(obj) {
    this.obj = obj;
    this.proxy = this.createProxy(obj);
    this.nodeMap = new Map();
    this.buildTree(obj);
  }

  produce(mutator) {
    this.oldToNew = new Map();
    this.newToOld = new Map();
    mutator(this.proxy);
    return this.oldToNew.get(this.obj) ?? this.obj;
  }

  buildTree(value, parent, key) {
    if (isJSONObject(value)) {
      const node = new MultiTreeNode(value, parent, key);
      const keys = Array.isArray(value)
        ? Array.from({ length: value.length }, (el, i) => i)
        : Object.keys(value);
      keys.forEach(subKey => this.buildTree(value[subKey], node, subKey));
      this.nodeMap.set(value, node);
    }
  }

  createProxy(obj) {
    return new Proxy(obj, {
      set: (target, key, newVal, receiver) => {
        const oldVal = target[key];
        if (oldVal === newVal) {
          return true;
        }
        const oldObj = this.newToOld.get(target) ?? target;
        let node = this.nodeMap.get(oldObj);
        let lastModify = true;
        while (node) {
          let cloned = this.oldToNew.get(node.value);
          if (cloned && !lastModify) {
            break;
          }
          lastModify = cloned === undefined;
          if (!cloned) {
            cloned = shallowCopy(node.value);
            this.oldToNew.set(node.value, cloned);
            this.newToOld.set(cloned, node.value);
          }
          cloned[key] = newVal;
          key = node.key;
          newVal = cloned;
          node = node.parent;
        }
        return true;
      },
      get: (target, key, receiver) => {
        const res = (this.oldToNew.get(target) ?? target)[key];
        return isJSONObject(res) ? this.createProxy(res) : res;
      },
    });
  }
}

使对象不可变

js
const arrayMutableMethods = [
  'pop',
  'push',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse',
];

function isJSONObject(value) {
  return typeof value === 'object' && value !== null;
}

function createProxy(obj) {
  const isArr = Array.isArray(obj);
  return new Proxy(obj, {
    set(target, key, newVal, receiver) {
      if (isArr && !isNaN(Number(key))) {
        throw `Error Modifying Index: ${key}`;
      }
      throw `Error Modifying: ${key}`;
    },
    get(target, key, receiver) {
      if (isArr && arrayMutableMethods.includes(key)) {
        return function () {
          throw `Error Calling Method: ${key}`;
        };
      }
      const res = Reflect.get(target, key, receiver);
      return isJSONObject(res) ? createProxy(res) : res;
    },
  });
}

function makeImmutable(obj) {
  return createProxy(obj);
}

无穷方法对象

js
function createInfiniteObject() {
  return new Proxy(
    {},
    {
      get(target, key, receiver) {
        return function () {
          return key;
        };
      },
    }
  );
}