如何用TypeScript的this实现Object#tap方法?
在TypeScript中实现全局tap方法并正确推导派生类型
背景与JavaScript实现
从Ruby迁移到JavaScript后,我习惯用全局tap方法简化命令式代码,它的JavaScript实现如下:
Object.prototype.tap = function(fn) { fn(this); return this; };
该方法适用于DOM元素构建或调试场景,示例代码:
navBar.append(createElement('div').tap(div => { div.classList = 'food-items' div.append(createElement('span').tap(s => s.textContent = 'Pasta')); }); user.withProperties({"a": 123}).tap(console.log).save();
TypeScript中的类型推导问题
在TypeScript中,我尝试通过declare global扩展Object接口来定义tap的类型:
declare global { interface Object { tap(fn: (self: this) => void): this } }
但派生类型无法正常工作,比如调用"test".tap(t => console.log(t.indexOf("t")))时,TypeScript会报错:Property 'indexOf' does not exist on type 'Object'.ts(2339)。明明"test"是字符串类型,继承了tap方法,但参数t却被识别为Object类型,而非实际的string类型。
解决方案
问题根源在于直接扩展Object接口时,TypeScript无法自动将this类型向下推导到具体的子类。我们可以通过泛型约束this类型来解决这个问题,让tap方法能正确识别调用者的具体类型。
方案1:扩展Object.prototype并使用泛型
修改TypeScript的全局类型声明,通过泛型T绑定调用者的具体类型:
declare global { interface Object { tap<T extends Object>(this: T, fn: (self: T) => void): T; } } // 保持原JavaScript实现不变 Object.prototype.tap = function(fn) { fn(this); return this; };
此时再调用"test".tap(t => console.log(t.indexOf("t"))),TypeScript会自动将t推导为string类型,不再报错。
方案2:使用全局函数替代原型扩展
如果不想修改Object.prototype(避免遍历对象时出现额外属性的潜在问题),可以改用全局函数的方式实现tap:
// 类型声明 declare function tap<T>(value: T, fn: (self: T) => void): T; // 实现 function tap<T>(value: T, fn: (self: T) => void): T { fn(value); return value; } // 使用示例 tap("test", t => console.log(t.indexOf("t"))); tap(createElement('div'), div => { div.classList = 'food-items'; });
这种方式同样能保证类型推导正常工作,且不会污染全局原型链。
内容的提问来源于stack exchange,提问作者nightpool
相关产品推荐
相关产品推荐

