文章目录

JavaScript Proxy拦截操作实现数据校验的完整示例

发布于 2026-04-25 03:14:54 · 浏览 11 次 · 评论 0 条

JavaScript Proxy拦截操作实现数据校验的完整示例

在 JavaScript 开发中,直接修改对象属性(如 user.age = -5)非常常见,但也极易引入脏数据。传统的解决方案是在赋值前手动编写 if 语句进行校验,这种方式代码冗余且容易遗漏。Proxy 对象提供了一种机制,可以在操作目标对象之前进行“拦截”,从而实现统一、非侵入式的数据校验。


1. 理解 Proxy 的基本工作原理

Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

它由两个核心部分组成:

  • target:要被代理的目标对象。
  • handler:一个对象,定义了拦截行为(称为“陷阱”)。

当外部操作作用于 Proxy 实例时,handler 中的逻辑会优先执行。

以下是 Proxy 拦截属性赋值操作的执行流程:

graph LR A[代码执行赋值] -->|proxy.name = value| B[Proxy 对象] B -->|触发 set 陷阱| C{Handler 逻辑校验} C -->|校验失败| D[抛出 TypeError] C -->|校验成功| E[Reflect.set 赋值] E --> F[Target 目标对象更新]

2. 实现基础的数据类型校验

本阶段将实现一个最简单的拦截器:确保对象的 age 属性只能是数字类型。

  1. 定义一个包含原始数据的对象 person

    const person = {
      name: '张三',
      age: 25
    };
  2. 创建 handler 对象,并编写 set 陷阱函数。set 方法接收四个参数:目标对象、属性名、属性值和 Proxy 实例本身。

    const validator = {
      set(target, property, value) {
        if (property === 'age') {
          if (typeof value !== 'number') {
            throw new TypeError('年龄必须是数字');
          }
        }
        // 校验通过,执行默认赋值行为
        target[property] = value;
        return true; // 表示赋值成功
      }
    };
  3. 实例化 Proxy 对象,将 personvalidator 传入。

    const proxyPerson = new Proxy(person, validator);
  4. 测试赋值操作。

    • 执行合法赋值:
      proxyPerson.age = 26;
      console.log(person.age); // 输出: 26
    • 执行非法赋值:
      proxyPerson.age = '三十岁'; // 抛出错误: Uncaught TypeError: 年龄必须是数字

3. 构建通用且健壮的校验器

为了解决硬编码属性名的问题,我们可以构建一个通用的校验工厂函数,通过配置对象来定义规则。

  1. 定义规则描述结构。我们需要一个对象来存储每个属性的校验逻辑(如类型、范围、是否必填)。

  2. 编写 createValidator 工厂函数。

    function createValidator(target, rules) {
      return new Proxy(target, {
        set(target, property, value) {
          // 1. 检查是否有该属性的规则定义
          if (!rules.hasOwnProperty(property)) {
            target[property] = value;
            return true;
          }
    
          const rule = rules[property];
          const errors = [];
    
          // 2. 校验类型
          if (rule.type && typeof value !== rule.type) {
            errors.push(`期望类型为 ${rule.type},实际得到 ${typeof value}`);
          }
    
          // 3. 校验最大值/最小值
          if (rule.max !== undefined && value > rule.max) {
            errors.push(`值不能大于 ${rule.max}`);
              }
              if (rule.min !== undefined && value < rule.min) {
                errors.push(`值不能小于 ${rule.min}`);
          }
    
          // 4. 校验是否必填(处理空字符串或 null)
          if (rule.required && (value === null || value === '')) {
            errors.push('该属性为必填项');
          }
    
          // 5. 如果有错误,抛出异常
          if (errors.length > 0) {
            throw new Error(`校验失败 [${property}]: ${errors.join('; ')}`);
          }
    
          // 6. 校验通过,赋值
          target[property] = value;
    
          // 可选:在这里添加副作用,如更新 UI、发送日志等
          console.log(`属性 ${property} 已更新为: ${value}`);
    
          return true;
        }
      });
    }
  3. 配置具体的校验规则。

    const productRules = {
      price: {
        type: 'number',
        min: 0,
        required: true
      },
      name: {
        type: 'string',
        required: true
      },
      stock: {
        type: 'number',
        min: 0
      }
    };
  4. 应用校验器到业务数据。

    const productData = {
      name: '机械键盘',
      price: 199
    };
    
    const safeProduct = createValidator(productData, productRules);
  5. 验证边界情况。

    • 尝试设置价格为负数:
      safeProduct.price = -50; 
      // 抛出错误: 校验失败 [price]: 值不能小于 0
    • 尝试设置正确的库存:
      safeProduct.stock = 100;
      // 输出: 属性 stock 已更新为: 100

4. 进阶:利用 get 拦截保护私有属性

除了限制赋值,我们还可以利用 get 拦截器防止读取以 _ 开头的内部私有属性。

  1. 修改 handler,增加 get 陷阱。

    const privateGuard = {
      get(target, property) {
        if (property.startsWith('_')) {
          throw new Error(`禁止访问私有属性: ${property}`);
            }
            return target[property];
          },
          set(target, property, value) {
            if (property.startsWith('_')) {
              throw new Error(`禁止修改私有属性: ${property}`);
        }
        target[property] = value;
        return true;
      }
    };
  2. 创建包含私有属性的对象。

    const userConfig = {
      apiKey: '123-456',
      _internalId: 'sys_001'
    };
    
    const guardedUser = new Proxy(userConfig, privateGuard);
  3. 测试访问权限。

    console.log(guardedUser.apiKey); // 正常输出: '123-456'
    console.log(guardedUser._internalId); // 抛出错误: 禁止访问私有属性: _internalId

5. 完整代码汇总

将上述逻辑整合为一个可直接运行的单文件示例。

// 校验工具函数
function createValidator(target, rules) {
  return new Proxy(target, {
    set(target, property, value) {
      if (rules[property]) {
        const rule = rules[property];

        if (rule.required && (value === null || value === '')) {
          throw new Error(`[${property}] 是必填项`);
        }
        
        if (rule.type && typeof value !== rule.type) {
          throw new Error(`[${property}] 类型错误,期望 ${rule.type}`);
        }
        
        if (typeof value === 'number') {
          if (rule.max !== undefined && value > rule.max) throw new Error(`[${property}] 超过最大值 ${rule.max}`);
          if (rule.min !== undefined && value < rule.min) throw new Error(`[${property}] 小于最小值 ${rule.min}`);
        }
      }

      target[property] = value;
      return true;
    }
  });
}

// 定义数据与规则
const gameData = {
  level: 1,
  score: 0
};

const gameRules = {
  level: { type: 'number', min: 1, max: 99 },
  score: { type: 'number', min: 0 }
};

// 代理对象
const proxyGame = createValidator(gameData, gameRules);

// 测试运行
try {
  proxyGame.level = 10;  // 成功
  proxyGame.score = -5;  // 失败并抛出错误
} catch (e) {
  console.error(e.message);
}

console.log(proxyGame.level); // 输出 10

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文