icon
代理与反射

代理与反射

Proxy 和 Reflect 是 ES6 引入的强大特性,用于拦截和自定义对象的基本操作。

Proxy

Proxy 用于创建一个对象的代理,可以拦截并自定义对象的基本操作。

const target = {
  name: 'Alice',
  age: 25
};

const handler = {
  get: function(target, prop) {
    console.log(`访问属性: ${prop}`);
    return target[prop];
  },
  set: function(target, prop, value) {
    console.log(`设置属性: ${prop} = ${value}`);
    target[prop] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // 访问属性: name, 输出: Alice
proxy.age = 26;          // 设置属性: age = 26

Proxy Handler 方法

  1. get:拦截属性读取
const handler = {
  get: function(target, prop, receiver) {
    if (prop in target) {
      return target[prop];
    }
    return `属性 ${prop} 不存在`;
  }
};

const obj = new Proxy({ name: 'Alice' }, handler);
console.log(obj.name);     // 'Alice'
console.log(obj.age);      // '属性 age 不存在'
  1. set:拦截属性设置
const handler = {
  set: function(target, prop, value, receiver) {
    if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
      throw new Error('Invalid age');
    }
    target[prop] = value;
    return true; // 表示设置成功
  }
};

const obj = new Proxy({}, handler);
obj.age = 25;    // 成功
obj.age = -1;    // Error: Invalid age
  1. has:拦截 in 操作符
const handler = {
  has: function(target, prop) {
    return prop.startsWith('_') ? false : prop in target;
  }
};

const obj = new Proxy({ name: 'Alice', _secret: 'hidden' }, handler);
console.log('name' in obj);    // true
console.log('_secret' in obj); // false
  1. deleteProperty:拦截 delete 操作
const handler = {
  deleteProperty: function(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('Cannot delete private property');
    }
    delete target[prop];
    return true;
  }
};

const obj = new Proxy({ name: 'Alice', _secret: 'hidden' }, handler);
delete obj.name;     // 成功
delete obj._secret;  // Error
  1. ownKeys:拦截 Object.keys() 等操作
const handler = {
  ownKeys: function(target) {
    return Object.keys(target).filter(key => !key.startsWith('_'));
  }
};

const obj = new Proxy({ name: 'Alice', _secret: 'hidden', age: 25 }, handler);
console.log(Object.keys(obj)); // ['name', 'age']
  1. apply:拦截函数调用
const handler = {
  apply: function(target, thisArg, argumentsList) {
    console.log('函数被调用');
    return target.apply(thisArg, argumentsList);
  }
};

function sum(a, b) {
  return a + b;
}

const proxySum = new Proxy(sum, handler);
console.log(proxySum(1, 2)); // 函数被调用, 输出: 3
  1. construct:拦截 new 操作
const handler = {
  construct: function(target, argumentsList, newTarget) {
    console.log('构造函数被调用');
    return new target(...argumentsList);
  }
};

function Person(name) {
  this.name = name;
}

const ProxyPerson = new Proxy(Person, handler);
const person = new ProxyPerson('Alice'); // 构造函数被调用

Reflect

Reflect 提供了操作对象的静态方法,与 Proxy 的方法一一对应。

const obj = { name: 'Alice' };

// 使用 Reflect
Reflect.get(obj, 'name');           // 'Alice'
Reflect.set(obj, 'age', 25);        // true
Reflect.has(obj, 'name');           // true
Reflect.deleteProperty(obj, 'age'); // true
Reflect.ownKeys(obj);               // ['name']

Reflect 与 Proxy 结合

Reflect 通常与 Proxy 一起使用,提供默认行为:

