当前位置:   article > 正文

ES6 Proxy拦截器详解

proxy可以修改原型对象吗

Proxy 拦截器

如有错误,麻烦指正,共同学习

Proxy的原意是“拦截”,可以理解为对目标对象的访问和操作之前进行一次拦截。提供了这种机制,所以可以对目标对象进行修改和过滤的操作。

  1. const proxy = new Proxy({}, {
  2. get(target, proper(Key) {
  3. console.log('你的访问被我拦截到了')
  4. return 1;s
  5. },
  6. set(target, properKey, properValue) {
  7. console.log('你修改这个属性被我拦截到了')
  8. }
  9. })

Proxy 实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义。

语法:
const proxy = new Proxy(target, hanlder)

new Proxy生成一个 proxy的实例, target表示要拦截的目标,可以是对象或函数等。凡是对目标对象的一些操作都会经过拦截器的拦截处理。 hanlder 参数也是一个对象,它表示拦截配置,就如上例所示。

Proxy 实例也可以作为其他对象的原型对象。对这个目标对象进行操作,如果它自身没有设置这个属性,就会去它的原型对象上面寻找,从而出发拦截行为。如下例:

  1. const proxy = new Proxy({}, {
  2. get(target, key) {
  3. consoloe.log(`你访问的属性是${key}`)
  4. }
  5. })
  6. const newObject = Object.create(proxy)
  7. 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值

  1. const proxy = new Proxy({a:1,b:2}, {
  2. get(target, key) {
  3. console.log('called')
  4. return target[key]
  5. }
  6. })
  7. proxy.a // 1
  8. // called 会被打印出来

上面的代码中,当读取代理对象属性的时候,会被get方法拦截。所以可以在拦截前做一些事情,比如必须访问这个对象存在的属性,如果访问对象不存在的属性就抛出错误! 如下例:

  1. const obj = {
  2. name: 'qiqingfu',
  3. age: 21
  4. }
  5. const proxy = new Proxy(obj, {
  6. get(target, key) {
  7. if (key in target) {
  8. return target[key]
  9. } else {
  10. throw Error(`${key}属性不存在`)
  11. }
  12. }
  13. })

以上代码读取代理对象的属性,如果存在就正常读取,负责提示错误访问的key值不存在。

如果一个属性不可配置(configurable), 或者不可写(writeble),则该属性不能被代理

  1. const obj = Object.defineProperties({}, {
  2. foo: {
  3. value: 'a',
  4. writeble: false, // 不可写
  5. configurable: false, //不可配置
  6. }
  7. })
  8. const proxy = new Proxy(obj, {
  9. get(target, key) {
  10. return 'qiqingfu'
  11. }
  12. })
  13. proxy.value // 报错
场景例子:

通过get()方法可以实现一个函数的链式操作

  1. const pipe = (function(){
  2. return function (value) {
  3. const funcStack = []; // 存放函数的数组
  4. const proxy = new Proxy({}, {
  5. get(target, fnName) {
  6. if (fnName === 'get') {
  7. return funcStack.reduce((val, nextfn) => {
  8. return fn(val)
  9. }, value)
  10. }
  11. funcStack.push(window[fnName])
  12. return proxy //返回一个proxy对象,以便链式操作
  13. }
  14. })
  15. return proxy
  16. }
  17. }())
  18. var add = x => x * 2;
  19. var math = y => y + 10;
  20. pipe(3).add.math.get // 16

set(target, key, value)方法用于拦截某个属性的赋值操作

target: 目标对象
key: 要设置的key值
value: 设置的value值
返回值: Boolean

假如有一个prosen对象,要设置的值不能小于100,那么就可以使用 set方法拦截。

  1. const prosen = {
  2. a: 101,
  3. b: 46,
  4. c: 200
  5. }
  6. const proxy = new Proxy(prosen, {
  7. set(target, key, value) {
  8. if (value < 100) {
  9. throw Error(`${value}值不能小于100`)
  10. }
  11. target[key] = value
  12. }
  13. })

上面代码对prosen对象赋值,我们可以拦截判断它赋值如果小于100就给它提示错误。

使用场景

  • 可以实现数据绑定,即数据发生变化时,我们可以拦截到,实时的更新DOM元素。
  • 还可以设置对象的内部数据不可被修改,表示这些属性不能被外部访问和修改,这是可以使用getset, 如下例

规定对象的内部属性以_开头的属性不能进行读写操作。

  1. const obj = {
  2. name: 'qiqingfu',
  3. age: 21,
  4. _money: -100000,
  5. _father: 'xxx'
  6. }
  7. function isSeal(key) {
  8. if (key.charAl(0) === '_') {
  9. return true
  10. }
  11. return false
  12. }
  13. const proxy = new Proxy(obj, {
  14. get(target, key) {
  15. if (isSeal(key)) {
  16. throw Error(`${key},为内部属性,不可以读取`)
  17. }
  18. return target[key]
  19. },
  20. set(target, key, value) {
  21. if (isSeal(key)) {
  22. throw Error(`${key},为内部属性,不可以修改`)
  23. }
  24. target[key] = value
  25. return true
  26. }
  27. })

以上代码obj对象设置了内部属性,以_开头的不支持读写。那么可以使用Proxy对其进行拦截判断。get和set中的key属性如果是以_开头的属性就提示错误。 set方法修改完值后,返回的是一个布尔值。 true成功,反则false为修改失败。


apply(target, context, args) 方法可以拦截函数的调用,call()、apply()

target: 目标对象,
context: 目标对象的上下文对象
args: 函数调用时的参数数组

  1. const proxy = new Proxy(function(){}, {
  2. apply(target, context, args) {
  3. console.log(target, 'target')
  4. console.log(context, 'context')
  5. console.log(args, 'args')
  6. }
  7. })
  8. const obj = {
  9. a: 1
  10. }
  11. 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

  1. const sum = (num1, num2) => {
  2. return num1 + num2
  3. }
  4. const proxy = new Proxy(sum, {
  5. apply(target, context, args) {
  6. // 我们可以通过 Reflect.apply()来调用目标函数
  7. return Reflect.apply(...arguments) * 2
  8. }
  9. })
  10. 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 操作符发现。 就比如说对象以_开头的属性不能被发现。

  1. const prosen = {
  2. name: 'qiqingfu',
  3. _age: 21
  4. }
  5. const proxy = new Proxy(prosen, {
  6. has(target, key) {
  7. if (key.chatAt(0) === '_') {
  8. return false
  9. }
  10. return key in target
  11. }
  12. })

例2: with检查

with的定义总结

  • 在with语句块中,只是改变了对变量的遍历顺序,由原本的从执行环境开始变为从with语句的对象开始。当尝试在with语句块中修改变量时,会搜索with语句的对象是否有该变量,有就改变对象的值,没有就创建,但是创建的变量依然属于with语句块所在的执行环境,并不属于with对象。

  • 离开with语句块后,遍历顺序就会再次变成从执行环境开始。
  • with语句接收的对象会添加到作用域链的前端并在代码执行完之后移除。

关于js with语句的一些理解

  1. let a = 'global a'
  2. const obj = {
  3. a: 1,
  4. b: 2
  5. }
  6. const fn = key => {
  7. console.log(key)
  8. }
  9. const proxy = new Proxy(obj, {
  10. has(target, key) {
  11. console.log(target, 'target')
  12. console.log(key, 'key')
  13. }
  14. })
  15. with(proxy) {
  16. fn('a')
  17. }
  18. //依此打印
  19. // {a: 1, b: 2} target
  20. // fn key
  21. // a

以上代码是对obj对象进行代理, 通过with检查, 访问代理对象的 a 属性会被 has方法拦截。那么拦截的第一个target就是目标对象, 而第二个参数key是访问 a时的with语句块所在的执行环境。


construct(target, args) 方法用于拦截 new 命令。

target: 目标函数,
args: 构造函数的参数对象
返回值必须是一个 对象, 否则会报错。

  1. const proxy = new Proxy(function() {}, {
  2. construct(target, args) {
  3. console.log(target, 'target')
  4. console.log(args, 'args')
  5. return new target(args)
  6. }
  7. })
  8. new proxy(1,2)
  9. // function() {} 'target'
  10. // [1,2] 'args'

如果返回值不是对象会报错


deleteProperty(target, key) 拦截对象的 delete操作

target: 目标对象
key: 删除的哪个key值
返回值: 布尔值, true成功,false失败

目标对象不可配置(configurable)属性不能被deleteProperty删除, 否则会报错

  1. const obj = Object.defineProperties({}, {
  2. a: {
  3. value: 1,
  4. configurable: false,
  5. },
  6. b: {
  7. value: 2,
  8. configurable: true
  9. }
  10. })
  11. const proxy = new Proxy(obj, {
  12. deleteProperty(target, key) {
  13. delete target[key]
  14. return true;
  15. }
  16. })
  17. delete proxy.a // 报错
  18. delete proxy.b // true

以上代码拦截 obj对象, 当进行删除不可配置的属性a时,会报错。删除b属性时则成功。

应用场景:
我们可以指定内置属性不可被删除。如以_开头的属性不能被删除

  1. const obj = {
  2. _a: 'a',
  3. _b: 'b',
  4. c: 'c'
  5. }
  6. const proxy = new Proxy(obj, {
  7. deleteProperty(target, key) {
  8. if (key.charAt(0) === '_') {
  9. throw Error(`${key}属性不可被删除`)
  10. return false
  11. }
  12. delete target[key]
  13. return true
  14. }
  15. })

defindProperty(target, key, descriptor)方法拦截Object.defindProperty()操作

target: 目标对象,
key: 目标对象的属性
descriptor: 要设置的描述对象
返回值: 布尔值, true添加属性成功, false则会报错

  1. const proxy = new Proxy({}, {
  2. defineProperty(target, key, descriptor) {
  3. console.log(target, 'target')
  4. console.log(key, 'key')
  5. console.log(descriptor, 'descriptor')
  6. return true
  7. }
  8. })
  9. Object.defineProperty(proxy, 'a', {
  10. value: 1
  11. })

以上代码是拦截一个对象的Object.defindProperty()添加属性的操作, 如果返回值为true,表示添加成功。返回值false则会报错。
以上代码的执行结果:
defindProperty.png


getPrototypeOf(target) 方法,用来拦截获取对象原型。

target: 代理对象

可以拦截一下获取原型的操作:

  • Object.prototype. __ proto __
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf() 获取一个对象的原型对象
  • instance 操作符

Object.prototype.isPrototypeOf() 方法

检测一个对象的原型链上有没有这个对象

语法: Objectactive.isPrototypeOf(object), 检测object对象的原型链上有没有Objectactive这个对象, 如果有返回true, 否则返回false

  1. const Objectactive = {a: 1}
  2. const object = Object.create(Objectactive)
  3. Objectactive.isPrototypeOf(object) // true

以上代码 Objectactive作为 object的原型对象,然后通过 isPrototypeOf 检测object对象的原型链上有没有Objectactive这个对象。 理所当然返回 true

使用 getPrototypeOf()拦截

  1. const Objectactive = {a: 1}
  2. const object = Object.create(Objectactive)
  3. const proxy = new Proxy(object, {
  4. getPrototypeOf(target) {
  5. console.log(target, 'target')
  6. return Object.getPrototypeOf(target)
  7. }
  8. })
  9. let bl = Objectactive.isPrototypeOf(proxy)
  10. console.log(bl)
  11. // 依此打印结果:
  12. /*
  13. {
  14. __proto__:
  15. a: 1,
  16. __proto__: Object
  17. } 'target'
  18. true
  19. */

以上代码对 object对象进行代理,当访问原型对象时,通过getPrototypeOf()方法拦截,target就是代理对象。

getPrototypeOf()方法的返回值必须是 null 或者对象,否则报错。

isExtensible(target) 方法拦截 Object.isExtensible()方法

Object.isExtensible() 方法返回一个布尔值,其检查一个对象是否可扩展。

target: 目标对象

isExtensible()方法有一个强限制,它的返回值必须与目标对象的 isExtensible属性保持一致。

  1. const testObj = {
  2. name: 'apy'
  3. }
  4. const proxy = new Proxy(testObj, {
  5. isExtensible(target) {
  6. console.log('拦截对象的isExtensible操作')
  7. return true; // 这里要返回true, 因为目标对象现在是可扩展的,如果返回 false会报错
  8. }
  9. })
  10. console.log(Object.isExtensible(proxy))
  11. // 打印:
  12. // 拦截对象的isExtensible操作
  13. // true

以上代码通过Object.isExtensible()检测一个对象是否可扩展,会被配置选项中的 isExtensible方法拦截。

那么什么情况下可以 return false

Object.preventExtensions(object): 将一个对象设置为不可扩展的

  1. const testObj = {
  2. name: 'apy'
  3. }
  4. Object.preventExtensions(testObj) // 将 testObj对象设置为不可扩展
  5. const proxy = new Proxy(testObj, {
  6. isExtensible(target) {
  7. console.log('拦截对象的isExtensible操作')
  8. return false // 因为testObj对象不可扩展,返回值要和目标对象的 Object.isExtensible一致。
  9. }
  10. })
  11. 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
  • 还有就是目标对象上不可遍历的属性
  1. const obj = {
  2. a: 1,
  3. b: 2,
  4. [Symbol.for('c')]: 3
  5. }
  6. Object.defineProperty(obj, 'd', {
  7. value: 4,
  8. enumerable: false
  9. })
  10. const proxy = new Proxy(obj, {
  11. ownKeys(target) {
  12. return ['a', 'b', [Symbol('c')], 'd', 'e']
  13. }
  14. })
  15. Object.keys(obj).forEach(key => {
  16. console.log(key)
  17. })
  18. // a
  19. // 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拦截。

  1. const obj = {}
  2. const proxy = new Proxy(obj, {
  3. setPrototypeOf(target, proto) {
  4. console.log('拦截设置原型操作')
  5. // 内部手动设置原型,并且返回 boolean
  6. return Object.setPrototype(target, proto)
  7. }
  8. })
  9. Object.setPrototypeOf(proxy, {a: 1})

以上代码拦截Object.setPrototypeOf方法,所以会打印 拦截设置原型操作

使用场景, 禁止修改一个对象的原型,否则报错

如上例子,拦截一个修改对象原型的操作,抛出相应的错误就可以。

  1. const foo = {}
  2. const proxy = new Proxy(foo, {
  3. setPrototypeOf(target, key) {
  4. throw Error(`${target}不可以修改原型对象`)
  5. }
  6. })
  7. Object.setPrototypeOf(proxy, {a: 1}) // 报错

转载于:https://www.cnblogs.com/qiqingfu/p/9978659.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/140097
推荐阅读
  

闽ICP备14008679号