JS设计模式大合集

仓库地址:JS设计模式大合集
欢迎大家 Star ,一起交流学习!

代理模式

代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问。我们可以举一个形象的例子:

小明的妈妈想要去买水果,但是有事情忙不开,所以叫小明帮忙去买水果。小明出去之后,他可能会先到玩具店逛一下,然后去水果店逛一下,最后把买到的水果带回家。小明在这个过程中,就相当于代理,他代替妈妈买了水果,但是在买水果的过程中,又可以做一些其他的事情。

代理模式的应用场景

有了代理之后,我们能做的事情可就多了。

比如我们日常前端开发过程中使用到的网络请求拦截axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// 添加请求拦截器
http.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('Sending request to:', config.url);

return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('Received response from:', response.config.url);

return response;
}, function (error) {
// 对响应错误做些什么
return Promise.reject(error);
});

比如我们经常在vite开发中对域名的代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
server: {
proxy: {
// 使用 "/api" 路径作为代理前缀
'/api': {
// 目标服务器地址,请求将被代理到此地址
target: 'http://api.example.com',
// 是否更改origin头为target URL
changeOrigin: true,
// 重写路径。移除请求路径中的 "/api" 前缀
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});

再比如我们大名鼎鼎的VUE的响应式原理proxyObject.defineProperty。(😂关于defineProperty感兴趣的同学可以去看一下我之前写的文章:vue源码阅读-Object.defineProperty)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const targetObject = {
a: 10,
b: 20
};

const handler = {
set: function(target, property, value, receiver) {
console.log(`Property ${property} changed from ${target[property]} to ${value}`);
target[property] = value;
return true; // 表示成功设置了属性
}
};

const proxy = new Proxy(targetObject, handler);

proxy.a = 30;
// 控制台输出: Property a changed from 10 to 30

代理模式的优缺点

优点

  • 代理模式能够将代理对象与真实被调用的目标对象分离。
  • 一定程度上降低了系统的耦合度,扩展性好。
  • 可以起到保护目标对象的作用。
  • 可以对目标对象的功能进行扩展。

缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

倘若无proxy在js中怎么代理?

在ES6引入Proxy对象之前,JavaScript中的代理模式通常通过以下几种方式实现:

  1. 对象封装:
    通过创建一个封装了原始对象的新对象。这个新对象(代理)提供了一个接口,这个接口与原始对象的接口相同,但会添加额外的逻辑,如访问控制、日志记录等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function createProxy(subject) {
    const origMethod = subject.someMethod;
    subject.someMethod = function() {
    // 额外逻辑
    console.log('someMethod was called');
    // 调用原始方法
    return origMethod.apply(subject, arguments);
    };
    return subject;
    }
  2. 函数拦截:
    使用高阶函数来拦截函数调用。可以在函数前后添加额外的行为,并且能够控制是否调用原始函数。

    1
    2
    3
    4
    5
    6
    7
    8
    function createFunctionProxy(originalFunction) {
    return function() {
    // 额外逻辑
    console.log('Function was called with arguments:', arguments);
    // 调用原始函数
    return originalFunction.apply(this, arguments);
    };
    }
  3. 发布/订阅模式:
    虽然这不是传统的代理模式,但在某些情况下可以起到类似代理的作用。对象不直接调用方法,而是发布一个事件,而另一个对象订阅这个事件并在触发时执行操作。(感兴趣可以去翻一下我之前的文章:JS设计模式-发布订阅者模式,这里我就不贴代码啦~)

  4. 访问器属性:
    使用getter和setter可以在对象属性被访问或修改时执行特定逻辑。这在某种程度上能够模拟属性级别的代理行为。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const obj = {
    _a: 1,
    get a() {
    // 额外逻辑
    console.log('a was accessed');
    return this._a;
    },
    set a(value) {
    // 额外逻辑
    console.log('a was set to', value);
    this._a = value;
    }
    };

image.png

总结

在JavaScript的世界里,代理模式展现了其独有的魅力,通过在目标对象和访问者之间引入一个代理层,它不仅为我们提供了一条灵活控制和增强对象行为的途径,而且也极大地丰富了我们对设计模式在现代Web开发中运用的理解和深度。从简单的事件处理到复杂的系统监控,代理模式都能以其独到的方式,帮助我们构建更加高效和可维护的应用系统。