Github : esdelegates
先看效果
const del = delegates() //创建事件分发器
const handler = () => { console.log('This is a handler') } //创建一个委托
del.propertyChanged += handler //添加委托到事件
del.propertyChanged() //触发事件
// => This is a handler
del.propertyChanged -= handler //移除委托
del.propertyChanged() //触发事件
// ...空
重载运算符?
JS是无法重载运算符的,除非修改解释器或者编译器替换模板。唯一能做的只有从现有的能够运算的预定义类型入手,这时候最合适的就是 number
valueOf
在进行 +=
/ -=
运算的时候,会尝试计算对象的 valueOf
得到预定义类型,他们通常会返回自身然后再调用 toString
转化为字符串输出,如果要想实现
del.propertyChanged += function(){}
就必须同时重写左右两者的 valueOf
,
右边很显然是 Function
,我们希望能够在计算之后依然能得到这个 Function
,而参与计算的是一个 number
, 这就不得不使用下标
const globalDelegates = [] //创建一个全局委托池
Function.prototype.valueOf = function(){ //重写Function的valueOf
if(this.globalIndex != null) return this.globalIndex; //返回自身的全局索引
const index = globalDelegates.length; //记录自己的下标
Object.defineProperty(this, 'globalIndex', { //写入自己的全局下标
get(){ return index }
})
globalDelegates.push(this) //将自己添加到全局委托中
return index
}
当创建一个委托之后进行计算
0 + () => {} // => 0 代表他在全局委托池中的下标
那么左边应该是什么呢? 按照要求左边是能调用的事件,同时也应当是能够被传递的事件,那么也应当是一个方法。
del.propertyChanged() // 调用所有的委托
other.onClick += del.propertyChanged; //继续传递事件
delegates
现在来到最神秘的这个 del
,它产生的属性既可以调用也可以被追加事件,能做到的就是通过 get
/ set
分别写他的两种事件
对于 get
它应当返回一个可以被调用的函数,同时提供自己的事件下标
const handlers = {}
const callers = {}
get(target, p)
{
handlers[p] ??= [] //被调用的时候初始化事件组
return callers[p] ??= function (...args) //返回事件调用方
{
handlers[p]?.forEach(x => x(...args))
}
},
对于 set
它应当接受一个 Function
或者一个 number
set(target, p, newValue)
{
const event = handlers[p] ??= [];
switch (typeof newValue)
{
case 'function':
event.push(newValue)
return true;
case 'number':
const eventIndex = callers[p].valueOf();
let index = Math.abs(newValue - eventIndex);
const delegate = globalDelegates[index];
if (delegate == null) return false;
if (newValue > eventIndex)
{
handlers[p].push(delegate)
return true;
}
else
{
handlers[p].splice(handlers[p].findIndex(x => x === delegate), 1)
return true;
}
}
return false;
}
至此就实现了全部的效果
完整代码
const globalDelegates = []
Function.prototype.valueOf = function ()
{
if (this.globalIndex !== undefined) return this.globalIndex
const index = globalDelegates.length;
Object.defineProperty(this, 'globalIndex', {
get()
{
return index
}
})
globalDelegates.push(this)
return index
}
function delegates()
{
const handlers = {}
const callers = {}
return new Proxy(
{}, {
get(target, p)
{
handlers[p] ??= []
return callers[p] ??= function (...args)
{
handlers[p]?.forEach(x => x(...args))
}
},
set(target, p, newValue)
{
const event = handlers[p] ??= [];
switch (typeof newValue)
{
case 'function':
event.push(newValue)
return true;
case 'number':
const eventIndex = callers[p].valueOf();
let index = Math.abs(newValue - eventIndex);
const delegate = globalDelegates[index];
if (delegate == null) return false;
if (newValue > eventIndex)
{
handlers[p].push(delegate)
return true;
}
else
{
handlers[p].splice(handlers[p].findIndex(x => x === delegate), 1)
return true;
}
}
return false;
}
})
}
// TESTS
const del = delegates()
const handler = () => { console.log('this is a handler') }
del.propertyChanged += handler;
del.onClick += del.propertyChanged
del.propertyChanged()
del.onClick()
del.propertyChanged -= handler;
del.propertyChanged()