代理模式

68 阅读3分钟

为了改变JS中原生对象的默认行为

示例场景

假设你在代码中编写了一个通用的A方法,其他同事在他们编写的B、C、D方法中都调用了这个方法。

现在因为业务升级,你希望在A方法中添加一些新的扩展逻辑

如果不允许修改A方法的现有代码,那么你要如何来实现呢?

我们最容易想到的方法就是新建一个A2方法,

在它的函数体中先调用一次A方法,

然后再加入新增的逻辑,

这样的确可以达到上述要求,

但你也不得不将B、C、D方法中调用的A方法逐个修改为A2方法,

所以在代码实现上最好能够对调一下A和A2的函数名,

这样对于上层的B、C、D而言,它们调用的仍然是A方法,

而A方法的实现其实已经更换成了新的函数,

新函数通过调用A2方法(即原来的A方法逻辑)

保留了之前的逻辑,然后执行自己函数体中新增加的逻辑,

这样就完成了A方法的功能扩展。

其实,JavaScript中代理模式的实现也是这样一个过程

扩展知识

代理模式也称为Proxy模式,

有时还被称为“劫持”模式,

是前端使用率较高的经典设计模式中的一种,

其目的是为其他对象提供一种代理机制,

以控制对这个对象的访问。

代理模式使用代理对象来控制具体对象的引用,

代理对象几乎可以是任何对象:

文件、资源、内存中的对象,或者是一些难以复制的东西。

例如生活中的房屋中介,可以代表卖家把房子卖给买家,

这中间卖家提出期望的价钱,买家也可以提出自己心仪的户型和预算,

房产中介可以帮忙处理各类中间环节,从而促成交易,

双方都只面对中介开展业务。

从买家的视角来看,房产中介就相当于卖家的代理,

尽管他不是卖家本身,但是在交易活动中可以代表卖家。

Vue2中为了能够让数组具备“响应式”的特点,对典型的数组变异方法

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]

/**
 * 插入代理方法并发送事件通知
 */
methodsToPatch.forEach(function (method) {
    // 缓存原方法
    const original = arrayProto[method]
    def(arrayMethods, method, function mutator (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if (inserted) ob.observeArray(inserted)
        // 将变化通知给监听者
        ob.dep.notify()
        return result
    })
})
methodsToPatch.forEach(function (method) {
    //保存旧方法
    const original = arrayProto[method]
    //定义新方法
    def(arrayMethods, method, function mutator (...args) {
        //执行旧方法
        const result = original.apply(this, args)
        //新增逻辑,用于增加响应式特性
        //...
        return result;
    })
})

原生的方法被保存在original上(也就是将原生方法更为original),

而原生方法名则在def方法中指向了新的函数