Proxy是ES6引入的一个强大功能,允许你拦截并自定义对象的基本操作(如属性查找、赋值、枚举、函数调用等)。

// 基本语法
const proxy = new Proxy(target, handler);
 
// target: 被代理的原始对象
// handler: 定义拦截操作的对象

基础用法示例

1. 属性访问拦截

const person = {
  name: 'John',
  age: 30
};
 
const personProxy = new Proxy(person, {
  // 拦截属性读取
  get(target, property, receiver) {
    console.log(`访问属性: ${property}`);
    
    if (property in target) {
      return target[property];
    } else {
      return `属性 ${property} 不存在`;
    }
  },
  
  // 拦截属性设置
  set(target, property, value, receiver) {
    console.log(`设置属性: ${property} = ${value}`);
    
    // 类型验证
    if (property === 'age' && typeof value !== 'number') {
      throw new Error('age必须是数字');
    }
    
    target[property] = value;
    return true; // 表示设置成功
  }
});
 
// 使用代理对象
console.log(personProxy.name);     // 访问属性: name -> "John"
console.log(personProxy.height);   // 访问属性: height -> "属性 height 不存在"
 
personProxy.age = 25;              // 设置属性: age = 25
// personProxy.age = '25';         // 抛出错误: age必须是数字

2. 函数调用拦截

function greet(name) {
  return `Hello, ${name}!`;
}
 
const greetProxy = new Proxy(greet, {
  // 拦截函数调用
  apply(target, thisArg, argumentsList) {
    console.log(`调用函数,参数:`, argumentsList);
    
    // 参数验证
    if (argumentsList.length === 0) {
      return target.call(thisArg, 'World');
    }
    
    // 参数转换
    const processedArgs = argumentsList.map(arg => 
      typeof arg === 'string' ? arg.trim().toLowerCase() : arg
    );
    
    return target.apply(thisArg, processedArgs);
  }
});
 
console.log(greetProxy('Alice'));    // 调用函数,参数: ['Alice'] -> "Hello, alice!"
console.log(greetProxy());           // 调用函数,参数: [] -> "Hello, World!"

Proxy Handler方法详解

1. 属性相关的拦截器

const obj = { x: 1, y: 2 };
 
const proxy = new Proxy(obj, {
  // 拦截属性读取
  get(target, property, receiver) {
    console.log(`get: ${property}`);
    return target[property];
  },
  
  // 拦截属性设置
  set(target, property, value, receiver) {
    console.log(`set: ${property} = ${value}`);
    target[property] = value;
    return true;
  },
  
  // 拦截 in 操作符
  has(target, property) {
    console.log(`has: ${property}`);
    return property in target;
  },
  
  // 拦截 delete 操作
  deleteProperty(target, property) {
    console.log(`delete: ${property}`);
    delete target[property];
    return true;
  },
  
  // 拦截 Object.keys()、for...in 等
  ownKeys(target) {
    console.log('ownKeys called');
    return Object.keys(target);
  },
  
  // 拦截 Object.getOwnPropertyDescriptor()
  getOwnPropertyDescriptor(target, property) {
    console.log(`getOwnPropertyDescriptor: ${property}`);
    return Object.getOwnPropertyDescriptor(target, property);
  }
});
 
// 测试各种操作
proxy.x;              // get: x
proxy.z = 3;          // set: z = 3
'x' in proxy;         // has: x
delete proxy.y;       // delete: y
Object.keys(proxy);   // ownKeys called

2. 函数相关的拦截器

function Calculator() {
  this.result = 0;
}
 
Calculator.prototype.add = function(n) {
  this.result += n;
  return this;
};
 
const CalculatorProxy = new Proxy(Calculator, {
  // 拦截 new 操作符
  construct(target, argumentsList, newTarget) {
    console.log('创建新实例');
    const instance = new target(...argumentsList);
    
    // 为实例添加额外功能
    instance.history = [];
    return instance;
  },
  
  // 拦截函数调用
  apply(target, thisArg, argumentsList) {
    console.log('直接调用构造函数');
    return target.apply(thisArg, argumentsList);
  }
});
 
const calc = new CalculatorProxy();  // 创建新实例
console.log(calc.history);           // []

实际应用场景

1. 数据验证和转换

class User {
  constructor() {
    return new Proxy(this, {
      set(target, property, value) {
        // 数据验证
        switch (property) {
          case 'email':
            if (!/\S+@\S+\.\S+/.test(value)) {
              throw new Error('无效的邮箱格式');
            }
            break;
          case 'age':
            if (value < 0 || value > 150) {
              throw new Error('年龄必须在0-150之间');
            }
            break;
        }
        
        // 数据转换
        if (property === 'name') {
          value = value.trim().toLowerCase();
        }
        
        target[property] = value;
        return true;
      }
    });
  }
}
 
const user = new User();
user.name = '  ALICE  ';      // 自动转换为 'alice'
user.email = 'alice@test.com'; // 验证通过
user.age = 25;                // 验证通过
// user.age = -5;             // 抛出错误

2. 属性的默认值和计算属性

