该博客是个人学习 delegates 总结篇,如理解有误,请留言或在 GitHub 提交 issue 纠正。
转载请标明出处。
前言
在学习 Koa 源码时,context.js 文件上下文对象将 request.js 和 response.js 文件导出对象的方法和存取描述符属性都伪添加到了 context 对象上,对内部实现很感兴趣,所以学习 delegates 做一篇学习总结。
伪添加:从 context 上访问 request 或 response 对象的属性或方法
delegates 基本使用
用于 Node 方法和访问器属性的委托,将一个对象的方法或 getter 和 setter 属性代理到指定对象上。
基础示例
const Delegates = require('delegates');
const options = {
url: 'http://localhost:3001',
method: 'POST',
};
const request = {
getData() {
return 'data';
},
getList() {
return [1, 2, 3];
},
get url() {
return options.url;
},
set url(newUrl) {
options.url = newUrl;
},
get method() {
return options.method;
},
};
const ctx = {
request,
};
Delegates(ctx, 'request')
.method('getData')
.method('getList')
.access('url')
.getter('method');
console.log(ctx);
将 ctx 上的 request 对象的方法和属性都伪添加到 ctx 对象上,可以通过 ctx.getData() 或 ctx.url 方便的访问 request 对象上的同名方法。
以下访问是同等的:
ctx.getData === ctx.request.getData
ctx.url === ctx.request.url
Koa 内部也是这样实现的,方便通过 ctx 上下文对象访问 request 或 response 对象上的属性或方法。
Koa context.js 部分源码
上面示例 ctx 的输出结果是:
delegates 源码分析
constructor
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 = [];
}
支持直接调用 Delegator 构造函数, 内部根据 this 指向决定内部是否帮创建出实例对象返回。instanceof 作用在 this 的整条 [[Prototype]] 链中是否有指向 Delegator.prototype 的对象。
proto 可以理解为 ctx,target 理解为 request 对象。要将 request 的属性或方法通过 proto 对象进行代理。
method
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;
};
将 target 对象上指定的方法代理到 proto 对象上。内部在 proto 对象上添加了与 target 同名的方法。当访问 proto 上方法时内部会代理到 target 上指定的方法。因为,name 方法名保存在闭包环境中。
Delegates(ctx, 'request')
.method('getData')
ctx.getData() === ctx.request.getData()
getter
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__
方法将一个函数绑定在当前对象的指定的属性上,当那个属性的值被读取时,所绑定的函数就会被调用。getter 方法将 target 上指定的属性添加到 proto 对象上。
setter
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;
};
setter 和 getter 的基本原理是一样的。 __defineSetter__
方法将一个函数绑定在当前对象指定的属性上,当那个属性被赋值时,你所绑定的函数就会被调用。当使用 ctx.url = xxx
时,函数内部会给真正的 request 对象 url 属性赋值。
access
Delegator.prototype.access = function(name){
return this.getter(name).setter(name);
};
access 是 getter 和 setter 的组合。指定 request 对象上的属性在 ctx 上具有可读写性。
fluent
先看一下 fluent
使用例子:
const Delegates = require('delegates');
const a = {
b: {
name: "qqf"
}
}
Delegates(a, "b")
.fluent("name")
a.name() // qqf
a.name("abc")
a.name() // abc
这是 fluent 的使用方法,和 jquery 的 val()
取值和 val(xxx)
设置值有相同的味道。其 fluent 内部实现很简单。
Delegator.prototype.fluent = function (name) {
var proto = this.proto;
var target = this.target;
this.fluents.push(name);
proto[name] = function(val){
if ('undefined' != typeof val) {
this[target][name] = val;
return this;
} else {
return this[target][name];
}
};
return this;
};
将 request 对象的指定属性在 ctx 对象上添加同名函数,当调用时根据参数进行读和写的处理。如果参数有值则为设置,没有值则为获取。
auto 方法的实现暂不说了,感兴趣可以自行查看。