You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:40:07