Làm thế nào để gọi một phương thức cha từ lớp con trong javascript?


156

Tôi đã dành vài giờ cuối cùng để cố gắng tìm giải pháp cho vấn đề của mình nhưng dường như là vô vọng.

Về cơ bản tôi cần biết cách gọi một phương thức cha từ một lớp con. Tất cả những thứ mà tôi đã thử cho đến nay đều không hoạt động hoặc viết quá nhiều phương thức cha.

Tôi đang sử dụng đoạn mã sau để thiết lập OOP trong 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);

Tôi cần gọi phương thức của cha mẹ trước và sau đó thêm một số nội dung khác vào lớp con.

Trong hầu hết các ngôn ngữ OOP sẽ đơn giản như cách gọi parent.myMethod() Nhưng tôi thực sự không thể nắm bắt được cách nó được thực hiện trong javascript.

Bất kỳ trợ giúp được nhiều đánh giá cao, cảm ơn bạn!

Câu trả lời:


196

Đây là cách nó được thực hiện: ParentClass.prototype.myMethod();

Hoặc nếu bạn muốn gọi nó trong ngữ cảnh của trường hợp hiện tại, bạn có thể làm: ParentClass.prototype.myMethod.call(this)

Tương tự như vậy khi gọi một phương thức cha từ lớp con với các đối số: ParentClass.prototype.myMethod.call(this, arg1, arg2, ..)* Gợi ý: sử dụng apply()thay vì call()để truyền các đối số dưới dạng một mảng.


7
Nếu bạn muốn gọi nó trong ngữ cảnh của cá thể hiện tại, bạn phải thực hiện ParentClass.prototype.myMethod.apply() or ParentClass.prototype.myMethod.call () `, giống như bạn làm với hàm tạo của mình.
JMM

3
Chỉ cần thêm vào đó nếu bạn muốn gọi bằng đối số, chúng sẽ vào bên trong hàm áp dụng hoặc gọi ( ParentClass.prototype.myMethod.call(this, arg1, arg2, arg3...);)
Gershom

Tôi không hiểu Nếu tôi gọi ParentClass.prototype.myMethod.call (cái này); từ myMethod của ChildObject, tôi đã gặp lỗi "Uncaught TypeError: Không thể đọc thuộc tính 'cuộc gọi' của không xác định".
zhekaus

@zhekaus, điều đó có nghĩa là bạn không có myMethodtrong lớp học của mình.
YemSalat

2
Tôi hiện đang sử dụng this.myFun = function () {} để khai báo một phương thức đối tượng, vì vậy hãy gọi ParentClass.prototype.myFun.call (...) không hoạt động, vì vậy tôi phải sử dụng CurrentClass.prototype.myFun.call ( ...). JS là ... một thứ nhảm nhí, chúng ta nên sử dụng OOP thực sự.
Loenix

156

Phong cách ES6 cho phép bạn sử dụng các tính năng mới, chẳng hạn như supertừ khóa. supertừ khóa đó là tất cả về bối cảnh lớp cha, khi bạn đang sử dụng cú pháp lớp ES6. Như một ví dụ rất đơn giản, hãy kiểm tra:

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

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

Ngoài ra, bạn có thể sử dụng superđể gọi hàm tạo cha mẹ:

class Foo {}

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

Và tất nhiên bạn có thể sử dụng nó để truy cập các thuộc tính lớp cha super.prop. Vì vậy, sử dụng ES6 và hạnh phúc.


10
@ fsinisi90 Tôi tin rằng, câu hỏi không phải là về các phương thức lớp của cha mẹ, mà là về các phương thức cá thể của cha mẹ mà không thể được gọi bằng siêu từ khóa như ES6.
mcmlxxxiii

nó cũng hoạt động đối với các phương thức không tĩnh (được thử nghiệm với Chrome, không có dịch chuyển, không thử từ khóa tĩnh)
Gianluca Casati

Tại sao nó cần superphải được gọi? Có sự tương đương trong phiên bản cũ của JS?
1252748

3
super () phải được gọi trong hàm tạo của lớp con trước bất kỳ thứ gì khác.
dùng938363

1
@GianlucaCasati: bạn chỉ có thể sử dụng super()trong các phương thức tĩnh; Có vẻ như bạn đã sử dụng nó trong constructor.
ZzZombo

5

Để làm điều này, bạn không bị giới hạn với sự Classtrừu tượng của ES6. Có thể truy cập các phương thức nguyên mẫu của hàm tạo của cha mẹ thông qua thuộc __proto__tính (tôi khá chắc chắn sẽ có các lập trình viên JS đồng ý phàn nàn rằng nó bị khấu hao) nhưng đồng thời phát hiện ra rằng nó thực sự là một công cụ thiết yếu cho các nhu cầu phân lớp ( đặc biệt là cho các nhu cầu phân lớp Array mặc dù). Vì vậy, trong khi __proto__tài sản vẫn có sẵn trong tất cả các công cụ JS chính mà tôi biết, ES6 đã giới thiệu Object.getPrototypeOf()chức năng trên nó. Các super()công cụ trong Classtrừu tượng là một đường tổng hợp của điều này.

Vì vậy, trong trường hợp bạn không có quyền truy cập vào tên của nhà xây dựng cha mẹ và không muốn sử dụng sự Classtrừu tượng hóa, bạn vẫn có thể làm như sau;

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

Trong trường hợp có nhiều mức kế thừa, hàm này có thể được sử dụng như một phương thức super () trong các ngôn ngữ khác. Đây là một fiddle demo , với một số thử nghiệm, bạn có thể sử dụng nó như thế này, bên trong phương pháp của bạn sử dụng:call_base(this, 'method_name', arguments);

Nó sử dụng các chức năng ES khá gần đây, khả năng tương thích với các trình duyệt cũ hơn không được đảm bảo. Đã thử nghiệm trong 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

Làm thế nào về một cái gì đó dựa trên ý tưởng Douglas Crockford:

    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

Đây là một cách hay để các đối tượng con có quyền truy cập vào các thuộc tính và phương thức cha mẹ bằng chuỗi nguyên mẫu của JavaScript và nó tương thích với Internet Explorer. JavaScript tìm kiếm chuỗi nguyên mẫu cho các phương thức và chúng tôi muốn chuỗi nguyên mẫu của trẻ giống như thế này:

Ví dụ con -> Nguyên mẫu của con (với phương thức con) -> Nguyên mẫu của cha mẹ (với phương thức cha mẹ) -> Nguyên mẫu đối tượng -> null

Các phương thức con cũng có thể gọi các phương thức cha bị che khuất, như thể hiện ở ba dấu sao *** bên dưới.

Đây là cách thực hiện:

//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

Có một giải pháp nhỏ gọn và dễ dàng hơn nhiều cho việc tra cứu nguyên mẫu đa cấp, nhưng nó cần có Proxysự hỗ trợ. Cách sử dụng: SUPER(<instance>).<method>(<args>)ví dụ: giả sử hai lớp AB extends Avới phương thức m: SUPER(new B).m().

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

0

Trong khi bạn có thể gọi phương thức cha mẹ bởi nguyên mẫu của phụ huynh, bạn sẽ cần phải vượt qua dụ trẻ em hiện nay cho việc sử dụng call, applyhoặc bindphương pháp. Các bindphương pháp sẽ tạo ra một chức năng mới vì vậy tôi không khuyên rằng nếu bạn chăm sóc cho hiệu suất ngoại trừ nó chỉ gọi một lần.

Để thay thế, bạn có thể thay thế phương thức con và đặt phương thức cha vào ví dụ trong khi gọi phương thức con ban đầu.

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'));

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.