介绍
delegates,可以帮我们方便快捷地使用设计模式当中的委托模式(Delegation Pattern),即外层暴露的对象将请求委托给内部的其他对象进行处理。
用法
delegates 基本用法就是将内部对象的变量或者函数绑定在暴露在外层的变量上,直接通过 delegates
方法进行如下委托,基本的委托方式包含:
- getter:外部对象可以直接访问内部对象的值
- setter:外部对象可以直接修改内部对象的值
- access:包含 getter 与 setter 的功能
- method:外部对象可以直接调用内部对象的函数
const delegate = require('delegates');
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 风格的函数,即:
- 函数不传参数的时候,获取对应的值
- 函数传参数的时候,修改对应的值
const delegate = require('delegates');
const petShop = {
dog: {
name: '旺财',
},
}
delegates(petShop, 'dog')
.fluent('name');
// 不传参数,获取内部属性
console.log(petShop.name());
// 传参数,修改内部属性
// 还可以链式调用
console.log(
petShop.name('二哈')
.name('蠢二哈')
.name();
);
源码学习
源代码比较简洁。
初始化
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 均为数组,用于记录委托了哪些属性和函数。
上述初始化函数的第一行值得引起注意: 如果 this
不是 Delegator
的实例的话,则调用 new Delegator(proto, target)
。通过这种方式,可以避免在调用初始化函数时忘记写 new
造成的问题,因为此时下面两种写法是等价的:
let x = new Delegator(petShop, 'dog')
let x = Delegator(petShop, 'dog')
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__
的使用,它可以在已存在的对象上添加可读属性,其中第一个参数为属性名,第二个参数为函数,返回值为对应的属性值:
const obj = {};
obj.__defineGetter__('name', () => 'elvin');
console.log(obj.name);
// => 'elvin'
obj.name = '旺财';
console.log(obj.name);
// => 'elvin'
需要注意的是尽管 __defineGetter__
曾被广泛使用,但是已不被推荐,建议通过 Object.defineProperty
实现同样功能,或者通过 get
操作符实现类似功能:
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'elvin',
});
Object.defineProperty(obj, 'sex', {
get() {
return 'male';
}
});
const dog = {
get name() {
return '旺财';
}
};
Github 上已有人提出相应的 PR#20.
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;
};
上述代码与 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
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
都是类似的,就不重复说明了。