Các phương pháp ghi đè JavaScript


87

Giả sử bạn có mã dưới đây:

function A() {
    function modify() {
       x = 300;
       y = 400;
    }

    var c = new C();
}

function B() {
    function modify(){
       x = 3000;
       y = 4000;
    }

    var c = new C();
}

C = function () {
   var x = 10;
   var y = 20;

   function modify() {
      x = 30;
      y = 40;
   };

   modify();
   alert("The sum is: " + (x+y));
}

Bây giờ câu hỏi là, nếu có bất kỳ cách nào mà tôi có thể ghi đè phương thức modifytừ đó Cvới các phương thức có trong AB. Trong Java, bạn sẽ sử dụng super-keyword, nhưng làm thế nào bạn có thể đạt được thứ như thế này trong JavaScript?


6
modifykhông phải là một phương pháp nhưng một hàm lồng nhau - có sự khác biệt giữa hai ...
Šime Vidas

2
Trong Java, bạn sử dụng supertừ khóa để truy cập các trường và phương thức không riêng tư của một siêu lớp. Bạn không sử dụng nó để ghi đè chúng.
FK82,

Câu trả lời:


138

Chỉnh sửa: Đã sáu năm kể từ khi câu trả lời ban đầu được viết và rất nhiều điều đã thay đổi!

  • Nếu bạn đang sử dụng phiên bản JavaScript mới hơn, có thể được biên dịch bằng một công cụ như Babel , bạn có thể sử dụng các lớp thực .
  • Nếu bạn đang sử dụng các hàm tạo thành phần giống lớp được cung cấp bởi Angular hoặc React , bạn sẽ muốn xem tài liệu cho khung đó.
  • Nếu bạn đang sử dụng ES5 và tạo các lớp "giả" bằng tay bằng các nguyên mẫu, thì câu trả lời dưới đây vẫn đúng như trước đây.

Chúc may mắn!


Kế thừa JavaScript trông hơi khác so với Java. Đây là giao diện của hệ thống đối tượng JavaScript gốc:

// Create a class
function Vehicle(color){
  this.color = color;
}

// Add an instance method
Vehicle.prototype.go = function(){
  return "Underway in " + this.color;
}

// Add a second class
function Car(color){
  this.color = color;
}

// And declare it is a subclass of the first
Car.prototype = new Vehicle();

// Override the instance method
Car.prototype.go = function(){
  return Vehicle.prototype.go.call(this) + " car"
}

// Create some instances and see the overridden behavior.
var v = new Vehicle("blue");
v.go() // "Underway in blue"

var c = new Car("red");
c.go() // "Underway in red car"

Thật không may, điều này hơi xấu và nó không bao gồm một cách rất hay để "siêu": bạn phải chỉ định thủ công phương thức của lớp cha mà bạn muốn gọi. Do đó, có rất nhiều công cụ giúp tạo các lớp đẹp hơn. Hãy thử xem Prototype.js, Backbone.js hoặc một thư viện tương tự bao gồm cú pháp đẹp hơn để thực hiện OOP trong js.


2
Ngoài việc sử dụng một công cụ để "tạo các lớp đẹp hơn", bạn hoàn toàn không thể tạo các lớp. Giả lập OO cổ điển trong js luôn lộn xộn.
Raynos,

1
(nhận xét không mang tính xây dựng) vì là ngôn ngữ "cấp thấp" trong thế giới trình duyệt là rất xấu không có lý do. Vẫn đang học nó, cảm ơn!
dnuske

9
Tôi tin rằng thay vì Car.prototype = new Vehicle (); đây phải là Car.prototype = Object.create (Vehicle.prototype); Không?
Jordan

@Martin nói đúng, xem javascript-
inherit

Một trong những lý do tại sao Car.prototype = new Vehicle (); không chính xác vì không có gì để chuyển đến Xe () khi nó nhận được màu.
Tushar Arora

62

Vì đây là một thành công hàng đầu trên Google, tôi muốn đưa ra câu trả lời cập nhật.

Sử dụng các lớp ES6 giúp cho việc kế thừa và ghi đè phương thức trở nên dễ dàng hơn rất nhiều:

'use strict';

class A {
    speak() {
        console.log("I'm A");
    }
}

