일반 ES6 클래스 메소드에서 정적 메소드 호출


176

정적 메소드를 호출하는 표준 방법은 무엇입니까? 나는 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

3
@dfsq printN는 정적이 아닙니다.
simonzack

당신은 옳고 혼란스러운 이름입니다.
dfsq

1
왜이 질문에 그렇게 많은 공감대가 없는지 궁금합니다! 유틸리티 기능을 만드는 일반적인 방법이 아닙니까?
Thoran

답변:


211

두 방법 모두 실행 가능하지만 재정의 된 정적 메서드를 사용하여 상속 할 때 다른 작업을 수행합니다. 행동이 예상되는 것을 선택하십시오 :

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동적 파견을 사용하고 정적 속성은 현재 인스턴스의 클래스를 참조 할 대신 할 수 상속 된 값을 가질뿐만 아니라, 오버라이드 (override) 할 수 있습니다.

이것은 클래스 이름이나 instance를 통해 정적 속성을 참조하도록 선택할 수있는 Python의 동작과 일치합니다 self.

Java 에서처럼 정적 특성이 대체되지 않고 항상 현재 클래스 중 하나를 참조 할 것으로 예상되는 경우 명시 적 참조를 사용하십시오.


생성자 속성과 클래스 메서드 정의를 설명 할 수 있습니까?
Chris

2
@Chris : 모든 클래스 생성자 함수입니다 ( class구문 없이 ES5에서 아는 것처럼 ). 메소드 정의에는 차이가 없습니다. 상속 된 constructor속성을 통해 또는 이름으로 직접 조회하는 방식에 달려 있습니다.
Bergi

또 다른 예는 PHP의 Late Static Bindings 입니다. this.constructor는 상속을 존중할뿐만 아니라 클래스 이름을 변경하면 코드를 업데이트하지 않아도됩니다.
ricanontherun

@ricanontherun 변수 이름을 변경할 때 코드를 업데이트해야한다는 것은 이름 사용에 대한 논쟁이 아닙니다. 또한 리팩토링 도구는 어쨌든 자동으로 수행 할 수 있습니다.
Bergi

typescript에서 이것을 구현하는 방법은 무엇입니까? 그것은 오류를 준다Property 'staticProperty' does not exist on type 'Function'
ayZagen

72

비슷한 사례에 대한 답을 찾기 위해이 스레드를 우연히 발견했습니다. 기본적으로 모든 답변이 있지만 여전히 필수 답변을 추출하기는 어렵습니다.

접근의 종류

아마도 다른 클래스에서 파생 된 Foo 클래스가 더 많은 클래스에서 파생되었다고 가정하십시오.

그런 다음

  • Foo의 정적 메소드 / getter에서
    • 아마도 재정의 된 정적 메소드 / getter :
      • this.method()
      • this.property
    • 아마도 재정의 된 인스턴스 메소드 / getter :
      • 의도적으로 불가능
    • 자신의 오버라이드 (override)되지 않는 정적의 방법 / 게터 :
      • Foo.method()
      • Foo.property
    • 재정의되지 않은 자체 인스턴스 메소드 / 게터 :
      • 의도적으로 불가능
  • Foo의 인스턴스 메소드 / getter에서
    • 아마도 재정의 된 정적 메소드 / getter :
      • this.constructor.method()
      • this.constructor.property
    • 아마도 재정의 된 인스턴스 메소드 / getter :
      • this.method()
      • this.property
    • 자신의 오버라이드 (override)되지 않는 정적의 방법 / 게터 :
      • Foo.method()
      • Foo.property
    • 재정의되지 않은 자체 인스턴스 메소드 / 게터 :
      • 해결 방법을 사용하지 않으면 의도적으로 불가능합니다 .
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

사용하는 것을 명심 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 );


1
Own non-overridden instance method/getter / not possible by intention unless using some workaround--- 그것은 진짜 동정입니다. 제 생각에 이것은 ES6 +의 단점입니다. 어쩌면 참조 간단하게 할 수 있도록 업데이트해야합니다 method즉, - method.call(this). 보다 낫다 Foo.prototype.method. 바벨 등 NFE (명명 된 함수 표현식)를 사용하여 구현할 수 있습니다.
Roy Tinker

method.call( this )method원하는 기본 "클래스"에 바인딩되지 않고 오버라이드되지 않은 인스턴스 메소드 / getter 가 아닌 것을 제외하고 가능한 해결책 입니다. 이런 방식으로 클래스 독립적 인 메소드로 작업하는 것이 항상 가능합니다. 그럼에도 불구하고 현재 디자인이 그렇게 나쁘지 않다고 생각합니다. 기본 클래스 Foo에서 파생 된 클래스의 객체와 관련하여 인스턴스 메서드를 재정의해야 할 이유가있을 수 있습니다. 재정의 된 메소드는 super구현 을 호출해야 할 이유가있을 수 있습니다 . 두 경우 모두 자격이 있으며 준수해야합니다. 그렇지 않으면 잘못된 OOP 디자인으로 끝납니다.
Thomas Urban

OOP 설탕에도 불구하고 ES 방법은 여전히 기능적 이며 사람들은이를 사용하고 참조하기를 원할 것입니다. ES 클래스 구문의 문제는 현재 실행중인 메소드에 대한 직접적인 참조를 제공하지 않는다는 것입니다 arguments.callee.
Roy Tinker

어쨌든 나쁜 습관이나 최소한 나쁜 소프트웨어 디자인처럼 들립니다. OOP 패러다임의 맥락에서 참조로 현재 호출 된 메소드에 액세스하는 것과 관련된 적절한 이유를 보지 못하기 때문에 두 관점이 서로 반대되는 것으로 간주합니다 this. 베어 C의 포인터 산술의 장점을 상위 C #과 혼합하려고하는 것처럼 들립니다. 호기심에서 벗어나 arguments.callee깔끔하게 설계된 OOP 코드에서 무엇을 사용 하시겠습니까?
Thomas Urban

나는 통해 현재 방법의 슈퍼 클래스 (들) 구현 (들)을 호출 허용 Dojo의 클래스 시스템에 내장 된 대규모 프로젝트에서 일하고 있어요 this.inherited(currentFn, arguments);- currentFn현재 실행중인 함수에 대한 참조입니다. 현재 실행중인 함수를 직접 참조 할 수 없기 때문에 ES6의 클래스 구문을 사용하는 TypeScript에서 약간 털이 있습니다.
Roy Tinker

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

1
@ Bergi 나는 당신이 지적하고있는 것을 이해하지 못하지만, 내가 만난 한 가지 구체적인 사례는 MVC 모델 수화 패턴입니다. 모델을 확장하는 하위 클래스는 정적 수화물 함수를 구현할 수 있습니다. 그러나 이러한 코드를 하드 코딩하면 기본 모델 인스턴스 만 반환됩니다. 이것은 매우 구체적인 예이지만 등록 된 인스턴스의 정적 컬렉션을 사용하는 많은 패턴이 이로 인해 영향을받습니다. 하나의 큰 면책 조항은 우리가 프로토 타입 상속보다는 여기에서 클래식 상속을 시뮬레이션하려고한다는 것입니다. 그리고 그것은 인기가 없습니다. : P
Andrew Odri

네, 지금 내 자신의 대답으로 결론을 내렸을 때 이것은 "클래식"상속에서 일관되게 해결되지 않습니다. 때로는 재정의를 원할 수도 있습니다. 내 의견의 첫 번째 부분은 정적 클래스 함수를 가리키며, "멤버"로 간주되지 않았습니다. 그것을 무시하는 것이
좋습니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.