3_代理模式
1 简介
- 为一个对象提供一个代用品或占位符,以便控制对它的访问
应用:明星都有经纪人作为代理
- 关键:当客户不方便直接访问一个对象或不满足需求时,提供一个替身对象来控制对这个对象的访问,实际上访问的是替身对象
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(操作系统中的动态链接库)是其典型运用场景