class B extends A {
    speak() {
        super.speak();

        console.log("I'm B");
    }
}

var a = new A();
a.speak();
// Output:
// I'm A

var b = new B();
b.speak();
// Output:
// I'm A
// I'm B

Các supertừ khóa liên quan đến các tầng lớp phụ huynh khi được sử dụng trong lớp kế thừa. Ngoài ra, tất cả các phương thức trên lớp cha được liên kết với thể hiện của lớp con, vì vậy bạn không cần phải viết super.method.apply(this);.

Về khả năng tương thích: bảng tương thích của ES6 chỉ hiển thị các phiên bản mới nhất của các lớp hỗ trợ người chơi chính (hầu hết). Các trình duyệt V8 đã có chúng từ tháng 1 năm nay (Chrome và Opera) và Firefox, sử dụng công cụ SpiderMonkey JS, sẽ có các lớp vào tháng tới với bản phát hành Firefox 45 chính thức của họ. Về mặt di động, Android vẫn chưa hỗ trợ tính năng này, trong khi iOS 9, phát hành cách đây 5 tháng, đã hỗ trợ một phần.

May mắn thay, có Babel , một thư viện JS để biên dịch lại mã Harmony thành mã ES5. Các lớp và rất nhiều tính năng thú vị khác trong ES6 có thể giúp mã Javascript của bạn dễ đọc và dễ bảo trì hơn rất nhiều.


10
Câu trả lời này đáng được tín nhiệm hơn và hiện là câu trả lời đúng.
Klompenrunner

6

Một lần nên tránh mô phỏng OO cổ điển và thay vào đó sử dụng OO nguyên mẫu. Một thư viện tiện ích tốt đẹp cho OO nguyên mẫu là các đặc điểm .

Thay vì ghi đè các phương thức và thiết lập chuỗi kế thừa (nên luôn ưu tiên thành phần đối tượng hơn là kế thừa đối tượng), bạn nên gói các chức năng có thể sử dụng lại thành các đặc điểm và tạo các đối tượng với chúng.

Ví dụ trực tiếp

var modifyA = {
    modify: function() {
        this.x = 300;
        this.y = 400;
    }
};

var modifyB = {
    modify: function() {
        this.x = 3000;
        this.y = 4000;
    }
};

C = function(trait) {
    var o = Object.create(Object.prototype, Trait(trait));

    o.modify();
    console.log("sum : " + (o.x + o.y));

    return o;
}

//C(modifyA);
C(modifyB);

10
Bạn không trả lời câu hỏi. Đây sẽ là một bình luận nếu bất cứ điều gì.
FK82,


3

Sửa đổi () trong ví dụ của bạn là một hàm riêng tư, sẽ không thể truy cập được từ bất kỳ đâu ngoại trừ trong định nghĩa A, B hoặc C của bạn. Bạn sẽ cần phải khai báo nó là

this.modify = function(){}

C không có tham chiếu đến cha mẹ của nó, trừ khi bạn chuyển nó cho C. Nếu C được thiết lập để kế thừa từ A hoặc B, nó sẽ kế thừa các phương thức public của nó (không phải các hàm private như bạn đã sửa đổi () xác định). Khi C kế thừa các phương thức từ cha của nó, bạn có thể ghi đè các phương thức được kế thừa.


sửa đổi là một chức năng cục bộ. Không có tin trong javascript
Raynos

local / private, đó không phải là điều giống nhau, chỉ là một thuật ngữ khác nhau?
Alex Heyd,

2

phương thức modify()mà bạn đã gọi cuối cùng được gọi trong ngữ cảnh toàn cục nếu bạn muốn ghi đè modify()trước tiên bạn phải kế thừa Ahoặc B.

Có thể bạn đang cố gắng làm điều này:

Trong trường hợp này Ckế thừaA

function A() {
    this.modify = function() {
        alert("in A");
    }
}

function B() {
    this.modify = function() {
        alert("in B");
    }
}

C = function() {
    this.modify = function() {
        alert("in C");
    };

    C.prototype.modify(); // you can call this method where you need to call modify of the parent class
}

C.prototype = new A();

