js语法 proxy对象拦截方法详解,proxy代理一个对象(数组,函数)的操作方法

如果还不能理解什么是proxy代理可以参考:js语法---理解反射Reflect对象和代理Proxy对象_js代理对象-CSDN博客

proxy代理对象(数组,函数),可以拦截的操作

  • apply---拦截方法的执行(包括直接执行,通过apply,call执行),和对应的Reflect.apply
  • construct---拦截new关键字,和对应的Reflect.construct
  • defineProperty---拦截拦截目标对象的以下操作:Object.defineProperty , proxy.property='value',和对应的Reflect.defineProperty,
  • deleteProperty---拦截delete 关键字,和对应的Reflect.deleteProperty
  • get---拦截所有读取属性的操作(包括直接读取,访问原型属性),和对应的Reflect.get
  • getOwnPropertyDescriptor---拦截方法Object.getOwnPropertyDescriptor,Reflect.getOwnPropertyDescriptor
  • getPrototypeOf---拦截对原型的访问(Object.getPrototypeOf,Reflect.getPrototypeOf,Object.prototype.__proto__,Object.prototype.isPrototypeOf,instanceof),
  • has--拦截对属性的查询,(in关键字,with检查),和对应的Reflect.has
  • ownKeys---拦截读取属性集合,(Object.getOwnPropertyNames,Object.getOwnPropertySymbols,Object.keys),以及对应的Reflect.ownKeys
  • set---拦截设置属性的操作(直接赋值,通过Object.create创建的代理对象赋值),和对应的Refl.set
  • setPrototypeOf---拦截Object.setPrototypeOf方法,和对应的Reflect.setPrototypeOf

proxy的拦截主要分为两类:

  1. 拦截对属性,原型,原型的属性操作;例如,添加/删除属性,读取/修改值,读取/修改原型,
  2. 拦截特定的方法;例如Object.setPrototypeOf,Object.getOwnPropertyDescriptor,

其中,第一类是比较常用的,第二类是对特定一些方法的拦截,一般情况下用不上,

set和get---拦截属性操作

set和get是代理拦截最常见的两个方法,它们可以在代理对象读取属性和修改属性时执行一些其他操作

get: function (target, key, proxy) 
// 代理的目标对象,代理目标的属性,代理对象

set: function(target,key,value,proxy)
// 代理的目标对象,代理目标的属性,本次拦截的修改值,代理对象

以下演示了拦截基本的读取和赋值操作

const obj = {
  type: 'object',
  toString: () => {
    return this.type;
  }
}

const p = new Proxy(obj, {
  get: function (target, key, proxy) {// 代理的目标对象,代理目标的属性,代理对象(p)
    console.log("原目标对象:",target,"\n拦截访问的属性:"+key,"\n触发本次拦截的对象",proxy);// 访问属性时,触发拦截打印
    return Reflect.get(target, key);
  },
  set: function(target,key,value,proxy){
    console.log('数据试图被修改成',value,'已拦截此操作');
    const result = Reflect.set(target,key,Reflect.get(target,key)); // set,修改key属性的值,返回布尔值
    return result
  }
});

console.log(p.type);// 直接访问属性,触发get拦截,
p.type = 'Array';
console.log(p.type);// 直接访问属性,触发get拦截,

p是obj对象的代理对象,用p来代替obj操作,当访问p的type属性时触发get拦截,本次的访问结果就变成了get拦截返回的结果,若这里没有返回值,则返回undefined;

当对p的type属性进行赋值时,触发set拦截,set内阻止了赋值,再次打印结果可以看到type的值还是object

(set,get拦截替换了原本的属性访问,修改,如果proxy内没有任何拦截方法,则不会触发代理的效果,即相当于 const p = obj )

construct---拦截new关键字

proxy可以代理拦截对象,而函数(数组)也是对象的一种,所以一样可以来拦截函数的操作,construct拦截可以在函数使用new关键字时执行一些其他操作

construct:function(fn,args,proxy)
// 代理的目标函数,目标函数被调用时的参数列表,代理对象
// 定义一个构造函数
function Fn(name){
  this.name = name;
}

const People = new Proxy(Fn,{
  construct : (fn,args,proxy)=>{// 代理的目标函数,目标函数被调用时的参数列表,代理对象
    const result = new fn(...args);
    console.log('正在使用new新建对象',result)
    return result;
  }
})

const one = new People('Tom')
console.log(one);

construct拦截将原本的new操作拦截了,并由自身代替执行,如果没有返回值则返回undefined

 apply---拦截方法的执行

当proxy代理的对象是一个函数时可以设置apply拦截,否则将会产生错误,当函数直接执行时,或使用apply,call 替换执行时 触发拦截执行一些操作,

