เรียกวิธีการคงที่จากวิธีการเรียน ES6 ปกติ


176

วิธีมาตรฐานในการเรียกใช้วิธีการคงที่คืออะไร ฉันสามารถคิดถึงการใช้constructorหรือใช้ชื่อของคลาสเองฉันไม่ชอบอันหลังเพราะมันไม่จำเป็น อดีตเป็นวิธีที่แนะนำหรือมีอย่างอื่นอีกหรือไม่

นี่คือตัวอย่าง (contrived):

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
ฉันอยากรู้ว่าทำไมคำถามนี้ไม่มี upvotes มากมาย! นี่ไม่ใช่การปฏิบัติทั่วไปสำหรับการสร้างฟังก์ชันยูทิลิตี้ใช่ไหม
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แทนจะใช้การจัดส่งแบบไดนามิกและอ้างอิงคลาสของอินสแตนซ์ปัจจุบันโดยที่คุณสมบัติสแตติกอาจมีค่าที่สืบทอดมา แต่อาจแทนที่ได้

selfนี้ตรงกับพฤติกรรมของงูใหญ่ที่คุณสามารถเลือกที่จะอ้างถึงคุณสมบัติคงที่ทั้งผ่านชื่อชั้นหรืออินสแตนซ์

หากคุณคาดว่าคุณสมบัติสแตติกจะไม่ถูกแทนที่ (และอ้างอิงถึงหนึ่งในคลาสปัจจุบัน) เช่นใน Javaให้ใช้การอ้างอิงที่ชัดเจน


คุณสามารถอธิบายนิยามวิธีการของตัวสร้างและคลาสได้หรือไม่
Chris

2
@Chris: ทุกคลาสเป็นฟังก์ชั่นคอนสตรัคเตอร์ (เหมือนที่คุณรู้จักจาก ES5 โดยไม่มีclassไวยากรณ์) ไม่มีความแตกต่างในนิยามของวิธีการ เป็นเพียงเรื่องของวิธีการที่คุณค้นหาผ่านคุณสมบัติที่สืบทอดconstructorหรือโดยตรงโดยใช้ชื่อ
Bergi

อีกตัวอย่างหนึ่งคือPHP ดึกคงผูก สิ่งนี้ไม่เพียง แต่คอนสตรัคเคารพการสืบทอด แต่ยังช่วยให้คุณหลีกเลี่ยงการอัปเดตรหัสหากคุณเปลี่ยนชื่อคลาส
ricanontherun

@ricanontherun การอัพเดทรหัสเมื่อคุณเปลี่ยนชื่อตัวแปรไม่ใช่ข้อโต้แย้งในการใช้ชื่อ นอกจากนี้เครื่องมือการรีแฟคเตอร์สามารถทำได้โดยอัตโนมัติ
Bergi

วิธีการใช้สิ่งนี้ใน typescript? มันให้ข้อผิดพลาดProperty 'staticProperty' does not exist on type 'Function'
ayZagen

72

ฉันสะดุดกับหัวข้อนี้เพื่อค้นหาคำตอบของกรณีที่คล้ายกัน โดยทั่วไปจะพบคำตอบทั้งหมด แต่ก็ยังยากที่จะดึงข้อมูลสำคัญจากพวกเขา

ชนิดของการเข้าถึง

สมมติว่าคลาส Foo อาจได้รับมาจากคลาสอื่น (es) ที่มีคลาสที่น่าจะมาจากคลาสนั้นมากกว่า

จากนั้นเข้าถึง

  • จากวิธีการคงที่ / ทะเยอทะยานของ Foo
    • บางคนอาจจะแทนที่วิธีคงที่ / ทะเยอทะยาน:
      • this.method()
      • this.property
    • บางคนอาจจะแทนที่วิธีการ / ทะเยอทะยาน:
      • เป็นไปไม่ได้ด้วยการออกแบบ
    • วิธีการแบบคงที่ / แทนที่ตัวเองที่ไม่ได้ถูกแทนที่ :
      • Foo.method()
      • Foo.property
    • วิธี / getter อินสแตนซ์ที่ไม่ถูกแทนที่ของตัวเอง:
      • เป็นไปไม่ได้ด้วยการออกแบบ
  • จากตัวอย่าง method / getter ของ Foo
    • บางคนอาจจะแทนที่วิธีคงที่ / ทะเยอทะยาน:
      • this.constructor.method()
      • this.constructor.property
    • บางคนอาจจะแทนที่วิธีการ / ทะเยอทะยาน:
      • this.method()
      • this.property
    • วิธีการแบบคงที่ / แทนที่ตัวเองที่ไม่ได้ถูกแทนที่ :
      • Foo.method()
      • Foo.property
    • วิธี / getter อินสแตนซ์ที่ไม่ถูกแทนที่ของตัวเอง:
      • เป็นไปไม่ได้โดยเจตนาเว้นแต่จะใช้วิธีแก้ปัญหาบางอย่าง :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

โปรดทราบว่าการใช้thisไม่ทำงานในลักษณะนี้เมื่อใช้ฟังก์ชั่นลูกศรหรือเรียกใช้เมธอด / getters ที่เชื่อมโยงกับค่าที่กำหนดเองอย่างชัดเจน

