前言

关键字
noImplicitThis、any、this

参考资料

  • 「知识库 es6(袁进)」回顾 this 指向:4-6. 箭头函数
  • Understanding JavaScript Function Invocation and “this”:链接
  • noImplicitThis 配置:链接

    notes

在 JavaScript 中,this 指向的几种情况

大多数情况下,this 的指向取决于函数的调用方式。

  • 如果直接调用函数(全局调用),this 指向全局对象或 undefined(启用严格模式)
  • 如果使用 对象.方法 调用,this 指向对象本身。
  • 如果是 DOM 事件的处理函数,this 指向事件处理对象。

特殊情况

  • 箭头函数,this 在函数声明时确定指向,this 指向函数位置的 this。
  • 使用 bind、apply、call 手动绑定 this 对象。

TS 中的 this

配置 noImplicitThis 为 true,表示不允许 this 隐式地指向 any。
在 TS 中,允许在书写函数时,手动声明该函数中的 this 指向。
将 this 作为第一个参数,该参数只用于约束 this,并不是真正的实参,也不会出现在编译结果中。

codes

  1. const u = {
  2. name: "dahuyou",
  3. age: 23,
  4. sayHello() {
  5. console.log(`my name is ${this.name}`);
  6. console.log(`I am ${this.age} years old this year.`);
  7. }
  8. }
  9. u.sayHello();
  10. // my name is dahuyou
  11. // I am 23 years old this year.
  12. const say = u.sayHello;
  13. say(); // => this 指向 global
  14. // my name is undefined
  15. // I am undefined years old this year.
  • say()如果直接调用函数(全局调用),this 指向全局对象或 undefined(启用严格模式)
  • u.sayHello()如果使用 对象.方法 调用,this 指向对象本身。

因为此时的宿主环境是 node,在 node 中,全局对象是 global,global 身上没有 name、age 属性。

image.png

我们采用这种对象字面量的写法来书写 sayHello 方法,方法中的 this 被识别为 any 类型,这种情况下没法确定 this 的具体指向,得看 sayHello 函数具体是如何被调用的。
所以 TS 没法得知此时的 this 指向,它只能将其识别为 any 类型,我们在写 this. 的时候,也没法给我们提供智能提示。

  1. class User {
  2. constructor(
  3. public name: string,
  4. public age: number
  5. ) {}
  6. sayHello() {
  7. console.log(`my name is ${this.name}`);
  8. console.log(`I am ${this.age} years old this year.`);
  9. }
  10. }
  11. const u = new User('dahuyou', 23);
  12. u.sayHello();
  13. // my name is dahuyou
  14. // I am 23 years old this year.
  15. const say = u.sayHello;
  16. say();
  17. // 抛出错误 - 无法从 undefined 身上读取 name 和 age
  • say()如果直接调用函数(全局调用),this 指向全局对象或 undefined(启用严格模式)
  • u.sayHello()如果使用 对象.方法 调用,this 指向对象本身。

和上述程序的不同点在于,此时如果全局调用 sayHello 方法,那么会报错。
原因分析:因为在 JS 中,只要是写在 class 中的代码,默认都是启用严格模式的,此时 this 在默认情况下,将别再指向全局,而是指向 undefined。

this 指向
由于我们采用的是类的写法,当我们写类时,我们调用类身上的方法时,一般都是通过 对象.方法 的方式来调用的。为此,TS 会默认类方法中的 this 默认是指向当前类实例的。

image.png

this: this 后边的 this,其实就是由 User 类创建的实例对象。

image.png

  1. function sayHello() {
  2. console.log(`my name is ${this.name}`);
  3. console.log(`I am ${this.age} years old this year.`);
  4. }
  5. const u = {
  6. name: "dahuyou",
  7. age: 23,
  8. sayHello,
  9. };
  10. sayHello(); // this -> global
  11. // my name is undefined
  12. // I am undefined years old this year.
  13. u.sayHello(); // this -> u
  14. // my name is dahuyou
  15. // I am 23 years old this year.

image.png

由于我们没法确认 sayHello 函数具体是如何被调用的,this 的指向存在很多种可能,TS 没法帮我们完成推导,它只能将 this 识别为 any 类型。

  1. {
  2. "compilerOptions": {
  3. "target": "es2016",
  4. "module": "commonjs",
  5. "lib": [
  6. "es2016"
  7. ],
  8. "outDir": "./dist",
  9. "strictNullChecks": true,
  10. "strictPropertyInitialization": true,
  11. "removeComments": true,
  12. "noImplicitUseStrict": true,
  13. "noImplicitAny": true,
  14. "noImplicitThis": true
  15. },
  16. "include": [
  17. "./src"
  18. ],
  19. }

image.png

⚠️ 在测试 noImplicitThis 这个配置项时,和预期结果不一致。写了跟没写一样。

  1. interface IUser {
  2. name: string;
  3. age: number;
  4. sayHello(this: IUser): void;
  5. }
  6. const u: IUser = {
  7. name: "dahuyou",
  8. age: 23,
  9. sayHello() {
  10. console.log(`my name is ${this.name}`);
  11. console.log(`I am ${this.age} years old this year.`);
  12. },
  13. };
  14. u.sayHello();
  15. // my name is dahuyou
  16. // I am 23 years old this year.
  17. const say = u.sayHello;
  18. say(); // ×

sayHello(this: IUser): void; 限制 sayHello 方法中的 this 必须指向 IUser 接口。
将 this 作为第一个参数,该参数只用于约束 this,并不是真正的实参,也不会出现在编译结果中。

报错
image.png
预防我们写出 say() 这样的隐患代码。
如果这么调用,this 将默认指向全局对象 global,这和接口约束发生冲突,会报错。