Proxy详解

news2025/1/15 6:34:02

Proxy

Proxy(代理),首先理解代理是什么意思,才能更便于了解Proxy的作用。
Proxy是一个代理,可以这么理解,就相当于它是一个快递代理点,快递会运输到该代理点,我们取件只需要去对应的代理点取件即可,代理点说快递没到就是没到,代理点说要出示证件就要出示证件。
Proxy代理的是一个对象,该对象被代理后我们就不能直接访问,需要通过代理访问。我们想要获取对象内的某个值,代理说有就有,说没有就没有,代理返回的值长这样,这个值它就是这样,这就是代理,也可以将它理解成拦截。

创建Proxy

语法:let proxy = new Proxy(target,handler)
new Proxy()用来生成Proxy实例
target参数表示要拦截的目标对象
handler参数是一个对象,用来制定拦截行为

案例一:

let figure = {
    name:'东方不败'
   }
// 创建代理的方法,当通过代理访问目标对象时,此对象中的对应方法会执行
let handlers = {
    get(target,prop){
        return '我是代理,我说返回什么就是什么'
   }
}

// 创建代理:代理的目标对象、代理的操作
let proxys = new Proxy(figure,handlers)
console.log(proxys.name);  // 我是代理,我说返回什么就是什么
console.log(proxys.age);  // 我是代理,我说返回什么就是什么

上面的例子就是利用Proxy,通过Proxy访问想要的值时,代理对取值进行拦截,返回给我们指定的信息,在这里,不论我们怎么取值,返回的都是我是代理,我说返回什么就是什么

案例二

let proxy2 = new Proxy({},{
    get: function(target,propKey){
          return 10;
    }
})
proxy2.num = 20;
console.log(proxy2.num);  // 10
console.log(proxy2.name);  // 10

上面代码中,我们给Proxy两个参数,第一个参数:要代理的目标对象(也就是{}),第二个参数:配置对象(被代理对象的操作),在这里,配置对象有个get方法,用来拦截目标对象属性的访问请求。

通过Proxy访问属性的时候,get方法将访问的这一层拦截了,这里拦截函数返回的10,所以访问任何属性得到的都是10。也就是说,给目标对象添加了属性值,但是在访问这一层被拦截了,任何访问都会返回拦截的这一层。

get方法的两个参数分别是目标对象和所要访问的属性,这里没有做任何操作,直接返回10,所以我们获取的值都是10

注意:如果想要Proxy生效,必须操作Proxy的实例,操作原对象是没有任何效果的。

案例三
如果Proxy的第二个参数(配置对象)没有设置任何拦截,就等同于直接访问原对象。

let target = {}
let handler = {}
let proxy = new Proxy(target,handler)
proxy.time = 20;
console.log(proxy.time);  // 20
//handler没有设置任何拦截效果,访问proxy就等于访问target

对象内是可以设置函数的,可以将Proxy对象设置到object.proxy属性,这样就可以在object对象上调用

let object = { proxy : new Proxy(target,handler) }
// object.proxy调用

Proxy实例也可以作为其他对象的原型对象

let proxy = new Proxy({},{
    get : function(target,propKsy){
        return 10
    }
 })
 
let obj = Object.create(proxy)
obj.time = 10;  
console.log(obj);

在这里插入图片描述
上面这段代码中,proxyobj对象的原型,obj对象本身没有time属性,所以根据原型链,会在proxy对象上读取该对象。


Proxy的实例方法

get()

get()用于拦截某个属性的读取操作,可以接受三个参数:
1、目标对象
2、属性名
3、Proxy实例本身(操作指向的对象),该参数为可选参数。

let person = {
    name : '张三'
}
let proxy = new Proxy(person,{
    get:function(target,propKey){
    // 判断对象上是否存在该属性名
        if(propKey in target){
            // 存在,返回该属性
             return target[propKey]
         } else {
           // 不存在,抛出错误
              throw new ReferenceError("属性名:" + propKey + "不存在")
         }
    }
})
console.log(proxy.name);  // 张三
// console.log(proxy.age);  // Uncaught ReferenceError: 属性名:age不存在

in运算符:in操作符用来判断某个属性属于某个对象,可以是对象的直接属性,也可以是通过prototype继承的属性。

