วิธีการโทรโดยใช้ต้นแบบ JavaScript


129

เป็นไปได้หรือไม่ที่จะเรียกเมธอดพื้นฐานจากเมธอดต้นแบบใน JavaScript หากถูกลบล้าง

MyClass = function(name){
    this.name = name;
    this.do = function() {
        //do somthing 
    }
};

MyClass.prototype.do = function() {  
    if (this.name === 'something') {
        //do something new
    } else {
        //CALL BASE METHOD
    }
};

วิธีการใดเป็นฐาน? this.do = function () {} ในตัวสร้าง?
Ionuț G. Stan

คำตอบ:


200

ฉันไม่เข้าใจว่าคุณกำลังพยายามทำอะไรกันแน่ แต่โดยปกติแล้วการปรับใช้พฤติกรรมเฉพาะวัตถุจะทำตามบรรทัดเหล่านี้:

function MyClass(name) {
    this.name = name;
}

MyClass.prototype.doStuff = function() {
    // generic behaviour
}

var myObj = new MyClass('foo');

var myObjSpecial = new MyClass('bar');
myObjSpecial.doStuff = function() {
    // do specialised stuff
    // how to call the generic implementation:
    MyClass.prototype.doStuff.call(this /*, args...*/);
}

1
ฉันไม่เข้าใจฉันมาที่นี่ด้วยจุดประสงค์เดียวกับ markvpc ฉันหมายความว่าฉันต้องการมีเมธอด "ส่วนตัว" ชนิดหนึ่งซึ่งใช้จากวิธีการอื่นในต้นแบบ คำตอบของคุณบังคับให้ฉันสร้างโรงงานและฉันต้องการทราบว่ามีวิธีหลีกเลี่ยงสิ่งนั้นหรือไม่
R01010010

10
อย่าใช้คำว่า "คลาส" ใน JS เพราะจะสร้างความสับสน ไม่มีคลาสใน JavaScript
Polaris

1
ใช้ได้กับอ็อบเจ็กต์ที่สร้างอินสแตนซ์เท่านั้น จะเป็นอย่างไรหากคุณต้องการลบล้างอินสแตนซ์ทั้งหมด
supertonsky

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

3
ฉันไม่เข้าใจว่าเหตุใดจึงมีการโหวตเพิ่มจำนวนมากและถูกระบุว่าถูกต้อง สิ่งนี้จะแทนที่พฤติกรรมสำหรับอินสแตนซ์เดียวของคลาสพื้นฐานเท่านั้นไม่ใช่อินสแตนซ์ทั้งหมดของคลาสย่อย ดังนั้นสิ่งนี้ไม่ตอบคำถาม
BlueRaja - Danny Pflughoeft

24

วิธีหนึ่งที่จะทำได้ก็คือการบันทึกเมธอดพื้นฐานแล้วเรียกมันจากเมธอด overriden เช่นนั้น

MyClass.prototype._do_base = MyClass.prototype.do;
MyClass.prototype.do = function(){  

    if (this.name === 'something'){

        //do something new

    }else{
        return this._do_base();
    }

};

11
สิ่งนี้จะสร้างการเรียกซ้ำที่ไม่มีที่สิ้นสุด
Johan Tidén

3
ฉันเคยไปที่นั่น: การเรียกซ้ำไม่มีที่สิ้นสุด
jperelli

ส่วนคำสั่งอื่นกำลังเรียกและเรียกใช้ _do_base ซึ่งเป็นการอ้างอิงถึง prototype.do เนื่องจากไม่มีการเปลี่ยนแปลงพารามิเตอร์ (หรือสถานะ) โค้ดจะกลับไปที่อื่นอีกครั้งเรียก _do_base อีกครั้ง
Johan Tidén

10
@ JohanTidénจัด_do_baseเก็บข้อมูลอ้างอิงถึงฟังก์ชันที่กำหนดไว้ก่อนหน้านี้ undefinedถ้าไม่มีใครแล้วมันจะเป็น JavaScript ไม่ใช่ - การmakeแสดงออกจะไม่ได้รับการประเมินอย่างเฉื่อยชาอย่างที่คุณแนะนำ ตอนนี้ถ้าต้นแบบที่ได้รับการสืบทอดจาก_do_base ยังใช้เพื่อจัดเก็บสิ่งที่คิดว่าเป็นการใช้งานประเภทพื้นฐานแสดงdoว่าคุณมีปัญหา Function.prototype.call(this)ดังนั้นใช่ปิดจะเป็นวิธีที่ดีกว่าการรับมรดกปลอดภัยในการจัดเก็บการอ้างอิงถึงการดำเนินงานฟังก์ชั่นเดิมถ้าใช้วิธีนี้แม้ว่าที่จะต้องใช้
binki

