本文深入解析ES6中Proxy对象的get方法,详细说明target、property和receiver三个参数的作用。重点指出在get中直接访问receiver会导致死循环,并探讨当目标对象或其原型链包含getter时可能引发的异常。通过代码示例演示如何利用Object.defineProperty或Object.assign遮蔽原型链上的getter以规避错误,帮助开发者安全使用Proxy进行对象属性拦截。
Proxy与get方法概述
Proxy是ES2015(即ES6)规范中引入的一个内置对象,主要用于自定义对象的基本操作行为。
本文记录了笔者在学习Proxy过程中,针对get方法的一些实践心得与总结。
作为ES2015新增的内置对象,Proxy具备拦截并自定义对象及函数基本操作的能力。它拥有较高的优先级和便利性,为开发者提供了一种解决复杂问题的新途径。
Proxy的get方法专门用于拦截对象属性的读取操作,例如访问obj.key或obj[key]。当在Proxy的handler参数中定义get方法后,每次执行读取操作时,系统会优先调用该get方法,从而允许开发者对读取行为进行自定义拦截。具体实现可参考以下代码:
const obj = { key: 1 }
const proxy = new Proxy(obj, {
get: function(target, property, receiver) {
console.log('get', property)
return target[property]
}
})
console.log(proxy.key)
// get key
// 1
get方法参数解析
get方法接收三个参数:target表示创建Proxy实例时传入的目标对象,在示例中即为obj;property表示当前被读取的属性名,示例中为key;receiver则是Proxy实例本身,即proxy对象。在示例代码中,get方法最后返回了target[property],目的是确保读取操作能正常执行。由于Proxy的get方法最先被调用,其返回值即为读取操作的结果;若此处不返回任何值,读取结果将为undefined。
receiver参数与死循环陷阱
关于receiver参数及潜在的死循环问题
需要特别注意,切勿在get方法中直接读取receiver的属性。因为receiver实质上就是proxy自身,执行receiver.key等同于执行proxy.key,这会再次触发get方法的调用,从而导致无限递归死循环。
const obj = { key: 1 }
const proxy = new Proxy(obj, {
get: function(target, property, receiver) {
console.log(receiver.key)
return target[property]
}
})
console.log(proxy.key)
// 死循环!
原型链getter的影响与处理
原型链上的getter影响
有时开发者会在对象中使用getter和setter来定制属性的赋值与读取逻辑。此时,如果Proxy的get方法内部使用了target[property],该值的获取会受到目标对象自身getter的影响。因此,在调用get方法时,务必检查目标对象中是否存在getter定义。
const obj = {
get key() {
return 'string'
},
set key(value) {
console.log(`key is ${value}, it is a ${typeof value}`)
}
}
const proxy = new Proxy(obj, {
get: function(target, property, receiver) {
if(typeof target[property] !== 'string') {
return target[property]
} else {
throw new TypeError(`The type of ${property} is String!`)
}
}
})
proxy.key = 100
console.log(proxy, obj)
// key is 100, it is a number
// The type of key is String!
在上述示例中,若访问obj的非字符串类型属性会抛出错误。但由于obj中定义了getter,无论给key属性赋予何值,访问key时都会因getter逻辑而抛出错误。
若仅关注目标对象自身的getter,问题尚易处理;但如果目标对象继承自其他对象,情况则变得复杂。请参考以下示例:
const parentObj = {
get key() {
return 'string'
},
set key(value) {
console.log(`key is ${value}, it is a ${typeof value}`)
}
}
const obj = Object.create(parentObj)
const proxy = new Proxy(obj, {
get: function (target, property, receiver) {
if (typeof target[property] !== 'string') {
return target[property]
} else {
throw new TypeError(`The type of ${property} is String!`)
}
}
})
proxy.key = 100
console.log(proxy.key)
// key is 100, it is a number
// The type of key is String!
如代码所示,目标对象obj继承自parentObj,而parentObj中定义了getter方法。此时使用Proxy的get方法仍会报错。在实际应用中,原型链可能很长,getter可能存在于原型链的任何层级,因此使用Proxy的get方法时必须格外小心。
然而,如果通过某种方式遮蔽(override)parentObj上的key属性,即可避免抛出错误。例如,在创建obj时直接声明key属性,代码如下:
const parentObj = {
get key() {
return 'string'
},
set key(value) {
console.log(`key is ${value}, it is a ${typeof value}`)
}
}
const obj = Object.create(parentObj, {
key: {
value: null,
writable: true
}
})
const proxy = new Proxy(obj, {
get: function (target, property, receiver) {
if (typeof target[property] !== 'string') {
return target[property]
} else {
throw new TypeError(`The type of ${property} is String!`)
}
}
})
proxy.key = 100
console.log(proxy.key)
// 100
同样,我们也可以使用Object.defineProperty()和Object.assign()这两个方法达到遮蔽key属性的相同目的:
Object.defineProperty(obj, 'key', {
value: null,
writable: true
})
obj = Object.assign({}, obj, { key: null })
但需注意,使用Object.assign()时不能采用如下写法:
obj = Object.assign(obj, { key: null })
上述写法无法有效遮蔽parentObj上的key属性,使用时仍会抛出错误。
以上内容即为本文的全部讲解,希望对各位的学习有所帮助,也欢迎大家继续支持本站。