3_代理模式

1 简介

  • 为一个对象提供一个代用品或占位符,以便控制对它的访问

应用:明星都有经纪人作为代理

  • 关键:当客户不方便直接访问一个对象或不满足需求时,提供一个替身对象来控制对这个对象的访问,实际上访问的是替身对象

image.png

2 例子 -- 小明追mm的故事

1. 不使用代理

class Flower {}
let xiaoming = {
  sendFlower(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let A = {
  receiveFlower(flower) {
    console.log('收到花' + flower)
  }
}
xiaoming.sendFlower(A)

2. 使用代理

假设当A心情好时收到花,小明表白成功几率会高一些。此时需要通过其朋友B,在A心情好时帮忙将花转送给A

class Flower {}
let xiaoming = {
  sendFlower(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let B = {
  receiveFlower(flower) {
    A.listenGoodMood(() => { // 监听A的好心情
      A.receiveFlower(flower)
    })
  }
}
let A = {
  receiveFlower(flower) {
    console.log('收到花' + flower)
  },
  listenGoodMood(fn) {
    setTimeout(() => { // 假设 10 秒之后 A 的心情变好
      fn()
    }, 1000)
  }
}
xiaoming.sendFlower(B)

3 保护代理和虚拟代理

1. 保护代理

过滤掉不符合条件的请求

  • 用于控制不同权限的对象对目标对象的访问

2. 虚拟代理

  • 把开销大的对象延迟到真正需要时再执行
let B = {
  receiveFlower() {
    A.listenGoodMood(() => { // 监听A的好心情
      let flower = new Flower() // 延迟创建flower对象
      A.receiveFlower(flower)
    })
  }
}

虚拟代理是最常用的一种代理模式

4 虚拟代理实现图片预加载

图片预加载:先用一张loading图片占位,用异步的方式加载图片,等图片加载好了再把它填充到img节点里

1. 本体对象

职责:

  • 往页面创建img标签
  • 提供一个对外的setSrc接口 -> 外界调用给img设置src属性
let myImage = (() => {
  let imgNode = document.createElement('img')
  document.body.appendChild(imgNode)
  return {
    setSrc(src) {
      imgNode.src = src
    }
  }
})()

2. 定义代理对象 proxyImage

  • 实现在图片被加载完成之前页面中出现一张loading图占位
let proxyImage = (() => {
  let img = new Image()
  img.onload = function() {
    myImage.setSrc(this.src)
  } 
  return {
    setSrc(src) {
      myImage.setSrc('http://linshixxx')
      img.src = src
    }
  }
})()
proxyImage.setSrc('http://xxx')

5 代理的意义

  • 单一职责原则: 就一个类(对象和函数等)而言,应该仅有一个引起它变化的原因

  • 意义:低耦合 + 开放封闭原则

6 代理和本体接口的一致性

  • 用户可以放心地请求代理,他只关心是否能得到想要的结果
  • 在任何使用本体的地方都可以替换成使用代理

7 虚拟代理合并HTTP请求

每点击一个checkbox就发送一次请求

function synchronous(id) {
  console.log('开始同步文件,id为' + id)
}
let checkbox = document.getElementsByTagName('input')
for(let c in checkbox) {
  c.onclick = function() {
    if(this.checked === true) {
      synchronous(this.id)
    }
  }
}
  • 通过代理函数收集一段时间内的请求,一次性发送给服务器
function synchronousFile(id) {
  console.log('开始同步文件,id为' + id)
}
let proxySynchronousFile = (() => {
  let cache = [], // 保存一段时间内需要同步的is
    timer; // 定时器
  return id => {
    cache.push(id)
    if(timer) return // 保证不会覆盖已启动的定时器
    timer = setTimeout(() => {
      synchronousFile(cache.join(','))
      clearTimeout(timer)
      timer = null
      cache.length = 0
    }, 2000)
  }
})()
let checkbox = document.getElementsByTagName('input')
for(let i = 0, c; c = checkbox[i++]; ) {
  c.onclick = function() {
    if(this.checked === true) {
      proxySynchronousFile(this.id)
    }
  }
}

8 虚拟代理在惰性加载中的应用

用一个占位的 miniConsole 代理对象来给用户提前使用,这个代理对象提供给用户的接口,跟实际的 miniConsole 是一样的

按下F2唤出控制台

1. 未真正加载 miniConsole.js(1000+行代码)之前的代码

let cache = []
let miniConsole = {
  log() {
    let args = arguments
    cache.push(() => {
      return miniConsole.log.apply(miniConsole, args)
    })
  }
}
miniConsole.log(1)

2. 用户按下F2,开始加载 miniConsole.js

function handler(ev) {
  if(ev.keyCode === 113) {
    let script = document.createElement('script')
    script.onload = () => {
      for(let fn in cache) {
        fn()
      }
    }
    script.src = 'miniConsole.js'
    document.getElementsByTagName('head')[0].appendChild(script)
  } 
}
document.body.addEventListener('keydown', handler, false)
miniConsole.js 代码
miniConsole = {
  log() {
    // ...真正代码略
    console.log(Array.prototype.join.call(arguments))
  }
}; 

3. 整理代理对象

let miniConsole = (() => {
  let cache = []
  let handler = ev => {
    if(ev.keyCode === 113) {
      let script = document.createElement('script')
      script.onload = () => {
        for(let i = 0, fn; fn = cache[i++]; ) {
          fn()
        }
      }
      script.src = 'miniConsole.js'
      document.getElementsByTagName('head')[0].appendChild(script)
    } 
  }
  document.body.addEventListener('keydown', handler, false)
  return {
    log() {
      let args = arguments
      cache.push(() => {
        return miniConsole.log.apply(miniConsole, args)
      })
    }
  }
})()
miniConsole.log(11)

9 缓存代理

例子-计算乘积

1. 乘积函数
funtion mult() {
  let a = 1
  for(let i = 0, l = arguments.length; i < 1; i++) {
    a *= arguments[i]
  }
  return a
}
mult(2, 3) // 6
mult(2, 3, 4) // 24
2. 缓存代理函数
let proxyMult = (() => {
  let cache = {}
  return function() {
    let args = Array.prototype.join.call(argumrnts, ',')
    if(args in cache) {
      return cache[args]
    }
    return cache[args] = mult.apply(this, arguments)
  }
})()
proxyMult(1, 2, 3, 4) // 24
proxyMult(1, 2, 3, 4) // 24

缓存代理用于ajax异步请求数据

  • 分页数据
  • 回调

10 用高阶函数动态创建代理

/**************** 计算乘积 *****************/
function mult() {
  let a = 1
  for(let i = 0, l = arguments.length; i < l; i++) {
    a *= arguments[i]
  }
  return a
}
/**************** 计算加和 *****************/
function plus() {
  let a = 0
  for(let i = 0, l = arguments.length; i < l; i++) {
    a += arguments[i]
  }
  return a
}
/**************** 创建缓存代理的工厂 *****************/
function createProxyFactory(fn) {
  let cache = {}
  return function() {
    let args = Array.prototype.join.call(arguments, ',')
    if(args in cache) {
      return cache[args]
    }
    return cache[args] = fn.apply(this, arguments)
  }
}
let proxyMult = createProxyFactory(mult),
proxyPlus = createProxyFactory(plus)
alert(proxyMult(1, 2, 3, 4)); // 输出:24
alert(proxyMult(1, 2, 3, 4)); // 输出:24
alert(proxyPlus(1, 2, 3, 4)); // 输出:10
alert(proxyPlus(1, 2, 3, 4)); // 输出:10

11 其它代理模式

  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近
  • 远程代理:为一个对象在不同的地址空间提供局部代表
    • 在 Java 中,远程代理可以是另一个虚拟机中的对象
  • 保护代理:用于对象应该有不同访问权限的情况
  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作
    • 计算一个对象被引用的次数
  • 写时复制代理:通常用于复制一个庞大对象的情况
    • 写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作
    • 写时复制代理是虚拟代理的一种变体,DLL(操作系统中的动态链接库)是其典型运用场景
posted on 2023-05-09 10:48  pleaseAnswer  阅读(8)  评论(0编辑  收藏  举报