代理模式

为其他对象提供一种代理以控制对这个对象的访问

特点如下

  1. 代理对象可预先处理请求,再决定是否转交给本体;
  2. 代理和本体对外显示接口保持一致性
  3. 代理对象仅对本体做一次包装

1. 模式细分

  1. 虚拟代理(将开销大的运算交给代理,延迟到需要时执行)
  2. 缓存代理(为开销大的运算结果提供缓存)
  3. 保护代理(用于对象应该有不同的访问权限)
  4. 防火墙代理(控制网络资源的访问)
  5. 远程代理(为一个对象在不同的地址控件提供局部代表)
  6. 智能引用代理(访问对象执行一些附加操作)
  7. 写时复制代理(延迟对象复制过程,对象需要真正修改时才进行)

2. 虚拟代理实现图片预加载

在所需图片未加载前,用loading图片占位,然后用异步的方式加载图片,等图片加载好了,替换掉loading,下面先不用代理模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let MyImage = (function() {
let imgNode = document.createElement('img');
document.body.appendChild(imgNode);
let img = new Image;

img.onload = function () {
imgNode.src = img.src; // 图片加载完成后替换loading
};

return {
setSrc: function (src) {
imgNode.src = "file://loading.gif"; // 先显示loading图片
img.src = src; // 虚拟dom结构加载图片
}
}
})

违背了单一职责原则,在这种高耦合的情况下,如果不需要loading效果,那么只能在MyImage下动刀。

接下来我们用虚拟代理重构一下这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 加载图片
let myImage = (function () {
let imgNode = document.createElement('img');
document.body.appendChild(imgNode);

return {
setSrc: function (src) {
imgNode.src = src;
}
}
})();
// 虚拟代理
let proxyImage = (function () {
let img = new Image;

img.onload = function () {
myImage.setSrc(this.src);
};

return {
setSrc: function (src) {
myImage.setSrc("file://loading.gif");
img.src = src;
}
}
})();
proxyImage.setSrc( 'http://www.lll.com/photo/true.jpg' );

这样就解耦了

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

实现一个等用户按F2唤出控制台的时候,再开始加载miniConsole.js代码,并在加载完成后执行cache中的log函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let miniConsole = (function () {
let cache = [];
let handler = function (e) {
if (e.keyCode === 113) {
let script = document.createElement("script");
script.onload = function () {
for (let i = 0, fn; fn = cache[i++];) {
fn();
}
};
script.src = "miniConsole.js";
document.getElementsByClassName("head")[0].appendChild(script);
document.body.removeEventListener("keydown", handler); // 只加载一次miniConsole.js
}
}

document.body.addEventListener("keydown", handler, false);

return {
log: function () {
let args = arguments;
cache.push(function () {
return miniConsole().log.apply(miniConsole().args);
})
}
}
})();

miniConsole.log(11);

4. 缓存代理计算乘积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let mult = function () {
let a = 1;
for (let i = 0; i < arguments.length; i++) {
a *= arguments[i];
}
return a;
}

let proxyMult = (function () {
let cache = {};
return function () {
let args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = mult.apply(this, arguments);
}
})();

proxyMult(1, 2, 3) // 6
proxyMult(1, 2, 3) // 不会重复计算

5. ES6中的Proxy

ES6 的 Proxy语法:let proxyObj = new Proxy(target, handler);

  • target: 本体,要代理的对象
  • handler: 自定义操作方法集合
  • proxyObj: 返回的代理对象,拥有本体的方法,不过会被 handler 预处理

可以拦截并控制对对象的访问,下面是用Proxy实现的代理工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getCacheProxy = (fn, cache = new Map()) => {
return new Proxy(fn, {
apply(target, context, args) {
const argsString = args.join(',');
if (cache.has(argsString)) {
// 如果有缓存,直接返回缓存数据 console.log(`输出${args}的缓存结果: ${cache.get(argsString)}`);
return cache.get(argsString);
}
const result = fn(...args);
cache.set(argsString, result);
return result;
}
})
}

参考

《javascript设计模式与开发实践》
https://segmentfault.com/a/1190000019574843

作者

Liang

发布于

2021-01-31

更新于

2021-12-25

许可协议


评论