上面这段代码表示,如果访问目标不存在则抛出错误。如果没有设置拦截函数,访问不存在的属性会返回undefined


set()

set()方法用来拦截某个属性的赋值操作,接收四个参数,依次为:
1、目标对象
2、属性名
3、属性值
4、Proxy实例本身(可选)
案例一
判断属性值是否大于200,大于200则报错,并且属性值必须是数字

let numHold = {
    set: function(obj,prop,value){
        if(prop === 'age'){
            if(!Number(value)){
                throw new TypeError('参数不是数字')
            }
            if(value > 200){
                throw new RangeError('参数不能大于200')
            }
        }
        // 如果条件满足则直接保存,将值赋值给该属性
        obj[prop] = value
   }
}
let persons = new Proxy({},numHold)
persons.age = 100
console.log(persons.age);  // 100
// persons.age = 300  // 报错,参数不能大于200
// persons.age = '东方'  // 报错,参数不是数字

上面代码中,设置了一个存值函数set,对对象的age属性进行赋值时,如果不满足age属性的赋值要求则会抛出一个相应的错误,其实这种写法就相当于是在做数据验证。

利用set方法还可进行数据绑定,每当数据发生变化时,自动更新DOM

同一个拦截器可以设置多个拦截操作,有时候,我们会在对象内部设置内部属性,该内部属性以下划线_开头,表示这些属性不应该被外部使用,结合getset方法,可以做到防止这些内部属性被外部读写。

// 拦截方法
let handler = {
    // 读取
    get(target,key){
       invariant(key,'get')
       return target[key]
    },
    // 写入
    set(target,key,value){
       invariant(key,'set');
       target[key] = value
       return true
        
    }
 }
 
 function invariant(key,action){
    // 属性名的第一个字符是_则为私有方法,不允许外部读写
    if(key[0] === '_'){
        throw new Error(`${action}私有属性${key}无效`)
    }
}

let target = {}  // 当前对象
let proxy = new Proxy(target,handler)
// proxy._prop;  // get私有属性_prop无效
// proxy._name = '东方不败' // set私有属性_name无效

apply()

apply方法拦截函数的调用,callapply操作,apply方法可以接受三个参数,分别是:
1、目标对象
2、目标对象的上下文对象(this)
3、目标对象的参数数组

案例一

let target = function(){return '东方不败'}
let handler = {
    apply:function(){
        return '我是Proxy'
    }
}
let p = new Proxy(target,handler)
console.log(p());  // 我是Proxy

上面代码中,变量pProxy的实例,当它作为函数调用时,就会触发apply方法拦截,返回apply的返回结果

案例二

let twice = {
    apply(target,ctx,args){
        // console.log(Reflect.apply(...arguments)); // 3
        return Reflect.apply(...arguments) * 2
    }
}
function sum(left,right){
    return left + right  // 1 + 2
}
let proxy6 = new Proxy(sum,twice)
console.log(proxy6(1,2)); // 6

上面代码中,每当执行Proxy函数(直接调用或callapply调用),都会被apply方法拦截。

console.log(proxy6.call(null,2,3)); // 10

has()

has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性,该方法会生效。典型的操作就是in运算符。
has()方法接受两个参数:
1、目标对象
2、需要查询的属性名

// 使用has()方法隐藏某些属性,不被in运算符发现
let handler = {
    has(target,key){
        // key为传入的属性名,这里key[0]就是属性名的第一个字符
        if(key[0] === '_'){
           // 第一个字符是_则返回false
            return false
        }
        return key in target
    }
}
let target = { _prop:'foo',prop:'fuu' }
let proxy = new Proxy(target,handler)
console.log('_prop' in proxy);  // false  '_prop'属性不属于proxy对象
console.log('_prop' in target);  // true  '_prop'属性属于target对象

注意:
如果原对象不可配置或禁止扩展,这是has()拦截就会报错
虽然for...in循环也用到了了in运算符,但是has()拦截对for...in循环不生效。


construct()

construct()方法用于拦截new命令,当对Proxy实例使用new命令的时候触发
construct()接受三个参数:
1、目标对象
2、构造函数的参数数组
3、创建实例对象时,new命令作用的构造函数(也就是下面例子中的p2)

