es6——Proxy

本文详细介绍了ES6的Proxy特性,它可以对目标对象进行拦截,实现如this指向、get、set、apply、has等操作的拦截与自定义。通过Proxy,我们可以方便地实现链式调用、属性隐藏、对象属性的验证与保护,甚至在web服务客户端的应用。同时,文章提到了Proxy在处理某些原生对象属性时的限制,并给出了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Proxy 可以理解成在目标对象前架设一个“拦截”层,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制可以对外界的访问进行过滤和改写

let obj = new Proxy({}, {
  get (target, p, receiver) {
    console.log('getter')
    return Reflect.get(target, p, receiver)
  },
  set (target, p, value, receiver) {
    console.log('setter')
    return Reflect.set(target, p, value, receiver)
  },
})

obj.count = 1// setter
console.log(obj.count)// getter 1

同一拦截器函数可以设置拦截多个操作

const handler = {
  get (target, name) {
    if (name === 'prototype') {
      return Object.prototype
    }
    return `hello ${name}`
  },
  apply (target, thisBinding, args) {
    return args[0]
  },
  construct (target, args) {
    return { value: args[1] }
  },
}

let fproxy = new Proxy(function (x, y) {
  return x + y
}, handler)

console.log(fproxy(1, 2))// 1
console.log(new fproxy(1, 2))// {value: 2}
console.log(fproxy.prototype === Object.prototype)// true
console.log(fproxy.foo)// hello foo

this 问题

在 Proxy 代理的的情况下,目标对象内部的 this 关键字会指向 Proxy 代理

const target = {
    test: function() {
        console.log(this === proxy)
    }
}

const proxy = new Proxy(target, {})

target.test()// false
proxy.test()// true

然而,有些原生对象的内部属性只有通过正确的 this 才能获取,所以 Proxy 也无法代理这些原生对象的属性

const target = new Date()
const handler = {}
const proxy = new Proxy(target, handler)

console.log(target.getDate())// 13
console.log(proxy.getDate())// Uncaught TypeError: this is not a Date object.

解决方法:

const target = new Date()

const handler = {
    get(target, prop) {
        if (prop === 'getDate') {
        	// 用 this 绑定原始原始对象解决这个问题
            return target.getDate.bind(target)
        }
        return Reflect.get(target, prop)
    }
}

const proxy = new Proxy(target, handler)

console.log(target.getDate()) // 13
console.log(proxy.getDate()) // 13
方法属性
get(target,propKey,receiver)拦截对象属性的读取(如 proxy.foo),最后一个参数是可选参数,参见下面 Reflect.get 部分
set(target,propKey,value,receiver)拦截对象属性的设置(如 proxy.foo=v),返回一个布尔值
has(target,propKye)拦截 propKey in proxy 的操作,返回一个布尔值
deleteProperty(target,propKey)拦截 delete proxy[propKey] 的操作,返回一个布尔值
ownKeys(target)拦截 Object.getOwnPropertyNames(proxy) Object.getOwnPropertySymbols(proxy) Object.keys(proxy) 返回一个数组
getOwnPropertyDescriptor(target,propKey)拦截 Object.getOwnPropertyDescriptor(proxy,propKey),返回属性的描述对象
defineProperty(target,propKey,propDesc)拦截 Object.defineProperty(proxy,propKey,PropDesc) Object.defineProperties(propxy,propDescs) 返回一个布尔值
preventExtensions(target)拦截 Object.preventExtensions(proxy),返回一个对象
getPrototypeOf(target)拦截 Object.getPrototypeOf(proxy),返回一个对象
isExtensible(target)拦截 Object.isExtensible(proxy),返回一个布尔值
setPrototypeOf(target,proto)拦截 Object.setPrototypeOf(proxy,proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截
apply(target,object,args)拦截 Proxy 实例,并将其作为函数调用的操作,比如 proxy(...args) proxy.call(object,...args) proxy.apply(...)
construct(target,args)拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args)

实例:

