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


175

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

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

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

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

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

8
SomeObject.print感觉很自然。但是this.n如果我们谈论静态方法,则内部没有意义,因为没有实例。
dfsq,2015年

3
@dfsq printN不是静态的。
simonzack 2015年

您是正确的名字。
dfsq,2015年

1
我很好奇,为什么这个问题没有那么多赞成票!这不是创建实用程序功能的常见做法吗?
索兰2015年

Answers:


209

两种方法都是可行的,但是当涉及到覆盖静态方法的继承时,它们会做不同的事情。选择您期望其行为的一种:

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的行为相符,在Python中,您可以选择通过类名或实例来引用静态属性self

如果您希望不重写静态属性(并且始终引用当前类之一),例如在Java中,请使用显式引用。


您能解释构造函数属性与类方法定义吗?
克里斯

2
@Chris:每个类都是构造函数(就像您从ES5知道的那样没有class语法),方法的定义没有区别。只是通过继承的constructor属性或直接通过其名称查找它的方式即可。
Bergi 2016年

另一个示例是PHP的Late Static Bindings。this.constructor不仅尊重继承,而且还可以帮助您避免在更改类名时不必更新代码。
ricanontherun

@ricanontherun更改变量名称时不必更新代码,并不是反对使用名称。重构工具也可以自动执行此操作。
Bergi

如何在打字稿中实现呢?它给出了错误Property 'staticProperty' does not exist on type 'Function'
ayZagen

71

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

访问种类

假设一个类Foo可能是从其他某个类派生的,可能还有更多的类派生自它。

然后访问

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

请记住,this在使用箭头功能或调用显式绑定到自定义值的方法/获取器时,使用不能以这种方式工作。

背景

  • 在实例的方法或获取器的上下文中
    • this 是指当前实例。
    • super 基本上指的是同一实例,但是某种寻址方法和在某种类的上下文中获取的方法正在扩展(通过使用Foo原型的原型)。
    • 实例类的定义可用于创建实例this.constructor
  • 当在静态方法或吸气剂的上下文中,没有意向的“当前实例”,因此
    • 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 );


1
Own non-overridden instance method/getter / not possible by intention unless using some workaround---真是可惜。我认为这是ES6 +的缺点。也许应该对其进行更新以允许简单地引用method-即method.call(this)。比更好Foo.prototype.method。Babel /等 可以使用NFE(命名函数表达式)实现。
罗伊·廷克

method.call( this )是一个可能的解决方案,除了method没有绑定到所需的基础“类”,因此不能成为不可重写的实例方法/获取器。这样总是可以使用与类无关的方法。尽管如此,我认为当前的设计并不那么糟糕。在从您的基类Foo派生的类的对象的上下文中,可能有充分的理由重写实例方法。该覆盖的方法可能有充分的理由调用super或不调用其实现。两种情况都符合条件,应该服从。否则,它将导致糟糕的OOP设计。
托马斯·厄本

尽管OOP糖,ES方法仍然功能,人们会想参考使用这些程序。我对ES类语法的问题是,它没有直接引用当前正在执行的方法-以前通过via arguments.callee或NFE都很容易。
罗伊·廷克

听起来像是不好的做法,或者至少是不好的软件设计。我认为两种观点相互矛盾,因为在OOP范式的上下文中我看不到符合条件的理由,该范式涉及通过引用访问当前调用的方法(不仅仅是通过可以访问的上下文this)。听起来好像试图将裸C的指针算法的优点与更高级别的C#混合在一起。只是出于好奇:您将arguments.callee在整洁设计的OOP代码中使用什么?
Thomas Urban

我正在使用Dojo的类系统构建的一个大型项目中,该项目允许通过this.inherited(currentFn, arguments);- 调用当前方法的超类实现,其中currentFn是对当前执行函数的引用。无法直接引用当前执行的函数使TypeScript显得有些毛茸茸,它采用了ES6的类语法。
罗伊·廷克

20

如果您打算进行任何继承,那么我建议您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仍然认为它属于超类。在此示例中,这似乎没什么大不了的,但是如果您尝试引用已被重写的成员函数或新的成员变量,则会发现自己遇到了麻烦。


3
但是静态函数不是重写的成员方法吗?通常,您尝试静态引用任何被覆盖的东西。
Bergi 2015年

1
@Bergi我不确定我是否理解您所指出的内容,但是我遇到的一种具体情况是MVC模型的水合模式。扩展模型的子类可能要实现静态水合函数。但是,当这些代码被硬编码时,只会返回基本模型实例。这是一个非常具体的示例,但是许多依赖于已注册实例的静态集合的模式都将受到此影响。一个大的免责声明是我们试图在这里模拟经典继承,而不是原型继承...而且这并不流行:P
Andrew Odri

是的,正如我现在在我自己的答案中总结的那样,在“经典”继承中甚至不能始终如一地解决-有时您可能需要重写,有时则不需要。我的评论的第一部分针对静态类函数,我不认为它们是“成员”。最好忽略它:-)
Bergi 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.