从vue谈设计模式之代理模式

代理模式

Posted by wsf on July 26, 2019

我们在阅读各种框架源码的时候,除了了解它实现的原理,更需要从中学习到对方好的代码结构,其中所用到设计模式。以此来完善我们的代码,建立良好的代码习惯,便于代码的可维护以及可重用性。

这一篇我们讨论的是设计模式中的代理模式。
代理模式是为一个对象提供一个代用品或占位符,以便控制对他的访问。

常见的代理应用:跨域解决方式之一(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)