Lớp mở rộng JavaScript


77

Tôi có một lớp cơ sở:

function Monster() {
  this.health = 100;
}

Monster.prototype.growl = function() {
  console.log("Grr!");
}

Điều đó tôi muốn mở rộng và tạo một lớp khác với:

function Monkey extends Monster() {
  this.bananaCount = 5;
}

Monkey.prototype.eatBanana {
  this.bananaCount--;
  this.health++; //Accessing variable from parent class monster
  this.growl();  //Accessing function from parent class monster
}

Tôi đã thực hiện khá nhiều nghiên cứu và dường như có nhiều giải pháp phức tạp để thực hiện điều này trong JavaScript. Cách đơn giản và đáng tin cậy nhất để thực hiện điều này trong JS là gì?


Câu trả lời:


148

Cập nhật bên dưới cho ES6

Tháng 3 năm 2013 và ES5

Tài liệu MDN này mô tả tốt các lớp mở rộng:

https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript

Đặc biệt, đây là bây giờ họ xử lý nó:

// define the Person Class
function Person() {}

Person.prototype.walk = function(){
  alert ('I am walking!');
};
Person.prototype.sayHello = function(){
  alert ('hello');
};

// define the Student class
function Student() {
  // Call the parent constructor
  Person.call(this);
}

// inherit Person
Student.prototype = Object.create(Person.prototype);

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;

// replace the sayHello method
Student.prototype.sayHello = function(){
  alert('hi, I am a student');
}

