JavaScript设计模式详解:07、代理模式

无需原生开发基础,也能完美呈现京东商城。《混合开发京东商城系统,提前布局大前端》课程融合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通知明星开始工作。

最后就是客户ClientClient持有一个经纪人(代理类)的引用,当他想要明星工作的时候,他需要通知经纪人,调用经纪人的working方法,然后经纪人会去通知明星开始工作。

这样我们就通过代码来实现了一个代理模式,还是比较简单的。然后我们看一下代理模式的使用场景。

使用场景

代理模式的使用场景比较典型的就是jQuery$.proxy()Vue中的_proxyData这两个方法了。我们这里以Vue中的_proxyData做场景演示,我们将通过Vue的源码来进行演示,不过在看Vue的源码之前,我们需要带着问题去看,_proxyData他有什么作用。

大家先来想一下,我们在new Vue的时候去传入了一个data的对象,执行的代码一般都是这样:

new Vue({
	data: {
		msg: 'Hello world'
	},
	....
});

那么这里的data作为一个独立的对象是如何被Vue去访问当的?我们在Vuecreated方法里面,通过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()的代码实现,我们只截取出来和代理有关的代码,在这些代码里面,它首先获取到了所有datakey值,然后进行了一个遍历操作,遍历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对象一样。

猜你喜欢

转载自blog.csdn.net/u011068996/article/details/84304852