พื้นหลัง

  • เมื่ออยู่ในบริบทของวิธีการของอินสแตนซ์หรือทะเยอทะยาน
    • this อ้างถึงอินสแตนซ์ปัจจุบัน
    • super โดยทั่วไปหมายถึงอินสแตนซ์เดียวกัน แต่วิธีการที่อยู่ค่อนข้างและ getters เขียนในบริบทของบางคลาสปัจจุบันหนึ่งขยาย (โดยใช้ต้นแบบของต้นแบบของ Foo)
    • this.constructorความหมายของการเรียนเช่นที่ใช้ในการสร้างมันมีอยู่ต่อ
  • เมื่ออยู่ในบริบทของวิธีการคงที่หรือทะเยอทะยานไม่มี "อินสแตนซ์ปัจจุบัน" โดยเจตนาและอื่น ๆ
    • this พร้อมใช้งานเพื่ออ้างถึงคำจำกัดความของคลาสปัจจุบันโดยตรง
    • super ไม่ได้อ้างถึงอินสแตนซ์บางตัวเช่นกัน แต่สำหรับเมธอดสแตติกและตัวเขียนแบบสแตติกในบริบทของคลาสปัจจุบันที่คลาสหนึ่งกำลังขยาย

ข้อสรุป

ลองรหัสนี้:

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ไม่ได้ผูกไว้กับฐานที่ต้องการ "ชั้น" จากนั้นจึงล้มเหลวที่จะเป็นวิธีการเช่นไม่ใช่ overriden / ทะเยอทะยาน เป็นไปได้เสมอที่จะทำงานกับวิธีการที่ไม่ขึ้นกับคลาส อย่างไรก็ตามฉันไม่คิดว่าการออกแบบในปัจจุบันจะไม่ดี ในบริบทของวัตถุของคลาสที่ได้มาจากคลาสพื้นฐานของคุณ Foo อาจมีเหตุผลที่ดีที่จะแทนที่วิธีการอินสแตนซ์ วิธี overriden นั้นอาจมีเหตุผลที่ดีที่จะเรียกsuperใช้งานหรือไม่ ทั้งสองกรณีมีสิทธิ์และควรได้รับการเชื่อฟัง ไม่งั้นมันจะจบด้วยการออกแบบ OOP ที่แย่
Thomas Urban

แม้จะมีน้ำตาล OOP แต่วิธี ES ก็ยังคงใช้งานได้และผู้คนจะต้องการใช้และอ้างอิงพวกเขาเช่นนี้ ปัญหาของฉันกับไวยากรณ์คลาส ES คือมันไม่ได้ให้การอ้างอิงโดยตรงกับวิธีการดำเนินการในปัจจุบัน - สิ่งที่เคยเป็นผ่านง่ายarguments.calleeหรือ NFE
รอยทิงเกอร์

ฟังดูเหมือนเป็นการฝึกฝนที่ไม่ดีหรือการออกแบบซอฟต์แวร์อย่างน้อยไม่ดีอยู่ดี ฉันพิจารณามุมมองทั้งสองที่ขัดแย้งกันเนื่องจากฉันไม่เห็นเหตุผลที่เหมาะสมในบริบทของกระบวนทัศน์ OOP ที่เกี่ยวข้องกับการเข้าถึงวิธีที่เรียกใช้ในปัจจุบันโดยอ้างอิง (ซึ่งไม่ได้เป็นเพียงบริบทตามที่มีให้ผ่านthis) ดูเหมือนจะพยายามผสมผสานประโยชน์ของตัวคำนวณเลขคณิตของ bare C กับระดับสูงกว่า C # เพิ่งออกมาจากความอยากรู้: สิ่งที่คุณจะใช้arguments.calleeสำหรับในรหัส OOP ที่ออกแบบมาอย่างหมดจด?
โทมัส Urban

ฉันกำลังทำงานในโครงการขนาดใหญ่ที่สร้างขึ้นด้วยระบบคลาสของ Dojo ซึ่งช่วยให้เรียกใช้การดำเนินการ superclass (s) ของวิธีการปัจจุบันผ่านthis.inherited(currentFn, arguments);- ซึ่งcurrentFnเป็นการอ้างอิงถึงฟังก์ชั่นการดำเนินการในปัจจุบัน การไม่สามารถอ้างอิงฟังก์ชันการดำเนินการโดยตรงในขณะนี้ได้ทำให้มันมีขนดกเล็กน้อยใน TypeScript ซึ่งใช้ไวยากรณ์คลาสจาก ES6
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

ใช่ตามที่ฉันได้ข้อสรุปแล้วในคำตอบของฉันเองนี่ไม่ได้ถูกแก้ไขอย่างสม่ำเสมอในการสืบทอด "คลาสสิค" - บางครั้งคุณอาจต้องการแทนที่บางครั้งก็ไม่ใช่ ส่วนแรกของความคิดเห็นของฉันชี้ไปที่ฟังก์ชั่นระดับคงที่ซึ่งฉันไม่คิดว่าจะเป็น "สมาชิก" ไม่ต้องสนใจ :-) ดีกว่า
Bergi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.