วิธีการเรียกวิธีผู้ปกครองจากชั้นเรียนเด็กในจาวาสคริปต์?


156

ฉันใช้เวลาสองสามชั่วโมงสุดท้ายพยายามหาวิธีแก้ปัญหาของฉัน แต่ดูเหมือนจะสิ้นหวัง

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

ฉันใช้รหัสต่อไปนี้เพื่อตั้งค่า OOP ใน javascript:

// SET UP OOP
// surrogate constructor (empty function)
function surrogateCtor() {}

function extend(base, sub) {
    // copy the prototype from the base to setup inheritance
    surrogateCtor.prototype = base.prototype;
    sub.prototype = new surrogateCtor();
    sub.prototype.constructor = sub;
}

// parent class
function ParentObject(name) {
    this.name = name;
}
// parent's methods
ParentObject.prototype = {
    myMethod: function(arg) {
        this.name = arg;
    }
}

// child
function ChildObject(name) {
    // call the parent's constructor
    ParentObject.call(this, name);
    this.myMethod = function(arg) {
        // HOW DO I CALL THE PARENT METHOD HERE?
        // do stuff
    }
}

// setup the prototype chain
extend(ParentObject, ChildObject);

ฉันต้องเรียกวิธีการของผู้ปกครองก่อนแล้วจึงเพิ่มสิ่งอื่น ๆ ลงในชั้นเรียนของเด็ก

ในภาษา OOP ส่วนใหญ่ที่จะง่ายเหมือนการโทรparent.myMethod() แต่ฉันไม่สามารถเข้าใจวิธีการทำในจาวาสคริปต์

ความช่วยเหลือใด ๆ ที่ชื่นชมมากขอบคุณ!

คำตอบ:


196

นี่เป็นวิธีที่ทำได้: ParentClass.prototype.myMethod();

หรือถ้าคุณต้องการเรียกมันในบริบทของอินสแตนซ์ปัจจุบันคุณสามารถทำได้: ParentClass.prototype.myMethod.call(this)

กันไปสำหรับการเรียกวิธีผู้ปกครองจากระดับเด็กที่มีข้อโต้แย้ง: ParentClass.prototype.myMethod.call(this, arg1, arg2, ..)* คำแนะนำ: ใช้apply()แทนcall()การส่งผ่านข้อโต้แย้งเป็นอาร์เรย์


