我们在阅读各种框架源码的时候,除了了解它实现的原理,更需要从中学习到对方好的代码结构,其中所用到设计模式。以此来完善我们的代码,建立良好的代码习惯,便于代码的可维护以及可重用性。
这一篇我们讨论的是设计模式中的代理模式。
代理模式是为一个对象提供一个代用品或占位符,以便控制对他的访问。
常见的代理应用:跨域解决方式之一(eg:webpack插件:webpack-dev-server来配置代理;跨域请求是设置代理服务器作为客户端和服务端中转站);
在Vue中的应用
应用一:
我们一般访问自定义设置的data属性的时候,都是直接访问vue实例的属性。而vue为了提供方便,同时提供了$data属性让我们可以直接访问自定义的data。
new Vue({
data: function(){
return {
test: '123'
}
}
mounted(){
this.test; // 123
this.$data; // {test: '123'}
}
})
而我们在上面修改上面的test属性,修改会在$data对象中反应过来。然而实际上test的值是一个基础类型,为什么会这样呢?
先来简单的回顾一下Vue的初始化。我们知道,vue在最开始构建的过程中,会先进行系列初始化,eg:生命周期,事件中心,渲染以及用户自定义的data、props、methods等。
而在初始化data的过程中,会把vue自定义选项中的data赋给vue实例对象的_data。然后遍历data对象,修改每个data中每个属性的数据存取描述符:每次get/set操作都是操作的_data对象(get,set方法),然后再通过Object.defineProperty定义在vue实例上。
目录:src/core/instance/state.js
// 截取部分代码
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
// target: vue实例
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)
}
我们已经说到了为什么我们可以直接通过访问vue实例对象获取到定义在data内的值。但是并没有说明上面代码例子中,test值的改变会导致$data中值改变。这个实现也类似于前面说提到的方式。代码如下:
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
}
Object.defineProperty(Vue.prototype, '$data', dataDef);
应用二:
ES6中新增的Proxy,用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。这里关于Proxy是使用不做过多描述。
在vue中,对于对象类型来说,实现响应式数据采用的是Object.defineProperty的存取描述(get,set)。对于数组来说,通过拦截数组push,unshift之类会修改原数组的方法实现监听,从而依赖派发。但是数组的这种实现方式无法拦截根据下标修改值,直接修改数组的长度这些操作。
在即将发布的vue3.0的版本中,vue使用的是Proxy,它提供13种原生方法/属性的拦截。可以避免我们现在数组的问题。
简单用Proxy实现了一下
let data = {
array: [1, 2, 3],
obj: {}
}
let proxy = new Proxy(data,{
get: function(target, fnName, reciever){
// 依赖收集
console.log("收集依赖过程")
return Reflect.get(target, fnName);
},
set: function(target, propKey, value, receiver){
let val = Reflect.get(target, propKey);
if(val !== value){
// 数据改变,依赖派发
console.log("数据改变,依赖派发");
}
return Reflect.set(target, propKey, value, receiver);
}
})
proxy.tt=2
proxy.array.length = 1;
proxy.array[0]= 3;
console.log(proxy.tt)
console.log(proxy.array)