從源碼學習使用 node-delegates

node-delegatesTJ 大神所寫的一個簡單的小工具,源碼只有 157 行,做用在於將外部對象接受到的操做委託到內部屬性進行處理,也能夠理解爲講對象的內部屬性暴露到外部,簡化咱們所須要書寫的代碼。node

安裝和使用的代碼在源碼倉庫均可以找到,這裏主要先講一下 API。git

API

Delegate(proto, prop)

用於建立一個 delegator 實例,用於把 proto 接收到的一些操做委託給它的 prop 屬性進行處理。github

Delegate.auto(proto, targetProto, targetProp)

根據 targetProp 所包含的鍵,自動判斷類型,把 targetProto 上的對應屬性代理到 proto。能夠是 getter、setter、value 或者 method。api

Delegate.prototype.method(name)

在 proto 對象上新增一個名爲 name 的函數,調用該函數至關於調用 proto 的 prop 屬性上的 name 函數。app

Delegate.prototype.getter(name)

新增一個 getter 到 proto 對象,訪問該 getter 便可訪問 proto 的 prop 的對應 getter。koa

Delegate.prototype.setter(name)

同 getter。函數

Delegate.prototype.access(name)

在 proto 上同時新增一個 getter 和一個 setter,指向 proto.prop 的對應屬性。工具

Delegate.prototype.fluent(name)

access 的特殊形式。學習

delegate(proto, 'request')
  .fluent('query')

// getter
var q = request.query();

// setter (chainable)
request
  .query({ a: 1 })
  .query({ b: 2 });

源碼閱讀

/**
 * Expose `Delegator`.
 */

// 暴露 Delegator 構造函數
module.exports = Delegator;

/**
 * Initialize a delegator.
 * 構造一個 delegator 實例
 * @param {Object} proto 外部對象,供外部調用
 * @param {String} target 外部對象的某個屬性,包含具體處理邏輯
 * @api public
 */

function Delegator(proto, target) {
  // 若是沒有使用 new 操做符調用構造函數,則使用 new 構造
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  // 構造實例屬性
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}

/**
 * Automatically delegate properties
 * from a target prototype
 * 根據 targetProp 自動委託,綁定一個屬性到 Delegator 構造函數
 * @param {Object} proto 接受請求的外部對象
 * @param {object} targetProto 處理具體邏輯的內部對象
 * @param {String} targetProp 包含要委託的屬性的對象
 * @api public
 */

Delegator.auto = function(proto, targetProto, targetProp){
  var delegator = Delegator(proto, targetProp);
  // 根據 targetProp 獲取要委託的屬性
  var properties = Object.getOwnPropertyNames(targetProto);
  // 遍歷全部要委託的屬性
  for (var i = 0; i < properties.length; i++) {
    var property = properties[i];
    // 獲取 targetProto 上對應屬性的 descriptor
    var descriptor = Object.getOwnPropertyDescriptor(targetProto, property);
    // 若是當前屬性的 get 被重寫過,就做爲 getter 委託(使用 __defineGetter__ 或者 Object.defineProperty 指定 getter 都會重寫 descriptor 的 get 屬性)
    if (descriptor.get) {
      delegator.getter(property);
    }
    // 同 get,若是 set 被重寫過,那就做爲 setter 委託
    if (descriptor.set) {
      delegator.setter(property);
    }
    // 若是當前 property 具備 value,那麼判斷是函數仍是普通值
    if (descriptor.hasOwnProperty('value')) { // could be undefined but writable
      var value = descriptor.value;
      if (value instanceof Function) {
        // 是函數就進行函數委託
        delegator.method(property);
      } else {
        // 是普通值就做爲 getter 委託
        delegator.getter(property);
      }
      // 若是這個值能夠重寫,那麼繼續進行 setter 委託
      if (descriptor.writable) {
        delegator.setter(property);
      }
    }
  }
};

/**
 * Delegate method `name`.
 * 
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */

Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  // 在 proto 上定義一個 name 的方法
  proto[name] = function(){
    // 實際仍是調用的 proto[target][name],內部的 this 仍是指向 proto[target]
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};

/**
 * Delegator accessor `name`.
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */

Delegator.prototype.access = function(name){
  // 同時定義 getter 和 setter
  return this.getter(name).setter(name);
};

/**
 * Delegator getter `name`.
 * 委託 name getter
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */

Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  // 使用 __defineGetter__ 綁定 name getter 到 proto
  proto.__defineGetter__(name, function(){
    // 注意 this 指向 proto 自己,因此 proto[name] 最終訪問的仍是 proto[target][name]
    return this[target][name];
  });

  // 此處 this 指向 delegator 實例,構造鏈式調用
  return this;
};

/**
 * Delegator setter `name`.
 * 在 proto 上委託一個 name setter
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */

Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  // 經過 __defineSetter__ 方法指定一個 setter 到 proto
  proto.__defineSetter__(name, function(val){
    // 注意 this 指向 proto 自己,因此對 proto[name] 設置值即爲爲 proto[target][name] 設置值
    return this[target][name] = val;
  });

  // 返回自身實現鏈式調用
  return this;
};

/**
 * Delegator fluent accessor
 *
 * @param {String} name
 * @return {Delegator} self
 * @api public
 */

Delegator.prototype.fluent = function (name) {
  var proto = this.proto;
  var target = this.target;
  this.fluents.push(name);

  proto[name] = function(val){
    // 若是 val 不爲空,那麼就做爲 setter 使用
    if ('undefined' != typeof val) {
      this[target][name] = val;
      // 完過後返回 proto 自身,實現鏈式調用
      return this;
    } else {
      // 若是 val 未定義,那麼做爲 getter 使用,返回具體的值
      return this[target][name];
    }
  };

  return this;
};

具體案例

之因此會研究一下這個庫是由於在看 koa 源碼的時候看到使用了這個庫,在 koa 中經過使用 node-delegatescontext.requestcontext.response 上的屬性都委託到了 context 自身。因此咱們能夠直接使用 context.querycontext.status 來進行操做,簡化了咱們所寫的代碼。this

koa 源碼位置連接:https://github.com/koajs/koa/blob/b7fc526ea49894f366153bd32997e02568c0b8a6/lib/context.js#L191

總結

  • 經過 __defineGetter____defineSetter__ 能夠設置 getter 和 setter,可是 MDN 顯示這兩個 API 已被 deprecated,github 也已經有人提了 issue 和 pr。另外,經過這兩個 API 設置 getter 和 setter 時,傳遞的函數的內部 this 指向原來的屬性,好比:

    let a = { nickName: 'HotDog' }
    a.__defineGetter__('name', function() {
      return this.nickName // 此處 this 仍然指向 a
    })
  • 學習了委託模式,能夠把外部對象接收到的操做委託給內部屬性(或其餘對象)進行具體的處理。

相關文章
相關標籤/搜索