TypeScript this 的类型声明

只要是写过 JavaScript 的小伙伴,就一定被 this 坑过。在原生 JavaScript 中,this 的指向非常诡异:它不取决于函数是在哪里定义,而取决于函数是在哪里被调用。

这种灵活性在 TypeScript 的严谨世界里就是个 “定时炸弹”。为了解决这个问题,TypeScript 允许我们在函数中显式地声明 this 的类型。

JavaScript 中迷失的 this

我们先来看一个在原生 JavaScript 中非常经典的错误场景。

示例 1:丢失的 this 指向

const calculator = {
    count: 10,
    // 这里的 this 指向 calculator 对象
    add() {
        console.log("当前计数:", this.count);
    }
};

// 正常调用
calculator.add();

// 错误调用:把方法提取出来赋值给变量
const myAdd = calculator.add;
myAdd();

运行结果如下。

当前计数:10
当前计数:undefined

分析:

在上面的代码中,当我们把 calculator.add 赋值给 myAdd 并直接调用时,this 的指向就不再是 calculator 对象,而是变成了全局对象(或者是 undefined)。

在 TypeScript 中,如果开启了严格模式,这种不确定的 this 会直接导致报错。为了让代码更安全,我们需要给 this 定一个 “名分”。

TypeScript 中的 “假参数” 语法

在 TypeScript 中,我们可以给函数添加一个特殊的 this 参数。这个参数必须放在所有参数列表的第一位。

需要注意的是,这个 this 并不是一个真正的参数,它在编译成 JavaScript 后会被自动抹除。它的唯一作用就是告诉编译器:“这个函数在执行时,this 必须是什么类型。”

示例 2:显式声明 this 类型

// 定义接口
interface Calculator {
    count: number;
}

// 显式声明这个函数执行时,this 必须符合 Calculator 接口
function add(this: Calculator, step: number) {
    this.count += step;
    console.log("当前计数:", this.count);
}

const calculator: Calculator = {
    count: 10
};

// 使用 call() 方法绑定 this 调用
add.call(calculator, 5);

// 错误调用:如果没有绑定正确的 this,TypeScript 会直接报错
// add(5); 
// 报错:The 'this' context of type 'void' is not assignable to method's 'this' of type 'Calculator'.

运行结果如下。

当前计数:15

分析:

在这个例子中,this: Calculator 就是一个 “假参数”。

  • 它占据了第一个参数的位置,但在调用 add.call(calculator, 5) 时,我们只需要传入第二个参数 step 的值 5,并不需要给 this 传值。
  • 它强行规定了:如果你想调用这个函数,你绑定的 this 必须包含 count 属性。

如果我们尝试直接调用 add(5),那么 TypeScript 会立马报错拦截,因为它发现此时的 this 类型不匹配。

TypeScript 箭头函数中的 this

虽然 this 参数很强大,但在现代企业级开发(如 Vue 或 React)中,我们其实用得越来越少了。为什么呢?

因为我们有了 “箭头函数”。其中,箭头函数本身没有自己的 this,它会直接捕获定义位置处的 this。由于它的 this 是固定的(即 “词法作用域”),所以 TypeScript 也不允许在箭头函数中使用这种 this 声明语法。

我们把计算器升级成一个类(Class),看看使用箭头函数是如何彻底解决这个问题的。

示例 3:箭头函数的 this 自动推断

class SmartCalculator {
    count: number = 10;

    // 使用箭头函数,this 会自动指向当前实例
    add = () => {
        console.log("当前计数:", this.count);
    }
}

const calculator = new SmartCalculator();
const myAdd = calculator.add;

// 即使提取出来调用,this 依然指向 calculator 实例
myAdd();

运行结果如下。

当前计数:10

分析:

在箭头函数中,我们不需要(也不能)手动声明 this。TypeScript 会根据上下文自动推断出 this 的指向。在这个例子中,不管你把 calculator.add 赋值给谁,只要调用它,它永远都能精准地找到自己的 count。

在实际开发中,我们也更倾向于使用箭头函数来规避 this 指向混乱的问题。

进阶:多态 this 类型与链式调用

在 TypeScript 的类或接口中,this 不仅可以用来当作 “假参数”,它还可以被直接用作函数的返回值类型。这种特性被称为 “多态 this 类型”。

借助这个特性,我们可以轻松地实现企业级框架中非常流行的 “链式调用” 语法。

示例 4:返回 this 实现链式调用

class ChainCalculator {
    count: number = 0;

    // 将返回值类型设置为 this
    add(step: number): this {
        this.count += step;
        return this; // 返回当前实例对象本身
    }

    minus(step: number): this {
        this.count -= step;
        return this;
    }

    print(): void {
        console.log("当前计数:", this.count);
    }
}

const calculator = new ChainCalculator();

// 优雅的链式调用
calculator.add(10).minus(3).add(5).print();

运行结果如下。

当前计数:12

分析:

在这个例子中,我们将之前的计算器升级成了 ChainCalculator。重点在于 add() 和 minus() 方法的返回值类型被指定为了 this,并且在函数体最后显式地 return this;。

这里的 this 指代的就是 calculator 实例本身。正因为每次调用加减方法后,返回的都是实例本身。所以我们可以像接火车一样,在后面不断地通过点号(.)继续调用该计算器身上的其他方法。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号