对于大型的代码库,当属性名称存在书写错误时(不会抛错)会导致严重的问题。这时使用 get 代理陷阱验证对象结构(Object Shape),访问不存在的属性时就抛出错误,使对象结构验证变得简单。
get 陷阱函数会在读取属性时被调用,即使该属性在对象中并不存在,它能接受三个参数:
trapTarget :将会被读取属性的对象( 即代理的目标对象)
key :需要读取的属性的键( 字符串类型或符号类型)
receiver :操作发生的对象( 通常是代理对象)
Reflect.get()方法接受与之相同的参数,并返回默认属性的默认值。
let proxyObj = new Proxy(targetObj, { set: set, get: get }); /* 定义 get 陷阱函数 */ function get(trapTarget, key, receiver) { if (!(key in receiver)) { throw new TypeError("Property " + key + " doesn't exist."); } return Reflect.get(trapTarget, key, receiver); } console.log(proxyObj.count); // 123 console.log(proxyObj.newcount) // TypeError: Property newcount doesn't exist.
这段代码允许添加新的属性,并且此后可以正常读取该属性的值,但当读取的属性并
不存在时,程序抛出了一个错误,而不是将其默认为undefined。
还可以使用 has 陷阱验证in运算符,使用 deleteProperty 陷阱函数避免属性被delete删除。
注:in运算符用于判断对象中是否存在某个属性,如果自有属性或原型属性匹配这个名称字符串或Symbol,那么in运算符返回 true。
targetObj = { name: 'targetObject' }; console.log("name" in targetObj); // true console.log("toString" in targetObj); // true
其中 name 是对象自身的属性,而 toString 则是原型属性( 从 Object 对象上继承而来),所以检测结果都为 true。
has 陷阱函数会在使用in运算符时被调用,并且会传入两个参数(同名反射Reflect.has()方法也一样):
trapTarget :需要读取属性的对象( 代理的目标对象)
key :需要检查的属性的键( 字符串类型或 Symbol符号类型)
deleteProperty 陷阱函数会在使用delete运算符去删除对象属性时下被调用,并且也会被传入两个参数(Reflect.deleteProperty() 方法也接受这两个参数):
trapTarget :需要删除属性的对象( 即代理的目标对象) ;
key :需要删除的属性的键( 字符串类型或符号类型) 。
一些思考:分析过 Vue 源码的都了解过,给一个 Vue 实例中挂载的 data,是通过Object.defineProperty代理 vm._data 中的对象属性,实现双向绑定...... 同理可以考虑使用 ES6 的 Proxy 的 get 和 set 陷阱实现这个代理。
三、对象属性陷阱
3.1 数据属性与访问器属性
ES5 最重要的特征之一就是引入了 Object.defineProperty() 方法定义属性的特性。属性的特性是为了实现javascript引擎用的,属于内部值,因此不能直接访问他们。
属性分为数据属性和访问器属性。使用Object.defineProperty()方法修改数据属性的特性值的示例如下:
let obj1 = { name: 'myobj', } /* 数据属性*/ Object.defineProperty(obj1,'name',{ configurable: false, // default true writable: false, // default true enumerable: true, // default true value: 'jenny' // default undefined }) console.log(obj1.name) // 'jenny'
其中[[Configurable]] 表示能否通过 delete 删除属性从而重新定义为访问器属性;[[Enumerable]] 表示能否通过for-in循环返回属性;[[Writable]] 表示能否修改属性的值; [[Value]] 包含这个属性的数据值。
对于访问器属性,该属性不包含数据值,包含一对getter和setter函数,定义访问器属性必须使用Object.defineProperty()方法:
let obj2 = { age: 18 } /* 访问器属性 */ Object.defineProperty(obj2,'_age',{ configurable: false, // default true enumerable: false, // default true get () { // default undefined return this.age }, set (num) { // default undefined this.age = num } }) /* 修改访问器属性调用 getter */ obj2._age = 20 console.log(obj2.age) // 20 /* 输出访问器属性 */ console.log(Object.getOwnPropertyDescriptor(obj2,'_age')) // { get: [Function: get], // set: [Function: set], // enumerable: false, // configurable: false }
[[Get]] 在读取属性时调用的函数, [[Set]] 再写入属性时调用的函数。使用访问器属性的常用方式,是设置一个属性的值导致其他属性发生变化。
3.2 检查属性的修改
代理允许你使用 defineProperty 同名函数陷阱函数拦截Object.defineProperty()的调用,defineProperty 陷阱函数接受下列三个参数:
trapTarget :需要被定义属性的对象( 即代理的目标对象);
key :属性的键( 字符串类型或符号类型);
descriptor :为该属性准备的描述符对象。