前端JS: Proxy 和 Reflect

43 阅读3分钟

Proxy 和 Reflect

1. Proxy(代理)

什么是 Proxy?

Proxy 是 ES6 引入的元编程特性,用于创建一个对象的代理,从而拦截并自定义对象的基本操作(如属性访问、赋值、枚举等)。

基本语法

const proxy = new Proxy(target, handler)

核心 handler 方法(陷阱)

方法触发时机用途
get(target, prop, receiver)读取属性拦截属性访问
set(target, prop, value, receiver)设置属性拦截属性赋值
has(target, prop)in操作符拦截 in检查
deleteProperty(target, prop)delete操作拦截属性删除
ownKeys(target)Object.keys()拦截键枚举
construct(target, args)new操作拦截构造函数调用
apply(target, thisArg, args)函数调用拦截函数调用

使用示例

const target = { name: '小明', age: 20 }

const handler = {
  get(target, prop) {
    console.log(`读取属性: ${prop}`)
    return prop in target ? target[prop] : '属性不存在'
  },
  
  set(target, prop, value) {
    console.log(`设置属性: ${prop} = ${value}`)
    
    // 添加验证逻辑
    if (prop === 'age' && (value < 0 || value > 150)) {
      throw new Error('年龄无效')
    }
    
    target[prop] = value
    return true  // 表示成功
  },
  
  deleteProperty(target, prop) {
    console.log(`删除属性: ${prop}`)
    delete target[prop]
    return true
  }
}

const proxy = new Proxy(target, handler)

console.log(proxy.name)  // 输出: 读取属性: name → 小明
proxy.age = 25           // 输出: 设置属性: age = 25
delete proxy.name        // 输出: 删除属性: name

2. Reflect(反射)

什么是 Reflect?

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

为什么需要 Reflect?

  1. 统一操作:将对象的操作(如 Object.definePropertydelete obj[key])统一为函数形式
  2. 默认行为:为 Proxy 的 handler 提供默认实现
  3. 更好的返回值:提供更合理的返回值(如 Reflect.defineProperty返回布尔值)

Reflect 的常用方法

方法对应操作说明
Reflect.get(target, prop, receiver)target[prop]获取属性
Reflect.set(target, prop, value, receiver)target[prop] = value设置属性
Reflect.has(target, prop)prop in target判断属性
Reflect.deleteProperty(target, prop)delete target[prop]删除属性
Reflect.ownKeys(target)Object.keys(target)获取所有键
Reflect.construct(target, args)new target(...args)构造函数调用

3. Proxy 和 Reflect 的配合使用

最佳实践:在 Proxy 中使用 Reflect

const target = { count: 0 }

const handler = {
  get(target, prop, receiver) {
    console.log(`get ${prop}`)
    // 使用 Reflect 调用默认行为
    return Reflect.get(target, prop, receiver)
  },
  
  set(target, prop, value, receiver) {
    console.log(`set ${prop} = ${value}`)
    // 使用 Reflect 调用默认行为
    return Reflect.set(target, prop, value, receiver)
  }
}

const proxy = new Proxy(target, handler)

为什么要配合使用?

  1. 保持默认行为:Reflect 方法执行了对象的默认操作
  2. 正确处理 receiver:确保 this指向正确
  3. 简化代码:避免手动实现默认逻辑
// ❌ 不推荐的写法
const handler = {
  get(target, prop) {
    return target[prop]  // 丢失了 this 绑定
  }
}

// ✅ 推荐的写法
const handler = {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver)  // 保持正确的 this
  }
}

4. 实际应用场景

场景1:数据验证

const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (typeof value !== 'number' || value < 0) {
        throw new Error('年龄必须是正数')
      }
    }
    return Reflect.set(target, prop, value)
  }
}

const person = new Proxy({}, validator)
person.age = 25  // 成功
person.age = -5  // 抛出错误

场景2:属性观察

function observe(obj, callback) {
  return new Proxy(obj, {
    set(target, prop, value, receiver) {
      const oldValue = target[prop]
      const result = Reflect.set(target, prop, value, receiver)
      
      if (oldValue !== value) {
        callback(prop, oldValue, value)
      }
      
      return result
    }
  })
}

const data = observe({ count: 0 }, (prop, oldVal, newVal) => {
  console.log(`${prop}${oldVal} 变为 ${newVal}`)
})

data.count = 1  // 输出: count 从 0 变为 1

场景3:实现简单的 Vue3 响应式

// 简化版的 reactive
function reactive(target) {
  const handler = {
    get(target, prop, receiver) {
      console.log(`收集依赖: ${String(prop)}`)
      const result = Reflect.get(target, prop, receiver)
      
      // 深层代理
      if (result && typeof result === 'object') {
        return reactive(result)
      }
      
      return result
    },
    
    set(target, prop, value, receiver) {
      const oldValue = target[prop]
      const result = Reflect.set(target, prop, value, receiver)
      
      if (oldValue !== value) {
        console.log(`触发更新: ${String(prop)} = ${value}`)
      }
      
      return result
    }
  }
  
  return new Proxy(target, handler)
}

const state = reactive({ 
  user: { 
    name: '小明',
    age: 20 
  } 
})

console.log(state.user.name)  // 收集依赖: user → 收集依赖: name
state.user.age = 25          // 触发更新: age = 25

5. 重要特性对比

特性Object.defineProperty(Vue2)Proxy(Vue3)
监听范围只能监听属性,需遍历可监听整个对象
监听操作只能监听 get/set可监听13种操作
动态添加需用 Vue.set直接监听
数组监听需重写方法直接监听索引和长度
性能初始化递归,性能较差惰性代理,性能更优

6. 在 Vue 3 响应式中的应用

Vue 3 的响应式系统核心就是 Proxy + Reflect

// Vue 3 reactive 的简化实现
const reactiveMap = new WeakMap()

function reactive(target) {
  // 已经是代理则直接返回
  if (reactiveMap.has(target)) {
    return reactiveMap.get(target)
  }
  
  const handler = {
    get(target, key, receiver) {
      // 使用 Reflect 获取值
      const res = Reflect.get(target, key, receiver)
      
      // 收集依赖
      track(target, key)
      
      // 如果是对象,继续代理
      if (typeof res === 'object' && res !== null) {
        return reactive(res)
      }
      
      return res
    },
    
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      
      if (oldValue !== value) {
        // 触发更新
        trigger(target, key)
      }
      
      return result
    }
  }
  
  const proxy = new Proxy(target, handler)
  reactiveMap.set(target, proxy)
  return proxy
}

总结

  1. Proxy​ 是拦截器:用于拦截和自定义对象的操作

  2. Reflect​ 是操作器:提供操作对象的标准化方法

  3. 配合使用:在 Proxy 的 handler 中使用 Reflect 调用默认行为

  4. 优势

    • 功能更强大(可拦截13种操作)
    • 性能更优(惰性代理)
    • 支持数组和动态属性
  5. 在 Vue 3 中:Proxy + Reflect 实现了更完善的响应式系统

简单记忆

  • Proxy​ = "你想做什么,我都要先过问一下"
  • Reflect​ = "我来帮你执行默认操作"
  • 最佳组合:Proxy 负责拦截,Reflect 负责执行默认行为