const smartObject = new Proxy({}, {
  get(target, property) {
    // 如果属性不存在,返回默认值
    if (!(property in target)) {
      // 计算属性
      if (property === 'fullName') {
        return `${target.firstName || ''} ${target.lastName || ''}`.trim();
      }
      
      // 默认值
      if (property === 'status') {
        return 'active';
      }
      
      return undefined;
    }
    
    return target[property];
  }
});
 
smartObject.firstName = 'John';
smartObject.lastName = 'Doe';
 
console.log(smartObject.fullName);  // "John Doe"
console.log(smartObject.status);    // "active"

3. API调用的动态方法

const api = new Proxy({}, {
  get(target, property) {
    // 动态生成API方法
    return function(...args) {
      const method = property.startsWith('get') ? 'GET' : 
                    property.startsWith('post') ? 'POST' :
                    property.startsWith('put') ? 'PUT' :
                    property.startsWith('delete') ? 'DELETE' : 'GET';
      
      const endpoint = property
        .replace(/^(get|post|put|delete)/, '')
        .replace(/([A-Z])/g, '/$1')
        .toLowerCase();
      
      console.log(`${method} ${endpoint}`, args);
      
      // 这里可以实际发起HTTP请求
      return Promise.resolve({ method, endpoint, args });
    };
  }
});
 
// 动态API调用
api.getUsers();           // GET /users []
api.postUser({name: 'John'}); // POST /user [{name: 'John'}]
api.getUserById(123);     // GET /user/by/id [123]

4. 观察者模式

function createObservable(target, onChange) {
  return new Proxy(target, {
    set(obj, property, value) {
      const oldValue = obj[property];
      obj[property] = value;
      
      // 触发变化回调
      onChange(property, value, oldValue);
      return true;
    },
    
    deleteProperty(obj, property) {
      const oldValue = obj[property];
      delete obj[property];
      
      onChange(property, undefined, oldValue);
      return true;
    }
  });
}
 
const data = createObservable({}, (property, newValue, oldValue) => {
  console.log(`${property}: ${oldValue} -> ${newValue}`);
});
 
data.name = 'Alice';    // name: undefined -> Alice
data.age = 25;          // age: undefined -> 25
data.name = 'Bob';      // name: Alice -> Bob
delete data.age;        // age: 25 -> undefined

5. 数组的负索引访问

function createArray(arr) {
  return new Proxy(arr, {
    get(target, property) {
      // 处理负索引
      if (typeof property === 'string' && /^-\d+$/.test(property)) {
        const index = target.length + parseInt(property);
        return target[index];
      }
      
      return target[property];
    }
  });
}
 
const arr = createArray([1, 2, 3, 4, 5]);
 
console.log(arr[-1]);   // 5 (最后一个元素)
console.log(arr[-2]);   // 4 (倒数第二个元素)
console.log(arr[0]);    // 1 (正常索引)

Proxy vs Object.defineProperty

1. 功能对比

// Object.defineProperty - 只能拦截属性访问
const obj1 = {};
Object.defineProperty(obj1, 'name', {
  get() { console.log('get name'); return this._name; },
  set(value) { console.log('set name'); this._name = value; }
});
 
// Proxy - 可以拦截更多操作
const obj2 = new Proxy({}, {
  get(target, property) { console.log(`get ${property}`); return target[property]; },
  set(target, property, value) { console.log(`set ${property}`); target[property] = value; return true; },
  has(target, property) { console.log(`has ${property}`); return property in target; },
  deleteProperty(target, property) { console.log(`delete ${property}`); delete target[property]; return true; }
});

2. 数组监听对比

// Object.defineProperty - 无法监听数组变化
const arr1 = [1, 2, 3];
// 无法直接监听 arr1.push(), arr1[0] = 5 等操作
 
// Proxy - 可以完美监听数组
const arr2 = new Proxy([1, 2, 3], {
  set(target, property, value) {
    console.log(`数组变化: ${property} = ${value}`);
    target[property] = value;
    return true;
  }
});
 
arr2.push(4);    // 数组变化: 3 = 4, 数组变化: length = 4
arr2[0] = 10;    // 数组变化: 0 = 10

注意事项和限制

1. 性能考虑

// Proxy会有性能开销
const obj = { x: 1 };
const proxy = new Proxy(obj, {
  get(target, property) {
    // 每次访问都会执行这个函数
    return target[property];
  }
});
 
// 频繁访问时,直接访问原对象会更快
console.time('direct');
for (let i = 0; i < 1000000; i++) {
  obj.x;
}
console.timeEnd('direct');
 
console.time('proxy');
for (let i = 0; i < 1000000; i++) {
  proxy.x;
}
console.timeEnd('proxy');

2. 不可撤销的代理

// 普通Proxy无法撤销
const proxy1 = new Proxy({}, {});
 
// 可撤销的Proxy
const { proxy: proxy2, revoke } = Proxy.revocable({}, {});
 
console.log(proxy2.x);  // 正常访问
revoke();               // 撤销代理
// console.log(proxy2.x);  // TypeError: Cannot perform 'get' on a proxy that has been revoked

Proxy是一个非常强大的特性,可以用来实现很多高级功能,如数据绑定、API包装、调试工具等。但也要注意性能影响和浏览器兼容性(IE不支持)。