16

ฉันเกรงว่าตัวอย่างของคุณไม่ได้ผลอย่างที่คุณคิด ส่วนนี้:

this.do = function(){ /*do something*/ };

เขียนทับนิยามของ

MyClass.prototype.do = function(){ /*do something else*/ };

เนื่องจากวัตถุที่สร้างขึ้นใหม่มีคุณสมบัติ "do" อยู่แล้วจึงไม่ค้นหาห่วงโซ่ต้นแบบ

รูปแบบคลาสสิกของการสืบทอดใน Javascript นั้นน่ากลัวและยากที่จะเข้าใจ ฉันขอแนะนำให้ใช้รูปแบบการสืบทอดแบบง่ายๆของ Douglas Crockfords แทน แบบนี้:

function my_class(name) {
    return {
        name: name,
        do: function () { /* do something */ }
    };
}

function my_child(name) {
    var me = my_class(name);
    var base_do = me.do;
    me.do = function () {
        if (this.name === 'something'){
            //do something new
        } else {
            base_do.call(me);
        }
    }
    return me;
}

var o = my_child("something");
o.do(); // does something new

var u = my_child("something else");
u.do(); // uses base function

ในความคิดของฉันวิธีที่ชัดเจนกว่ามากในการจัดการวัตถุตัวสร้างและการสืบทอดในจาวาสคริปต์ คุณสามารถอ่านเพิ่มเติมใน Crockfords Javascript: ส่วนที่ดี


3
ดีมาก แต่คุณไม่สามารถเรียกbase_doเป็นฟังก์ชันได้จริง ๆเพราะคุณสูญเสียการthisผูกมัดในdoวิธีการเดิมด้วยวิธีนั้น ดังนั้นการตั้งค่าวิธีการพื้นฐานจึงซับซ้อนกว่าเล็กน้อยโดยเฉพาะอย่างยิ่งถ้าคุณต้องการเรียกใช้โดยใช้วัตถุฐานthisแทนวัตถุลูก base_do.apply(me, arguments)ผมจะแนะนำสิ่งที่คล้ายกับ
Giulio Piancastelli

แค่สงสัยทำไมไม่ใช้varสำหรับbase_do? นี่คือจุดประสงค์และถ้าเป็นเช่นนั้นทำไม? และตามที่ @GiulioPiancastelli กล่าวว่าการทำลายการthisผูกมัดภายในbase_do()หรือการเรียกนั้นจะสืบทอดthisจากผู้โทรหรือไม่?
binki

@binki ฉันอัปเดตตัวอย่าง varควรจะมี การใช้call(หรือapply) ช่วยให้เราสามารถผูกthisได้อย่างถูกต้อง
Magnar

10

ฉันรู้ว่าโพสต์นี้มาจาก 4 ปีที่แล้ว แต่เนื่องจากพื้นหลัง C # ของฉันฉันกำลังมองหาวิธีเรียกคลาสพื้นฐานโดยไม่ต้องระบุชื่อคลาส แต่ได้มาจากคุณสมบัติในคลาสย่อย ดังนั้นการเปลี่ยนแปลงเพียงคำตอบของคริสตอฟก็คือ

จากนี้:

MyClass.prototype.doStuff.call(this /*, args...*/);

สำหรับสิ่งนี้:

this.constructor.prototype.doStuff.call(this /*, args...*/);

6
อย่าทำเช่นนี้อาจจะไม่เสมอชี้ไปที่this.constructor MyClass
Bergi

ก็ควรเว้นเสียแต่ว่าจะมีคนคาดคั้นเรื่องการตั้งมรดก 'this' ควรอ้างถึงออบเจ็กต์ระดับบนสุดเสมอและคุณสมบัติ 'ตัวสร้าง' ควรชี้ไปที่ฟังก์ชันตัวสร้างของมันเองเสมอ
Triynko

5

หากคุณกำหนดฟังก์ชันเช่นนี้ (โดยใช้ OOP)

function Person(){};
Person.prototype.say = function(message){
   console.log(message);
}

มีสองวิธีในการเรียกใช้ฟังก์ชันต้นแบบ: 1) สร้างอินสแตนซ์และเรียกใช้ฟังก์ชันวัตถุ:

var person = new Person();
person.say('hello!');

และอีกวิธีหนึ่งคือ ... 2) กำลังเรียกใช้ฟังก์ชันโดยตรงจากต้นแบบ:

Person.prototype.say('hello there!');

5

วิธีนี้ใช้ Object.getPrototypeOf

TestA เป็นซุปเปอร์ที่มี getName

