Phương thức gọi bằng nguyên mẫu JavaScript


129

Có thể gọi phương thức cơ sở từ phương thức nguyên mẫu trong JavaScript nếu nó bị ghi đè?

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
    }
};

Đó là phương pháp cơ bản? this.do = function () {} trong hàm tạo?
Ionuț G. Stan

Câu trả lời:


200

Tôi không hiểu chính xác những gì bạn đang cố gắng làm, nhưng thông thường việc thực hiện hành vi dành riêng cho đối tượng được thực hiện dọc theo các dòng này:

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
Tôi không hiểu điều này, tôi đến đây chính xác với cùng mục đích như markvpc. Ý tôi là, tôi muốn có một loại phương thức "riêng tư" được sử dụng từ các phương thức khác trong nguyên mẫu. Phản ứng của bạn buộc tôi phải tạo ra một nhà máy và tôi muốn biết liệu có cách nào để tránh điều đó không.
R01010010

10
Không sử dụng các từ "Class" trong JS vì nó tạo ra sự nhầm lẫn. Không có các lớp trong JavaScript
Polaris

1
Điều này chỉ hoạt động cho các đối tượng khởi tạo. Điều gì nếu bạn muốn ghi đè tất cả các trường hợp?
supertonsky

Làm việc cho tôi! Chính xác những gì tôi cần. Tôi có 4 constructor kế thừa từ cha mẹ. Hai trong số chúng cần ghi đè và gọi một phương thức trên hàm tạo cơ sở là nguyên mẫu của chúng. Điều này cho phép tôi chính xác điều đó.
nhị phân

3
Tôi không hiểu tại sao điều này có rất nhiều upvote và được đánh dấu là chính xác. Điều này chỉ ghi đè hành vi cho một phiên bản của lớp cơ sở, không phải tất cả các phiên bản của lớp con. Do đó, điều này không trả lời câu hỏi.
BlueRaja - Daniel Pflughoeft

24

Vâng, một cách để làm điều đó là lưu phương thức cơ bản và sau đó gọi nó từ phương thức overriden, như vậy

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

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

        //do something new

    }else{
        return this._do_base();
    }

};

11
Điều này sẽ tạo ra đệ quy vô hạn.
Johan Tidén

3
Tôi đã từng ở đó: đệ quy vô hạn.
jperelli

Mệnh đề khác đang gọi và chạy _do_base, là tham chiếu đến nguyên mẫu.do. Vì không có tham số (hoặc trạng thái) nào thay đổi, mã sẽ lại đi vào phần khác, gọi lại _do_base.
Johan Tidén

10
@ JohanTidén các _do_basecửa hàng tham chiếu đến bất kỳ chức năng nào đã được xác định trước đó. Nếu không có, thì nó sẽ như vậy undefined. JavaScript không phải là makebiểu thức không bao giờ được đánh giá một cách lười biếng như bạn đề xuất. Bây giờ, nếu nguyên mẫu được kế thừa từ cũng được sử dụng _do_base để lưu trữ những gì nó cho là triển khai kiểu cơ sở của nó do, thì bạn có vấn đề. Vì vậy, yeah, đóng cửa sẽ là một cách tốt hơn, an toàn kế thừa để lưu trữ tham chiếu đến việc thực hiện chức năng ban đầu nếu sử dụng phương thức này, mặc dù điều đó sẽ yêu cầu sử dụng Function.prototype.call(this).
b Liệu

16

Tôi sợ ví dụ của bạn không hoạt động theo cách bạn nghĩ. Phần này:

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

ghi đè định nghĩa của

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

Vì đối tượng mới được tạo đã có thuộc tính "do", nên nó không tra cứu chuỗi nguyên mẫu.

Hình thức kế thừa cổ điển trong Javascript là khó hiểu và khó nắm bắt. Tôi sẽ đề nghị sử dụng mô hình thừa kế đơn giản Douglas Crockfords thay thế. Như thế này:

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

