这是我参与更文挑战的第10天,活动详情查看:更文挑战
简要介绍
delegates 是由 TJ 所开发的一个用于实现简单委托的工具包,在 Koa 中有使用到该工具。Koa 通过使用 delegates
将 context.request
和 context.response
内的属性都委托到 context
上,从而让相关方法的调用更加简便,例如context.request.query
方法可以直接写成 context.query
。
delegates 简单使用
在项目中通过npm i delegates
安装该依赖包后,就能够使用 delegates
了,简单示例如下所示:
var delegate = require('delegates');
var obj = {};
obj.request = {
foo: function(){
console.log('do something ...');
},
get name() {
return this._name
},
set name(val) {
this._name = val
}
};
// 将 obj.request 的相关属性委托到 obj 上,使调用更加简便
delegate(obj, 'request').method('foo').getter('name').setter('name');
obj.foo(); // do something ...
obj.name = 'LvLin';
console.log(obj.name); // 'LvLin'
复制代码
源码分析
delegates 源码总共 157 行,实现了一个 Delegator
类,该类具备构造函数、静态方法 auto
和原型方法method
、access
、getter
、setter
、fluent
。
module.exports = Delegator;
// 构造函数
function Delegator(proto, target) {...}
// 静态方法
Delegator.auto = function(proto, targetProto, targetProp) {...}
// 原型方法
Delegator.prototype.method = function(name) {...}
Delegator.prototype.access = function(name) {...}
Delegator.prototype.getter = function(name) {...}
Delegator.prototype.setter = function(name) {...}
Delegator.prototype.fluent = function(name) {...}
复制代码
构造函数
构造函数首先判断当前函数是否被 new
调用,如果不是就创建一个 Delegator
实例返回,所以我们在通过delegate(proto, target)
调用时,等同于new delegate(proto, target)
。
然后定义了各项属性,如下所示:
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 = [];
}
复制代码
原型方法
method
方法用于将 target
上的方法通过闭包的方式绑定到proto
上,最后返回 delegator
实例对象,以便实现链式调用,源码如下:
Delegator.prototype.method = function(name){
var proto = this.proto;
var target = this.target;
this.methods.push(name); // 存入 methods 数组中
// 以闭包的方式,将对 proto 方法的调用转为对 this[target] 上相关方法的调用
// apply 改变 this 的指向为 this[target]
proto[name] = function(){
return this[target][name].apply(this[target], arguments);
};
// 返回 delegator 实例对象,从而实现链式调用
return this;
};
复制代码
getter
、setter
方法用于将属性(getter
或setter
) proto[target][name]
绑定到 proto[name]
,access
是同时包含了getter
和setter
。源码如下:
Delegator.prototype.access = function(name){
return this.getter(name).setter(name);
};
Delegator.prototype.getter = function(name){
var proto = this.proto;
var target = this.target;
this.getters.push(name); // 将属性名称存入对应类型的数组
// 利用 __defineGetter__ 设置 proto 的 getter,
// 使得访问 proto[name] 获取到的是 proto[target][name] 的值
proto.__defineGetter__(name, function(){
return this[target][name];
});
// 返回 delegator 实例,实现链式调用
return this;
};
Delegator.prototype.setter = function(name){
var proto = this.proto;
var target = this.target;
this.setters.push(name); // 将属性名称存入对应类型的数组
// 利用 __defineSetter__ 设置 proto 的 setter,
// 实现给 proto[name] 赋值时,实际改变的是 proto[target][name] 的值
proto.__defineSetter__(name, function(val){
return this[target][name] = val;
});
// 返回 delegator 实例,实现链式调用
return this;
};
复制代码
delegates
是通过对象的__defineGetter__
和__defineSetter__
方法配置对象的getter
和setter
,但是这两个方法已经从 Web 标准中删除,建议不要再使用该特性。目前推荐使用的是 Object.prototype.definePrototype
。关于setter
、getter
以及Object.prototype.definePrototype
的知识,可以通过我的这篇文章进行了解。
fluent
方法用于将普通属性 proto[target][name]
委托到 proto[name]
上,使得通过 proto[name]
对 proto[target][name]
进行访问和修改,源码如下所示:
Delegator.prototype.fluent = function (name) {
var proto = this.proto;
var target = this.target;
this.fluents.push(name); // 将属性名称存入对应类型的数组
proto[name] = function(val){
// 通过 val 是否为 null 或 undefined 判断是取值还是赋值
if ('undefined' != typeof val) {
// 赋值,修改 proto[target][name] 的值,返回 proto
this[target][name] = val;
return this;
} else {
// 取值,直接获取 proto[target][name]的值
return this[target][name];
}
};
return this;
};
复制代码
可以看到,如果需要获取或修改一个普通属性proto[target][name]
,只能以函数的形式进行获取或调用,如下所示:
var obj = {};
obj.request = {
name: ''
};
delegate(obj, 'request').fluent('name');
obj.name('LvLin');
console.log(obj.name()) // LvLin
复制代码
静态方法 auto
该方法接受一个对象 proto
,一个内部对象 targetProto
,内部对象的属性名 targetProp
,实现自动将 proto
内 targetProp
对象的属性委托到 proto
上,targetProp
提供委托的判断依据。源码分析如下所示:
Delegator.auto = function(proto, targetProto, targetProp){
// 构建一个 delegator 实例
var delegator = Delegator(proto, targetProp);
// 获取到 targetProto 上的所有属性
var properties = Object.getOwnPropertyNames(targetProto);
// 遍历属性,判断以何种方式委托到 proto
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
// 获取该属性的属性描述符
var descriptor = Object.getOwnPropertyDescriptor(targetProto, property);
// 存储描述符的情况,调用相关 api 实现委托
if (descriptor.get) {
delegator.getter(property);
}
if (descriptor.set) {
delegator.setter(property);
}
// 数据描述符的情况
if (descriptor.hasOwnProperty('value')) {
var value = descriptor.value;
// 如果是函数,进行 method 委托
if (value instanceof Function) {
delegator.method(property);
} else {
// 否则进行 getter 委托
delegator.getter(property);
}
// 如果可以重写,则进行 setter 委托
// 这里感觉有点问题,如果是 Function 类型的话,就不应该再做这步判断才是
// 但是如果是用 Object.defineProperty 定义,writable 默认值为 false
if (descriptor.writable) {
delegator.setter(property);
}
}
}
};
复制代码
关于对象属性描述符的相关知识,我在这篇文章中有进行详细的说明,欢迎阅读~
总结
delegate
将对象属性分为四类:fluent
、getter
、setter
、method
,分别提供相应的接口实现对象属性的委托操作,同时还提供了简便的access
跟auto
方法。
delegate
中的 api 最后都返回调用该 api 的 delegator
实例对象,从而实现多个 api 的链式调用。
资料
node-delegates 源码,by TJ Holowaychuk