일반 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.