使用 JavaScript 中的 Proxy 对象进行对象类型检查

前段时间,我在一个项目中遇到了一些对象具有严格的结构要求。由于我并不想使用 TypeScript,所以我决定使用 Proxy 对象创建一个对象的类型检查机制。

受到 React 的 PropTypes 的启发,我为最常见的类型创建了一些类型检查函数

const bool = v => typeof v === 'boolean';
const num = v => typeof v === 'number' && v === v;
const str = v => typeof v === 'string';
const date = v => v instanceof Date;

下一步是决定如何定义对象的形状。这证明是一项简单的任务,因为我可以简单地使用类型检查函数的名称作为对象键的值。

const shape = { name: 'str', age: 'num', active: 'bool', birthday: 'date' };

在决定了如何定义形状之后,我需要将这个形状定义转换为一个函数,该函数将接受一个对象并用 Proxy 对象包装它。Proxy 对象将拦截任何尝试设置属性的操作,并检查要设置的值是否为正确的类型。如果是,则设置值如预期。如果不是,则陷阱返回 false,表示操作不成功。同样,不在形状定义中的属性也不应该被设置,因此陷阱也会对这些属性返回 false

const createShapeCheckerProxy = (types, shape) => {
  const validProps = Object.keys(shape);
  const handler = {
    set(target, prop, value) {
      if (!validProps.includes(prop)) return false;
      const validator = types[shape[prop]];
      if (!validator || typeof validator !== 'function') return false;
      if (!validator(value)) return false;
      target[prop] = value;
    }
  };
  return obj => new Proxy(obj, handler);
};

设置好了一切,是时候进行测试了。下面是整个过程的一个示例:

const createShapeCheckerProxy = shape => {
  const types = {
    bool: v => typeof v === 'boolean',
    num: v => typeof v === 'number' && v === v,
    str: v => typeof v === 'string',
    date: v => v instanceof Date
  };
  const validProps = Object.keys(shape);

```javascript
const handler = {
  set(target, prop, value) {
    if (!validProps.includes(prop)) return false;
    const validator = types[shape[prop]];
    if (!validator || typeof validator !== 'function') return false;
    if (!validator(value)) return false;
    target[prop] = value;
  }
};

return obj => new Proxy(obj, handler);
};

const shapeCheckerProxy = createShapeCheckerProxy({
  name: 'str', age: 'num', active: 'bool', birthday: 'date'
});

const obj = {};
const proxiedObj = shapeCheckerProxy(obj);

// 这些是有效的
proxiedObj.name = 'John';
proxiedObj.age = 34;
proxiedObj.active = false;
proxiedObj.birthday = new Date('1989-04-01');

// 这些将失败
proxiedObj.name = 404;
proxiedObj.age = false;
proxiedObj.active = 'no';
proxiedObj.birthday = null;
proxiedObj.whatever = 'something';

如你所见,createShapeCheckerProxy可以与普通对象一起使用,创建一个可重用的函数,该函数使用类型检查的Proxy包装对象。定义的types用于对单个属性进行类型检查,可以扩展以支持更复杂的类型和特殊规则。总体而言,这是一个非常有用的工具,用于在运行时对对象进行类型检查,而无需使用TypeScript或类似的工具。