• ES6的代理模式-Proxy


    语法

    • target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
    • handler 一个通常以函数作为属性的对象,用来定制拦截行为
    1. const proxy = new Proxy(target, handle)

    举个例子

    1. <script setup lang="ts">
    2. const proxy = {}
    3. const obj = new Proxy(proxy, {
    4. get(target, key, receiver) {
    5. console.log('get', target,key)
    6. // return 10
    7. return Reflect.get(target, key, receiver)
    8. },
    9. set(target, key, value, receiver) {
    10. console.log('set', key, value)
    11. // return 20
    12. return Reflect.set(target, key, value, receiver)
    13. }
    14. })
    15. const test1 = () => {
    16. obj.a = 1
    17. }
    18. const test2 = () => {
    19. console.log(obj.a)
    20. }
    21. </script>
    22. <template>
    23. <div>
    24. <button @click="test1">test1</button>
    25. <button @click="test2">test2</button>
    26. </div>
    27. </template>

    需要注意的是,代理只会对proxy对象生效,如上方的origin就没有任何效果

    #Handler 对象常用的方法

    方法描述
    handler.has()in 操作符的捕捉器。
    handler.get()属性读取操作的捕捉器。
    handler.set()属性设置操作的捕捉器。
    handler.deleteProperty()delete 操作符的捕捉器。
    handler.ownKeys()Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
    handler.apply()函数调用操作的捕捉器。
    handler.construct()new 操作符的捕捉器

    #handler.get

    get我们在上面例子已经体验过了,现在详细介绍一下,用于代理目标对象的属性读取操作

    授受三个参数 get(target, propKey, ?receiver)

    • target 目标对象
    • propkey 属性名
    • receiver Proxy 实例本身

    举个例子

    1. const person = {
    2. like: "vuejs"
    3. }
    4. const obj = new Proxy(person, {
    5. get: function(target, propKey) {
    6. if (propKey in target) {
    7. return target[propKey];
    8. } else {
    9. throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
    10. }
    11. }
    12. })
    13. obj.like // vuejs
    14. obj.test // Uncaught ReferenceError: Prop name "test" does not exist.

    上面的代码表示在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误

    注意:

    • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同
    • 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined

    如下面的例子

    1. const obj = {};
    2. Object.defineProperty(obj, "a", {
    3. configurable: false,
    4. enumerable: false,
    5. value: 10,
    6. writable: false
    7. })
    8. const p = new Proxy(obj, {
    9. get: function(target, prop) {
    10. return 20;
    11. }
    12. })
    13. p.a // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable..

    #可撤消的Proxy

    proxy有一个唯一的静态方法Proxy.revocable(target, handler)

    Proxy.revocable()方法可以用来创建一个可撤销的代理对象

    该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}

    • proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
    • revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

    该方法常用于完全封闭对目标对象的访问, 如下示例

    1. const target = { name: 'vuejs'}
    2. const {proxy, revoke} = Proxy.revocable(target, handler)
    3. proxy.name // 正常取值输出 vuejs
    4. revoke() // 取值完成对proxy进行封闭,撤消代理
    5. proxy.name // TypeError: Revoked

    Proxy的应用场景

    Proxy的应用范围很大,简单举几个例子:

    #校验器

    1. const target = {
    2. _id: '1024',
    3. name: 'vuejs'
    4. }
    5. const validators = {
    6. name(val) {
    7. return typeof val === 'string';
    8. },
    9. _id(val) {
    10. return typeof val === 'number' && val > 1024;
    11. }
    12. }
    13. const createValidator = (target, validator) => {
    14. return new Proxy(target, {
    15. _validator: validator,
    16. set(target, propkey, value, proxy){
    17. console.log('set', target, propkey, value, proxy)
    18. let validator = this._validator[propkey](value)
    19. if(validator){
    20. return Reflect.set(target, propkey, value, proxy)
    21. }else {
    22. throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`)
    23. }
    24. }
    25. })
    26. }
    27. const proxy = createValidator(target, validators)
    28. proxy.name = 'vue-js.com' // vue-js.com
    29. proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type.
    30. proxy._id = 1025 // 1025
    31. proxy._id = 22 // Uncaught Error: Cannot set _id to 22. Invalid type

    #私有属性

    在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私‘私有化’,下面使用Proxy轻松实现私有属性拦截

    1. const target = {
    2. _id: '1024',
    3. name: 'vuejs'
    4. }
    5. const proxy = new Proxy(target, {
    6. get(target, propkey, proxy){
    7. if(propkey[0] === '_'){
    8. throw Error(`${propkey} is restricted`)
    9. }
    10. return Reflect.get(target, propkey, proxy)
    11. },
    12. set(target, propkey, value, proxy){
    13. if(propkey[0] === '_'){
    14. throw Error(`${propkey} is restricted`)
    15. }
    16. return Reflect.set(target, propkey, value, proxy)
    17. }
    18. })
    19. proxy.name // vuejs
    20. proxy._id // Uncaught Error: _id is restricted
    21. proxy._id = '1025' // Uncaught Error: _id is restricted

    Proxy 使用场景还有很多很多,不再一一列举,如果你需要在某一个动作的生命周期内做一些特定的处理,那么Proxy 都是适合的

    #开发中可能遇到的场景

    1. 增强操作的透明性:可以在不修改目标对象本身的情况下,为其增加额外的功能,比如验证、日志记录、性能监控等。这使得代码更加模块化,易于维护和扩展。

    1. let target = { message: "Hello, world!" };
    2. let handler = {
    3. get(target, prop, receiver) {
    4. console.log(`Getting property '${prop}'`);
    5. return Reflect.get(...arguments);
    6. },
    7. set(target, prop, value, receiver) {
    8. console.log(`Setting property '${prop}' to '${value}'`);
    9. return Reflect.set(...arguments);
    10. }
    11. };
    12. let proxy = new Proxy(target, handler);
    13. proxy.message; // 控制台输出: Getting property 'message'
    14. proxy.message = "Hi there!"; // 控制台输出: Setting property 'message' to 'Hi there!'

    2、属性访问控制:允许你控制对对象属性的访问,比如禁止某些属性被修改、只读访问、或者在访问不存在的属性时提供默认值等,这对于构建安全或用户友好的API特别有用

    1. const target = { message: "Read only" };
    2. const handler = {
    3. set(target, prop, value, receiver) {
    4. if (prop === 'message') {
    5. throw new Error("Message is read-only.");
    6. }
    7. return Reflect.set(...arguments);
    8. }
    9. };
    10. const proxy = new Proxy(target, handler);
    11. console.log(proxy.message); // 输出: Read only
    12. proxy.message = "New Value"; // 抛出错误: Message is read-only.

    3、数据绑定与自动更新:在前端开发中,尤其是与React、Vue等框架结合时,可以利用Proxy监听对象属性变化,自动触发UI更新,实现数据双向绑定的效果。

    1. class SimpleReactComponent extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = { count: 0 };
    5. this.handler = {
    6. get(target, prop) {
    7. return target[prop];
    8. },
    9. set(target, prop, value) {
    10. target[prop] = value;
    11. this.forceUpdate(); // 模拟React组件的更新
    12. return true;
    13. }
    14. };
    15. this.proxyState = new Proxy(this.state, this.handler);
    16. }
    17. increment = () => {
    18. this.proxyState.count++;
    19. };
    20. render() {
    21. return (
    22. <div>
    23. <p>Count: {this.proxyState.count}</p>
    24. <button onClick={this.increment}>Increment</button>
    25. </div>
    26. );
    27. }
    28. }
    29. ReactDOM.render(<SimpleReactComponent />, document.getElementById('root'));

    4、资源管理与优化:例如,实现惰性加载(lazy loading),只有当属性第一次被访问时才去加载资源;或者实现缓存机制,减少重复的计算或网络请求。

    1. function loadImage(url) {
    2. return new Promise((resolve, reject) => {
    3. const img = new Image();
    4. img.onload = () => resolve(img);
    5. img.onerror = reject;
    6. img.src = url;
    7. });
    8. }
    9. class LazyImage {
    10. constructor(url) {
    11. this.url = url;
    12. this.image = null;
    13. }
    14. async get() {
    15. if (!this.image) {
    16. this.image = await loadImage(this.url);
    17. }
    18. return this.image;
    19. }
    20. }
    21. const proxyImage = new Proxy(new LazyImage("path/to/image.jpg"), {
    22. get(target, prop) {
    23. if (prop === 'src') {
    24. return target.get().then(img => img.src);
    25. }
    26. return Reflect.get(...arguments);
    27. }
    28. });
    29. // 当需要时才真正加载图片
    30. proxyImage.src.then(src => console.log("Loaded image src:", src));

    5、模拟对象或环境:在测试、模拟数据或实现某些高级抽象时,可以使用Proxy来创建具有特定行为的对象,模拟数据库、文件系统或其他复杂系统的行为。

    1. const db = {
    2. users: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
    3. };
    4. const dbProxy = new Proxy(db, {
    5. get(target, prop) {
    6. if (prop === 'getUser') {
    7. return (id) => {
    8. return target.users.find(user => user.id === id);
    9. };
    10. }
    11. return Reflect.get(...arguments);
    12. }
    13. });
    14. console.log(dbProxy.getUser(1)); // 输出: { id: 1, name: "Alice" }

    总之,Proxy 提供了一种强大的工具,用于控制对象的访问和操作,它在很多方面都能帮助开发者编写更加灵活、高效和可控的代码。

  • 相关阅读:
    【人工智能学习之图像操作(四)】
    基于 Redis 实现接口限流
    如何编辑图片合成图片?让我们来看看这些合成方法
    设计模式笔记03-工厂模式-Factory
    upload-labs靶场通关指南(16-17关)
    Devops学习Day3--jacoco的详细使用
    基于leetcode的算法训练:Day2
    缺少比较器,运放来救场!(运放当做比较器电路记录)
    SPSS多元方差分析
    CSS动画(动态导航栏)
  • 原文地址:https://blog.csdn.net/qq_47040462/article/details/140018454