代理模式
代理的概念
由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用
以上便是代购,不,是中介,嗨,代理的定义了,由此可见,在我们的日常生活中,代理扮演着众多重要的角色。
在面向对象的编程中,代理模式的合理使用,能够很好的体现下面两条原则:
1、单一职责原则:面向对象设计中鼓励将不同的职责分布到细粒度的对象中,
Proxy
在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念。
2、开发-封闭原则:代理可以随时从程序中去除,而无需对其他部分的代码进行修改,在实际场景中,随着版本的迭代可能会有多种原因不在不需要代理,那么就可以很容易的将代理对象换成原对象的调用。
代理的实现
// 你自己
var self = {
buyGoods: function(name) {
console.log('买了想要的' + name)
}
}
// 你想买的物品
var Goods = function(name) {
this.name = name
}
Goods.prototype.getName = function() {
return this.name
}
// 一个代购
var assistant = {
buyGoods: function(goods) {
self.buyGoods(goods.getName())
}
}
assistant.buyGoods(new Goods('iPhone 11 Pro Max')) //买了想要的iPhone 11 Pro Max
一个简单的代理便跃然纸上了。
虚拟代理
把一些开销很大的对象,延迟到真正需要它的时候才去创建执行。
图片的预加载便是常见的虚拟代理的应用。当图片过大或者网络不佳时,我们不会直接给某个img
标签节点设置src
属性,而是使用一张loading
图片作为占位符,然后用异步的方式来加载图片,等到图片加载完毕,我们再把它填充到img
的节点里。
var myImage = (function() {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
var preImage = (function() {
var img = new Image;
img.onload = function() {
myImage.setSrc = img.src;
};
return {
setSrc: function(src) {
myImage.setSrc( '../loading.gif');
img.src = src;
}
}
})();
preImage.setSrc('https://cn.bing.com/th?id=OHR.MaldivesDragonfly_ZH-CN5949519396_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp');
缓存代理
简单的说,对第一次运行的结果进行缓存,当再次运行相同的运算时,直接从缓存里读取,避免重复运算。
对于复杂运算而言,可以使用缓存对象,可以提高性能。
// 计算加法
var plus = function(){
var a = 0;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a += arguments[i];
}
return a;
}
// 代理函数
var proxyFunc = function(fn) {
var cache = {}; // 缓存对象
return function(){
var args = Array.prototype.join.call(arguments,',');
if(args in cache) {
return cache[args]; // 使用缓存代理
}
return cache[args] = fn.apply(this,arguments);
}
};
var proxyMult = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 缓存取 24
我们不用代理当然也能实现缓存就和,但是为了达到单一职责,我们可以让mult
实现求和,而缓存则放在代理中来实现。
ES6中的代理模式
ES6
提供了Proxy
构造函数,让我们能够轻松的使用代理模式:
let proxy = new Proxy(target, handler);
Proxy
构造函数传入两个参数,第一个参数target
表示所要代理的对象,第二个参数handler
也是一个对象,用来设置对所代理的对象的行为。
ES6如何实现缓存代理
下面我们以没有经过任何优化的计算斐波那契数列的函数来假设为开销很大的方法,这种递归调用在计算40
以上的斐波那契项时,就能明显的感到延迟感。
const getFib = (number) => {
if (number <= 2) {
return 1;
} else {
return getFib(number - 1) + getFib(number - 2);
}
}�
// 创建一个缓存代理的工厂对象:
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;
}
})
}
// 调用方法
const getFibProxy = getCacheProxy(getFib);
getFibProxy(40); // 102334155
getFibProxy(40); // 输出40的缓存结果: 102334155
总结
在vue3.0
中,便使用了Proxy
重写了核心代码数据双向绑定,相对于Object.defineProperty
而言,使用Proxy
效率更高。代理模式虽然很方便,但仍应注意其使用场景,只有当对象的功能变得复杂或者我们需要进行一定的访问限制时,再考虑使用代理。