get
  • 设置 Proxy 实现链式使用函数名的效果
  const pipe = (function () {
    return (value) => {
      let funcStack = []
      let oproxy = new Proxy({}, {
        get (target, p, receiver) {
          if (p === 'get') {
            return funcStack.reduce((val, fn) => {
              console.log(fn, val)
              /*
              n => n * 2 4
              n => n * n 8
              n => n.toString().split('').reverse().join('') | 0 64 
              */
              return fn(val)
            }, value)
          }
          // 浏览器的全局对象是 window,node 的全局对象是 global
          // 因此要放在页面执行, node 执行会出错
          funcStack.push(window[p])
          return oproxy
        },
      })

      return oproxy
    }
  }())

  // 注意要使用 var ,全局定义变量
  var double = n => n * 2
  var pow = n => n * n
  var reverseInt = n => n.toString().split('').reverse().join('') | 0

  console.log(pipe(4).double.pow.reverseInt.get)// 46

我的优化

let math = {
  double: n => n * 2,
  pow: n => n * n,
  sqrt: n => Math.sqrt(n),
}

let pipe = x => new Proxy({}, {
  get (target, key) {
    if (key === 'end') {
      return x
    }
    return pipe(math[key](x))
  },
})

console.log(`result: ${pipe(4).pow.double.end}`)// result: 32
console.log(`result: ${pipe(4).sqrt.pow.double.end}`)// result: 8
  • 函数传值
function fun(data) {
    return new Proxy({}, {
        get(target, prop, receiver) {
            return `${data}——${prop}`
        }
    })
}
/*proxy.hhh().then(res => {
    console.log(res)
})*/
const instanceFun = fun('pig')

console.log(instanceFun.wenhua)// pig——wenhua

注意:如果一个属性不可配置(configurable)或不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性将会报错

set

  • 验证年龄
  let validator = {
    set (obj, prop, value) {
      if (prop === 'age') {
        if (!Number.isInteger(value)) {
          throw new TypeError('The age is not an integer')
        }
        if (value > 200) {
          throw new TypeError('The age seems invalid')
        }
      }
      obj[prop] = value
    },
  }
  let person = new Proxy({}, validator)
  person.age = 100
  
  // 报错
  // person.age = 201
  // person.age = 1.1
  • 防止内部属性被外部读/写
  const handler = {
    get (target, key) {
      invariant(key, 'get')
      return target[key]
    },
    set (target, key, value) {
      invariant(key, 'set')
      target[key] = value
      return true
    },
  }

  function invariant (key, action) {
    if (key[0] === '_') {
      throw new Error(`Invalid attempt to ${action} private "${key}" property`)
    }
  }

  const proxy = new Proxy({}, handler)
  proxy._prop = 1// Invalid attempt to set private "_prop" property
  console.log(proxy._prop)// Invalid attempt to get private "_prop" property

注意:如果目标对象自身的某个属性不可写也不可配置,那么 set 不得改变这个属性的值,只能返回同样的值,否则报错

apply

apply 方法拦截函数的调用、call 和 apply 操作,可以接收三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组

  // 下面代码,变量 p 是 Proxy 的实例,作为函数调用时就会被 apply 方法拦截,返回一个字符串
  const handler = {
    apply () {
      return 'I am the proxy'
    },
  }
  const target = function () {
    return 'I am the target'
  }

  let p = new Proxy(target, handler)
  console.log(p())// I am the proxy
  • 二倍乘积拦截
  const twice = {
    apply (target, ctx, args) {
      return Reflect.apply(...arguments) * 2
    },
  }

  function sum (left, right) {
    return left + right
  }

  let proxy = new Proxy(sum, twice)
  console.log(proxy(1, 2))// 6
  console.log(proxy.call(null, 4, 3))// 14
  console.log(proxy.apply(null, [5, 9]))// 28

has

