【问题标题】:What is the difference between class method vs. class field function vs. class field arrow function?类方法与类字段函数与类字段箭头函数有什么区别?
【发布时间】:2020-07-14 04:14:42
【问题描述】:

类方法、类属性是函数、类属性是箭头函数有什么区别? this 关键字在方法的不同变体中的行为是否不同?

class Greeter {
  constructor() {
    this.greet();
    this.greet2();
    this.greet3();
  }

  greet() {
    console.log('greet1', this);
  }

  greet2 = () => {
    console.log('greet2', this);
  }

  greet3 = function() {
    console.log('greet3', this);
  }
}
      
let bla = new Greeter();

这是从 TypeScript 转译后生成的 JavaScript。

var Greeter = /** @class */ (function () {
function Greeter() {
    var _this = this;
    this.greet2 = function () {
        console.log('greet2', _this);
    };
    this.greet3 = function () {
        console.log('greet3', this);
    };
    this.greet();
    this.greet2();
    this.greet3();
}
Greeter.prototype.greet = function () {
    console.log('greet1', this);
};
return Greeter;
}());
var bla = new Greeter();

我的 TypeScript 版本是 3.4.5。

【问题讨论】:

标签: javascript typescript class class-method class-fields


【解决方案1】:

this关键字区别:

在上面所有三个都有相同的this,但是当你将方法传递给另一个函数时你会看到不同。

class Greeter {
  constructor() {
  }
  greet() {
    console.log(this);
  }

  greet2 = () => {
    console.log(this);
  }

  greet3 = function() {
    console.log(this);
  }
}


let bla = new Greeter();
function wrapper(f){
  f();
}
wrapper(bla.greet) //undefined
wrapper(bla.greet2) //Greeter 
wrapper(bla.greet3) //undefined

但还有一个区别,第一种方法是在class 的prototype 上,而其他两个不是。它们是对象实例的方法。

class Greeter {
  constructor() {
  }
  greet() {
    console.log('greet1', this);
  }

  greet2 = () => {
    console.log('greet2', this);
  }

  greet3 = function() {
    console.log('greet3', this);
  }
}

let bla = new Greeter();
console.log(Object.getOwnPropertyNames(Greeter.prototype))

如果我在课堂上 -> str = "my string"; 并且在所有 3 种方法中我可以说 console.log(this.str) 并输出“我的字符串”。但我想知道 - 这真的是一回事吗

不,它们不是同一个东西。正如我提到的,greet2 和 greet3 不会出现在 Greeter.prototype 上,而是会出现在实例本身上。这意味着如果您创建 Greeter 的 1000 实例,它们将在内存中存储 1000 个不同的方法(greet2 和 greet3),用于 1000 个不同的实例。但是所有实例都会有一个 greet 方法。

请参阅下面的带有两个 Greeter() 实例的 sn-p

【讨论】:

  • 如果我在课堂上-> str = "my string";在所有 3 种方法中,我可以说 console.log(this.str) 并输出“我的字符串”。但我想知道 - 'this' 在方法的所有 3 个变体中真的是一样的吗?
  • @Combine 我在答案末尾添加了更多解释。请检查一下。
  • 你的意思是this没有区别,箭头函数捕获this常规函数没有,如果你在函数变量中传递方法会有很大的影响..
  • 它仍然说“all three have the same this”这显然是不正确的?
  • @Bergi 我的意思是在给定的代码中,当 OP 从constructor 调用函数时,所有三个都具有相同的this
【解决方案2】:

所有 3 个版本之间存在差异。这种差异体现在 3 个方面:

  1. 谁是运行时this
  2. 函数的分配位置
  3. 打字稿中this 的类型是什么。

让我们从它们的工作原理开始。考虑这个类,有一个类字段:

class Greeter {
  constructor(private x: string) {
  }
  greet() {
    console.log('greet1', this.x);
  }

  greet2 = () => {
    console.log('greet2', this.x);
  }

  greet3 = function () {    
    // this is typed as any 
    console.log('greet3', this.x);
  }
}

let bla = new Greeter(" me");

使用这个类,所有 3 个函数调用都将按预期打印:'greet* me' 当在 bla 上调用时

bla.greet()
bla.greet2()
bla.greet3()

运行时这是谁

箭头函数从声明上下文中捕获this,因此greet2 中的this 始终保证是创建此函数的类实例。其他版本(方法和函数)不做这样的保证。

所以在这段代码中,并非所有 3 个都打印相同的文本:

function call(fn: () => void) {
  fn();
}

call(bla.greet) // greet1 undefined 
call(bla.greet2) //greet2 me
call(bla.greet3) // greet3 undefined

在将函数作为事件处理程序传递给另一个组件时,这一点尤为重要。

函数的分配位置

类方法(如greet)在原型上赋值,字段初始化(如greet2和greet3)在构造函数中赋值。这意味着greet2 和greet3 将占用更大的内存,因为它们需要在每次实例化Greeter 时分配一个新的闭包。

打字稿中 this 的类型是什么。

Typescript 将在方法 (greet) 和箭头函数 (greet2) 中键入 this 作为 Greeter 的实例,但将在 greet3 中键入 this。如果您尝试在greet3 下的noImplictAny 下使用this,这将导致错误

何时使用它们

  1. 如果此函数不会作为事件处理程序传递给另一个组件,请使用方法语法(除非您使用 bind 或其他方式来确保 this 仍然是类的实例)

  2. 当您的函数将被传递给其他组件并且您需要访问函数内部的this 时,请使用箭头函数语法。

  3. 实在想不出一个好的用例,通常避免。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-03
    • 2018-12-30
    • 2022-07-27
    相关资源
    最近更新 更多