Proxy 拦截器
如有错误,麻烦指正,共同学习
Proxy的原意是“拦截”,可以理解为对目标对象的访问和操作之前进行一次拦截。提供了这种机制,所以可以对目标对象进行修改和过滤的操作。
- const proxy = new Proxy({}, {
- get(target, proper(Key) {
- console.log('你的访问被我拦截到了')
- return 1;s
- },
- set(target, properKey, properValue) {
- console.log('你修改这个属性被我拦截到了')
- }
- })
Proxy 实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。
语法:const proxy = new Proxy(target, hanlder)
new Proxy生成一个 proxy的实例, target表示要拦截的目标,可以是对象或函数等。凡是对目标对象的一些操作都会经过拦截器的拦截处理。 hanlder 参数也是一个对象,它表示拦截配置,就如上例所示。
Proxy
实例也可以作为其他对象的原型对象。对这个目标对象进行操作,如果它自身没有设置这个属性,就会去它的原型对象上面寻找,从而出发拦截行为。如下例:
- const proxy = new Proxy({}, {
- get(target, key) {
- consoloe.log(`你访问的属性是${key}`)
- }
- })
- const newObject = Object.create(proxy)
- newObject.a // 你访问的属性是a
提醒
同一个拦截器可以拦截多个操作,只需要在第二个参数(hanlder)配置添加
如果对这个目标对象没有设置拦截行为,则直接落在目标对象上。
Proxy 支持的拦截操作
- get(target, propKey, receiver) 拦截对象属性读取
- set(target, propKey, value, receiver) 拦截对象的属性设置
- has(target, propKey) 拦截
propkey in proxy
- deleteProperty(target, propKey) 拦截
delete proxy[propKey]
- ownKeys(target)
- getOwnPropertyDescriptor(target, propKey) 返回对象属性的描述对象拦截
- defineProperty(target, propKey, propDesc)
- proventExtensions(target)
- getPrototypeOf(target)
- isExtensible(target)
- setPrototypeOf(target, proto)
- apply(target, object, args)
- construct(target, args) 拦截 proxy 实例作为构造函数调用的操作
Proxy 实例的方法
get(target, key): 当访问目标对象属性的时候,会被拦截。
target
: 目标对象key
: 访问的key值
- const proxy = new Proxy({a:1,b:2}, {
- get(target, key) {
- console.log('called')
- return target[key]
- }
- })
- proxy.a // 1
- // called 会被打印出来
上面的代码中,当读取代理对象属性的时候,会被get方法拦截。所以可以在拦截前做一些事情,比如必须访问这个对象存在的属性,如果访问对象不存在的属性就抛出错误! 如下例:
- const obj = {
- name: 'qiqingfu',
- age: 21
- }
- const proxy = new Proxy(obj, {
- get(target, key) {
- if (key in target) {
- return target[key]
- } else {
- throw Error(`${key}属性不存在`)
- }
- }
- })
以上代码读取代理对象的属性,如果存在就正常读取,负责提示错误访问的key值不存在。
如果一个属性不可配置(configurable), 或者不可写(writeble),则该属性不能被代理
- const obj = Object.defineProperties({}, {
- foo: {
- value: 'a',
- writeble: false, // 不可写
- configurable: false, //不可配置
- }
- })
- const proxy = new Proxy(obj, {
- get(target, key) {
- return 'qiqingfu'
- }
- })
- proxy.value // 报错
场景例子:
通过get()方法可以实现一个函数的链式操作
- const pipe = (function(){
- return function (value) {
- const funcStack = []; // 存放函数的数组
- const proxy = new Proxy({}, {
- get(target, fnName) {
- if (fnName === 'get') {
- return funcStack.reduce((val, nextfn) => {
- return fn(val)
- }, value)
- }
- funcStack.push(window[fnName])
- return proxy //返回一个proxy对象,以便链式操作
- }
- })
- return proxy
- }
- }())
-
- var add = x => x * 2;
- var math = y => y + 10;
- pipe(3).add.math.get // 16
set(target, key, value)方法用于拦截某个属性的赋值操作
target
: 目标对象key
: 要设置的key值value
: 设置的value值返回值
: Boolean
假如有一个prosen对象,要设置的值不能小于100,那么就可以使用 set
方法拦截。
- const prosen = {
- a: 101,
- b: 46,
- c: 200
- }
- const proxy = new Proxy(prosen, {
- set(target, key, value) {
- if (value < 100) {
- throw Error(`${value}值不能小于100`)
- }
- target[key] = value
- }
- })
上面代码对prosen对象赋值,我们可以拦截判断它赋值如果小于100就给它提示错误。
使用场景
- 可以实现数据绑定,即数据发生变化时,我们可以拦截到,实时的更新DOM元素。
- 还可以设置对象的内部数据不可被修改,表示这些属性不能被外部访问和修改,这是可以使用
get
和set
, 如下例
规定对象的内部属性以_开头的属性不能进行读写操作。
- const obj = {
- name: 'qiqingfu',
- age: 21,
- _money: -100000,
- _father: 'xxx'
- }
- function isSeal(key) {
- if (key.charAl(0) === '_') {
- return true
- }
- return false
- }
- const proxy = new Proxy(obj, {
- get(target, key) {
- if (isSeal(key)) {
- throw Error(`${key},为内部属性,不可以读取`)
- }
- return target[key]
- },
- set(target, key, value) {
- if (isSeal(key)) {
- throw Error(`${key},为内部属性,不可以修改`)
- }
- target[key] = value
- return true
- }
- })
以上代码obj对象设置了内部属性,以_开头的不支持读写。那么可以使用Proxy对其进行拦截判断。get和set中的key属性如果是以_开头的属性就提示错误。 set
方法修改完值后,返回的是一个布尔值。 true成功,反则false为修改失败。
apply(target, context, args) 方法可以拦截函数的调用,call()、apply()
target
: 目标对象,context
: 目标对象的上下文对象args
: 函数调用时的参数数组
- const proxy = new Proxy(function(){}, {
- apply(target, context, args) {
- console.log(target, 'target')
- console.log(context, 'context')
- console.log(args, 'args')
- }
- })
- const obj = {
- a: 1
- }
- proxy.call(obj,1,2,3)
上面的代码是拦截一个函数的执行,分别打印:
target -> function(){}
: 目标对象
context -> {a: 1}
: 目标对象的上下文对象,也就是函数的调用者,这里我们使用call
,让obj对象来调用这个函数。
args -> [1,2,3]
: 目标对象函数调用时我们传递的参数,这里会以数组的形式接受。
例子:
再说下面一个例子之前,先了解一下Reflect.apply()
, 下面是 MDN
的解释Reflect.apply() 通过指定的参数列表发起对目标(target)函数的调用。
语法: Reflect.apply(target, context, args)
target
: 目标函数context
: 目标函数执行的上下文args
: 函数调用时传入的实参列表,该列表应该是一个类数组的对象
该方法和ES5的 function.prototype.apply()
方法类似。
下面对 sum
函数的调用进行拦截,并且将函数的执行结果 *2
- const sum = (num1, num2) => {
- return num1 + num2
- }
- const proxy = new Proxy(sum, {
- apply(target, context, args) {
- // 我们可以通过 Reflect.apply()来调用目标函数
- return Reflect.apply(...arguments) * 2
- }
- })
-
- proxy(3,4) // 14
以上代码是对 sum
函数进行代理,并且将其执行结果 * 2
has(target, key ) 方法即拦截 hasProperty操作, 判断对象是否具有某个属性时,这个方法会生效。
target
: 目标对象,key
: 对象的属性
返回值是一个布尔值
如果原对象不可配置或者禁止扩展, 那么has拦截会报错。 for in循环虽然也有 in 操作符,但是has对 for in 循环不生效.
has在什么情况下会进行拦截:
- 属性查询: 例如 foo in window
- 继承属性查询: foo in Object.create(proxy)
- with检查: with(proxy) {}
- Reflect.has()
例1:
使用 has方法隐藏属性,使其不被 in
操作符发现。 就比如说对象以_开头的属性不能被发现。
- const prosen = {
- name: 'qiqingfu',
- _age: 21
- }
- const proxy = new Proxy(prosen, {
- has(target, key) {
- if (key.chatAt(0) === '_') {
- return false
- }
- return key in target
- }
- })
例2: with检查
with的定义总结
在with语句块中,只是改变了对变量的遍历顺序,由原本的从执行环境开始变为从with语句的对象开始。当尝试在with语句块中修改变量时,会搜索with语句的对象是否有该变量,有就改变对象的值,没有就创建,但是创建的变量依然属于with语句块所在的执行环境,并不属于with对象。
- 离开with语句块后,遍历顺序就会再次变成从执行环境开始。
with语句接收的对象会添加到作用域链的前端并在代码执行完之后移除。
- let a = 'global a'
- const obj = {
- a: 1,
- b: 2
- }
- const fn = key => {
- console.log(key)
- }
- const proxy = new Proxy(obj, {
- has(target, key) {
- console.log(target, 'target')
- console.log(key, 'key')
- }
- })
- with(proxy) {
- fn('a')
- }
- //依此打印
- // {a: 1, b: 2} target
- // fn key
- // a
以上代码是对obj对象进行代理, 通过with
检查, 访问代理对象的 a
属性会被 has
方法拦截。那么拦截的第一个target
就是目标对象, 而第二个参数key
是访问 a
时的with语句块所在的执行环境。
construct(target, args) 方法用于拦截 new 命令。
target
: 目标函数,args
: 构造函数的参数对象
返回值必须是一个 对象
, 否则会报错。
- const proxy = new Proxy(function() {}, {
- construct(target, args) {
- console.log(target, 'target')
- console.log(args, 'args')
- return new target(args)
- }
- })
- new proxy(1,2)
-
- // function() {} 'target'
- // [1,2] 'args'
如果返回值不是对象会报错
deleteProperty(target, key) 拦截对象的 delete操作
target
: 目标对象key
: 删除的哪个key值返回值:
布尔值, true
成功,false
失败
目标对象不可配置(configurable)属性不能被deleteProperty删除, 否则会报错
- const obj = Object.defineProperties({}, {
- a: {
- value: 1,
- configurable: false,
- },
- b: {
- value: 2,
- configurable: true
- }
- })
- const proxy = new Proxy(obj, {
- deleteProperty(target, key) {
- delete target[key]
- return true;
- }
- })
-
- delete proxy.a // 报错
- delete proxy.b // true
以上代码拦截 obj
对象, 当进行删除不可配置的属性a
时,会报错。删除b
属性时则成功。
应用场景:
我们可以指定内置属性不可被删除。如以_开头的属性不能被删除
- const obj = {
- _a: 'a',
- _b: 'b',
- c: 'c'
- }
- const proxy = new Proxy(obj, {
- deleteProperty(target, key) {
- if (key.charAt(0) === '_') {
- throw Error(`${key}属性不可被删除`)
- return false
- }
- delete target[key]
- return true
- }
- })
defindProperty(target, key, descriptor)方法拦截Object.defindProperty()操作
target
: 目标对象,key
: 目标对象的属性descriptor
: 要设置的描述对象返回值
: 布尔值, true
添加属性成功, false
则会报错
- const proxy = new Proxy({}, {
- defineProperty(target, key, descriptor) {
- console.log(target, 'target')
- console.log(key, 'key')
- console.log(descriptor, 'descriptor')
- return true
- }
- })
- Object.defineProperty(proxy, 'a', {
- value: 1
- })
以上代码是拦截一个对象的Object.defindProperty()
添加属性的操作, 如果返回值为true,表示添加成功。返回值false则会报错。
以上代码的执行结果:
getPrototypeOf(target) 方法,用来拦截获取对象原型。
target
: 代理对象
可以拦截一下获取原型的操作:
- Object.prototype. __ proto __
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf() 获取一个对象的原型对象
- instance 操作符
Object.prototype.isPrototypeOf() 方法
检测一个对象的原型链上有没有这个对象
语法: Objectactive.isPrototypeOf(object), 检测object
对象的原型链上有没有Objectactive
这个对象, 如果有返回true
, 否则返回false
- const Objectactive = {a: 1}
- const object = Object.create(Objectactive)
- Objectactive.isPrototypeOf(object) // true
以上代码 Objectactive
作为 object
的原型对象,然后通过 isPrototypeOf
检测object
对象的原型链上有没有Objectactive
这个对象。 理所当然返回 true
使用 getPrototypeOf()拦截
- const Objectactive = {a: 1}
- const object = Object.create(Objectactive)
- const proxy = new Proxy(object, {
- getPrototypeOf(target) {
- console.log(target, 'target')
- return Object.getPrototypeOf(target)
- }
- })
- let bl = Objectactive.isPrototypeOf(proxy)
- console.log(bl)
-
- // 依此打印结果:
- /*
- {
- __proto__:
- a: 1,
- __proto__: Object
- } 'target'
-
- true
- */
以上代码对 object
对象进行代理,当访问原型对象时,通过getPrototypeOf()
方法拦截,target
就是代理对象。
getPrototypeOf()方法的返回值必须是 null 或者对象,否则报错。
isExtensible(target) 方法拦截 Object.isExtensible()方法
Object.isExtensible()
方法返回一个布尔值,其检查一个对象是否可扩展。
target
: 目标对象
isExtensible()方法有一个强限制,它的返回值必须与目标对象的 isExtensible属性保持一致。
- const testObj = {
- name: 'apy'
- }
- const proxy = new Proxy(testObj, {
- isExtensible(target) {
- console.log('拦截对象的isExtensible操作')
- return true; // 这里要返回true, 因为目标对象现在是可扩展的,如果返回 false会报错
- }
- })
- console.log(Object.isExtensible(proxy))
-
- // 打印:
-
- // 拦截对象的isExtensible操作
- // true
以上代码通过Object.isExtensible()
检测一个对象是否可扩展,会被配置选项中的 isExtensible
方法拦截。
那么什么情况下可以 return false
呢
Object.preventExtensions(object)
: 将一个对象设置为不可扩展的
- const testObj = {
- name: 'apy'
- }
- Object.preventExtensions(testObj) // 将 testObj对象设置为不可扩展
-
- const proxy = new Proxy(testObj, {
- isExtensible(target) {
- console.log('拦截对象的isExtensible操作')
- return false // 因为testObj对象不可扩展,返回值要和目标对象的 Object.isExtensible一致。
- }
- })
-
- Object.isExtensible(testObj)
以上代码通过 proxy拦截对象的 Object.isExtensible
方法, 并且拦截的返回值与Object.isExtensible
一致。否则报错
ownKeys(target)方法用于拦截对象自身的属性读取操作
target
: 目标对象返回值
: Array<String, Symbol>, 返回值为数组,且数组中只能包含字符串或Symbol类型的
会被 ownKeys 拦截的读取操作
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
使用Object.keys
方法时,有三类属性会被 ownKeys 过滤掉,并不会返回.
- 目标对象
target
上压根不存在的属性 - 属性名为 Symbol
- 还有就是目标对象上不可遍历的属性
- const obj = {
- a: 1,
- b: 2,
- [Symbol.for('c')]: 3
- }
- Object.defineProperty(obj, 'd', {
- value: 4,
- enumerable: false
- })
- const proxy = new Proxy(obj, {
- ownKeys(target) {
- return ['a', 'b', [Symbol('c')], 'd', 'e']
- }
- })
- Object.keys(obj).forEach(key => {
- console.log(key)
- })
- // a
- // b
以上代码定义了一个 obj
对象, 有其属性a, b, [Symbol],d
。并且d
属性是不可扩展的。那么 ownKeys
方法显式返回 不可遍历的属性(d)
、Symbol
和不存在的属性e
都会被过滤掉,那么最终返回a和b
。
注意:
- 如果目标对象包含不可配置(configurable)的属性,那么该属性必须被 ownkeys方法返回。
- 如果目标对象是不可扩展(preventExtensions)的对象,那么 ownkeys返回必须返回这个对象的原有属性,不能包含额外的属性。
setPrototypeOf(target, proto) 方法拦截 Object.setPrototypeOf方法
target
: 目标对象proto
: 要设置的原型对象返回值
布尔值
设置一个对象的原型对象操作,会被 setPrototypeOf
拦截。
- const obj = {}
- const proxy = new Proxy(obj, {
- setPrototypeOf(target, proto) {
- console.log('拦截设置原型操作')
- // 内部手动设置原型,并且返回 boolean
- return Object.setPrototype(target, proto)
- }
- })
- Object.setPrototypeOf(proxy, {a: 1})
以上代码拦截Object.setPrototypeOf
方法,所以会打印 拦截设置原型操作
使用场景, 禁止修改一个对象的原型,否则报错
如上例子,拦截一个修改对象原型的操作,抛出相应的错误就可以。
- const foo = {}
- const proxy = new Proxy(foo, {
- setPrototypeOf(target, key) {
- throw Error(`${target}不可以修改原型对象`)
- }
- })
- Object.setPrototypeOf(proxy, {a: 1}) // 报错