Gọi các phương thức tĩnh từ các phương thức lớp ES6 thông thường


175

Cách tiêu chuẩn để gọi các phương thức tĩnh là gì? Tôi có thể nghĩ đến việc sử dụng constructorhoặc sử dụng tên của chính lớp đó, tôi không thích cái thứ hai vì nó không cảm thấy cần thiết. Đây có phải là cách được đề xuất, hoặc có cái gì khác không?

Đây là một ví dụ (giả định):

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

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

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

8
SomeObject.printcảm thấy tự nhiên Nhưng this.nbên trong không có ý nghĩa gì vì không có trường hợp nào, nếu chúng ta đang nói về các phương thức tĩnh.
dfsq

3
@dfsq printNkhông tĩnh mặc dù.
simonzack

Bạn nói đúng, nhầm tên.
dfsq

1
Tôi tò mò tại sao câu hỏi này không có quá nhiều câu hỏi! Đây không phải là một cách phổ biến để tạo các chức năng tiện ích sao?
Thoran

Câu trả lời:


210

Cả hai cách đều khả thi, nhưng chúng làm những việc khác nhau khi nói đến kế thừa với một phương thức tĩnh được ghi đè. Chọn một hành vi mà bạn mong đợi:

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

Tham chiếu đến thuộc tính tĩnh thông qua lớp sẽ thực sự tĩnh và liên tục đưa ra cùng một giá trị. Sử dụng this.constructorthay thế sẽ sử dụng công văn động và tham chiếu đến lớp của cá thể hiện tại, trong đó thuộc tính tĩnh có thể có giá trị được kế thừa nhưng cũng có thể bị ghi đè.

Điều này phù hợp với hành vi của Python, nơi bạn có thể chọn tham chiếu đến các thuộc tính tĩnh thông qua tên lớp hoặc thể hiện self.

Nếu bạn mong muốn các thuộc tính tĩnh không bị ghi đè (và luôn tham chiếu đến một trong các lớp hiện tại), như trong Java , hãy sử dụng tham chiếu rõ ràng.


bạn có thể giải thích thuộc tính constructor vs định nghĩa phương thức lớp không?
Chris

2
@Chris: Mỗi lớp một hàm xây dựng (giống như bạn biết nó từ ES5 không có classcú pháp), không có sự khác biệt trong định nghĩa của phương thức. Đó chỉ là vấn đề bạn tìm kiếm nó như thế nào, thông qua tài sản được thừa kếconstructor hoặc trực tiếp bằng tên của nó.
Bergi

Một ví dụ khác là ràng buộc tĩnh muộn của PHP . Điều này không chỉ tôn trọng sự kế thừa mà còn giúp bạn tránh phải cập nhật mã nếu bạn thay đổi tên lớp.
ricanontherun

@ricanontherun Phải cập nhật mã khi bạn thay đổi tên biến không phải là đối số chống lại việc sử dụng tên. Ngoài ra các công cụ tái cấu trúc có thể làm điều đó tự động bằng mọi cách.
Bergi

Làm thế nào để thực hiện điều này trong bản thảo? Nó báo lỗiProperty 'staticProperty' does not exist on type 'Function'
ayZagen

71

Tôi tình cờ tìm thấy chủ đề này để tìm câu trả lời cho trường hợp tương tự. Về cơ bản tất cả các câu trả lời được tìm thấy, nhưng vẫn khó để trích xuất các yếu tố cần thiết từ chúng.

Các loại quyền truy cập

Giả sử một lớp Foo có thể xuất phát từ một số lớp khác với nhiều lớp có lẽ bắt nguồn từ nó.

