Classe di estensione JavaScript

Ho una class base:

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

Che voglio estendere e creare un’altra class con:

 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 } 

Ho fatto un bel po ‘di ricerche e sembra che ci siano molte soluzioni contorte per fare ciò in JavaScript. Quale sarebbe il modo più semplice e affidabile per realizzare questo in JS?

Aggiornato di seguito per ES6

Marzo 2013 e ES5

Questo documento MDN descrive bene l’estensione delle classi:

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

In particolare, ecco che ora lo gestiscono:

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

Si noti che Object.create() non è supportato in alcuni browser meno recenti, incluso IE8:

Supporto del browser Object.create

Se si è nella posizione di doverli supportare, il documento MDN collegato suggerisce di utilizzare un polyfill o la seguente approssimazione:

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

L’utilizzo di questo come Student.prototype = createObject(Person.prototype) è preferibile all’utilizzo di new Person() in quanto evita di chiamare la funzione di costruzione del genitore quando eredita il prototipo e chiama il costruttore genitore solo quando viene chiamato il costruttore dell’erede.

Maggio 2017 e ES6

Per fortuna, i designer JavaScript hanno ascoltato le nostre richieste di aiuto e hanno adottato un modo più adeguato di affrontare questo problema.

MDN ha un altro grande esempio di ereditarietà di class ES6, ma mostrerò lo stesso identico insieme di classi come sopra riprodotto in 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 

Pulito e comprensibile, proprio come tutti noi vogliamo. Tieni presente che sebbene ES6 sia piuttosto comune, non è supportato ovunque :

Supporto per browser ES6

ES6 ti offre ora l’opportunità di utilizzare la class e estende le parole chiave:

Quindi, il tuo codice sarà:

Hai una class base:

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

Che vuoi estendere e creare un’altra class con:

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

Prova questo:

 Function.prototype.extends = function(parent) { this.prototype = Object.create(parent.prototype); }; Monkey.extends(Monster); function Monkey() { Monster.apply(this, arguments); // call super } 

Modifica: ho inserito una breve demo qui http://jsbin.com/anekew/1/edit . Nota che extends è una parola riservata in JS e potresti ricevere degli avvertimenti quando elimini il tuo codice, puoi semplicemente nominarlo inherits , questo è quello che faccio di solito.

Con questo helper in posizione e l’utilizzo di un object props come solo parametro, l’ereditarietà in JS diventa un po ‘più semplice:

 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" 

Se non ti piace l’approccio prototipo, perché in realtà non si comporta in modo piacevole con OOP, puoi provare questo:

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

Utilizzando la libreria leggera (2k minified): https://github.com/haroldiedema/joii

Questa è un’estensione (scusate il gioco di parole) della soluzione di elclanrs per includere dettagli sui metodi di istanza, oltre ad adottare un approccio estensibile a quell’aspetto della domanda; Riconosco pienamente che questo è stato realizzato grazie a “JavaScript: The Definitive Guide” di David Flanagan (parzialmente adattato per questo contesto). Si noti che questo è chiaramente più prolisso di altre soluzioni, ma probabilmente trarrebbe vantaggio a lungo termine.

Per prima cosa usiamo la semplice funzione “estendere” di David, che copia le proprietà su un object specificato:

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

Quindi implementiamo la sua utility di definizione Sottoclass:

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

Per l’ultimo pezzetto di preparazione miglioriamo il nostro prototipo Function con il nuovo jiggery-pokery di David:

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

Dopo aver definito la nostra class Monster, facciamo quanto segue (che è riutilizzabile per ogni nuova class che vogliamo estendere / ereditare):

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

Per l’estensione tradizionale è sufficiente scrivere la superclass come funzione di costruzione, quindi applicare questo costruttore per la class ereditata.

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

Esempio su angularjs:

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

Per gli autodidatti:

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

Crea “istanza” con getter e setter:

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

Posso proporre una variante, ho appena letto nel libro, sembra la più semplice:

 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 

Sommario:

Esistono diversi modi per risolvere il problema dell’estensione di una funzione di costruzione con un prototipo in Javascript. Quale di questi metodi è la soluzione “migliore” è basata sull’opinione pubblica. Tuttavia, qui ci sono due metodi usati frequentemente per estendere il prototipo di funzione di un costruttore.

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