无需原生开发基础,也能完美呈现京东商城。《混合开发京东商城系统,提前布局大前端》课程融合vue、Android、IOS等目前流行的前端和移动端技术,混合开发经典电商APP——京东。课程将各种复杂功能与知识点完美融合,从技术原理到开发上线,让你真实感受到一个明星产品开发的全过程。功能实现之外,还有一流用户体验和优秀交互设计等你一探究竟,拓宽开发眼界。
什么是代理模式
代理模式是:使用代理对象完成用户请求,屏蔽用户对真实对象的访问。
代理模式很好理解,“有事别找我,找我的代理去”这就是代理模式。我们在这里打个比方,现在有三个类,目标类、代理类、用户类,这代理模式中,用户类没办法直接与目标类进行沟通,如果用户想要联系目标类那怎么办呢? 好办,找代理类,把你的需求告诉代理类,然后再由代理类去联系目标类,最后能不能办,办成什么样,完全靠着代理类来做。这种形式就是代理模式。
举例说明
看了上面的解释之后,我们会不会感觉怪怪的,这样做有什么好处呢?用户类直接去联系目标类不就可以了,我们中间加这个代理有什么用?我们来看一个具体的实例,看完这个实例我们就明白了。
代理模式最常使用的例子就是明星与经纪人的例子了。对于明星来说他的联系方式是不会随便透露出来的,如果客户有什么需要则需要去联系的是明星的代理人,也就是经纪人,由经纪人来把所有的需求进行整理之后在通知给明星,在这里客户是不能也没有办法去直接联系到明星的。
这种通过代理人来联系目标的方式就是代理模式。
绘制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
对象一样。