一 说说对代理模式的理解?

1.1 是什么

代理模式(Proxy Pattern)是为⼀个对象提供⼀个代⽤品或占位符,以便控制对它的访问

代理模式的关键是,当客⼾不⽅便直接访问⼀个对象或者不满⾜需要时,提供⼀个替⾝对象来控制这个对象的访问,客⼾实际上访问的是替⾝对象

在⽣活中,代理模式的场景是⼗分常⻅的,例如我们现在如果有租房、买房的需求,更多的是去找链家等房屋中介机构,⽽不是直接寻找想卖房或出租房的⼈谈。此时,链家起到的作⽤就是代理的作⽤

1.2 使用

在 ES6 中,存在 proxy 构建函数能够让我们轻松使⽤代理模式:

const proxy = new Proxy(target, handler);

⽽按照功能来划分, javascript 代理模式常⽤的有:

  • 缓存代理
  • 虚拟代理

1.2.1 缓存代理

缓存代理可以为⼀些开销⼤的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前⼀致,则可以直接返回前⾯存储的运算结果

如实现⼀个求积乘的函数,如下:

var muti = function() {
	console.log("开始计算乘积");
	var a = 1;
	for (var i = 0, l = arguments.length; i < l; i++) {
		a = a * arguments[i];
	}
	return a;
};

现在加载缓存代理,如下:

var proxyMult = (function() {
	var cache = {};
	return function() {
		var args = Array.prototype.join.call(arguments, ",");
		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

当第⼆次调⽤ proxyMult(1, 2, 3, 4) 时,本体 mult 函数并没有被计算, proxyMult 直接返回了之前缓存好的计算结果

1.2.2 虚拟代理

虚拟代理把⼀些开销很⼤的对象,延迟到真正需要它的时候才去创建

常⻅的就是图⽚预加载功能:

未使⽤代理模式如下:

let MyImage = (function() {
	let imgNode = document.createElement('img');
	document.body.appendChild(imgNode);
	// 创建⼀个Image对象,⽤于加载需要设置的图⽚
	let img = new Image;
	img.onload = function() {
		// 监听到图⽚加载完成后,设置src为加载完成后的图⽚
		imgNode.src = img.src;
	};
	return {
		setSrc: function(src) {
			// 设置图⽚的时候,设置为默认的loading图
			imgNode.src =
				ttps: //img.zcool.cn/community/01deed576019060000018c1bd2352d.gif';
				// 把真正需要设置的图⽚传给Image对象的src属性
				img.src = src;
		}
	}
})();
MyImage.setSrc('https://xxx.jpg');

MyImage 对象除了负责给 img 节点设置 src 外,还要负责预加载图⽚,违反了⾯向对象设计的原则-单⼀职责原则

上述过程 loding 则是耦合进 MyImage 对象⾥的,如果以后某个时候,我们不需要预加载显⽰loading这个功能了,就只能在 MyImage 对象⾥⾯改动代码

使⽤代理模式,代码则如下:

// 图⽚本地对象,负责往⻚⾯中创建⼀个img标签,并且提供⼀个对外的setSrc接⼝
let myImage = (function() {
	let imgNode = document.createElement('img');
	document.body.appendChild(imgNode);
	return {
		//setSrc接⼝,外界调⽤这个接⼝,便可以给该img标签设置src属性
		setSrc: function(src) {
			imgNode.src = src;
		}
	}
})();
// 代理对象,负责图⽚预加载功能
let proxyImage = (function() {
	// 创建⼀个Image对象,⽤于加载需要设置的图⽚
	let img = new Image;
	img.onload = function() {
		// 监听到图⽚加载完成后,给被代理的图⽚本地对象设置src为加载完成后的图⽚
		myImage.setSrc(this.src);
	}
	return {
		setSrc: function(src) {
			// 设置图⽚时,在图⽚未被真正加载好时,以这张图作为loading,提⽰⽤⼾图⽚正在加载
			myImage.setSrc(
				'https://img.zcool.cn/community/01deed576019060000018c1bd2352d.gif');
			img.src = src;
		}
	}
})();
proxyImage.setSrc('https://xxx.jpg');

使⽤代理模式后,图⽚本地对象负责往⻚⾯中创建⼀个 img 标签,并且提供⼀个对外的 setSrc 接⼝

代理对象负责在图⽚未加载完成之前,引⼊预加载的 loading 图,负责了图⽚预加载的功能

上述并没有改变或者增加 MyImage 的接⼝,但是通过代理对象,实际上给系统添加了新的⾏为

并且上述代理模式可以发现,代理和本体接⼝的⼀致性,如果有⼀天不需要预加载,那么就不需要代理对象,可以选择直接请求本体。其中关键是代理对象和本体都对外提供了 setSrc ⽅法

1.3 应用场景

现在的很多前端框架或者状态管理框架都使⽤代理模式,⽤与监听变量的变化

使⽤代理模式代理对象的访问的⽅式,⼀般⼜被称为拦截器,⽐如我们在项⽬中经常使⽤ Axios 的实例来进⾏ HTTP 的请求,使⽤拦截器 interceptor 可以提前对 请求前的数据 服务器返回的数据进⾏⼀些预处理

以及上述应⽤到的缓存代理和虚拟代理