apply:function(fn,thisArg,args)
// 代理的目标函数,函数执行时的this的指向(默认指向全局,值为undefined),代理函数被调用时的参数列表,
const add = (a,b)=>a+b;
const Add = new Proxy(add,{
  apply:(fn,thisArg,args)=>{// 代理的目标函数,函数执行时的this的指向(默认指向全局,值为undefined),代理函数被调用时的参数列表,
    console.log(fn,thisArg,args);
    return fn(...args);
  }
})

console.log(Add(1,2));
let f = '代理执行';
Add.apply(f,[2,3]);// apply方法 将Add函数内部的this指向修改成f

拦截原本的函数执行(阻止原函数执行,替换为自身执行),如果没有返回值则返回undefined 

 

ownKeys---拦截读取属性集合

当读取对象的所有属性时(通常时Object.keys方法),会触发ownKeys拦截,执行一些其他操作

ownKeys:function(target) 
// 原目标对象
const info = {
  a:1,
  b:2,
  c:3
}
const Info = new Proxy(info,{
  ownKeys:(target)=>{// 原目标对象
    const result = Reflect.ownKeys(target);
    console.log('正在获取所有属性',result)
    return result;
  }
})
console.log(Object.keys(Info));
for(let key in Info){
  
}

可以看到除了直接使用Object.keys,for in循环也会获取对象的所有属性,如果没有返回值则返回undefined 

deleteProperty---拦截delete 关键字

使用delete删除对象属性时,代理对象会触发deleteProperty拦截

deleteProperty:(target,key)=>{}
// 原目标对象,当前删除的属性
const delObj = {
  del:'要删除的属性',
  other:'其他内容'
}
const DelObj = new Proxy(delObj,{
  deleteProperty:(target,key)=>{// 原目标对象,当前删除的属性
    if(key == 'del'){
      console.log('成功删除属性'+key);
      const result = Reflect.deleteProperty(target,key);
      return result;
    }else{
      console.log('删除失败,属性'+key+'不可删除')
      return false;
    }
    
  }
})

delete DelObj.del
delete DelObj.other

 

拦截了delete的删除操作,并指定了可以删除的内容,需要返回布尔值,表示删除的执行成功和失败

has--拦截对属性的查询(in 关键字)

使用in 关键字判断属性是否存在于对象中时触发

has:(target,key)=>{}
// 目标对象,查询的属性
const has = new Proxy({name:'Tom'},{
  has:(target,key)=>{// 目标对象,查询的属性
    console.log('正在查询属性',key,"是否存在")
    return Reflect.has(target,key);
  }
})

console.log('name' in has)
console.log('age' in has)

返回查询的结果,默认返回false

getPrototypeOf---拦截对原型的访问

当代理对象触发 JS 引擎读取一个对象的原型时,触发 getPrototypeOf拦截,

以下几种方式都会访问到原型

  1. Object.getPrototypeOf()
  2. Reflect.getPrototypeOf()
  3. Object.prototype.__proto__
  4. Object.prototype.isPrototypeOf()
  5. instanceof

getPrototypeOf拦截的 返回值必须是一个对象或者 null,否则将产生错误

const arr = [0,1,2,3,4];
const Arr = new Proxy(arr,{
  getPrototypeOf:(target)=>{// 原目标对象
    console.log('访问了'+target+'的原型');
    return Reflect.getPrototypeOf(target);
  }// 返回值必须是一个对象或者 null,否则将产生错误
})


console.log(Arr instanceof Array)

当返回值为null时结果为false,返回值不为null也不为对象是产生错误

其他的特定拦截

其他拦截属性,都是对特定的方法进行拦截,当代理对象使用这些特定的方法时就会触发对应的拦截,想查看这些特定方法可以参考:Proxy() 构造函数 - JavaScript | MDN (mozilla.org)

总结

proxy的代理功能

        proxy可以代理原目标对象执行一些操作,并且这些操作都是可以拦截的;例如赋值,可以在赋值的过程执行其他操作,甚至改变或者阻止赋值;

        使用proxy代理可以实现对对象(包括数组,函数,map等引用数据类型)更加精细的控制,掌控对象操作的每一个流程,vue框架的响应式原理就依赖于proxy的代理,当虚拟dom对象执行操作改变时,拦截操作并修改(重新渲染)ui视图;

关于Reflect

        Reflect对象提供了proxy拦截属性的对应方法,它的作用是还原拦截的操作,例如,Reflect.construct就是执行原本被拦截的new 关键字操作,使用Reflect可以保证原本的操作可以正常执行,并在此基础上新增操作;

        虽然使用其他方法也能达到相同的效果,例如本文construct的例子就是再执行的原构造函数的结果,但是,使用Reflect的方法和proxy的拦截属性方法的参数完全一致(Reflect.construct(fn,args,proxy)),这种用法能够更加简洁的还原原本拦截的操作