let con = {
    construct:function(target,arg){
        // target是一个函数(){}
        // args是一个参数数组
        // this : construct
        console.log(this === con); // true
        console.log('回调:'+arg.join(','));  // 回调:1,2
        return { value : arg[0] * 10 }  // construct返回的必须是一个对象,否则报错
        // return 1  // 返回的不是对象,报错'construct' on proxy: trap returned non-object ('1')
    }
}
let p = new Proxy(function(){},con)
console.log((new p(1,2).value));  // new p()触发construct拦截

在这里插入图片描述

注意:由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则会报错
注意:construct()中的this指向的是con,而不是实例对象


deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或返回false,当前属性就无法被delete命令删除,当对Proxy实例使用delete命令的时候触发

let del = {
    deleteProperty(target,key){
        invariant(key,'delete')
        delete target[key]  // 如invariant未抛出错误就证明是可以删除的,删除操作
        return true  // 抛出true
    }
}

function invariant(key,action){
    // 如果属性名的第一个字符是_说明是私有属性,抛出错误
    if(key[0] === '_'){
        throw new Error(`当前操作:${action}对于私有属性${key}无效`)
    }
}
let target = {_prop:'foo'}
let proxy = new Proxy(target,del)
// console.log(delete proxy._prop);  // 报错:当前操作:delete对于私有属性_prop无效

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。


defineProperty()

defineProperty()方法拦截Object.defineProperty()操作

let h = {
    defineProperty (target,key,desc){
        // target 目标对象
        // 目标对象的属性名
        // desc目标对象的赋值
        return false
        // return target[key] = desc.value
    }
}
let t = {}
let pr = new Proxy(t,h)
console.log(pr.foo = 'bar');  // 不会生效,被拦截
console.log(t);  // {}

上面代码中,defineProperty()方法内部没有任何操作,只返回false,导致新添加的属性总是无效。
这里返回的false只是用来提示操作失败,本身并不能阻止添加新属性。


Proxy.revocable()

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

let target = {}
let handler = {}
let {proxy, revoke} = Proxy.revocable(target , handler );

console.log(proxy.foo = 100);  // 100
revoke()  // 取消Proxy实例
console.log(proxy.foo); //  Cannot perform 'get' on a proxy that has been revoked

Proxy.revocable()返回一个对象,该对象内有proxy属性和revoke属性。
proxyProxy实例
revoke是一个函数,用来取消Proxy实例
上面代码中revoke执行完后,取消了Proxy实例,当再次访问Proxy实例时会报错。
Proxy.revocable()的一个使用场景:目标对象不允许直接访问,必须通过代理访问,一但访问结束,就是收回代理权,不允许再次访问。


this问题

虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理。

let target = {
   m : function () {
   console.log('proxy',this);  
   // m:() false
   // Proxy {m: ƒ} true
   console.log(this === proxy);
    }
 }
 
let handler = {}
let proxy = new Proxy(target,handler)
target.m();  // false 
proxy.m();  // true

上面代码中,一旦proxy代理target,target.m()内部的this就是指向proxy,而不是target。所以,虽然proxy没有做任何拦截,但target.m()proxy.m()返回不一样的结果。

案例一
由于this指向的变化,导致Proxy无法代理目标对象

let _name = new WeakMap()  // 将值保存在这里

class Person {
   constructor(name){
     _name.set(this,name)  // set一个_name等于name
   }
   get name(){
       return _name.get(this)  // 返回_name的值
  }
}

let jane = new Person('东方不败')
console.log(jane.name);  // 东方不败

let proxy2 = new Proxy(jane,{})
console.log(proxy2.name);  // undefined,这里的this指向的是Proxy,所以找不到值

上面代码中,目标对象东方不败name属性,实际保存在外部WeakMap对象_name上面,通过this键区分。由于通过proxy2.name访问时,this指向proxy2,导致无法取到值,所以返回undefined

此外,有些原生对象的内部属性,只有通过正确的this才能拿到,所以Proxy也无法代理这些原生对象的属性

let t =  new Date()
let h = {}
let p = new Proxy(t,h)
// console.log(p.getDate());  // 报错 this is not a Date object.
console.log(t.getDate());  // 8   

上面代码中,getData()方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错。这时,this绑定原始对象,就可以解决这个问题。