7
ถ้าคุณต้องการเรียกมันในบริบทของอินสแตนซ์ปัจจุบันคุณต้องทำParentClass.prototype.myMethod.apply() or ParentClass.prototype.myMethod.call () `เช่นเดียวกับที่คุณทำกับคอนสตรัคของคุณ
JMM

3
เพียงแค่เพิ่มในว่าถ้าคุณต้องการที่จะเรียกมีข้อโต้แย้งพวกเขาไปภายในใช้หรือฟังก์ชั่นการโทร ( ParentClass.prototype.myMethod.call(this, arg1, arg2, arg3...);)
ชอม

ฉันไม่เข้าใจ ถ้าฉันเรียก ParentClass.prototype.myMethod.call (อันนี้); จาก myMethod ของ ChildObject ฉันมีข้อผิดพลาด "Uncaught TypeError: ไม่สามารถอ่านคุณสมบัติ 'call' จาก undefined"
zhekaus

@zhekaus ก็หมายความว่าคุณไม่ได้มีmyMethodในชั้นเรียนของคุณ
YemSalat

2
ขณะนี้ฉันใช้ this.myFun = function () {} เพื่อประกาศเมธอด object ดังนั้นให้เรียก ParentClass.prototype.myFun.call (... ) ไม่ทำงานดังนั้นฉันต้องใช้ CurrentClass.prototype.myFun.call ( ... ) JS คือ ... อึเราควรใช้ OOP จริง
Loenix

156

สไตล์ ES6 ช่วยให้คุณใช้คุณสมบัติใหม่เช่นsuperคำหลัก superคำหลักมันคือทั้งหมดที่เกี่ยวกับบริบทระดับผู้ปกครองเมื่อคุณใช้ไวยากรณ์คลาส ES6 เป็นตัวอย่างง่าย ๆ การชำระเงิน:

class Foo {
    static classMethod() {
        return 'hello';
    }
}

class Bar extends Foo {
    static classMethod() {
        return super.classMethod() + ', too';
    }
}
Bar.classMethod(); // 'hello, too'

นอกจากนี้คุณยังสามารถใช้superเรียกตัวสร้างพาเรนต์:

class Foo {}

class Bar extends Foo {
    constructor(num) {
        let tmp = num * 2; // OK
        this.num = num; // ReferenceError
        super();
        this.num = num; // OK
    }
}

และแน่นอนคุณสามารถใช้เพื่อเข้าถึงคุณสมบัติคลาสพาเรนsuper.propต์ ดังนั้นใช้ ES6 และมีความสุข


10
@ fsinisi90 ฉันเชื่อว่าคำถามไม่ได้เกี่ยวกับวิธีการเรียนของผู้ปกครอง แต่เกี่ยวกับวิธีการตัวอย่างของผู้ปกครองที่ไม่สามารถเรียกด้วยคำหลักที่ยอดเยี่ยมเป็น ES6
mcmlxxxiii

มันทำงานยังสำหรับวิธีการที่ไม่คงที่ (ทดสอบด้วย Chrome โดยไม่มี transpiliing ไม่ tryed คำหลักที่คงที่)
Gianluca Casati

ทำไมถึงต้องsuperถูกเรียก? มีความเท่าเทียมกันใน JS "เก่า" หรือไม่
1252748

3
super () ต้องถูกเรียกใน constructor คลาสเด็กก่อนสิ่งอื่นใด
user938363

1
@GianlucaCasati: คุณสามารถใช้super()ในวิธีการคงที่เท่านั้น ดูเหมือนว่าคุณจะใช้มันในตัวสร้าง
ZzZombo

5

เพื่อที่จะทำสิ่งนี้คุณไม่ได้ถูก จำกัด ด้วยสิ่งที่Classเป็นนามธรรมของ ES6 การเข้าถึงวิธีการสร้างต้นแบบของผู้สร้างเป็นไปได้ผ่าน__proto__คุณสมบัติ (ฉันค่อนข้างแน่ใจว่าจะมีเพื่อน coders JS ที่จะบ่นว่ามันเป็นค่าเสื่อมราคา) ซึ่งเป็นค่าเสื่อมราคา แต่ในเวลาเดียวกันค้นพบว่ามันเป็นเครื่องมือสำคัญสำหรับความต้องการย่อย โดยเฉพาะอย่างยิ่งสำหรับความต้องการการจัดกลุ่มย่อยของ Array) ดังนั้นในขณะที่__proto__คุณสมบัติยังคงมีอยู่ในเอนจิน JS หลัก ๆ ทั้งหมดที่ฉันรู้ ES6 แนะนำObject.getPrototypeOf()ฟังก์ชั่นด้านบนของมัน super()เครื่องมือในClassนามธรรมเป็นน้ำตาลประโยคนี้

ดังนั้นในกรณีที่คุณไม่สามารถเข้าถึงชื่อคอนสตรัคเตอร์หลักและไม่ต้องการใช้สิ่งที่Classเป็นนามธรรมคุณยังอาจทำดังนี้

function ChildObject(name) {
    // call the parent's constructor
    ParentObject.call(this, name);
    this.myMethod = function(arg) {
    //this.__proto__.__proto__.myMethod.call(this,arg);
    Object.getPrototypeOf(Object.getPrototypeOf(this)).myMethod.call(this,arg);
    }
}

4

ในกรณีที่มีหลายระดับการสืบทอดฟังก์ชันนี้สามารถใช้เป็นวิธี super () ในภาษาอื่น ๆ นี่คือซอเดโมชั่นที่มีการทดสอบบางอย่างคุณสามารถใช้มันในแบบของคุณ:call_base(this, 'method_name', arguments);

มันใช้ประโยชน์จากฟังก์ชั่น ES ล่าสุดค่อนข้างเข้ากันได้กับเบราว์เซอร์รุ่นเก่าไม่รับประกัน ทดสอบใน IE11, FF29, CH35

/**
 * Call super method of the given object and method.
 * This function create a temporary variable called "_call_base_reference",
 * to inspect whole inheritance linage. It will be deleted at the end of inspection.
 *
 * Usage : Inside your method use call_base(this, 'method_name', arguments);
 *
 * @param {object} object The owner object of the method and inheritance linage
 * @param {string} method The name of the super method to find.
 * @param {array} args The calls arguments, basically use the "arguments" special variable.
 * @returns {*} The data returned from the super method.
 */
function call_base(object, method, args) {
    // We get base object, first time it will be passed object,
    // but in case of multiple inheritance, it will be instance of parent objects.
    var base = object.hasOwnProperty('_call_base_reference') ? object._call_base_reference : object,
    // We get matching method, from current object,
    // this is a reference to define super method.
            object_current_method = base[method],
    // Temp object wo receive method definition.
            descriptor = null,
    // We define super function after founding current position.
            is_super = false,
    // Contain output data.
            output = null;
    while (base !== undefined) {
        // Get method info
        descriptor = Object.getOwnPropertyDescriptor(base, method);
        if (descriptor !== undefined) {
            // We search for current object method to define inherited part of chain.
            if (descriptor.value === object_current_method) {
                // Further loops will be considered as inherited function.
                is_super = true;
            }
            // We already have found current object method.
            else if (is_super === true) {
                // We need to pass original object to apply() as first argument,
                // this allow to keep original instance definition along all method
                // inheritance. But we also need to save reference to "base" who
                // contain parent class, it will be used into this function startup
                // to begin at the right chain position.
                object._call_base_reference = base;
                // Apply super method.
                output = descriptor.value.apply(object, args);
                // Property have been used into super function if another
                // call_base() is launched. Reference is not useful anymore.
                delete object._call_base_reference;
                // Job is done.
                return output;
            }
        }
        // Iterate to the next parent inherited.
        base = Object.getPrototypeOf(base);
    }
}

2

สิ่งที่เกี่ยวกับแนวคิดดักลาสครอคฟอร์ด:

    function Shape(){}

    Shape.prototype.name = 'Shape';

    Shape.prototype.toString = function(){
        return this.constructor.parent
            ? this.constructor.parent.toString() + ',' + this.name
            : this.name;
    };


    function TwoDShape(){}

    var F = function(){};

    F.prototype = Shape.prototype;

    TwoDShape.prototype = new F();

    TwoDShape.prototype.constructor = TwoDShape;

    TwoDShape.parent = Shape.prototype;

    TwoDShape.prototype.name = '2D Shape';


    var my = new TwoDShape();

    console.log(my.toString()); ===> Shape,2D Shape

2

นี่เป็นวิธีที่ดีสำหรับวัตถุลูกที่จะเข้าถึงคุณสมบัติผู้ปกครองและวิธีการโดยใช้โซ่ต้นแบบของ JavaScript และเข้ากันได้กับ Internet Explorer JavaScript ค้นหาต้นแบบลูกโซ่สำหรับวิธีการต่างๆและเราต้องการให้ลูกโซ่ต้นแบบของเด็กนั้นมีลักษณะดังนี้:

อินสแตนซ์ของเด็ก -> ต้นแบบของเด็ก (พร้อมเมธอดย่อย) -> ต้นแบบของผู้ปกครอง (พร้อมเมธอดพาเรนต์) -> ต้นแบบวัตถุ -> null

เมธอดลูกยังสามารถเรียกเมธอดพาเรนต์ที่มีเงาดังแสดงในสามดอกจัน *** ด้านล่าง

นี่คือวิธี:

//Parent constructor
function ParentConstructor(firstName){
    //add parent properties:
    this.parentProperty = firstName;
}

//add 2 Parent methods:
ParentConstructor.prototype.parentMethod = function(argument){
    console.log(
            "Parent says: argument=" + argument +
            ", parentProperty=" + this.parentProperty +
            ", childProperty=" + this.childProperty
    );
};

ParentConstructor.prototype.commonMethod = function(argument){
    console.log("Hello from Parent! argument=" + argument);
};

//Child constructor    
function ChildConstructor(firstName, lastName){
    //first add parent's properties
    ParentConstructor.call(this, firstName);

    //now add child's properties:
    this.childProperty = lastName;
}

//insert Parent's methods into Child's prototype chain
var rCopyParentProto = Object.create(ParentConstructor.prototype);
rCopyParentProto.constructor = ChildConstructor;
ChildConstructor.prototype = rCopyParentProto;

//add 2 Child methods:
ChildConstructor.prototype.childMethod = function(argument){
    console.log(
            "Child says: argument=" + argument +
            ", parentProperty=" + this.parentProperty +
            ", childProperty=" + this.childProperty
    );
};

ChildConstructor.prototype.commonMethod = function(argument){
    console.log("Hello from Child! argument=" + argument);

    // *** call Parent's version of common method
    ParentConstructor.prototype.commonMethod(argument);
};

//create an instance of Child
var child_1 = new ChildConstructor('Albert', 'Einstein');

//call Child method
child_1.childMethod('do child method');

//call Parent method
child_1.parentMethod('do parent method');

//call common method
child_1.commonMethod('do common method');


1

มีวิธีแก้ปัญหาที่ง่ายและกะทัดรัดมากขึ้นสำหรับการค้นหาต้นแบบหลายระดับ แต่ต้องการProxyการสนับสนุน การใช้งาน: SUPER(<instance>).<method>(<args>)ตัวอย่างเช่นสมมติว่าสองชั้นAและB extends Aด้วยวิธีการ:mSUPER(new B).m()

function SUPER(instance) {
    return new Proxy(instance, {
        get(target, prop) {
            return Object.getPrototypeOf(Object.getPrototypeOf(target))[prop].bind(target);
        }
    });
}

0

ในขณะที่คุณสามารถเรียกวิธีปกครองโดยต้นแบบของผู้ปกครองที่คุณจะต้องผ่านอินสแตนซ์ของเด็กในปัจจุบันสำหรับการใช้call, applyหรือbindวิธีการ bindวิธีการจะสร้างฟังก์ชั่นใหม่ดังนั้นผมจึงไม่แนะนำว่าถ้าคุณดูแลการปฏิบัติงานยกเว้นว่ามันจะเรียกว่าเพียงครั้งเดียว

คุณสามารถแทนที่เมธอด child และวางเมธอด parent บนอินสแตนซ์ขณะเรียกใช้เมธอด child ดั้งเดิม

function proxy(context, parent){
  var proto = parent.prototype;
  var list = Object.getOwnPropertyNames(proto);
  
  var child = {};
  for(var i=0; i<list.length; i++){
    var key = list[i];

    // Create only when child have similar method name
    if(context[key] !== proto[key]){
      child[key] = context[key];
      context[key] = function(){
        context.super = proto[key];
        return child[key].apply(context, arguments);
      }
    }
  }
}

// ========= The usage would be like this ==========

class Parent {
  first = "Home";

  constructor(){
    console.log('Parent created');
  }

  add(arg){
    return this.first + ", Parent "+arg;
  }
}

class Child extends Parent{
  constructor(b){
    super();
    proxy(this, Parent);
    console.log('Child created');
  }

  // Comment this to call method from parent only
  add(arg){
    return this.super(arg) + ", Child "+arg;
  }
}

var family = new Child();
console.log(family.add('B'));

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