什麼是代理模式
代理模式是:使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問。
代理模式很好理解,“有事別找我,找我的代理去”這就是代理模式。我們在這裏打個比方,現在有三個類,目標類、代理類、用戶類,這代理模式中,用戶類沒辦法直接與目標類進行溝通,如果用戶想要聯繫目標類那怎麼辦呢? 好辦,找代理類,把你的需求告訴代理類,然後再由代理類去聯繫目標類,最後能不能辦,辦成什麼樣,完全靠着代理類來做。這種形式就是代理模式。
舉例說明
看了上面的解釋之後,我們會不會感覺怪怪的,這樣做有什麼好處呢?用戶類直接去聯繫目標類不就可以了,我們中間加這個代理有什麼用?我們來看一個具體的實例,看完這個實例我們就明白了。
代理模式最常使用的例子就是明星與經紀人的例子了。對於明星來說他的聯繫方式是不會隨便透露出來的,如果客戶有什麼需要則需要去聯繫的是明星的代理人,也就是經紀人,由經紀人來把所有的需求進行整理之後在通知給明星,在這裏客戶是不能也沒有辦法去直接聯繫到明星的。
這種通過代理人來聯繫目標的方式就是代理模式。
繪製UML類圖
然後我們就根據上面明星與經紀人的例子,來繪製UML類圖
。
這就是我們代理模式的UML類圖
,明星star
,他擁有的一個工作的方法working
,但是這個方法客戶Client
是沒有辦法去調用的,客戶如果想要讓明星工作,那麼需要首先聯繫明星的代理人Agent
,通過代理人的working
方法,來讓明星star
去工作。
OK,接下來我們來實現一下代碼。
代碼實現
class Star {
working () {
console.log('明星開始工作');
}
}
class Adent {
constructor () {
this.star = new Star();
}
working () {
this.star.working();
}
}
class Client {
constructor () {
this.adent = new Adent();
}
main () {
this.adent.working();
}
};
const client = new Client();
client.main();
看一下我們的代碼 ,首先是由一個明星的類Star
,他擁有一個方法working
,當調用working
的時候,明星開始工作,然後是一個代理類Adent
,它表示經紀人,經理人持有明星的引用,並且僅能有經紀人持有明星,經紀人也提供了一個working
的方法,當經紀人的working
被執行的時候,它會調用明星的working
通知明星開始工作。
最後就是客戶Client
,Client
持有一個經紀人(代理類)的引用,當他想要明星工作的時候,他需要通知經紀人,調用經紀人的working
方法,然後經紀人會去通知明星開始工作。
這樣我們就通過代碼來實現了一個代理模式,還是比較簡單的。然後我們看一下代理模式的使用場景。
使用場景
代理模式的使用場景比較典型的就是jQuery
的$.proxy()
和Vue
中的_proxyData
這兩個方法了。我們這裏以Vue
中的_proxyData
做場景演示,我們將通過Vue
的源碼來進行演示,不過在看Vue
的源碼之前,我們需要帶着問題去看,_proxyData
他有什麼作用。
大家先來想一下,我們在new Vue
的時候去傳入了一個data
的對象,執行的代碼一般都是這樣:
new Vue({
data: {
msg: 'Hello world'
},
....
});
那麼這裏的data
作爲一個獨立的對象是如何被Vue
去訪問當的?我們在Vue
的created
方法裏面,通過this.data
可以直接獲取到這個對象,爲什麼?Vue
究竟通過了什麼樣的方式,讓我們可以通過this.data
就能獲取到data
這個獨立的對象呢?
我們之所以可以直接通過this.data
來訪問data
對象,就是因爲Vue
在內部對data
設置了代理,所使用的模式就是代理模式。OK
,明白了這些,那麼我們看一下Vue
內部是怎麼去做的。
我們打開Vue
的源碼,我這裏的是2.5.16
版本的,大家可以去github
上去下載最新的版本哈,代碼下載下來之後,我們打開src/core/instance/index.js
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
這就是當我們進行new Vue()
之後執行的代碼,我們聲明Vue
的時候傳入的對象就是這裏的 options
,當我麼執行options.data
的時候,獲取到的就是
data: {
msg: 'Hello world'
}
這個data
對象。
然後在this._init(options)
的方法裏面Vue
會調用initState(vm)
,進而在initState()
中調用調用initData(vm)
,這個initData(vm)
就是使用代理模式來實現的,我們一起看一下initData()
的實現:
function initData (vm: Component) {
...
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
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)
}
}
...
}
這裏是initData()
的代碼實現,我們只截取出來和代理有關的代碼,在這些代碼裏面,它首先獲取到了所有data
的key
值,然後進行了一個遍歷操作,遍歷data
中的所有字段,並調用了proxy(vm, "_data", key)
這個方法,這個方法就是爲data
設置代理的方法,這裏的vm
就是Vue
的實例,"_data"
是一個字符串,key
就是data
對象中所有字段的key
值。然後是proxy()
的方法實現。
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)
}
這就是proxy
的實現代碼,在這裏Vue
就通過Object.defineProperty
爲每一個data
的字段去設置了代理,這段代碼這樣看可能會有點複雜,我們把它合起來看一下:
Object.defineProperty(vm, key, {
get: function () {
return vm._data[key];
},
set: function (newVal) {
vm._data[key] = newVal;
}
});
就是這麼一段代碼,讓我們可以直接通過this.data
來獲取到我們傳入的data
對象,在這裏this.data
就是代理對象,data
就是目標對象。所有的流程代碼被簡化之後,就是這樣:
function Vue (options) {
var vm = this;
this.$options = options;
var data = this._data = options.data || {};
Object.keys(data).forEach(function(key) {
vm._proxyData(key);
});
}
Vue.prototype = {
_proxyData: function(key) {
var vm = this;
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get: function () {
return vm._data[key];
},
set: function (newVal) {
vm._data[key] = newVal;
}
});
},
};
總結
進入總結部分,在這一章我們學習到的是代理模式,在代理模式裏面最核心的概念就是 目標類與用戶不能直接聯繫,所有的聯繫必須通過代理來完成。 就好像明星與客戶不會直接聯繫一樣,他們所有的聯繫都會通過經紀人來完成。那麼我們應該在什麼情況下去使用代理模式呢?
比如當我們的目標類擁有很多的私有方法,但也有一些公開的屬性或方法需要被外部調用,而在JavaScript
中又沒有限制現有方法不能被外部訪問的功能,這個時候,我們就可以通過代理類,來把目標類中的公開屬性或者方法代理出去,就好像Vue
中訪問data
對象一樣。