从常规 ES6 类方法调用静态方法

IT技术 javascript class static ecmascript-6 es6-class
2021-01-21 13:17:15

调用静态方法的标准方法是什么?我可以考虑使用constructor或使用类本身的名称,我不喜欢后者,因为它觉得没有必要。前者是推荐的方式,还是有别的方法?

这是一个(人为的)示例:

class SomeObject {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(n);
  }

  printN(){
    this.constructor.print(this.n);
  }
}
3个回答

这两种方式都是可行的,但是当涉及到使用重写的静态方法进行继承时,它们会做不同的事情。选择您期望的行为:

class Super {
  static whoami() {
    return "Super";
  }
  lognameA() {
    console.log(Super.whoami());
  }
  lognameB() {
    console.log(this.constructor.whoami());
  }
}
class Sub extends Super {
  static whoami() {
    return "Sub";
  }
}
new Sub().lognameA(); // Super
new Sub().lognameB(); // Sub

通过类引用静态属性实际上是静态的,并不断给出相同的值。使用this.constructor替代将使用动态调度并引用当前实例的类,其中静态属性可能具有继承的值,但也可能被覆盖。

这与 Python 的行为相匹配,您可以选择通过类名或实例来引用静态属性self

如果您希望静态属性不被覆盖(并且始终引用当前类之一),就像在 Java 中一样,请使用显式引用。

@ricanontherun 在更改变量名称时必须更新代码并不是反对使用名称的论据。无论如何,重构工具都可以自动完成。
2021-03-17 13:17:15
另一个例子是PHP 的后期静态绑定this.constructor 不仅尊重继承,而且还可以帮助您避免在更改类名时必须更新代码。
2021-03-19 13:17:15
如何在typescript中实现这一点?它给出了错误Property 'staticProperty' does not exist on type 'Function'
2021-03-20 13:17:15
@Chris:每个类都是一个构造函数(就像你从 ES5 知道它没有class语法一样),方法的定义没有区别。这只是您如何查找它的问题,通过继承的constructor属性或直接通过其名称。
2021-03-24 13:17:15
你能解释一下构造函数属性与类方法的定义吗?
2021-04-04 13:17:15

我偶然发现了这个线程,以寻找类似案例的答案。基本上所有的答案都找到了,但仍然很难从中提取要领。

访问类型

假设一个类 Foo 可能派生自其他一些类,并且可能有更多的类从它派生而来。

然后访问

  • 来自Foo 的静态方法/getter
    • 一些可能被覆盖的静态方法/getter:
      • this.method()
      • this.property
    • 一些可能被覆盖的实例方法/getter:
      • 设计不可能
    • 自己的非覆盖静态方法/getter:
      • Foo.method()
      • Foo.property
    • 自己的非覆盖实例方法/getter:
      • 设计不可能
  • 来自Foo 的实例方法/getter
    • 一些可能被覆盖的静态方法/getter:
      • this.constructor.method()
      • this.constructor.property
    • 一些可能被覆盖的实例方法/getter:
      • this.method()
      • this.property
    • 自己的非覆盖静态方法/getter:
      • Foo.method()
      • Foo.property
    • 自己的非覆盖实例方法/getter:
      • 除非使用一些解决方法,否则不可能有意为之
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

请记住,使用this箭头函数或调用明确绑定到自定义值的方法/getter 时, using不会以这种方式工作。

背景

  • 在实例的方法或 getter 的上下文中时
    • this 指的是当前实例。
    • super 基本上指的是同一个实例,但有些寻址方法和 getter 是在当前正在扩展的某个类的上下文中编写的(通过使用 Foo 原型的原型)。
    • 用于创建实例的类的定义在每个this.constructor.
  • 当在静态方法或 getter 的上下文中没有意图的“当前实例”时,所以
    • this 可以直接引用当前类的定义。
    • super 也不是指某个实例,而是指在当前正在扩展的某个类的上下文中编写的静态方法和 getter。

结论

试试这个代码:

class A {
  constructor( input ) {
    this.loose = this.constructor.getResult( input );
    this.tight = A.getResult( input );
    console.log( this.scaledProperty, Object.getOwnPropertyDescriptor( A.prototype, "scaledProperty" ).get.call( this ) );
  }

  get scaledProperty() {
    return parseInt( this.loose ) * 100;
  }
  
  static getResult( input ) {
    return input * this.scale;
  }
  
  static get scale() {
    return 2;
  }
}

class B extends A {
  constructor( input ) {
    super( input );
    this.tight = B.getResult( input ) + " (of B)";
  }
  
  get scaledProperty() {
    return parseInt( this.loose ) * 10000;
  }

  static get scale() {
    return 4;
  }
}

class C extends B {
  constructor( input ) {
    super( input );
  }
  
  static get scale() {
    return 5;
  }
}

class D extends C {
  constructor( input ) {
    super( input );
  }
  
  static getResult( input ) {
    return super.getResult( input ) + " (overridden)";
  }
  
  static get scale() {
    return 10;
  }
}


