3.9Proxy和Reflect
3.9.1Proxy 概述
- 用于修改某些操作的默认行为
- 属于一种“元编程”(meta programming),即对编程语言进行编程
- Proxyk可理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,
- 因此提供了一种机制,可以对外界的访问进行过滤和改写。
- 常见的 Proxy 支持的拦截操作方法
- get(target, propKey, receiver) 拦截对象属性的读取
- set(target, propKey, value, receiver) 拦截对象属性的设置,返回一个布尔值。
- has(target, propKey) 拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。
- deleteProperty(target, propKey) 拦截delete proxy[propKey]的操作,返回一个布尔值。
- apply(target, object, args) 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args) 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
3.9.2Proxy实例方法
get() 用于拦截某个属性的读取操作
var person = { name: "jack" }; var proxy = new Proxy(person, { get: function(target, property) { if (property in target) { return target[property]; } else { throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); proxy.name // "jack" proxy.age // 抛出一个错误 //结果分析:如果访问目标对象不存在的属性,会抛出一个错误。 //如果没有这个拦截函数,访问不存在的属性,只会返回undefined。
set() 来拦截某个属性的赋值操作。
let obj = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('错误信息:不是整数'); } if (value > 200) { throw new RangeError('错误信息:年龄大于200'); } } // 对于age以外的属性,直接保存 obj[prop] = value; } }; let person = new Proxy({}, obj); person.age = 13; person.age // 13 person.age = 'jack' // age不是整数报错 person.age = 300 // age大于200报错
apply() 拦截函数的调用、call和apply操作
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
var twice = { apply (target, ctx, args) { return Reflect.apply(...arguments) * 2; } }; function sum (num) { return num + 1 ; }; var proxy = new Proxy(sum, twice); // 第一个参数相当于原值,第二个参数相当于过滤器,变量proxy相当于过滤后的值 proxy(2) // 6 proxy.call(null, 5, 6) // 22 proxy.apply(null, [7, 8]) // 30
has()
- 用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。
- 典型的操作就是in运算符。
var handler = { has (target, key) { if (key[0] === '_') { return false; } return key in target; } }; var target = { _prop: 'foo', prop: 'foo' }; var proxy = new Proxy(target, handler); console.log( '_prop' in proxy ) // false
construct()
用于拦截new命令。
可以接受两个参数。
- target: 目标对象
- args:构建函数的参数对象
var p = new Proxy(function() {}, { construct: function(target, args) { console.log('called: ' + args.join(', ')); return { value: args[0] * 10 }; } }); new p(1).value // "called: 1" // 10
deleteProperty()
- 方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除
var handler = { deleteProperty (target, key) { invariant(key, 'delete'); return true; } }; function invariant (key, action) { if (key[0] === '_') { throw new Error('删除下划线开头的报错'); } } var target = { _prop: 'foo' }; var proxy = new Proxy(target, handler); delete proxy._prop // 报错内容:删除下划线开头的报错
3.9.3Reflect概述
将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上
Reflect 对象使用函数的方式实现了 Object 的命令式操作。
Reflect.get(target, name, rec)
查找并返回 target 对象的 name 属性。
可以有三个参数,target必须为对象类型,name是target的属性,rec为另一个对象。
let obj = { name: "jack", age: 14, get userInfo(){ return this.name + this.age; } } Reflect.get(obj, 'name'); // "jack" // 当 target 对象中存在getter 方法, getter 方法的 this 会指向 rec参数的name属性 let receiver = { name: "haha", age: 13 } Reflect.get(obj, 'userInfo', receiver); // haha13 // target 对象不存在该属性,返回 undefined Reflect.get(obj, 'aaa'); // undefined // 当 target 不是对象时,会报错 Reflect.get(1, 'name'); // 报错
Reflect.set(target, name, value, receiver)
将 target 的 name 属性设置为 value。
返回值为true 表示修改成功,false 表示失败。
当 target 为不存在的对象时即报错。
let obj= { name: "jack", age: 24, set userInfo(value){ return this.name = value; } } obj.name; // jack Reflect.set(obj, 'name', "haha"); obj.name; // haha // value值为空,表示删除属性 Reflect.set(obj, 'name', ); obj.name; // undefined // 当 target对象有set方法,setter 方法中的指向receiver, 所以修改的实际上是 receiver 的属性 let receiver = { name: 8888888888 } Reflect.set(obj, 'userInfo', "这是修改后的值", receiver); receiver.name; // 这是修改后的值