系列文章:javascript
由於準備深刻地探究 koa 及相關的生態,因此接下來一段時間閱讀的 npm 模塊都會和 koa 密切相關 ^_^java
今天閱讀的模塊是 delegates,它由大名鼎鼎的 TJ 所寫,能夠幫咱們方便快捷地使用設計模式當中的委託模式(Delegation Pattern),即外層暴露的對象將請求委託給內部的其餘對象進行處理,當前版本是 1.0.0,周下載量約爲 364 萬。node
delegates 基本用法就是將內部對象的變量或者函數綁定在暴露在外層的變量上,直接經過 delegates
方法進行以下委託,基本的委託方式包含:git
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
時主要作了如下事情:設計模式
this
指向新建立的空對象 {}
// 源碼 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 社區,因此估計也不會更新這個倉庫了。
// 源碼 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;
}
};
複製代碼
// 源碼 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 中,其核心就在於 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');
// ...
複製代碼
又例如:
這些對響應參數的設置都得益於 koa 中 context.response
的許多屬性和方法都被委託在了 context
上:
// Koa 源碼 lib/context.js
delegate(proto, 'response')
.method('redirect')
.method('vary')
.access('status')
.access('body')
.getter('headerSent')
.getter('writable');
// ...
複製代碼
關於我:畢業於華科,工做在騰訊,elvin 的博客 歡迎來訪 ^_^