深入解析 JS 代理模式 Proxy

01-04 8065阅读

一、代理模式简介

在软件开发中,代理模式是一种常用的设计模式。它为其他对象提供一个代理以控制对这个对象的访问。在 JavaScript 中,ES6 引入了 Proxy 类,使得代理模式的实现变得更加简单和强大。代理模式可以在不改变原始对象代码的情况下,为其提供额外的功能,比如访问控制、日志记录、性能优化等。

二、基本语法与结构

(一)创建代理对象

const target = {
    name: '张三',
    age: 25
};

const proxy = new Proxy(target, {
    get(target, property) {
        console.log(`访问属性 ${property}`);
        return target[property];
    }
});

console.log(proxy.name);

在上述代码中,首先定义了一个目标对象 target,然后通过 Proxy 构造函数创建了一个代理对象 proxyProxy 的第二个参数是一个配置对象,这里定义了 get 拦截器。当访问代理对象的属性时,会触发 get 拦截器,先打印访问信息,再返回目标对象的对应属性值。

(二)拦截器方法

  1. get(target, property, receiver)
    • target 是目标对象。
    • property 是要访问的属性名。
    • receiver 是代理对象本身。 上述代码中的 get 拦截器就是利用了这三个参数,通过它可以在访问属性时进行额外的操作。
  2. set(target, property, value, receiver)

    const target = {};
    const proxy = new Proxy(target, {
       set(target, property, value) {
           console.log(`设置属性 ${property} 为 ${value}`);
           target[property] = value;
           return true;
       }
    });
    
    proxy.test = '测试';

    这个拦截器在设置属性时触发,打印设置信息并将属性设置到目标对象上,同时返回 true 表示设置成功。如果返回 false,则设置操作会被阻止。

  3. apply(target, thisArg, argumentsList) 当代理对象作为函数调用时会触发 apply 拦截器。

    const target = function(a, b) {
       return a + b;
    };
    const proxy = new Proxy(target, {
       apply(target, thisArg, argumentsList) {
           console.log('调用函数');
           return target.apply(thisArg, argumentsList);
       }
    });
    
    console.log(proxy(1, 2));

    这里在调用代理对象 proxy 时,会先打印 '调用函数',然后执行目标函数并返回结果。

三、代理模式的应用场景

(一)访问控制

  1. 权限管理

    const user = {
       name: '李四',
       age: 30,
       secretInfo: '这是秘密信息'
    };
    
    const authorizedUser = new Proxy(user, {
       get(target, property) {
           if (property ==='secretInfo' &&!isAuthorized()) {
               throw new Error('没有权限访问');
           }
           return target[property];
       }
    });
    
    function isAuthorized() {
       // 这里可以通过检查用户登录状态等方式返回是否授权
       return true;
    }
    
    try {
       console.log(authorizedUser.secretInfo);
    } catch (error) {
       console.error(error.message);
    }

    在这个例子中,通过代理模式实现了对 secretInfo 属性的访问控制。只有在授权的情况下才能访问该属性,否则会抛出错误。

  2. 数据过滤

    const dataSource = {
       list: [
           { id: 1, value: '正常数据' },
           { id: 2, value: '敏感数据' }
       ]
    };
    
    const filteredData = new Proxy(dataSource, {
       get(target, property) {
           if (property === 'list') {
               return target.list.filter(item => item.value!== '敏感数据');
           }
           return target[property];
       }
    });
    
    console.log(filteredData.list);

    此代码通过代理模式对数据源中的数据进行过滤,当访问 list 属性时,返回过滤后的列表,隐藏了敏感数据。

(二)日志记录

const targetFunction = function(a, b) {
    return a * b;
};

const loggedFunction = new Proxy(targetFunction, {
    apply(target, thisArg, argumentsList) {
        console.log(`调用函数 ${target.name},参数为 ${argumentsList}`);
        const result = target.apply(thisArg, argumentsList);
        console.log(`函数返回结果: ${result}`);
        return result;
    }
});

console.log(loggedFunction(3, 4));

在这个例子中,代理对象在调用目标函数前后记录了日志信息,包括函数名、参数和返回结果,方便调试和追踪函数调用情况。

(三)性能优化

  1. 缓存代理

    const expensiveFunction = function(a) {
       // 模拟耗时操作
       return a * a;
    };
    
    const cachedFunction = new Proxy(expensiveFunction, {
       cache: new Map(),
       apply(target, thisArg, argumentsList) {
           const key = argumentsList.toString();
           if (this.cache.has(key)) {
               return this.cache.get(key);
           }
           const result = target.apply(thisArg, argumentsList);
           this.cache.set(key, result);
           return result;
       }
    });
    
    console.log(cachedFunction(5));
    console.log(cachedFunction(5)); // 从缓存中获取结果,不会重复计算

    这里通过缓存代理,缓存了函数的计算结果。当相同参数再次调用函数时,直接从缓存中获取结果,避免了重复的耗时计算,提高了性能。

  2. 虚拟代理

    const largeImage = {
       src: 'bigImage.jpg',
       load: function() {
           console.log('加载大图');
       }
    };
    
    const virtualProxy = new Proxy(largeImage, {
       get(target, property) {
           if (property === 'load') {
               return function() {
                   console.log('开始加载代理');
                   setTimeout(() => {
                       target.load();
                   }, 1000);
               };
           }
           return target[property];
       }
    });
    
    virtualProxy.load();

    对于加载大图片这种耗时操作,使用虚拟代理。在代理对象的 load 方法中,先打印 '开始加载代理',然后通过 setTimeout 模拟异步加载,实际的加载操作由目标对象的 load 方法执行,这样可以避免在需要时立即加载大图,优化了性能。

四、总结

JS 代理模式 Proxy 为开发者提供了一种灵活且强大的方式来控制对象的访问和增强其功能。通过各种拦截器方法,我们可以轻松实现访问控制、日志记录、性能优化等多种应用场景。在实际开发中,合理运用代理模式可以提高代码的可维护性、安全性和性能。它使得我们能够在不改变原始对象核心逻辑的前提下,为其添加额外的行为和约束,是一种非常实用的设计模式。无论是处理复杂的业务逻辑还是优化系统性能,Proxy 都能发挥重要作用,帮助我们构建更加健壮和高效的 JavaScript 应用程序。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

目录[+]