用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效,典型操作就是 in 运算符

  • 隐藏某些属性,使其不被 in 运算符发现
  const handler = {
    has (target, key) {
      if (key[0] === '_') {
        return false
      }
      return key in target
    },
  }

  let target = { _prop: 'foo', prop: 'bar' }
  let proxy = new Proxy(target, handler)
  console.log('_prop' in proxy)// false
  console.log('prop' in proxy)// true

  // 注意:has 方法拦截的是 HasProperty 操作,而不是 HasOwnProperty 操作
  // 即 has 方法不判断一个属性是对象自身的属性还是继承的属性
  // has 拦截只对 in 循环生效,对 for ... in 循环不生效
  for (let key in proxy) {
    console.log(proxy[key])// foo bar
  }

construct

拦截 new 命令,接收两个参数:目标对象、构建函数的参数对象

  const p = new Proxy(function () {}, {
    construct (target, argArray, newTarget) {
      console.log(`called: ${argArray.join(',')}`)
      return { value: argArray[0] * 10 }
    },
  })

  console.log((new p(2, 4, 5)).value)// 2,4,5  20

construct 方法返回的必须是一个对象,否则会报错

  const p = new Proxy(function () {}, {
    construct (target, argArray, newTarget) {
      return 1
    },
  })

  new p()// 报错

deleteProperty

拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性就无法被 delete 命令删除

  const handler = {
    deleteProperty (target, key) {
      invariant(key, 'delete')
      return true
    },
  }

  function invariant (key, action) {
    if (key[0] === '_') {
      throw new Error(`Invalid attempt to ${action} private "${key}" property`)
    }
  }

  let target = { _prop: 'foo', prop: 'bar' }
  let proxy = new Proxy(target, handler)
  console.log(delete proxy._prop)// Invalid attempt to delete private "_prop" property
  console.log(delete proxy.prop)// true

注意:目标对象自身的不可配置(configurable)的属性不能被 deleteProperty 方法删除,否则会报错

defineProperty

defineProperty 方法拦截了 Object.defineProperty 操作

new Vue({
  render: h => h(App),
}).$mount('#app')

const handler = {
    defineProperty(target, key, descriptor) {
        return false
    }
}
let target = {}
let proxy = new Proxy(target, handler)
// 'defineProperty' on proxy: trap returned falsish for property 'foo'
proxy.foo = 'bar'

上面代码中,defineProperty 方法返回 false,导致添加新属性会抛出错误

注意:如果目标对象不可扩展(extensible),则 defineProperty 不能增加目标对象中不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则 defineProperty 方法不得改变这两个设置

getOwnPropertyDescriptor

getOwnPropertyDescriptor 方法拦截 Object.getOwnPropertyDescriptor,返回一个属性描述对象或者 undefined

  • 设置对有 _ 的返回 undefined
const handler = {
    getOwnPropertyDescriptor(target, key) {
        if (key[0] === '_') {
            return
        }
        return Object.getOwnPropertyDescriptor(target, key)
    }
}

let target = { _foo: 'bar', baz: 'tar' }
let proxy = new Proxy(target, handler)

let [a, b, c] = [
Object.getOwnPropertyDescriptor(proxy, 'wat'),
Object.getOwnPropertyDescriptor(proxy, '_foo'),
Object.getOwnPropertyDescriptor(proxy, 'baz'),
]

// undefined  undefined  {value: "tar", writable: true, enumerable: true, configurable: true}
console.log(a,b,c)

getPrototypeOf

getPrototypeOf 方法主要用来拦截获取对象原型,具体来说,用于拦截以下操作:
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof

  • 拦截 Object.prototype(),返回 proto 对象
const proto = {}

const proxy = new Proxy({}, {
    getPrototypeOf(target) {
        return proto
    }
})

console.log(Object.getPrototypeOf(proxy) === proto)// true

注意:getPrototypeOf 方法的返回值必须是对象或者 null,否则会报错。另外,如果目标对象不可扩展(extensible),getPrototypeOf 方法必须返回目标对象的原型对象

实例:web 服务的客户端

Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来编写 Web 服务的客户端

const service = createWebService('http://example.com/data')

service.employees().then(json => {
    const employees = JSON.parse(json)
    // ...
})

上面代码新建了一个 Web 服务的接口实例,这个接口实例返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截即可

function createWebservice(baseUrl) {
    return new Proxy({}, {
        get(target, propKey, receiver) {
            return () => httpGet(baseUrl + '/' + propKey)
        }
    })
}

