代理的应用场景

代理设计模式:
一个代理对象充当另一个主体对象的接口。
与门面模式做对比:
代理模式与门面模式不同,门面模式最主要的功能是简化了接口的设计,把复杂的逻辑实现隐藏在背后,把不同的方法调用结合成更便捷的方法提供出来。
代理对象在调用者和主体对象之间,主要起到的作用是保护和控制调用者对主体对象的访问。代理会拦截所有或部分要在主体对象上执行的操作,有时会增强或补充它的行为。

如上图所示,一般代理和主体具有相同的接口,这对调用者来说是透明的。代理将每个操作转发给主体,通过额外的预处理或后处理增强其行为。这种模式可能看起来像“二道贩子”,但存在即合理,代理特别是在性能优化方面还是起到了很大作用的。
延迟初始化和缓存
代理充当了主体对象的保护作用,减少了客户端对代理背后真实主体无效的消耗。

代理除了起到延迟初始化的作用外,还可以增加一层缓存。

代理的其它应用场景

比如数据验证,代理可以在将输入转发给主体之前对输入的内容进行验证,确保无误后,再传给后端。除此之外,代理模式也可以用于安全验证,代理可以用来验证客户端是否被授权执行操作,只有在检查结果为肯定的情况下,才将请求发送给后端。
代理还有一个应用场景是日志记录,代理可以通过拦截方法调用和相关参数,重新编码。另外,它还可以获取远程对象并放到本地。

代理的实现方式

代理模式在 JavaScript 中有很多种实现方式。其中包含了:

  1. 对象组合或对象字面量加工厂模式;
  2. 对象增强;
  3. 使用从 ES6 开始自带的内置的 Proxy。这几种方式分别有它们的优劣势。

组合模式
基于函数式编程的思想,组合可以被认为是创建代理的一种简单而安全的方法,因为它使主体保持不变,从而不会改变其原始行为。它唯一的缺点是我们必须手动 delegate 所有方法,即使我们只想代理其中的一个方法。

class Calculator {
  constructor () {
    /*...*/
  }
  plus () { /*...*/ }
  minus () { /*...*/ }
}

class ProxyCalculator {
  constructor (calculator) {
    this.calculator = calculator
  }
  // 代理的方法
  plus () { return this.calculator.divide() }
  minus () { return this.calculator.multiply() }
}

var calculator = new Calculator();
var proxyCalculator = new ProxyCalculator(calculator);

基于组合的思路用工厂函数来做代理创建

function factoryProxyCalculator (calculator) {
  return {
    // 代理的方法
    plus () { return calculator.divide() },
    minus () { return calculator.multiply() }
  }
}

var calculator = new Calculator();
var proxyCalculator = new factoryProxyCalculator(calculator);

对象增强
对象增强还有一个名字叫猴子补丁(Monkey Patching)。
对于对象增强来说,它的优点就是不需要 delegate 所有方法。但是它最大的问题是改变了主体对象。用这种方式确实是简化了代理创建的工作,但弊端是会造成函数式编程思想中的“副作用”,因为在这里,主体不再具有不可变性。

function patchingCalculator (calculator) {
  var plusOrig = calculator.plus
  calculator.plus = () => {
    // 额外的逻辑
    // 委托给主体
    return plusOrig.apply(calculator)
  }
  return calculator
}
var calculator = new Calculator();
var safeCalculator = patchingCalculator(calculator);

内置 Proxy
ES6 内置的 Proxy
从 ES6 之后,JavaScript 便支持了 Proxy。它结合了对象组合和对象增强各自的优点,我们既不需要手动的去 delegate 所有的方法,也不会改变主体对象,保持了主体对象的不变性。但是它也有一个缺点,就是它几乎没有 polyfill。也就是说,如果使用内置的代理,就要考虑在兼容性上做出一定的牺牲。真的是鱼和熊掌不能兼得。

var ProxyCalculatorHandler = {
  get: (target, property) => {
    if (property === 'plus') {
      // 代理的方法
      return function () {
        // 额外的逻辑
        // 委托给主体
        return target.divide();
      }
    }
    // 委托的方法和属性
    return target[property]
  }
}
var calculator = new Calculator();
var proxyCalculator = new Proxy(calculator, ProxyCalculatorHandler);

VUE 如何用代理实现响应式编程
Vue.js 通过代理创建了一种 Change Obsverver 的设计模式。
Vue.js 最显着的特点之一是无侵入的反应系统(unobtrusive reactivity system)。
组件状态是响应式 JavaScript 对象,当被修改时,UI 会更新。就像我们使用 Excel 时,如果我们在 A2 这个格子里设置了一个 A0 和 A1 相加的公式  “= A0 + A1”的话,当我们改动 A0 或 A1 的值的时候,A2 也会随之变化。这也是我们在前面说过很多次的在函数式编程思想中的副作用(side effect)。
在 JavaScript 中,如果我们用命令式编程的方式, 可以看到这种副作用是不存在的。

var A0 = 1;
var A1 = 2;
var A2 = A0 + A1;
console.log(A2) // 返回是 3
A0 = 2;
console.log(A2) // 返回仍然是 3

但响应式编程(Reactive Programming)是一种基于声明式编程的范式。如果要做到响应式编程,我们就会需要下面示例中这样一个 update 的更新功能。

var A2;
function update() {
  A2 = A0 + A1;
}
whenDepsChange(update);

在 JavaScript 中没有 whenDepsChange 这样的机制可以跟踪局部变量的读取和写入 。Vue.js 能做的,是拦截对象属性的读写。
Vue 2 仅使用 getter/setter。
在 Vue 3 中,Proxies 用于响应式对象,getter/setter 用于通过属性获取元素的 refs。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
     return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

handler 里包含一系列具有预定义名称的可选方法了,称为陷阱方法(trap methods)

延伸:Proxy 还可以用于哪些场景

JavaScript 内置的 Proxy 除了作为代理以外,还有很多作用。基于它的拦截和定制化的特点,Proxy 也广泛用于对象虚拟化(object virtualization)、运算符重载(operator overloading)和最近很火的元编程(meta programming)。这里我们不用伪代码,换上一些简单的真代码,看看陷阱方法(trap methods)的强大之处。
对象虚拟化
下面的例子中的 oddNumArr 单数数组就是一个虚拟的对象。我们可以查看一个单双数是不是在单数数组里,我们也可以获取一个单数,但是实际上这个数组里并没有储存任何数据。

const oddNumArr = new Proxy([], {
  get: (target, index) => index % 2 === 1 ? index : Number(index)+1,
  has: (target, number) => number % 2 === 1
})

console.log(4 in oddNumArr) // false
console.log(7 in oddNumArr) // true
console.log(oddNumArr[15])   // 15
console.log(oddNumArr[16])   // 17

运算符重载
运算符重载就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。比如在下面的例子中,我们就是通过重载“.”这个符号,所以在执行 obj.count 时,我们看到它同时返回了拦截 get 和 set 自定义的方法,以及返回了计数的结果。

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`获取 ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`设置 ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

obj.count = 1; // 返回:设置 count!
obj.count; 
// 返回:获取 count!
// 返回:设置 count!
// 返回:1
此文章为2月Day2学习笔记,内容来源于极客时间《Jvascript进阶实战课》,大家共同进步💪💪

豪猪
4 声望4 粉丝

undefined