JavaScript中的代理(Proxy)与反射(Reflect)

173 阅读2分钟

JavaScript中的代理(Proxy)与反射(Reflect)

代理(Proxy)

Proxy是ES6引入的一个强大特性,它允许你创建一个对象的代理,从而可以拦截和自定义该对象的基本操作。

基本语法

const proxy = new Proxy(target, handler);
  • target: 要代理的目标对象
  • handler: 一个对象,其属性是定义代理行为的函数

常见拦截操作

const handler = {
  // 拦截属性读取
  get(target, prop, receiver) {
    console.log(`Getting property ${prop}`);
    return Reflect.get(...arguments);
  },
  
  // 拦截属性设置
  set(target, prop, value, receiver) {
    console.log(`Setting property ${prop} to ${value}`);
    return Reflect.set(...arguments);
  },
  
  // 拦截in操作符
  has(target, prop) {
    console.log(`Checking if property ${prop} exists`);
    return Reflect.has(...arguments);
  },
  
  // 拦截delete操作
  deleteProperty(target, prop) {
    console.log(`Deleting property ${prop}`);
    return Reflect.deleteProperty(...arguments);
  },
  
  // 拦截函数调用
  apply(target, thisArg, argumentsList) {
    console.log(`Calling function with args: ${argumentsList}`);
    return Reflect.apply(...arguments);
  },
  
  // 拦截new操作符
  construct(target, argumentsList, newTarget) {
    console.log(`Constructing with args: ${argumentsList}`);
    return Reflect.construct(...arguments);
  }
};

使用场景

  1. ​验证和过滤​

    const validator = {
      set(obj, prop, value) {
        if (prop === 'age') {
          if (!Number.isInteger(value)) {
            throw new TypeError('Age must be an integer');
          }
          if (value < 0 || value > 150) {
            throw new RangeError('Age must be between 0 and 150');
          }
        }
        obj[prop] = value;
        return true;
      }
    };
    
    const person = new Proxy({}, validator);
    person.age = 100; // OK
    person.age = 'young'; // 抛出TypeError
    
  2. ​数据绑定和观察​

    function observe(obj, callback) {
      return new Proxy(obj, {
        set(target, prop, value) {
          callback(prop, target[prop], value);
          target[prop] = value;
          return true;
        }
      });
    }
    
    const observed = observe({}, (prop, oldVal, newVal) => {
      console.log(`Property ${prop} changed from ${oldVal} to ${newVal}`);
    });
    
  3. ​实现私有属性​

    const handler = {
      get(target, prop) {
        if (prop.startsWith('_')) {
          throw new Error('Attempt to access private property');
        }
        return target[prop];
      },
      set(target, prop, value) {
        if (prop.startsWith('_')) {
          throw new Error('Attempt to modify private property');
        }
        target[prop] = value;
        return true;
      }
    };
    
    const obj = new Proxy({ _secret: 42, public: 10 }, handler);
    

反射(Reflect)

Reflect是一个内置对象,它提供了拦截JavaScript操作的方法。这些方法与Proxy handler的方法一一对应。

主要方法

Reflect.apply(target, thisArg, args) // 调用函数
Reflect.construct(target, args) // 相当于new target(...args)
Reflect.get(target, property, receiver) // 获取属性值
Reflect.set(target, property, value, receiver) // 设置属性值
Reflect.defineProperty(target, property, descriptor) // 定义属性
Reflect.deleteProperty(target, property) // 删除属性
Reflect.has(target, property) // 检查属性是否存在
Reflect.ownKeys(target) // 获取所有自身属性键
Reflect.getPrototypeOf(target) // 获取原型
Reflect.setPrototypeOf(target, prototype) // 设置原型

使用场景

  1. ​更优雅的函数调用​

    // 传统方式
    Function.prototype.apply.call(Math.max, null, [1, 2, 3]);
    
    // Reflect方式
    Reflect.apply(Math.max, null, [1, 2, 3]);
    
  2. ​属性操作​

    const obj = { a: 1 };
    
    // 传统方式
    'a' in obj;
    delete obj.a;
    
    // Reflect方式
    Reflect.has(obj, 'a');
    Reflect.deleteProperty(obj, 'a');
    
  3. ​与Proxy配合使用​

    const proxy = new Proxy({}, {
      get(target, prop, receiver) {
        console.log(`Getting ${prop}`);
        return Reflect.get(target, prop, receiver);
      }
    });
    

代理与反射的关系

  1. Proxy的handler方法和Reflect方法一一对应
  2. Reflect方法通常用于在Proxy handler中实现默认行为
  3. Reflect方法提供了更规范的API来操作对象

实际应用示例

实现简单的ORM

class Model {
  constructor(data) {
    return new Proxy(this, {
      get(target, prop) {
        if (prop in target) {
          return target[prop];
        }
        if (data[prop] !== undefined) {
          return data[prop];
        }
        return undefined;
      },
      set(target, prop, value) {
        if (prop in target) {
          target[prop] = value;
        } else {
          data[prop] = value;
        }
        return true;
      }
    });
  }
}

class User extends Model {}
const user = new User({ name: 'John', age: 30 });
console.log(user.name); // John
user.age = 31;
console.log(user.age); // 31

实现不可变对象

function createImmutableObject(obj) {
  return new Proxy(obj, {
    get(target, prop) {
      const value = Reflect.get(target, prop);
      return typeof value === 'object' && value !== null 
        ? createImmutableObject(value) 
        : value;
    },
    set() {
      throw new Error('Cannot modify an immutable object');
    },
    deleteProperty() {
      throw new Error('Cannot delete from an immutable object');
    },
    defineProperty() {
      throw new Error('Cannot define property on an immutable object');
    }
  });
}

const immutable = createImmutableObject({ a: 1, b: { c: 2 } });
immutable.a = 2; // Error
immutable.b.c = 3; // Error

注意事项

  1. Proxy只能代理对象,不能代理原始值
  2. Proxy的handler必须返回正确的值类型,否则会抛出TypeError
  3. 不是所有操作都可以被拦截,例如typeofinstanceof
  4. 性能考虑:Proxy操作比直接对象操作要慢,在性能敏感的场景要谨慎使用