1
C.prototype.modify()sẽ có thisgiá trị sai . Cụ thể là C.prototypethay vì ví dụ của c. Vui lòng sử dụng .call(this)nhưng sau đó câu trả lời của bạn chỉ là một bản sao :)
Raynos

1

Không trừ khi bạn đặt tất cả các biến là "công khai", tức là biến chúng thành thành viên Functiontrực tiếp hoặc thông qua thuộc prototypetính.

var C = function( ) {
    this.x = 10 , this.y = 20 ;
    this.modify = function( ) {
        this.x = 30 , this.y = 40 ;
        console.log("(!) C >> " + (this.x + this.y) ) ;
    } ;
} ;

var A = function( ) {
    this.modify = function( ) {
       this.x = 300 , this.y = 400 ;
       console.log("(!) A >> " + (this.x + this.y) ) ;
    } ;
} ;
    A.prototype = new C ;

var B = function( ) {
    this.modify = function( ) {
       this.x = 3000 , this.y = 4000 ;
       console.log("(!) B >> " + (this.x + this.y) ) ;
    } ;
} ;


new C( ).modify( ) ;
new A( ).modify( ) ;
new B( ).modify( ) ; 

Bạn sẽ nhận thấy một vài thay đổi.

Quan trọng nhất là lời gọi đến hàm tạo "siêu lớp" được cho là ẩn trong dòng này:

<name>.prototype = new C ;

Cả hai ABbây giờ sẽ có các thành viên có thể sửa đổi riêng lẻxysẽ không đúng như vậy nếu chúng tôi viết thư ... = Cthay thế.

Sau đó, x, ymodifyđược tất cả các thành viên "công cộng" để gán một khác nhau Functionđối với họ

 <name>.prototype.modify = function( ) { /* ... */ }

sẽ "ghi đè" bản gốc Functionbằng tên đó.

Cuối cùng, lệnh gọi đến modifykhông thể được thực hiện trong Functionkhai báo vì lệnh gọi ngầm định tới "siêu lớp" sau đó sẽ được thực thi lại khi chúng ta đặt "siêu lớp" được cho là thuộc prototypetính của "lớp con" được cho là.

Nhưng tốt, đây ít nhiều là cách bạn thực hiện loại điều này trong JavaScript.

HTH,

FK


Không có điểm nào trong <name>.prototype = new C;shadowing của bạn tất cả các thành viên của nguyên mẫu nào
Raynos

@Raynos: Vâng, có. Nói một cách dí dỏm, tất cả phần kế thừa Objectssẽ được chia sẻ cùng một thành viên Cnếu bạn không khởi tạo một CĐối tượng. Vì vậy, thay đổi xtrong Asẽ thay đổi xtrong Cvà do đó thay đổi xtrong B. Mà rõ ràng là không thể đạt được.
FK82,

bạn đã bỏ lỡ điểm. Bạn có thể loại bỏ các dòng và mã vẫn sẽ hoạt động
Raynos

@Raynos: Tôi e rằng bạn đang thiếu điểm của chính mình. ;-) Chúng tôi muốn ABkế thừa từ C. Nếu dòng đó bị thiếu, đó sẽ không phải là trường hợp. Trên thực tế, nguyên mẫu duy nhất cả hai Acũng như B"bóng tối" sẽ có quyền truy cập trong trường hợp đó Object.prototype.
FK82,

nhìn vào mã. A và B không sử dụng bất kỳ thành viên nào của C trong nguyên mẫu. Vì vậy "kế thừa" từ C là vô ích. Điều này là do A và B Định nghĩa lại x, ymodifyvà do đó bóng tất cả các Cthành viên 's. Đặt C vào nguyên mẫu có ích gì nếu bạn không sử dụng nó? Đó là mã chết.
Raynos

0

function A() {
    var c = new C();
	c.modify = function(){
		c.x = 123;
		c.y = 333;
	}
	c.sum();
}

function B() {
    var c = new C();
	c.modify = function(){
		c.x = 999;
		c.y = 333;
	}
	c.sum();
}


C = function () {
   this.x = 10;
   this.y = 20;

   this.modify = function() {
      this.x = 30;
      this.y = 40;
   };
   
   this.sum = function(){
	this.modify();
	console.log("The sum is: " + (this.x+this.y));
   }
}

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