相关文档

Vue 3.0 使用 ES6 Proxy 代替 Object.defineProperty
Proxy 和 Reflect

### ES6Proxy 对象的核心特性与使用方法 #### 1. **Proxy 的基本概念** `Proxy` 是 ECMAScript 6ES6)引入的一种新功能,用于拦截并重新定义对象的基本操作行为。它允许开发者在访问或修改目标对象时插入自定义逻辑[^1]。 --- #### 2. **核心组成部分** `Proxy` 主要由两部分组成: - **目标对象 (`target`):** 被代理的对象。 - **处理器 (`handler`):** 定义了一系列陷阱函数(trap functions),用来捕获对目标对象的操作。 以下是 `Proxy` 构造器的标准语法: ```javascript const proxy = new Proxy(target, handler); ``` 其中,`target` 可以是一个普通的对象、数组甚至函数;而 `handler` 则包含了各种可选的方法来拦截不同的操作。 --- #### 3. **常见的陷阱函数及其用途** ##### (1)数据验证——`set` 方法 当尝试设置某个属性的值时,可以通过 `set` 来执行额外的数据校验逻辑。例如: ```javascript let validator = { set: function (obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new Error('The age must be an integer'); } if (value > 200 || value < 0) { throw new Error('The age seems invalid'); } } obj[prop] = value; return true; } }; let person = new Proxy({}, validator); person.age = 18; // 正常赋值 console.log(person.age); // 输 18 try { person.age = 'young'; // 抛错误 } catch (e) { console.error(e.message); // The age must be an integer } ``` 上述代码展示了如何通过 `set` 实现简单的年龄字段校验。 --- ##### (2)读取控制——`get` 方法 可以利用 `get` 拦截对属性的读取请求,并返回经过加工后的结果。比如隐藏敏感信息或者提供默认值: ```javascript let user = { firstName: 'John', lastName: 'Doe' }; let handlerGet = { get: function (target, key) { if (!(key in target)) { return `${key} is not defined`; } return target[key]; } }; let proxiedUser = new Proxy(user, handlerGet); console.log(proxiedUser.firstName); // John console.log(proxiedUser.email); // email is not defined ``` 此示例说明了如何优雅地处理未定义键的情况[^4]。 --- ##### (3)实例化支持——`construct` 和 `apply` 如果目标本身是个构造函数,则可通过 `construct` 或者对于普通函数调用则借助于 `apply` 进行扩展管理[^3]。下面展示了一个例子,演示如何记录每次类实例化的次数: ```javascript function MyClass() {} MyClass.prototype.incrementCount = function () {}; let countHandler = { construct: function (target, args, newTarget) { const instance = Reflect.construct(...arguments); Object.defineProperty(instance, '_count', { writable: true }); instance._count++; return instance; }, }; let CountableClass = new Proxy(MyClass, countHandler); let a = new CountableClass(); a._count++; // 修改计数器变量 console.log(a._count); // 结果为 1 ``` 这里我们看到即使是在复杂场景下也能灵活运用 Proxies 提供的功能。 --- #### 4. **实际应用案例分析** 为了更直观理解其强大之处,在日常开发过程中经常遇到的需求之一就是实时监控某些特定状态的变化情况。这正是前面提到过的关于工程师信息更新的通知机制: ```javascript var engineer = { name: 'Alice', skills: ['JavaScript'] }; var interceptor = { set: function (receiver, property, value) { console.log(`${property} was updated from ${receiver[property]} to ${value}`); receiver[property] = value; } }; new Proxy(engineer, interceptor).skills.push('Python'); // Output: // skills was updated from ["JavaScript"] to ["JavaScript", "Python"] ``` 这段脚本清楚表明每当技能列表发生变化都会打印相应的日志消息来。 --- ### 总结 综上所述,`Proxy` 不仅赋予了 JavaScript 更加丰富的元编程能力,还极大地增强了应用程序灵活性和可控度。无论是基础的数据验证还是高级别的框架设计都可以找到它的身影[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值