面试题:ES6 的 Proxy 可以实现什么功能?

ES6 引入的 Proxy 是一个非常强大的元编程(Metaprogramming)特性。它允许你创建一个代理对象,用于拦截并自定义对另一个对象(目标对象)的基本操作(如属性读取、赋值、枚举、函数调用等)。你可以把它想象成一个对象的“守门人”或“中间件”,所有对目标对象的操作都必须先经过这个“守门人”的检查和处理。

1. 基本语法

const proxy = new Proxy(target, handler);
  • target: 要被代理的原始对象。可以是任何类型的对象(普通对象、数组、函数、甚至另一个代理)。
  • handler: 一个对象,其属性定义了在执行各种操作时所对应的拦截器(traps)。这些拦截器是特殊的方法名(如 get, set, has 等)。
  • proxy: 返回的代理对象。你应该操作这个代理对象,而不是直接操作 target

2. Proxy 可以实现的核心功能

通过在 handler 中定义不同的“陷阱”(traps),Proxy 可以实现多种高级功能:

(1) 属性访问拦截 (get trap)

拦截对对象属性的读取操作。

  • 实现数据绑定/响应式系统
    const target = { message: 'Hello' };
    const handler = {
    get(obj, prop) {
    console.log(`读取属性: ${prop}`);
    return obj[prop];
    }
    };
    const proxy = new Proxy(target, handler);
    console.log(proxy.message); // 输出: 读取属性: message \n Hello
    这是 Vue 3 响应式系统的核心原理之一。当读取属性时,可以收集依赖(哪个组件/函数用到了这个属性)。
  • 提供默认值或计算属性
    const handler = {
      get(obj, prop) {
        if (prop in obj) {
          return obj[prop];
        } else {
          return `Property '${prop}' not found, returning default.`;
        }
      }
    };
    const proxy = new Proxy({}, handler);
    console.log(proxy.name); // Property 'name' not found, returning default.

(2) 属性赋值拦截 (set trap)

拦截对对象属性的设置操作。

  • 数据验证
    const validator = {
      set(obj, prop, value) {
        if (prop === 'age') {
          if (typeof value !== 'number' || value < 0) {
            throw new Error('Age must be a positive number');
          }
        }
        obj[prop] = value;
        return true; // 必须返回 true 表示设置成功
      }
    };
    const person = new Proxy({}, validator);
    person.age = 25; // OK
    // person.age = -5; // 抛出错误
  • 实现响应式更新
    const handler = {
      set(obj, prop, value) {
        console.log(`设置属性: ${prop} = ${value}`);
        obj[prop] = value;
        // 在这里可以触发视图更新 (如 Vue 3)
        updateView();
        return true;
      }
    };
  • 自动类型转换
    const handler = {
      set(obj, prop, value) {
        if (prop === 'price') {
          obj[prop] = Number(value); // 确保 price 是数字
        } else {
          obj[prop] = value;
        }
        return true;
      }
    };

(3) 属性删除拦截 (deleteProperty trap)

拦截 delete 操作。

const handler = {
  deleteProperty(obj, prop) {
    if (prop.startsWith('_')) {
      throw new Error('Private properties cannot be deleted');
    }
    delete obj[prop];
    return true;
  }
};
const obj = { _private: 'secret', public: 'ok' };
const proxy = new Proxy(obj, handler);
// delete proxy._private; // 抛出错误
delete proxy.public; // OK

(4) 属性存在性检查拦截 (has trap)

拦截 in 操作符。

const handler = {
  has(obj, prop) {
    if (prop.startsWith('_')) {
      return false; // 隐藏以 _ 开头的“私有”属性
    }
    return prop in obj;
  }
};
const obj = { _secret: 'hidden', visible: 'shown' };
const proxy = new Proxy(obj, handler);
console.log('_secret' in proxy); // false
console.log('visible' in proxy); // true

(5) 枚举拦截 (ownKeys, getOwnPropertyDescriptor traps)

拦截 Object.keys(), for...in 等操作。

const handler = {
  ownKeys(obj) {
    // 只返回非下划线开头的属性名
    return Object.keys(obj).filter(key => !key.startsWith('_'));
  }
};
const obj = { _hidden: 'no', shown: 'yes' };
const proxy = new Proxy(obj, handler);
console.log(Object.keys(proxy)); // ['shown']

(6) 函数调用拦截 (apply trap)

当代理的目标是一个函数时,可以拦截其调用。

function sum(a, b) {
  return a + b;
}
const handler = {
  apply(target, thisArg, argumentsList) {
    console.log(`调用函数 ${target.name},参数:`, argumentsList);
    return target.apply(thisArg, argumentsList) * 2; // 返回值翻倍
  }
};
const doubleSum = new Proxy(sum, handler);
console.log(doubleSum(1, 2)); // 输出: 调用函数 sum,参数: [1, 2] \n 6

(7) 构造函数拦截 (construct trap)

当代理的目标是一个构造函数时,可以拦截 new 操作。

function Person(name) {
  this.name = name;
}
const handler = {
  construct(target, argumentsList) {
    console.log(`创建 Person 实例,名字: ${argumentsList[0]}`);
    return new target(...argumentsList);
  }
};
const ProxiedPerson = new Proxy(Person, handler);
const alice = new ProxiedPerson('Alice'); // 输出: 创建 Person 实例,名字: Alice

3. Proxy 的实际应用场景

  1. 实现响应式框架:如 Vue 3 的核心就是基于 Proxy 构建的,它能高效地监听对象的所有属性变化。
  2. 数据验证库:在设置对象属性时自动进行类型、范围等校验。
  3. 调试和日志:为对象操作添加日志,方便调试。
  4. 访问控制:实现“私有”属性、只读属性或权限控制。
  5. 性能优化:实现懒加载、缓存(Memoization)。
  6. API 代理/适配器:拦截并修改对外部 API 的调用。
  7. 模拟对象 (Mocking):在测试中创建模拟对象。

4. 注意事项

  • 性能Proxy 会带来一定的性能开销,因为它在每次操作时都要执行拦截器函数。对于性能敏感的场景需谨慎使用。
  • 兼容性Proxy 不支持 IE 浏览器。
  • 复杂性:过度使用 Proxy 可能使代码逻辑变得难以追踪和调试。
  • this 问题:在 applyconstruct 陷阱中要特别注意 this 的绑定。

总结

ES6 的 Proxy 是一个元编程工具,它通过拦截对目标对象的底层操作,赋予了开发者前所未有的控制能力。它可以用来实现数据绑定、验证、日志、访问控制、函数拦截等多种高级功能。

它是现代 JavaScript 框架(如 Vue 3)实现响应式系统的关键技术。理解 Proxy 的核心是理解其 handler 对象中的各种“陷阱”(traps)方法,它们定义了如何拦截和自定义对象的行为。

THE END
喜欢就支持一下吧
点赞6 分享