Sau đó truy cập

  • từ phương thức tĩnh / getter của Foo
    • một số có thể ghi đè phương thức tĩnh / getter:
      • this.method()
      • this.property
    • một số phương thức / getter có thể bị ghi đè :
      • không thể bởi thiết kế
    • riêng tĩnh không ghi đè phương pháp / getter:
      • Foo.method()
      • Foo.property
    • riêng trường hợp không ghi đè phương pháp / getter:
      • không thể bởi thiết kế
  • từ phương thức cá thể / getter của Foo
    • một số có thể ghi đè phương thức tĩnh / getter:
      • this.constructor.method()
      • this.constructor.property
    • một số phương thức / getter có thể bị ghi đè :
      • this.method()
      • this.property
    • riêng tĩnh không ghi đè phương pháp / getter:
      • Foo.method()
      • Foo.property
    • riêng trường hợp không ghi đè phương pháp / getter:
      • không thể có chủ ý trừ khi sử dụng một số cách giải quyết :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Hãy nhớ rằng việc sử dụng thiskhông hoạt động theo cách này khi sử dụng các hàm mũi tên hoặc gọi các phương thức / getters ràng buộc rõ ràng với giá trị tùy chỉnh.

Lý lịch

  • Khi trong bối cảnh của một phương thức hoặc getter của một thể hiện
    • this đang đề cập đến ví dụ hiện tại.
    • super về cơ bản là đề cập đến cùng một thể hiện, nhưng phần nào giải quyết các phương thức và getters được viết trong ngữ cảnh của một số lớp hiện tại đang mở rộng (bằng cách sử dụng nguyên mẫu của nguyên mẫu của Foo).
    • định nghĩa của lớp thể hiện được sử dụng để tạo nó có sẵn cho mỗi this.constructor.
  • Khi trong bối cảnh của một phương thức tĩnh hoặc getter không có "trường hợp hiện tại" theo ý định và vì vậy
    • this có sẵn để tham khảo định nghĩa của lớp hiện tại trực tiếp.
    • super cũng không đề cập đến một số trường hợp, mà là các phương thức tĩnh và getters được viết trong ngữ cảnh của một số lớp hiện tại đang mở rộng.

Phần kết luận

Hãy thử mã này:

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--- Thật đáng tiếc. Theo tôi, đây là một thiếu sót của ES6 +. Có lẽ nó nên được cập nhật để cho phép chỉ cần tham khảo method- tức là method.call(this). Tốt hơn Foo.prototype.method. Babel / v.v. có thể thực hiện bằng cách sử dụng NFE (biểu thức hàm được đặt tên).
Roy Tinker

method.call( this )là một giải pháp có thể xảy ra ngoại trừ trường hợp methodkhông bị ràng buộc với "lớp" cơ sở mong muốn và do đó không thể là một phương thức / getter không quá chồng . Luôn luôn có thể làm việc với các phương thức độc lập lớp theo cách đó. Tuy nhiên, tôi không nghĩ rằng thiết kế hiện tại là xấu. Trong ngữ cảnh các đối tượng của lớp xuất phát từ lớp cơ sở Foo của bạn, có thể có những lý do chính đáng để ghi đè một phương thức cá thể. Phương pháp overriden đó có thể có lý do chính đáng để gọi superthực hiện hay không. Một trong hai trường hợp là đủ điều kiện và nên được tuân theo. Nếu không, nó sẽ kết thúc trong thiết kế OOP xấu.
Thomas Urban

Mặc dù có đường OOP, các phương thức ES vẫn là các hàm và mọi người sẽ muốn sử dụng và tham chiếu chúng như vậy. Vấn đề của tôi với cú pháp lớp ES là nó không cung cấp tham chiếu trực tiếp đến phương thức hiện đang thực thi - một cái gì đó được sử dụng dễ dàng thông qua arguments.calleehoặc NFE.
Roy Tinker

