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

Posted

技术标签:

【中文标题】从常规 ES6 类方法调用静态方法【英文标题】:Call static methods from regular ES6 class methods 【发布时间】:2015-04-22 01:38:59 【问题描述】:

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

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

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

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

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

【问题讨论】:

SomeObject.print 感觉很自然。但是 this.n 里面没有任何意义,因为没有实例,如果我们谈论的是静态方法。 @dfsq printN 不是静态的。 你是对的,混淆的名字。 我很好奇为什么这个问题没有那么多赞!这不是创建实用函数的常见做法吗? 【参考方案1】:

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

访问类型

假设一个类 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);

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

背景

在实例的方法或 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 );

【讨论】:

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

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

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 引用静态属性。

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

【讨论】:

你能解释一下构造函数属性与类方法的定义吗? @Chris:每个类都是一个构造函数(就像你从没有class语法的ES5中知道的一样),方法的定义没有区别。这只是你如何查找它的问题,通过the inherited constructor property 或直接通过它的名称。 另一个例子是php's Late Static Bindings。 this.constructor 不仅尊重继承,还可以帮助您避免在更改类名时更新代码。 @ricanontherun 更改变量名称时必须更新代码并不是反对使用名称的理由。无论如何,重构工具也可以自动完成。 如何在 typescript 中实现这个?它给出了错误Property 'staticProperty' does not exist on type 'Function'【参考方案3】:

如果您打算进行任何类型的继承,那么我会推荐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! 记录到控制台

See all the above running in Babel REPL.

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

【讨论】:

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

以上是关于从常规 ES6 类方法调用静态方法的主要内容,如果未能解决你的问题,请参考以下文章

ES6---class的静态方法静态属性和实例属性

静态方法 - 如何从另一个方法调用一个方法?

在 es6 Map 上调用 `Array.prototype.some()` 的更好方法

Redux-Saga ES6类语法方法在调度操作时不会被调用

ES6 class(基本语法+方法)

如何从另一个类调用非静态抽象方法