let instanceA = new A( 4 );
console.log( "A.loose", instanceA.loose );
console.log( "A.tight", instanceA.tight );

let instanceB = new B( 4 );
console.log( "B.loose", instanceB.loose );
console.log( "B.tight", instanceB.tight );

let instanceC = new C( 4 );
console.log( "C.loose", instanceC.loose );
console.log( "C.tight", instanceC.tight );

let instanceD = new D( 4 );
console.log( "D.loose", instanceD.loose );
console.log( "D.tight", instanceD.tight );

method.call( this )是一个可能的解决方案,除了method没有绑定到所需的基“类” then 并且因此不能是非覆盖的实例 method/getter总是可以以这种方式使用与类无关的方法。尽管如此,我认为目前的设计并没有那么糟糕。在从基类 Foo 派生的类的对象的上下文中,可能有很好的理由覆盖实例方法。该重写方法可能有充分的理由调用super或不调用其实现。任何一种情况都是合格的,应该被遵守。否则它会以糟糕的 OOP 设计告终。
2021-03-14 13:17:15
Own non-overridden instance method/getter / not possible by intention unless using some workaround---真是可惜了。在我看来,这是 ES6+ 的一个缺点。也许应该更新它以允许简单地引用method- 即method.call(this)比 好Foo.prototype.method巴别塔/等。可以使用 NFE(命名函数表达式)来实现。
2021-03-28 13:17:15
无论如何,这听起来像是不好的做法,或者至少是糟糕的软件设计。我认为这两种观点是相互矛盾的,因为在涉及通过引用访问当前调用的方法的 OOP 范式的上下文中,我没有看到符合条件的理由(这不仅仅是通过引用获得的上下文this)。这听起来像是试图将裸 C 的指针算法的好处与更高级别的 C# 混合在一起。只是出于好奇:你会arguments.callee在干净设计的 OOP 代码中使用什么?
2021-03-30 13:17:15
尽管有 OOP 糖,ES 方法仍然是函数,人们会希望像这样使用和引用它们。我对 ES 类语法的问题是它没有提供对当前正在执行的方法的直接引用——这在过去很容易通过arguments.callee或 NFE。
2021-03-31 13:17:15
我正在使用 Dojo 的类系统构建一个大型项目,该项目允许通过this.inherited(currentFn, arguments);-- wherecurrentFn是对当前正在执行的函数的引用调用当前方法的超类实现无法直接引用当前正在执行的函数使其在 TypeScript 中有点麻烦,它采用 ES6 的类语法。
2021-04-10 13:17:15

如果您打算进行任何类型的继承,那么我会推荐this.constructor. 这个简单的例子应该可以说明原因:

class ConstructorSuper {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(this.name, n);
  }

  callPrint(){
    this.constructor.print(this.n);
  }
}

class ConstructorSub extends ConstructorSuper {
  constructor(n){
    this.n = n;
  }
}

let test1 = new ConstructorSuper("Hello ConstructorSuper!");
console.log(test1.callPrint());

let test2 = new ConstructorSub("Hello ConstructorSub!");
console.log(test2.callPrint());
  • test1.callPrint()将登录ConstructorSuper Hello ConstructorSuper!到控制台
  • test2.callPrint()将登录ConstructorSub Hello ConstructorSub!到控制台

命名类不会很好地处理继承,除非您明确重新定义引用命名类的每个函数。下面是一个例子:

class NamedSuper {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(NamedSuper.name, n);
  }

  callPrint(){
    NamedSuper.print(this.n);
  }
}

class NamedSub extends NamedSuper {
  constructor(n){
    this.n = n;
  }
}

let test3 = new NamedSuper("Hello NamedSuper!");
console.log(test3.callPrint());

let test4 = new NamedSub("Hello NamedSub!");
console.log(test4.callPrint());
  • test3.callPrint()将登录NamedSuper Hello NamedSuper!到控制台
  • test4.callPrint()将登录NamedSuper Hello NamedSub!到控制台

查看以上在 Babel REPL 中运行的所有内容

从中可以看出,test4仍然认为它在超类中;在这个例子中,它可能看起来不是什么大问题,但是如果您试图引用已被覆盖的成员函数或新的成员变量,您会发现自己遇到了麻烦。

@Bergi 我不确定我是否理解您指出的内容,但我遇到的一个具体案例是 MVC 模型水化模式。扩展模型的子类可能想要实现静态水合物功能。但是,当这些被硬编码时,只返回基本模型实例。这是一个非常具体的示例,但是许多依赖于注册实例的静态集合的模式都会受到此影响。一个重要的免责声明是,我们试图在这里模拟经典继承,而不是原型继承……这并不流行:P
2021-03-12 13:17:15
但是静态函数没有被覆盖的成员方法吗?通常,您试图静态引用任何覆盖的内容。
2021-03-27 13:17:15
是的,正如我现在在我自己的回答中得出的结论,这甚至没有在“经典”继承中始终如一地解决-有时您可能需要覆盖,有时则不需要。我评论的第一部分指出了静态类函数,我不认为它们是“成员”。最好忽略它:-)
2021-04-08 13:17:15