Âm thanh như thực hành xấu hoặc ít nhất là thiết kế phần mềm xấu nào. Tôi xem xét cả hai quan điểm trái ngược với nhau, vì tôi không thấy lý do đủ điều kiện trong bối cảnh mô hình OOP liên quan đến việc truy cập phương thức hiện được viện dẫn bằng cách tham chiếu (không chỉ là bối cảnh của nó như có sẵn thông qua this). Nghe có vẻ như cố gắng trộn lẫn lợi ích của mỹ phẩm con trỏ của C trần với C # cấp cao hơn. Vì tò mò: bạn sẽ sử dụng cái gì arguments.calleetrong mã OOP được thiết kế sạch sẽ?
Thomas đô thị

Tôi đang làm việc trong một dự án lớn được xây dựng với hệ thống lớp của Dojo, cho phép gọi (các) triển khai siêu lớp của phương thức hiện tại thông qua this.inherited(currentFn, arguments);- trong đó currentFntham chiếu đến chức năng hiện đang thực thi. Không thể tham chiếu trực tiếp chức năng đang thực thi đang làm cho nó có một chút lông trong TypeScript, nó lấy cú pháp lớp của nó từ ES6.
Roy Tinker

20

Nếu bạn đang có kế hoạch thực hiện bất kỳ loại thừa kế, thì tôi muốn giới thiệu this.constructor. Ví dụ đơn giản này sẽ minh họa tại sao:

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()sẽ đăng nhập ConstructorSuper Hello ConstructorSuper!vào giao diện điều khiển
  • test2.callPrint()sẽ đăng nhập ConstructorSub Hello ConstructorSub!vào giao diện điều khiển

Lớp được đặt tên sẽ không xử lý sự kế thừa độc đáo trừ khi bạn xác định lại rõ ràng mọi chức năng tạo tham chiếu đến Lớp được đặt tên. Đây là một ví dụ:

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()sẽ đăng nhập NamedSuper Hello NamedSuper!vào giao diện điều khiển
  • test4.callPrint()sẽ đăng nhập NamedSuper Hello NamedSub!vào giao diện điều khiển

Xem tất cả các hoạt động ở trên trong Babel REPL .

Bạn có thể thấy từ đây mà test4vẫn nghĩ rằng nó thuộc siêu hạng; trong ví dụ này có vẻ như không phải là một vấn đề lớn, nhưng nếu bạn đang cố gắng tham chiếu các hàm thành viên đã bị ghi đè hoặc biến thành viên mới, bạn sẽ thấy mình gặp rắc rối.


3
Nhưng hàm tĩnh không có phương thức thành viên bị ghi đè? Thông thường bạn đang cố gắng không tham chiếu bất kỳ công cụ ghi đè tĩnh.
Bergi

1
@Bergi Tôi không chắc tôi hiểu những gì bạn đang chỉ ra, nhưng một trường hợp cụ thể mà tôi đã gặp phải là với các mẫu hydrat hóa mô hình MVC. Các lớp con mở rộng một mô hình có thể muốn thực hiện một hàm hydrate tĩnh. Tuy nhiên, khi chúng được mã hóa cứng, các thể hiện mô hình cơ sở chỉ được trả về. Đây là một ví dụ khá cụ thể, nhưng nhiều mẫu dựa trên việc có một bộ sưu tập tĩnh các thể hiện đã đăng ký sẽ bị ảnh hưởng bởi điều này. Một từ chối trách nhiệm lớn là chúng tôi đang cố gắng mô phỏng thừa kế cổ điển ở đây, thay vì thừa kế nguyên mẫu ... Và điều đó không phổ biến: P
Andrew Odri 24/2/2015

Vâng, như tôi đã kết luận bây giờ trong câu trả lời của riêng tôi, điều này thậm chí không được giải quyết một cách nhất quán trong thừa kế "cổ điển" - đôi khi bạn có thể muốn ghi đè, đôi khi không. Phần đầu tiên của bình luận của tôi chỉ vào các hàm lớp tĩnh, mà tôi không coi là "thành viên". Tốt hơn nên bỏ qua :-)
Bergi 24/2/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.