// add sayGoodBye method
Student.prototype.sayGoodBye = function(){
  alert('goodBye');
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

Lưu ý rằng điều đó Object.create()không được hỗ trợ trong một số trình duyệt cũ hơn, bao gồm IE8:

Hỗ trợ trình duyệt Object.create

Nếu bạn đang ở trong tình thế cần hỗ trợ những điều này, tài liệu MDN được liên kết đề xuất sử dụng polyfill hoặc ước lượng sau:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

Sử dụng như thế Student.prototype = createObject(Person.prototype)này thích hợp hơn khi sử dụng new Person()ở chỗ nó tránh gọi hàm tạo của cha khi kế thừa nguyên mẫu và chỉ gọi hàm tạo mẹ khi hàm tạo của trình kế thừa đang được gọi.

Tháng 5 năm 2017 và ES6

Rất may, các nhà thiết kế JavaScript đã lắng nghe lời cầu xin giúp đỡ của chúng tôi và đã áp dụng một cách phù hợp hơn để tiếp cận vấn đề này.

MDN có một ví dụ tuyệt vời khác về kế thừa lớp ES6, nhưng tôi sẽ hiển thị chính xác cùng một tập hợp các lớp như trên được tái tạo trong ES6:

class Person {
    sayHello() {
        alert('hello');
    }

    walk() {
        alert('I am walking!');
    }
}

class Student extends Person {
    sayGoodBye() {
        alert('goodBye');
    }

    sayHello() {
        alert('hi, I am a student');
    }
}

var student1 = new Student();
student1.sayHello();
student1.walk();
student1.sayGoodBye();

// check inheritance
alert(student1 instanceof Person); // true 
alert(student1 instanceof Student); // true

Sạch sẽ và dễ hiểu, giống như tất cả chúng ta muốn. Hãy nhớ rằng mặc dù ES6 khá phổ biến nhưng nó không được hỗ trợ ở mọi nơi :

Hỗ trợ trình duyệt ES6


3
Điều gì sẽ xảy ra nếu hàm tạo Person cần các tham số, ví dụ: Person (tên) ...?
Phung D. An

3
@PhamHuyAnh Chỉ cần làm điều gì đó giống nhưfunction Person(n) { this.name = n; }
Oliver Spryn 21/02/15

Student.prototype = new Person();Dòng này dẫn đến lỗi nếu tôi truy cập tham số trong siêu lớp.
Tối đa

1
Student.prototype = Object.create(Person.prototype);Phương pháp tạo cổ điển hoạt động ở đây
Tối đa

1
Tôi đang cố gắng tạo ra chức năng mở rộng tự nó thực hiện tất cả. Có thể bằng cách nào đó di chuyển "Person.call (this);" hoặc từ ví dụ bên dưới "Monster.apply (this, đối số);" đến chức năng như vậy? Tôi đang gặp vấn đề khi làm điều đó.
sebastian_t

19

Giờ đây, ES6 mang đến cho bạn cơ hội sử dụng từ khóa lớp & mở rộng :

Sau đó, mã của bạn sẽ là:

Bạn có một lớp cơ sở:

class Monster{
       constructor(){
             this.health = 100;
        }
       growl() {
           console.log("Grr!");
       }

}

Bạn muốn mở rộng và tạo một lớp khác với:

class Monkey extends Monster {
        constructor(){
            super(); //don't forget "super"
            this.bananaCount = 5;
        }


        eatBanana() {
           this.bananaCount--;
           this.health++; //Accessing variable from parent class monster
           this.growl(); //Accessing function from parent class monster
        }

}

2
Cái này sạch hơn rất nhiều, có thể xác nhận hoạt động trong chrome 51.0, Firefox 47.
Reahreic

1
Sử dụng try{}catch(e){}các khối để quản lý điều đó và yêu cầu người dùng cuối cập nhật trình duyệt của mình nếu được yêu cầu.
Abdennour TOUMI

10

Thử cái này:

Function.prototype.extends = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

Monkey.extends(Monster);
function Monkey() {
  Monster.apply(this, arguments); // call super
}

Chỉnh sửa: Tôi đặt một bản demo nhanh tại đây http://jsbin.com/anekew/1/edit . Lưu ý rằng đó extendslà một từ dành riêng trong JS và bạn có thể nhận được cảnh báo khi viết mã của mình, bạn có thể chỉ cần đặt tên cho nó inherits, đó là điều tôi thường làm.

Với trình trợ giúp này tại chỗ và chỉ sử dụng một đối tượng propslàm tham số, việc kế thừa trong JS trở nên đơn giản hơn một chút:

Function.prototype.inherits = function(parent) {
  this.prototype = Object.create(parent.prototype);
};

function Monster(props) {
  this.health = props.health || 100;
}

Monster.prototype = {
  growl: function() {
    return 'Grrrrr';
  }
};

Monkey.inherits(Monster);
function Monkey() {
  Monster.apply(this, arguments);
}

var monkey = new Monkey({ health: 200 });

console.log(monkey.health); //=> 200
console.log(monkey.growl()); //=> "Grrrr"

Sử dụng điều này, Monkeysẽ không kế thừa Monstercác thuộc tính của ( health). Bạn cũng sẽ nhận được "ReferenceError: Monkey is notined" nếu Monkeykhông được xác định trước khi gọiMonkey.extends(Monster)
Phil

@Phil, đó là một khai báo hàm mà nó được nâng lên, nó sẽ hoạt động. "Vấn đề" duy nhất mà bạn sẽ nhận được từ mã này là "mở rộng là một từ dành riêng", nhưng bạn có thể dễ dàng thay đổi nó thành bất kỳ số nhận dạng nào khác.
elclanrs

1
Cảm ơn cậu. Điều đó thật tuyệt. Có thể áp dụng nó trong Node.js để tạo ra một lớp cơ sở cho nhà xây dựng vv, vì vậy tôi không cần phải tạo ra các kết nối Mongo vv mỗi khi tôi tạo ra một lớp cấu trúc
Mattijs

6

Nếu bạn không thích cách tiếp cận nguyên mẫu, vì nó không thực sự hoạt động theo cách OOP tốt đẹp, bạn có thể thử cách này:

var BaseClass = function() 
{
    this.some_var = "foobar";

    /**
     * @return string
     */
    this.someMethod = function() {
        return this.some_var;
    }
};

var MyClass = new Class({ extends: BaseClass }, function()
{
    /**
     * @param string value
     */
    this.__construct = function(value)
    {
        this.some_var = value;
    }
})

Sử dụng thư viện nhẹ (2k được thu nhỏ): https://github.com/haroldiedema/joii


Cảm ơn bạn đã liên kết đến lib này !! Trông thật tuyệt vời: D Có vẻ như đây là thứ mà tôi tìm kiếm bấy lâu nay! <3
x3ro

1

Tôi có thể đề xuất một biến thể, chỉ cần đọc trong sách, nó có vẻ đơn giản nhất:

function Parent() { 
  this.name = 'default name';
};

function Child() {
  this.address = '11 street';
};

Child.prototype = new Parent();      // child class inherits from Parent
Child.prototype.constructor = Child; // constructor alignment

var a = new Child(); 

console.log(a.name);                // "default name" trying to reach property of inherited class

1

Đây là một phần mở rộng (miễn là chơi chữ) giải pháp của elclanrs để bao gồm chi tiết về các phương pháp thực thể, cũng như thực hiện một cách tiếp cận có thể mở rộng cho khía cạnh đó của câu hỏi; Tôi hoàn toàn thừa nhận rằng điều này được kết hợp lại với nhau nhờ "JavaScript: The Definitive Guide" của David Flanagan (được điều chỉnh một phần cho ngữ cảnh này). Lưu ý rằng điều này rõ ràng là dài dòng hơn các giải pháp khác, nhưng có thể sẽ có lợi về lâu dài.

Đầu tiên, chúng ta sử dụng hàm "mở rộng" đơn giản của David, sao chép các thuộc tính vào một đối tượng được chỉ định:

function extend(o,p) {
    for (var prop in p) {
        o[prop] = p[prop];
    }
    return o;
}

Sau đó, chúng tôi triển khai tiện ích định nghĩa Lớp con của anh ấy:

function defineSubclass(superclass,     // Constructor of our superclass
                          constructor,  // Constructor of our new subclass
                          methods,      // Instance methods
                          statics) {    // Class properties
        // Set up the prototype object of the subclass
    constructor.prototype = Object.create(superclass.prototype);
    constructor.prototype.constructor = constructor;
    if (methods) extend(constructor.prototype, methods);
    if (statics) extend(constructor, statics);
    return constructor;
}

Đối với bước chuẩn bị cuối cùng, chúng tôi nâng cao nguyên mẫu Chức năng của mình với jiggery-pokery mới của David:

Function.prototype.extend = function(constructor, methods, statics) {
    return defineSubclass(this, constructor, methods, statics);
};

Sau khi xác định lớp Quái vật của chúng ta, chúng ta làm như sau (có thể sử dụng lại cho bất kỳ Lớp mới nào mà chúng ta muốn mở rộng / kế thừa):

var Monkey = Monster.extend(
        // constructor
    function Monkey() {
        this.bananaCount = 5;
        Monster.apply(this, arguments);    // Superclass()
    },
        // methods added to prototype
    {
        eatBanana: function () {
            this.bananaCount--;
            this.health++;
            this.growl();
        }
    }
);

0

Đối với mở rộng truyền thống, bạn có thể chỉ cần viết lớp cha dưới dạng hàm khởi tạo, sau đó áp dụng hàm tạo này cho lớp kế thừa của bạn.

     function AbstractClass() {
      this.superclass_method = function(message) {
          // do something
        };
     }

     function Child() {
         AbstractClass.apply(this);
         // Now Child will have superclass_method()
     }

Ví dụ về anglejs:

http://plnkr.co/edit/eFixlsgF3nJ1LeWUJKsd?p=preview

app.service('noisyThing', 
  ['notify',function(notify){
    this._constructor = function() {
      this.scream = function(message) {
          message = message + " by " + this.get_mouth();
          notify(message); 
          console.log(message);
        };

      this.get_mouth = function(){
        return 'abstract mouth';
      }
    }
  }])
  .service('cat',
  ['noisyThing', function(noisyThing){
    noisyThing._constructor.apply(this)
    this.meow = function() {
      this.scream('meooooow');
    }
    this.get_mouth = function(){
      return 'fluffy mouth';
    }
  }])
  .service('bird',
  ['noisyThing', function(noisyThing){
    noisyThing._constructor.apply(this)
    this.twit = function() {
      this.scream('fuuuuuuck');
    }
  }])

0

Đối với Autodidacts:

function BaseClass(toBePrivate){
    var morePrivates;
    this.isNotPrivate = 'I know';
    // add your stuff
}
var o = BaseClass.prototype;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';


// MiddleClass extends BaseClass
function MiddleClass(toBePrivate){
    BaseClass.call(this);
    // add your stuff
    var morePrivates;
    this.isNotPrivate = 'I know';
}
var o = MiddleClass.prototype = Object.create(BaseClass.prototype);
MiddleClass.prototype.constructor = MiddleClass;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';



// TopClass extends MiddleClass
function TopClass(toBePrivate){
    MiddleClass.call(this);
    // add your stuff
    var morePrivates;
    this.isNotPrivate = 'I know';
}
var o = TopClass.prototype = Object.create(MiddleClass.prototype);
TopClass.prototype.constructor = TopClass;
// add your prototype stuff
o.stuff_is_never_private = 'whatever_except_getter_and_setter';


// to be continued...

Tạo "phiên bản" với getter và setter:

function doNotExtendMe(toBePrivate){
    var morePrivates;
    return {
        // add getters, setters and any stuff you want
    }
}

0

Tóm lược:

Có nhiều cách có thể giải quyết vấn đề mở rộng một hàm tạo với một nguyên mẫu trong Javascript. Phương pháp nào trong số những phương pháp này là giải pháp 'tốt nhất' dựa trên ý kiến. Tuy nhiên, đây là hai phương thức được sử dụng thường xuyên để mở rộng nguyên mẫu hàm của hàm tạo.

Các lớp ES 2015:

class Monster {
  constructor(health) {
    this.health = health
  }
  
  growl () {
  console.log("Grr!");
  }
  
}


class Monkey extends Monster {
  constructor (health) {
    super(health) // call super to execute the constructor function of Monster 
    this.bananaCount = 5;
  }
}

const monkey = new Monkey(50);

console.log(typeof Monster);
console.log(monkey);

Cách tiếp cận ở trên của việc sử dụng ES 2015các lớp không có gì khác hơn là đường cú pháp so với mẫu kế thừa nguyên mẫu trong javascript. Đây là nhật ký đầu tiên mà chúng tôi đánh giá, typeof Monsterchúng tôi có thể quan sát rằng đây là chức năng. Điều này là do các lớp chỉ là các hàm khởi tạo dưới mui xe. Tuy nhiên, bạn có thể thích cách triển khai kế thừa nguyên mẫu này và chắc chắn nên học nó. Nó được sử dụng trong các khuôn khổ chính như ReactJSAngular2+.

Chức năng nhà máy sử dụng Object.create():

function makeMonkey (bananaCount) {
  
  // here we define the prototype
  const Monster = {
  health: 100,
  growl: function() {
  console.log("Grr!");}
  }
  
  const monkey = Object.create(Monster);
  monkey.bananaCount = bananaCount;

  return monkey;
}


const chimp = makeMonkey(30);

chimp.growl();
console.log(chimp.bananaCount);

Phương thức này sử dụng Object.create()phương thức lấy một đối tượng sẽ là nguyên mẫu của đối tượng mới được tạo mà nó trả về. Do đó, đầu tiên chúng ta tạo đối tượng nguyên mẫu trong hàm này và sau đó gọi đối tượng này Object.create()trả về một đối tượng trống với thuộc __proto__tính được đặt thành đối tượng Monster. Sau đó, chúng ta có thể khởi tạo tất cả các thuộc tính của đối tượng, trong ví dụ này, chúng ta gán giá trị chuối cho đối tượng mới được tạo.


0

phiên bản hoàn toàn tối thiểu (và đúng, không giống như nhiều câu trả lời ở trên) là:

function Monkey(param){
  this.someProperty = param;
}
Monkey.prototype = Object.create(Monster.prototype);
Monkey.prototype.eatBanana = function(banana){ banana.eat() }

Đó là tất cả. Bạn có thể đọc ở đây giải thích dài hơn

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.