let t2 = new Date('2023-01-01')

let h2 = {
    get(target,prop){
        if(prop === 'getDate'){
            // 更改this指向,绑定原始对象
            return target.getDate.bind(target)
        }
        return Reflect.get(target,prop);
    }
}
let p2 = new Proxy(t2,h2)
p2.getDate  // 8

bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值。

另外,Proxy拦截函数内部的this,指向的是当前对象,对象内函数都是指向当前对象,其实就是对象内函数的this指向问题。

let handler = {
    get:function(target,key,receiver){
        return 'hello,'+key
    },
    set:function(target,key,value){
        console.log(this === handler );  // true
        target[key] = value
        return true
    }
 }
 
let proxy = new Proxy({},handler )
console.log(proxy.foo);  // hello,foo
proxy.foo = 1  // 触发set方法

关于对象内函数的this指向问题,请看另一篇:对象定义-解构-枚举属性遍历以及对象内函数


Proxy支持的拦截操作

1、get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']2、set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

3、has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。

4、deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
        
5、ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
       
6、getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
        
7、defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
       
8、preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
       
9、getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
       
10、isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
       
11、setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
       
12、apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)13、construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

案例源码:https://gitee.com/wang_fan_w/es6-science-institute

如果觉得这篇文章对你有帮助,欢迎点亮一下star哟

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149035.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年宝塔面板快速部署Rocket.Chat,安卓,PC,ios端使用

官方网站快速部署代码:Docker & Docker Compose - Rocket.Chat Docs 环境:centos 服务器必须大于2核2G,否则docker占用资源太多会起不起来 安装宝塔面板 一 . 宝塔面板安装docker 当然也可以手动安装:如果您还没有,请确保您已安装并运行Docker和Docker-comp…

Docker 数据卷

问题:通过镜像创建一个容器。容器一旦被销毁,则容器内的数据将一并被删除。但有些情况下,通过服务器上传的图片出会丢失。容器中的数据不是持久化状态的。 那有没有一种独立于容器、提供持久化并能服务于多个容器的东西呢? 什么是…

SpringCloud组件之Gateway网关详细教程

目录 一:概念 1.1:什么是微服务网关? 1.2: 为什么需要使用网关,网关的作用? 1.3网关的好处? 二:Gateway网关的快速入门 1.快速创建 2.网关路由的流程 3:Gateway断言工厂 &…

Java复习—运算符

运算符 运算符:对字面量或者变量进行操作的符号 表达式:用运算符把字面量或者变量连接起来,符合Java语法的式子就可以称为表达式。 算数运算符 符号作用加法作用-减法作用*乘法作用/除法法作用%取模、取余 在代码中,如果有小数…

Qt OpenGL(05)标准化设备坐标(NDC)

文章目录OpenGL中的坐标简介标准化设备坐标标准化设备坐标绘制 x y z 三个轴线完整代码顶点着色器片段着色器Widget.hWidget.cpp总结OpenGL中的坐标简介 OpenGL 基于绘制流水线模型,而且绘制流水线的第一个步骤是对顶点进行一 系列的操作, 其中大部分属于几何操作。…

3-2内存管理-虚拟内存

文章目录一.虚拟内存的基本概念二.请求分页管理方式(一)页表机制(二)缺页中断机构(三)地址变换机构三.页面置换算法(一)最佳置换算法OPT(二)先进先出页面置换…

flowable 简介

flowable 简介目录概述需求:设计思路实现思路分析1.管理2.二、初识Flowable五大引擎3.2.通过编写程序的方式来构造ProcessEngineConfiguration对象4.流程引擎API架构图5.flowable 表结构说明参考资料和推荐阅读Survive by day and develop by night. talk for impor…

Leetcode_单周赛_327

6283. 正整数和负整数的最大计数 代码 直接遍历统计即可 class Solution {public int maximumCount(int[] nums) {int a 0, b 0;for (int i 0; i < nums.length; i) {if (nums[i] > 0) a;else if (nums[i] < 0) b;}return Math.max(a, b);} }6285. 执行 K 次操作…

Python代码实现:坐标轮换法求解多维最优化问题

