JavaScript設計模式詳解:07、代理模式

什麼是代理模式

代理模式是:使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問。

代理模式很好理解,“有事別找我,找我的代理去”這就是代理模式。我們在這裏打個比方,現在有三個類,目標類、代理類、用戶類,這代理模式中,用戶類沒辦法直接與目標類進行溝通,如果用戶想要聯繫目標類那怎麼辦呢? 好辦,找代理類,把你的需求告訴代理類,然後再由代理類去聯繫目標類,最後能不能辦,辦成什麼樣,完全靠着代理類來做。這種形式就是代理模式。

舉例說明

看了上面的解釋之後,我們會不會感覺怪怪的,這樣做有什麼好處呢?用戶類直接去聯繫目標類不就可以了,我們中間加這個代理有什麼用?我們來看一個具體的實例,看完這個實例我們就明白了。

代理模式最常使用的例子就是明星與經紀人的例子了。對於明星來說他的聯繫方式是不會隨便透露出來的,如果客戶有什麼需要則需要去聯繫的是明星的代理人,也就是經紀人,由經紀人來把所有的需求進行整理之後在通知給明星,在這裏客戶是不能也沒有辦法去直接聯繫到明星的。

這種通過代理人來聯繫目標的方式就是代理模式。

繪製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對象一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章