完整代码和运行结果

// proxy代理对象(数组,函数),可以拦截的操作:
// apply---拦截方法的执行(包括直接执行,通过apply,call执行),和对应的Reflect.apply
// construct---拦截new关键字,和对应的Reflect.construct
// defineProperty---拦截目标对象的以下操作:Object.defineProperty , proxy.property='value',和对应的Reflect.defineProperty,
// deleteProperty---拦截delete 关键字,和对应的Reflect.deleteProperty
// get---拦截所有读取属性的操作(包括直接读取,访问原型属性),和对应的Reflect.get
// getOwnPropertyDescriptor---拦截方法Object.getOwnPropertyDescriptor,Reflect.getOwnPropertyDescriptor
// getPrototypeOf---拦截对原型的访问(Object.getPrototypeOf,Reflect.getPrototypeOf,Object.prototype.__proto__,Object.prototype.isPrototypeOf,instanceof),
// has--拦截对属性的查询,(in关键字,with检查),和对应的Reflect.has
// ownKeys---拦截读取属性集合,(Object.getOwnPropertyNames,Object.getOwnPropertySymbols,Object.keys),以及对应的Reflect.ownKeys
// set---拦截设置属性的操作(直接赋值,通过Object.create创建的代理对象赋值),和对应的Refl.set
// setPrototypeOf---拦截Object.setPrototypeOf方法,和对应的Reflect.setPrototypeOf

const obj = {
  type: 'object',
  toString: () => {
    return this.type;
  }
}

const p = new Proxy(obj, {
  get: function (target, key, proxy) {// 代理的目标对象,代理目标的属性,代理对象(p)
    console.log("原目标对象:",target,"\n拦截访问的属性:"+key,"\n触发本次拦截的对象",proxy);// 访问属性时,触发拦截打印
    return Reflect.get(target, key);
  },
  set: function(target,key,value,proxy){
    console.log('数据试图被修改成',value,'已拦截此操作');
    const result = Reflect.set(target,key,Reflect.get(target,key)); // set,修改key属性的值,返回布尔值
    return result
  }
});

console.log(p.type);// 直接访问属性,触发get拦截,
p.type = 'Array';
console.log(p.type);// 直接访问属性,触发get拦截,


console.log('-----------分割线----------\n')

// 定义一个构造函数
function Fn(name){
  this.name = name;
}

const People = new Proxy(Fn,{
  construct : (fn,args,proxy)=>{// 代理的目标函数,代理函数被调用时的参数列表,代理对象
    const result = new fn(...args);
    console.log('正在使用new新建对象',result)
    return result;
  }
})

const one = new People('Tom')
console.log(one);

console.log('-----------分割线----------\n')

const add = (a,b)=>a+b;
const Add = new Proxy(add,{
  apply:(fn,thisArg,args)=>{// 代理的目标函数,函数执行时的this的指向(默认指向全局,值为undefined),代理函数被调用时的参数列表,
    console.log(fn,thisArg,args);
    return fn(...args);
  }
})

console.log(Add(1,2));
let f = '代理执行';
Add.apply(f,[2,3]);// apply方法 将Add函数内部的this指向修改成f

console.log('-----------分割线----------\n')

const info = {
  a:1,
  b:2,
  c:3
}
const Info = new Proxy(info,{
  ownKeys:(target)=>{// 原目标对象
    const result = Reflect.ownKeys(target);
    console.log('正在获取所有属性',result)
    return result;
  }
})
console.log(Object.keys(Info));
for(let key in Info){
  
}

console.log('-----------分割线----------\n')

const delObj = {
  del:'要删除的属性',
  other:'其他内容'
}
const DelObj = new Proxy(delObj,{
  deleteProperty:(target,key)=>{// 原目标对象,当前删除的属性
    if(key == 'del'){
      console.log('成功删除属性'+key);
      const result = Reflect.deleteProperty(target,key);
      return result;
    }else{
      console.log('删除失败,属性'+key+'不可删除')
      return false;
    }
    
  }
})

delete DelObj.del
delete DelObj.other
console.log(DelObj)

console.log('-----------分割线----------\n')

const has = new Proxy({name:'Tom'},{
  has:(target,key)=>{// 目标对象,查询的属性
    console.log('正在查询属性',key,"是否存在")
    return Reflect.has(target,key);
  }
})

console.log('name' in has)
console.log('age' in has)

console.log('-----------分割线----------\n')

const arr = [0,1,2,3,4];
const Arr = new Proxy(arr,{
  getPrototypeOf:(target)=>{// 原目标对象
    console.log('访问了'+target+'的原型');
    return Reflect.getPrototypeOf(target);
  }// 返回值必须是一个对象或者 null,否则将产生错误
})


console.log(Arr instanceof Array)
Arr.push(5)

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值