代碼可以隨時(shí)從程序中去掉,而不用對(duì)其他部分的代碼進(jìn)行修改,在實(shí)際場(chǎng)景中,隨著版本的迭代可能會(huì)有多種原因不再需要代理,那么就可以容易的將代理對(duì)象換成原對(duì)象的調(diào)用
代理模式
代理模式 (Proxy Pattern)又稱委托模式,它為目標(biāo)對(duì)象創(chuàng)造了一個(gè)代理對(duì)象,以控制對(duì)目標(biāo)對(duì)象的訪問,也可以理解為對(duì)外暴露的接口并不是原對(duì)象。通俗地講,生活中也有比較常見的代理模式:中介、寄賣、經(jīng)紀(jì)人等等。而這種模式存在的意義在于當(dāng)訪問者與被訪問者不方便直接訪問/接觸的情況下,提供一個(gè)替身來處理事務(wù)流程,實(shí)際訪問的是替身,替身將事務(wù)做了一些處理/過濾之后,再轉(zhuǎn)交給本體對(duì)象以減輕本體對(duì)象的負(fù)擔(dān)。
常見代理模式
實(shí)現(xiàn)
保護(hù)代理:
/**
* 保護(hù)代理簡(jiǎn)單實(shí)現(xiàn)
* client向服務(wù)端發(fā)送一個(gè)請(qǐng)求
* proxy代理請(qǐng)求轉(zhuǎn)發(fā)給服務(wù)端
* 服務(wù)端處理請(qǐng)求
*/
// 改進(jìn)前
const Request = function () {};
const client = {
requestTo: (server) => {
const req = new Request();
server.receiveRequest(req);
},
};
const server = {
handleRequest: (request) => {
console.log('receive request: ', request);
},
};
const proxy = {
receiveRequest: (request) => {
console.log('proxy request: ', request);
server.handleRequest(request);
},
};
client.requestTo(proxy);
// 改進(jìn)后
const proxy = {
receiveRequest: (request) => {
// 校驗(yàn)身份
const pass = validatePassport(request);
if (pass) {
// 監(jiān)聽服務(wù)端 ready 后代理請(qǐng)求
server.listenReady(() {
console.log('proxy request: ', request);
server.handleRequest(request);
});
}
},
};
虛擬代理:虛擬代理是把一些開銷很大的對(duì)象或者方法,延遲到真正需要它的時(shí)候才去創(chuàng)建執(zhí)行
// 圖片懶加載
const img = (() => {
const imgNode = document.createElement('img');
imgNode.style.width = '200px'
document.body.appendChild(imgNode);
return {
setSrc: (src) => {
imgNode.src = src;
},
setLoading: () => {
imgNode.src = './img/loading.gif'
}
};
})();
const proxyImg = ((source) => {
// 替身圖片對(duì)象
const tempImg = new Image();
// 監(jiān)聽資源加載完成,將資源替換給實(shí)體圖片對(duì)象
tempImg.onload = function () {
source.setSrc(this.src);
};
return {
// 代理開始將實(shí)體對(duì)象設(shè)置為loading狀態(tài),使用替身對(duì)象開始加載圖片資源
setSrc:(src)=>{
source.setLoading()
tempImg.src = src;
}
}
})(img);
proxyImg.setSrc('https://static-prod.retech.us/onder-cender/DoorDash.svg')
// ------------------------------------------------------------
// 合并http請(qǐng)求
//上傳請(qǐng)求
let upload = function(ids){
$.ajax({
data: {
id:ids
}
})
}
//代理合并請(qǐng)求
let proxy = (function(){
let cache = [],
timer = null;
return function(id){
cache[cache.length] = id;
if(timer) return false;
timer = setTimeout(function(){
upload(cache.join(','));
clearTimeout(timer);
timer = null;
cache = [];
},2000);
}
})();
// 綁定點(diǎn)擊事件
let checkbox = document.getElementsByTagName( "input" );
for(var i= 0, c; c = checkbox[i++];){
c.onclick = function(){
if(this.checked === true){
proxy(this.id);
}
}
}
緩存代理:緩存代理可以作為一些開銷大的運(yùn)算結(jié)果提供暫時(shí)的存儲(chǔ),下次運(yùn)算時(shí),如果傳遞進(jìn)來的參數(shù)跟之前一致,則可以直接返回前面存儲(chǔ)的運(yùn)算結(jié)果
/**
* 對(duì)于一些比較消耗性能的操作
* 可以將結(jié)果緩存起來
* 在獲取結(jié)果時(shí)優(yōu)先從緩存中取,緩存中沒有再計(jì)算
*/
let fibonacci = function(n){
if(n === 1 || n === 0 ) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
//緩存代理
let proxy = (function(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);
}
})(fibonacci);
proxy(3)
實(shí)際案例
Vue中的代理模式:
// 將數(shù)據(jù)、方法、計(jì)算屬性等代理到組件實(shí)例上
let vm = new Vue({
data: {
msg: 'hello',
vue: 'vue'
},
computed:{
helloVue(){
return this.msg + ' ' + this.vue
}
},
mounted(){
console.log(this.helloVue)
}
})
源碼分析:
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
function initData (vm: Component) {
let data = vm.$options.data
// 初始化 _data,組件中 data 是函數(shù),調(diào)用函數(shù)返回結(jié)果
// 否則直接返回 data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
// 獲取 data 中的所有屬性
const keys = Object.keys(data)
// 獲取 props / methods
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 判斷 data 上的成員是否和 props/methods 重名
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
// 響應(yīng)式處理
observe(data, true /* asRootData */)
}
總結(jié)
在面向?qū)ο蟮木幊讨?,代理模式的合理使用能夠很好地體現(xiàn)下面兩條設(shè)計(jì)原則:
- 單一職責(zé)原則: 面向?qū)ο笤O(shè)計(jì)中鼓勵(lì)將不同的職責(zé)分布到細(xì)粒度的對(duì)象中,Proxy 在原對(duì)象的基礎(chǔ)上進(jìn)行了功能的衍生而又不影響原對(duì)象,符合松耦合高內(nèi)聚的設(shè)計(jì)理念。
- 開放-封閉原則:代碼可以隨時(shí)從程序中去掉,而不用對(duì)其他部分的代碼進(jìn)行修改,在實(shí)際場(chǎng)景中,隨著版本的迭代可能會(huì)有多種原因不再需要代理,那么就可以容易的將代理對(duì)象換成原對(duì)象的調(diào)用
對(duì)于代理模式 Proxy 作用主要體現(xiàn)在三個(gè)方面:
- 攔截和監(jiān)視外部對(duì)對(duì)象的訪問
- 降低對(duì)象的復(fù)雜度
- 在復(fù)雜操作前對(duì)操作進(jìn)行校驗(yàn)或?qū)λ栀Y源進(jìn)行管理
?文章出自:??前端餐廳ReTech??,如有轉(zhuǎn)載本文請(qǐng)聯(lián)系前端餐廳ReTech今日頭條號(hào)。
github:https://github.com/zuopf769