Theo tôi, một cách rõ ràng hơn nhiều để xử lý các đối tượng, các nhà xây dựng và kế thừa trong javascript. Bạn có thể đọc thêm trong Crockfords Javascript: Những phần hay .


3
Thực sự rất tốt, nhưng bạn thực sự không thể gọi base_dolà một hàm, bởi vì bạn mất bất kỳ thisràng buộc nào trong dophương thức ban đầu theo cách đó. Vì vậy, việc thiết lập phương thức cơ sở phức tạp hơn một chút, đặc biệt nếu bạn muốn gọi nó bằng cách sử dụng đối tượng cơ sở thisthay vì đối tượng con. Tôi sẽ đề nghị một cái gì đó giống như base_do.apply(me, arguments).
Giulio Piancastelli

Chỉ tự hỏi, tại sao bạn không sử dụng varcho base_do? Đây có phải là mục đích và, nếu vậy, tại sao? Và, như @GiulioPiancastelli đã nói, điều này có phá vỡ thisràng buộc trong base_do()hoặc cuộc gọi đó sẽ kế thừa thistừ người gọi của nó không?
b Liệu

@binki Tôi đã cập nhật ví dụ. Các varnên có mặt ở đó. Sử dụng call(hoặc apply) cho phép chúng tôi liên kết thisđúng.
Magnar

10

Tôi biết bài đăng này có từ 4 năm trước, nhưng vì nền tảng C # của tôi, tôi đã tìm cách gọi lớp cơ sở mà không phải chỉ định tên lớp mà thay vào đó là một thuộc tính trên lớp con. Vì vậy, thay đổi duy nhất của tôi đối với câu trả lời của Christoph sẽ là

Từ đây:

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

Về điều này:

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

6
Đừng làm điều này, this.constructorcó thể không phải lúc nào cũng chỉ đến MyClass.
Bergi

Vâng, nó nên, trừ khi ai đó làm hỏng việc thiết lập di sản. 'cái này' phải luôn luôn đề cập đến đối tượng cấp cao nhất và thuộc tính 'constructor' của nó phải luôn luôn trỏ đến hàm tạo của chính nó.
Triynko

5

nếu bạn xác định một hàm như thế này (sử dụng OOP)

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

có hai cách để gọi hàm nguyên mẫu: 1) tạo một thể hiện và gọi hàm đối tượng:

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

và cách khác là ... 2) đang gọi hàm trực tiếp từ nguyên mẫu:

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

5

Giải pháp này sử dụng Object.getPrototypeOf

TestA là siêu mà có getName

TestBlà một đứa trẻ ghi đè getNamenhưng, cũng có phiên bản getBothNamesđó gọi là superphiên bản getNamecũng như childphiên bản

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

Một thay thế:

// 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);
}

Mặc dù nó có thể hoạt động, tôi sẽ không đề xuất giải pháp này. Có nhiều lý do tại sao nên sử dụng __proto__(làm thay đổi [[Nguyên mẫu]] `của một đối tượng). Vui lòng sử dụng Object.create(...)tuyến đường thay thế.
KarelG

2

Nếu tôi hiểu chính xác, bạn muốn chức năng Cơ sở luôn được thực hiện, trong khi một phần của nó nên được để lại cho việc triển khai.

Bạn có thể được trợ giúp bởi mẫu thiết kế ' phương thức mẫu '.

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

Nếu bạn biết siêu hạng của mình theo tên, bạn có thể làm một cái gì đó như thế này:

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

Cái này sẽ in

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

như mong đợi.


1

Một cách khác với ES5 là duyệt qua chuỗi nguyên mẫu một cách rõ ràng bằng cách sử dụng 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

Không, bạn sẽ cần cung cấp hàm do trong hàm tạo và hàm do trong các tên khác nhau của nguyên mẫu.


0

Ngoài ra, nếu bạn muốn ghi đè tất cả các phiên bản và không chỉ một trường hợp đặc biệt, thì đây có thể là một trường hợp.

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

kết quả:

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();
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.