这是我参与更文挑战的第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























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)