文章目录多维最优化问题坐标轮换法原理代码实现坐标轮换法坐标轮换法优缺点多维最优化问题 此前介绍的黄金分割法和切线法都是针对一维最优化问题的解决方案。本文开始&#xff0c;我们将最优化问题从一维扩展到多维&#xff0c;暂时仍考虑无约束的优化场景。 坐标轮换法原理…

ArrayList | 简单的洗牌算法

一个洗牌程序需要包含&#xff1a; 创建一副扑克牌&#xff08;除去大小王剩下52张&#xff0c;每种花色13张&#xff09;。洗牌&#xff0c;打乱牌的顺序。揭牌&#xff0c;每位玩家轮流揭牌&#xff0c;从洗完后的牌组中获得自己的牌。因此&#xff0c;我们可以依照以下思路来…

R 语言 4.2.2安装 WGCNA

文章目录1 WGCNA库介绍2 安装踩坑还得是官方文档这样安装我出现的问题参考AppendixA. 安装RB. 配置环境C. 修改镜像1 WGCNA库介绍 WGCNA是用于加权相关网络分析的R包&#xff0c; 相关网络越来越多地用于生物信息学应用 加权基因共表达网络分析是一种系统生物学方法&#xff0…

按键控制电源通断,实现各种设备/电脑开关机低功耗IC

前言 今天记录一下一些硬件开关电的低功耗控制ic&#xff0c;代替物理机械开关&#xff0c;后续有新的更好用的芯片会继续更新此博。 环境 every machine 参考文档 正文 一版我们选择ic&#xff0c;除了功能之外还要看一些性能&#xff0c;这里我暂时录入的功能就是一个按…

SpringCloud从入门到精通(九)

bus bus-概述 • Spring Cloud Bus 是用轻量的消息中间件将分布式的节点连接起来&#xff0c;可以用于广播配置文件的更改或 者服务的监控管理。关键的思想就是&#xff0c;消息总线可以为微服务做监控&#xff0c;也可以实现应用程序之间相通信。 • Spring Cloud Bus 可选的…

【MySQL】为什么使用B+树做索引

【MySQL】为什么使用B树做索引? 索引这个词&#xff0c;相信大多数人已经相当熟悉了&#xff0c;很多人都知道MySQL的索引主要以B树为主&#xff0c;但是要问到为什么用B树&#xff0c;恐怕很少有人能把前因后果讲述的很完整。本文就来从头到尾介绍下数据库的索引。 索引是一…

linux系统中QT控件的操作的基本方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何学习QT中的控件使用方法。 目录 第一&#xff1a;QT控件基本简介 第二&#xff1a;QPushButton使用方法 第三&#xff1a;QTableWidget简介 第四&#xff1a;最终运行效果 第一&#xff1a;QT控件基本简介 老子曾说…

Rad Studio 11.2 安装 OrangeUI 组件教程

官方文档&#xff1a;http://www.orangeui.cn/components/install 本文参考官方文档进行 11 版本的安装 开始 打开 Rad Studio 11&#xff0c;点击 FIle–Open Project… 找到解压的目录下的 .groupproj 文件 出现移动端提示弹窗&#xff0c;关掉 即可 右键 点击右侧第一个程序…

大数取余公式

ab)modP[(amodP)(bmodP)]modP (ab)modP[(amodP)(bmodP)]modP欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 …

数据结构-随机化快速排序

一、概念及其介绍 快速排序由 C. A. R. Hoare 在 1960 年提出。 随机化快速排序基本思想&#xff1a;通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后再按此方法对这两部分数据分别进行快速排…

Jenkins凭证/凭据管理详解

文章目录一、Jenkins中的凭证凭证类型凭证范围系统全局用户凭证域凭证提供者系统凭证提供者 &#xff08;Jenkins 凭证提供者&#xff0c;常用&#xff09;用户凭证提供者文件夹凭证提供者BlueOcean 凭证插件凭证存储二、管理凭证选择凭证提供者选择凭证类型通过提供者指定凭证…

JavaScript---DOM---DOM重点核心---1.8

关于DOM操作&#xff0c;我们主要针对于元素的操作。主要有创建、增、删、改、查、属性操作、事件操作。 创建 document.writeinnerHTMLcreateElement 增 appendChildinsertBefore 删 removeChild 改 主要修改dom的元素属性&#xff0c;dom元素的内容、属性、表单的值等 …