const handler = {
  get: function(target, prop, receiver) {
    console.log(`获取属性: ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set: function(target, prop, value, receiver) {
    console.log(`设置属性: ${prop} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
};

const obj = new Proxy({ name: 'Alice' }, handler);
obj.name;        // 获取属性: name
obj.age = 25;    // 设置属性: age = 25

实际应用

  1. 数据验证
function createValidator(target) {
  return new Proxy(target, {
    set: function(target, prop, value) {
      if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
        throw new Error('Invalid age');
      }
      if (prop === 'email' && !value.includes('@')) {
        throw new Error('Invalid email');
      }
      return Reflect.set(target, prop, value);
    }
  });
}

const user = createValidator({});
user.age = 25;        // 成功
user.age = -1;        // Error
user.email = 'test';  // Error
user.email = '[email protected]'; // 成功
  1. 属性访问日志
function createLogger(target) {
  return new Proxy(target, {
    get: function(target, prop) {
      console.log(`读取: ${prop}`);
      return Reflect.get(target, prop);
    },
    set: function(target, prop, value) {
      console.log(`写入: ${prop} = ${value}`);
      return Reflect.set(target, prop, value);
    }
  });
}

const obj = createLogger({ name: 'Alice' });
obj.name;        // 读取: name
obj.age = 25;    // 写入: age = 25
  1. 默认值处理
function createDefaultObject(defaultValue) {
  return new Proxy({}, {
    get: function(target, prop) {
      return prop in target ? target[prop] : defaultValue;
    }
  });
}

const obj = createDefaultObject(0);
console.log(obj.count);     // 0
obj.count = 5;
console.log(obj.count);     // 5
console.log(obj.unknown);  // 0
  1. 负索引数组
function createNegativeArray(array) {
  return new Proxy(array, {
    get: function(target, prop, receiver) {
      const index = Number(prop);
      if (index < 0) {
        return target[target.length + index];
      }
      return Reflect.get(target, prop, receiver);
    }
  });
}

const arr = createNegativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
  1. 函数参数验证
function createValidatedFunction(fn, validator) {
  return new Proxy(fn, {
    apply: function(target, thisArg, argumentsList) {
      if (validator) {
        validator(argumentsList);
      }
      return Reflect.apply(target, thisArg, argumentsList);
    }
  });
}

function sum(a, b) {
  return a + b;
}

const validatedSum = createValidatedFunction(sum, (args) => {
  if (args.length !== 2) {
    throw new Error('需要两个参数');
  }
  if (typeof args[0] !== 'number' || typeof args[1] !== 'number') {
    throw new Error('参数必须是数字');
  }
});

console.log(validatedSum(1, 2));     // 3
validatedSum(1);                     // Error
validatedSum('1', 2);                // Error
  1. 响应式数据
function reactive(target) {
  const handlers = [];
  
  const proxy = new Proxy(target, {
    set: function(target, prop, value) {
      const oldValue = target[prop];
      const result = Reflect.set(target, prop, value);
      if (oldValue !== value) {
        handlers.forEach(handler => handler(prop, value, oldValue));
      }
      return result;
    }
  });
  
  proxy.onChange = function(handler) {
    handlers.push(handler);
  };
  
  return proxy;
}

const data = reactive({ count: 0 });
data.onChange((prop, newVal, oldVal) => {
  console.log(`${prop} changed from ${oldVal} to ${newVal}`);
});

data.count = 1; // count changed from 0 to 1
data.count = 5; // count changed from 1 to 5

Reflect 的返回值

Reflect 方法返回布尔值表示操作是否成功:

const obj = { name: 'Alice' };

// 成功时返回 true
Reflect.set(obj, 'age', 25);        // true
Reflect.deleteProperty(obj, 'age'); // true

// 失败时返回 false(不会抛出错误)
const frozen = Object.freeze({ name: 'Alice' });
Reflect.set(frozen, 'name', 'Bob'); // false
Reflect.deleteProperty(frozen, 'name'); // false

注意事项

  1. Proxy 只能代理对象,不能代理原始值
  2. Proxy 的 handler 方法必须返回正确的值(如 set 返回布尔值)
  3. 使用 Reflect 可以保持默认行为
  4. Proxy 的性能比直接访问属性慢,但提供了强大的拦截能力