# 天天閱讀一個 npm 模塊(7)- delegates

系列文章:javascript

  1. 天天閱讀一個 npm 模塊(1)- username
  2. 天天閱讀一個 npm 模塊(2)- mem
  3. 天天閱讀一個 npm 模塊(3)- mimic-fn
  4. 天天閱讀一個 npm 模塊(4)- throttle-debounce
  5. 天天閱讀一個 npm 模塊(5)- ee-first
  6. 天天閱讀一個 npm 模塊(6)- pify

由於準備深刻地探究 koa 及相關的生態,因此接下來一段時間閱讀的 npm 模塊都會和 koa 密切相關 ^_^java

一句話介紹

今天閱讀的模塊是 delegates,它由大名鼎鼎的 TJ 所寫,能夠幫咱們方便快捷地使用設計模式當中的委託模式(Delegation Pattern),即外層暴露的對象將請求委託給內部的其餘對象進行處理,當前版本是 1.0.0,周下載量約爲 364 萬。node

用法

delegates 基本用法就是將內部對象的變量或者函數綁定在暴露在外層的變量上,直接經過 delegates 方法進行以下委託,基本的委託方式包含:git

  • getter:外部對象能夠直接訪問內部對象的值
  • setter:外部對象能夠直接修改內部對象的值
  • access:包含 getter 與 setter 的功能
  • method:外部對象能夠直接調用內部對象的函數
const delegates = require('./index');

const petShop = {
  dog: {
    name: '旺財',
    age: 1,
    sex: '猛漢',
    bar() {
      console.log('bar!');
    }
  },
}

// 將內部對象 dog 的屬性、函數
// 委託至暴露在外的 petShop 上
delegates(petShop, 'dog')
  .getter('name')
  .setter('age')
  .access('sex')
  .method('bar');

// 訪問內部對象屬性
console.log(petShop.name)
// => '旺財'

// 修改內部對象屬性
petShop.age = 2;
console.log(petShop.dog.age)
// => 2

// 同時訪問和修改內部對象屬性
console.log(petShop.sex)
// => '猛漢'
petShop.sex = '公主';
console.log(petShop.sex);
// => '公主'

// 調用內部對象函數
petShop.bar();
// 'bar!'
複製代碼

除了上面這種方式以外,還能夠在外部對象上添加相似 jQuery 風格的函數,即:github

  • 函數不傳參數的時候,獲取對應的值
  • 函數傳參數的時候,修改對應的值
const delegates = require('./index');

const petShop = {
  dog: {
    name: '旺財',
  },
}

delegates(petShop, 'dog')
  .fluent('name');

// 不傳參數,獲取內部屬性
console.log(petShop.name());

// 傳參數,修改內部屬性
// 還能夠鏈式調用
console.log(
    petShop.name('二哈')
    	.name('蠢二哈')
    	.name();
);
複製代碼

源碼學習

初始化

// 源碼 7 - 1
function Delegator(proto, target) {
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}
複製代碼

this 對象中 methods | getters | setters | flaunts 均爲數組,用於記錄委託了哪些屬性和函數。npm

上述初始化函數的第一行值得引發注意: 若是 this 不是 Delegator 的實例的話,則調用 new Delegator(proto, target)。經過這種方式,能夠避免在調用初始化函數時忘記寫 new 形成的問題,由於此時下面兩種寫法是等價的:segmentfault

  • let x = new Delegator(petShop, 'dog')
  • let x = Delegator(petShop, 'dog')

另外講一講在調用 new 時主要作了如下事情:設計模式

  1. 將構造函數內的 this 指向新建立的空對象 {}
  2. 執行構造函數體
  3. 若是構造函數有顯示返回值,且該值爲對象的話,則返回對象的引用
  4. 若是構造函數沒有顯示返回值或者顯示返回值不是對象(例如顯示返回值爲 1, 'haha' 等)的話,則返回 this

getter

// 源碼 7-2
Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  proto.__defineGetter__(name, function(){
    return this[target][name];
  });

  return this;
};
複製代碼

上面代碼中的關鍵在於 __defineGetter__ 的使用,它能夠在已存在的對象上添加可讀屬性,其中第一個參數爲屬性名,第二個參數爲函數,返回值爲對應的屬性值:數組

const obj = {};
obj.__defineGetter__('name', () => 'elvin');

console.log(obj.name);
// => 'elvin'

obj.name = '旺財';
console.log(obj.name);
// => 'elvin'
// 我怎麼能被更名叫旺財呢!
複製代碼

須要注意的是儘管 __defineGetter__ 曾被普遍使用,可是已不被推薦,建議經過 Object.defineProperty 實現一樣功能,或者經過 get 操做符實現相似功能:app

const obj = {};
Object.defineProperty(obj, 'name', {
  value: 'elvin',
});

Object.defineProperty(obj, 'sex', {
  get() {
    return 'male';
  }
});

const dog = {
  get name() {
    return '旺財';
  }
};
複製代碼

Github 上已有人提出相應的 PR#20,不過由於 TJ 已經離開了 Node.js 社區,因此估計也不會更新這個倉庫了。

setter

// 源碼 7-3
Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  proto.__defineSetter__(name, function(val){
    return this[target][name] = val;
  });

  return this;
};
複製代碼

上述代碼與 getter 幾乎如出一轍,不過使用的是 __defineSetter__,它能夠在已存在的對象上添加可讀屬性,其中第一個參數爲屬性名,第二個參數爲函數,參數爲傳入的值:

const obj = {};
obj.__defineSetter__('name', function(value) {
  this._name = value;
});

obj.name = 'elvin';
console.log(obj.name, obj._name);
// undefined 'elvin'
複製代碼

一樣地,雖然 __defineSetter__ 曾被普遍使用,可是已不被推薦,建議經過 Object.defineProperty 實現一樣功能,或者經過 set 操做符實現相似功能:

const obj = {};
Object.defineProperty(obj, 'name', {
  set(value) {
    this._name = value;
  }
});

const dog = {
  set(value) {
    this._name = value;
  }
};
複製代碼

method

// 源碼 7-4
Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};
複製代碼

method 的實現也十分簡單,只須要注意這裏 apply 函數的第一個參數是內部對象 this[target],從而確保了在執行函數 this[target][name] 時,函數體內的 this 是指向對應的內部對象。

其它 delegates 提供的函數如 fluent | access 都是相似的,就不重複說明了。

koa 中的使用

在 koa 中,其核心就在於 context 對象,許多讀寫操做都是基於它進行,例如:

  • ctx.header 獲取請求頭

  • ctx.method 獲取請求方法

  • ctx.url 獲取請求 URL

  • ...

這些對請求參數的獲取都得益於 koa 中 context.request 的許多屬性都被委託在了 context 上:

// Koa 源碼 lib/context.js
delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .access('path')
  .access('url')
  .getter('headers')
  .getter('ip');
  // ...
複製代碼

又例如:

  • ctx.body 設置響應體
  • ctx.status 設置響應狀態碼
  • ctx.redirect() 請求重定向
  • ...

這些對響應參數的設置都得益於 koa 中 context.response 的許多屬性和方法都被委託在了 context 上:

// Koa 源碼 lib/context.js
delegate(proto, 'response')
  .method('redirect')
  .method('vary')
  .access('status')
  .access('body')
  .getter('headerSent')
  .getter('writable');
  // ...
複製代碼

關於我:畢業於華科,工做在騰訊,elvin 的博客 歡迎來訪 ^_^

相關文章
相關標籤/搜索