只要是写过 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 实例本身。正因为每次调用加减方法后,返回的都是实例本身。所以我们可以像接火车一样,在后面不断地通过点号(.)继续调用该计算器身上的其他方法。
