# 代理模式
# 1、基本概念
代理模式是为其他对象提供一种代理以控制对这个对象的访问
就拿前端经常简单的场景举例,比如有些操作并不想频繁的触发它,需要有人限制它的触发频率;就比如有些时候我们在操作数据的时候想做一些额外的事儿,比如Vue的双向数据绑定。
什么时候适合使用代理模式呢?——想在访问一个对象时做一些控制。
代理模式的UML图如下:

# 2、代码示例
interface Subject {
profit(number: number): void;
}
class RealSubject implements Subject {
profit(number: number): void {
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
console.log(`you can earn money ${number} every day`);
console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}
class ProxySubject implements Subject {
private readSubject = new RealSubject();
profit(number: number): void {
if (number <= 0) {
console.warn("salary must bigger than zero");
return;
}
this.readSubject.profit(number);
}
}
(function bootstrap() {
const sub = new ProxySubject();
for (let i = 0; i < 10; i++) {
const rnd = Math.random();
sub.profit(rnd > 0.5 ? Number.parseInt((rnd * 1000).toFixed(0)) : 0);
}
})();
# 3、前端开发中的实践
频繁触发的操作,出于性能考虑,我们会降低它触发的频率,这就是节流和防抖。
节流:规定的时间内重复触发,函数只会执行一次。
节流的应用场景:主要是按钮的点击,屏幕缩放等操作,需要处理一些计算等。
防抖:规定的时间内重复触发,每次触发都会重新计算时间,最后再触发一次真正的操作。比如你设置了一个1S的防抖,如果第1S内连续触发了10次,则第一秒内函数一次都不会执行,在第一秒末开始计算触发时间,第2S真正触发。
防抖的应用场景:搜索框搜索
另外,ES6新增了Proxy,我们可以直接使用语法糖实现代理模式。就比如Vue3数据的双向绑定就使用的是Proxy,还可以用Proxy来实现具有负数索引的数组.
# 3.1、实现数组负数的索引
function SafetyArray(arr) {
return new Proxy(arr, {
get(target, propKey, receiver) {
console.log(target, propKey, receiver)
// 如果数组为空,
const digitProp = Number.parseInt(propKey);
if (target.length === 0 && digitProp < 0) {
digitProp = Math.abs(digitProp);
}
if (propKey < 0) {
propKey = target.length + digitProp;
}
return target[propKey];
},
set(target, propKey, value, receiver) {
const digitProp = Number.parseInt(propKey);
// 不允许给数组设置除了数字以外的键
if (Number.isNaN(digitProp) && propKey !== "length") {
return false;
}
if (target.length === 0 && digitProp < 0) {
digitProp = Math.abs(digitProp);
}
if (propKey < 0) {
propKey = target.length + digitProp;
}
target[propKey] = value;
return true;
},
});
}
# 3.2、节流
由于JS语法特性比较灵活,在前端开发中,节流和防抖这两个代理模式的应用场景,你可能并没有发觉。这是因为JS的函数可以作为参数传递,和闭包搭配使用,避免了我们去编写上述那么多的代码。
下述代码中,callback函数就相当于UML图中的RealSubject类,返回的函数fn就相当于是图中的Proxy类,当我们调用fn的时候,它会根据间隔的时间是否执行真正的callback而改变了函数的行为。
function throttle(callback, ms) {
let pre = Date.now();
return function fn(...args) {
let now = Date.now();
// 如果超过了时间段,执行函数
if (now - pre >= ms) {
callback.apply(this, args);
// 开启下一个阶段可用
pre = now;
}
};
}
以上节流是不使用定时器的简单实现,此外,节流还可以使用定时器实现。
function throttle(callback, ms) {
let timer = null;
return function fn(...args) {
if (!timer) {
timer = setTimeout(() => {
callback.apply(this, args);
timer = null;
}, ms);
}
};
}
# 3.3、防抖
function debounce(callback, ms) {
let timer = null;
return function fn(...args) {
// 如果timer存在,说明还没有到执行的时间,需要清理定时器
if (timer) {
clearTimeout(timer);
return;
}
// 设置一个定时器,在规定的时间内触发即可。
timer = setTimeout(() => {
callback.apply(this, args);
}, ms);
};
}