TestBเป็นเด็กที่แทนที่getNameแต่ยังมี getBothNamesที่เรียกsuperรุ่นgetNameเช่นเดียวกับchildรุ่น

function TestA() {
  this.count = 1;
}
TestA.prototype.constructor = TestA;
TestA.prototype.getName = function ta_gn() {
  this.count = 2;
  return ' TestA.prototype.getName is called  **';
};

function TestB() {
  this.idx = 30;
  this.count = 10;
}
TestB.prototype = new TestA();
TestB.prototype.constructor = TestB;
TestB.prototype.getName = function tb_gn() {
  return ' TestB.prototype.getName is called ** ';
};

TestB.prototype.getBothNames = function tb_gbn() {
  return Object.getPrototypeOf(TestB.prototype).getName.call(this) + this.getName() + ' this object is : ' + JSON.stringify(this);
};

var tb = new TestB();
console.log(tb.getBothNames());


4
function NewClass() {
    var self = this;
    BaseClass.call(self);          // Set base class

    var baseModify = self.modify;  // Get base function
    self.modify = function () {
        // Override code here
        baseModify();
    };
}

4

ทางเลือก:

// shape 
var shape = function(type){
    this.type = type;
}   
shape.prototype.display = function(){
    console.log(this.type);
}
// circle
var circle = new shape('circle');
// override
circle.display = function(a,b){ 
    // call implementation of the super class
    this.__proto__.display.apply(this,arguments);
}

แม้ว่าอาจได้ผล แต่ฉันจะไม่แนะนำวิธีแก้ปัญหานี้ มีสาเหตุหลายประการที่__proto__ควรหลีกเลี่ยงการใช้(ซึ่งเปลี่ยน [[ต้นแบบ]] `ของวัตถุ) โปรดใช้Object.create(...)เส้นทางแทน
KarelG

2

ถ้าฉันเข้าใจถูกต้องคุณต้องการให้ฟังก์ชันพื้นฐานทำงานอยู่เสมอในขณะที่ส่วนหนึ่งควรปล่อยให้ใช้งานได้

คุณอาจได้รับความช่วยเหลือจากรูปแบบการออกแบบ' template method '

Base = function() {}
Base.prototype.do = function() { 
    // .. prologue code
    this.impldo(); 
    // epilogue code 
}
// note: no impldo implementation for Base!

derived = new Base();
derived.impldo = function() { /* do derived things here safely */ }

1

หากคุณรู้จักซูเปอร์คลาสตามชื่อคุณสามารถทำสิ่งต่อไปนี้:

function Base() {
}

Base.prototype.foo = function() {
  console.log('called foo in Base');
}

function Sub() {
}

Sub.prototype = new Base();

Sub.prototype.foo = function() {
  console.log('called foo in Sub');
  Base.prototype.foo.call(this);
}

var base = new Base();
base.foo();

var sub = new Sub();
sub.foo();

สิ่งนี้จะพิมพ์

called foo in Base
called foo in Sub
called foo in Base

อย่างที่คาดไว้.


1

อีกวิธีหนึ่งสำหรับ ES5 คือการสำรวจโซ่ต้นแบบอย่างชัดเจนโดยใช้ Object.getPrototypeOf(this)

const speaker = {
  speak: () => console.log('the speaker has spoken')
}

const announcingSpeaker = Object.create(speaker, {
  speak: {
    value: function() {
      console.log('Attention please!')
      Object.getPrototypeOf(this).speak()
    }
  }
})

announcingSpeaker.speak()

0

ไม่คุณจะต้องให้ฟังก์ชัน do ในตัวสร้างและฟังก์ชัน do ในชื่อที่แตกต่างกันของต้นแบบ


0

นอกจากนี้หากคุณต้องการลบล้างอินสแตนซ์ทั้งหมดไม่ใช่แค่อินสแตนซ์พิเศษอันนั้นอันนี้อาจช่วยได้

function MyClass() {}

MyClass.prototype.myMethod = function() {
  alert( "doing original");
};
MyClass.prototype.myMethod_original = MyClass.prototype.myMethod;
MyClass.prototype.myMethod = function() {
  MyClass.prototype.myMethod_original.call( this );
  alert( "doing override");
};

myObj = new MyClass();
myObj.myMethod();

ผลลัพธ์:

doing original
doing override

-1
function MyClass() {}

MyClass.prototype.myMethod = function() {
  alert( "doing original");
};
MyClass.prototype.myMethod_original = MyClass.prototype.myMethod;
MyClass.prototype.myMethod = function() {
  MyClass.prototype.myMethod_original.call( this );
  alert( "doing override");
};

myObj = new MyClass();
myObj.myMethod();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.