Come ereditare da una classe in javascript?


99

In PHP / Java si può fare:

class Sub extends Base
{
}

E automaticamente tutti i metodi, le proprietà, i campi, ecc. Pubblici / protetti della classe Super diventano parte della classe Sub che può essere sovrascritta se necessario.

Qual è l'equivalente in Javascript?





Quel modo di Crockford funziona ancora? ZParenizor.inherits (Parenizor);
Loren Shqipognja

Vedi anche: JavaScript
Extending

Risposte:


80

Ho cambiato il modo in cui lo faccio ora, cerco di evitare di usare le funzioni di costruzione e le loro prototypeproprietà, ma la mia vecchia risposta del 2010 è ancora in fondo. Adesso preferisco Object.create(). Object.createè disponibile in tutti i browser moderni.

Dovrei notare che di Object.createsolito è molto più lento rispetto all'utilizzo newcon un costruttore di funzioni.

//The prototype is just an object when you use `Object.create()`
var Base = {};

//This is how you create an instance:
var baseInstance = Object.create(Base);

//If you want to inherit from "Base":
var subInstance = Object.create(Object.create(Base));

//Detect if subInstance is an instance of Base:
console.log(Base.isPrototypeOf(subInstance)); //True

jsfiddle

Uno dei grandi vantaggi dell'utilizzo di Object.create è la possibilità di passare un argomento defineProperties , che ti dà un controllo significativo su come le proprietà sulla classe possono essere accedute ed enumerate, e io uso anche funzioni per creare istanze, queste servono come costruttori in un certo senso, poiché puoi eseguire l'inizializzazione alla fine invece di restituire semplicemente l'istanza.

var Base = {};

function createBase() {
  return Object.create(Base, {
    doSomething: {
       value: function () {
         console.log("Doing something");
       },
    },
  });
}

var Sub = createBase();

function createSub() {
  return Object.create(Sub, {
    doSomethingElse: {
      value: function () {
        console.log("Doing something else");
      },
    },
  }); 
}

var subInstance = createSub();
subInstance.doSomething(); //Logs "Doing something"
subInstance.doSomethingElse(); //Logs "Doing something else"
console.log(Base.isPrototypeOf(subInstance)); //Logs "true"
console.log(Sub.isPrototypeOf(subInstance)); //Logs "true

jsfiddle

Questa è la mia risposta originale del 2010:

function Base ( ) {
  this.color = "blue";
}

function Sub ( ) {

}
Sub.prototype = new Base( );
Sub.prototype.showColor = function ( ) {
 console.log( this.color );
}

var instance = new Sub ( );
instance.showColor( ); //"blue"

5
Che ne dici del valore di sub.prototype.constructor? Penso che dovrebbe essere impostato anche su un valore secondario.
maximus

Oltre a utilizzare parole chiave riservate ("super") come nomi di
classe

@MOnsDaR L'ho ribattezzato Base
Bjorn

Se uso alert()per vedere quali instance.showColor()ritorni ricevo ancora undefined. jsbin.com/uqalin/1
MOnsDaR

1
@MOnsDaR è perché registra la console, non restituisce nulla per la visualizzazione dell'avviso. Vedi una dichiarazione di ritorno in showColor?
Bjorn

190

In JavaScript non hai classi ma puoi ottenere ereditarietà e riutilizzo del comportamento in molti modi:

Eredità pseudo-classica (tramite prototipazione):

function Super () {
  this.member1 = 'superMember1';
}
Super.prototype.member2 = 'superMember2';

function Sub() {
  this.member3 = 'subMember3';
  //...
}
Sub.prototype = new Super();

Dovrebbe essere utilizzato con l' newoperatore:

var subInstance = new Sub();

Applicazione della funzione o "concatenamento del costruttore":

function Super () {
  this.member1 = 'superMember1';
  this.member2 = 'superMember2';
}


function Sub() {
  Super.apply(this, arguments);
  this.member3 = 'subMember3';
}

Questo approccio dovrebbe essere utilizzato anche con l' newoperatore:

var subInstance = new Sub();

La differenza con il primo esempio è che quando abbiamo applyil Supercostruttore thisdell'oggetto all'interno Sub, aggiunge le proprietà assegnate a thison Super, direttamente sulla nuova istanza, ad esempio subInstancecontiene le proprietà member1e member2direttamente ( subInstance.hasOwnProperty('member1') == true;).

Nel primo esempio, quelle proprietà vengono raggiunte attraverso la catena del prototipo , esistono su un [[Prototype]]oggetto interno .

Eredità parassitaria o Power Constructors:

function createSuper() {
  var obj = {
    member1: 'superMember1',
    member2: 'superMember2'
  };

  return obj;
}

function createSub() {
  var obj = createSuper();
  obj.member3 = 'subMember3';
  return obj;
}

Questo approccio si basa fondamentalmente sull '"aumento degli oggetti", non è necessario utilizzare l' newoperatore e, come puoi vedere, la thisparola chiave non è coinvolta.

var subInstance = createSub();

ECMAScript 5a Ed. Object.createmetodo:

// Check if native implementation available
if (typeof Object.create !== 'function') {
  Object.create = function (o) {
    function F() {}  // empty constructor
    F.prototype = o; // set base object as prototype
    return new F();  // return empty object with right [[Prototype]]
  };
}

var superInstance = {
  member1: 'superMember1',
  member2: 'superMember2'
};

var subInstance = Object.create(superInstance);
subInstance.member3 = 'subMember3';

Il metodo sopra è una tecnica di ereditarietà prototipale proposta da Crockford .

Le istanze di oggetti ereditano da altre istanze di oggetti, questo è tutto.

Questa tecnica può essere migliore di semplice "oggetto conversione" perché le proprietà ereditate non vengono copiate tutte le nuove istanze di oggetto, poiché la base di oggetto viene impostato come [[Prototype]]del estesa oggetto, nell'esempio di cui sopra subInstancecontiene fisicamente solo la member3struttura.


3
non utilizzare istanze per l'ereditarietà - utilizzare ES5 Object.create()o una clone()funzione personalizzata (ad esempio mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) per ereditare direttamente dall'oggetto prototipo; vedere i commenti a stackoverflow.com/questions/1404559/… per una spiegazione
Christoph

Grazie @Christoph, stavo per menzionare il Object.createmetodo :)
CMS

1
Questa non è un'eredità corretta, dal momento che avrai i membri dell'istanza di Super sul prototipo di Sub. Quindi tutte le istanze del Sub condivideranno la stessa member1variabile, il che non è affatto desiderabile. Ovviamente possono riscriverlo, ma non ha senso. github.com/dotnetwise/Javascript-FastClass è una soluzione di zucchero migliore.
Adaptabi

Ciao @CMS, potresti spiegare per favore, perché devo creare un'istanza della classe genitore nel primo esempio per impostare l'ereditarietà per la sottoclasse? Sto parlando di questa linea: Sub.prototype = new Super();. E se entrambe le classi non verranno mai utilizzate durante l'esecuzione dello script? Sembra un problema di prestazioni. Perché devo creare una classe genitore se la classe figlia non viene effettivamente utilizzata? Puoi approfondire per favore? Ecco la semplice dimostrazione del problema: jsfiddle.net/slavafomin/ZeVL2 Grazie!
Slava Fomin II

In tutti gli esempi - tranne l'ultimo - c'è una "classe" per Super e una "classe" per Sub, quindi crei un'istanza del Sub. Puoi aggiungere un esempio comparabile per l'esempio Object.create?
Luca

49

Per coloro che raggiungono questa pagina nel 2019 o dopo

Con l'ultima versione dello standard ECMAScript (ES6) , puoi utilizzare la parola chiave class.

Notare che la definizione della classe non è regolare object; quindi non ci sono virgole tra i membri della classe. Per creare un'istanza di una classe, è necessario utilizzare la newparola chiave. Per ereditare da una classe base, usa extends:

class Vehicle {
   constructor(name) {
      this.name = name;
      this.kind = 'vehicle';
   }
   getName() {
      return this.name;
   }   
}

// Create an instance
var myVehicle = new Vehicle('rocky');
myVehicle.getName(); // => 'rocky'

Per ereditare da una classe base, usa extends:

class Car extends Vehicle {
   constructor(name) {
      super(name);
      this.kind = 'car'
   }
}

var myCar = new Car('bumpy');

myCar.getName(); // => 'bumpy'
myCar instanceof Car; // => true
myCar instanceof Vehicle; // => true

Dalla classe derivata, puoi usare super da qualsiasi costruttore o metodo per accedere alla sua classe base:

  • Per chiamare il costruttore padre, usa super().
  • Per chiamare un altro membro, utilizzare, ad esempio super.getName(),.

C'è di più nell'usare le classi. Se vuoi approfondire l'argomento, ti consiglio " Classes in ECMAScript 6 " del Dr. Axel Rauschmayer. *

fonte


1
Sotto il cofano, classed extendsè (ultra utile) di zucchero sintassi per la catena di prototipi: stackoverflow.com/a/23877420/895245
Ciro Santilli郝海东冠状病六四事件法轮功

solo per tua informazione 'instance.name' qui 'mycar.name' restituirà il nome della classe. Questo è un comportamento predefinito di ES6 e ESnext. Qui per mycar.name restituirà 'Vehicle'
Shiljo Paulson

7

Ebbene, in JavaScript non esiste "ereditarietà di classe", esiste solo "ereditarietà del prototipo". Quindi non crei una classe "camion" e poi la contrassegni come una sottoclasse di "automobile". Invece, crei un oggetto "Jack" e dici che utilizza "John" come prototipo. Se John sa quanto "4 + 4" è, allora lo sa anche Jack.

Ti suggerisco di leggere l'articolo di Douglas Crockford sull'ereditarietà prototipale qui: http://javascript.crockford.com/prototypal.html Mostra anche come puoi fare in modo che JavaScript abbia un'ereditarietà "simile" come in altri linguaggi OO e poi spiega che questo in realtà significa rompere javaScript in un modo in cui non è stato concepito per essere utilizzato.


Supponiamo che il prototipo di Jack sia John. Durante l'esecuzione ho aggiunto una proprietà / comportamento a John. Riceverò quella proprietà / comportamento da Jack?
Ram Bavireddi

Di sicuro lo farai. Ad esempio, questo è il modo in cui le persone tipicamente aggiungono il metodo "trim ()" a tutti gli oggetti stringa (non è integrato) Vedi un esempio qui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...
ingenui

6

Trovo che questa citazione sia la più illuminante:

In sostanza, una "classe" JavaScript è solo un oggetto Function che funge da costruttore più un oggetto prototipo allegato. ( Fonte: Guru Katz )

Mi piace usare i costruttori piuttosto che gli oggetti, quindi sono parziale al metodo "ereditarietà pseudo-classica" descritto qui da CMS . Ecco un esempio di ereditarietà multipla con una catena di prototipi :

// Lifeform "Class" (Constructor function, No prototype)
function Lifeform () {
    this.isLifeform = true;
}

// Animal "Class" (Constructor function + prototype for inheritance)
function Animal () {
    this.isAnimal = true;
}
Animal.prototype = new Lifeform();

// Mammal "Class" (Constructor function + prototype for inheritance)
function Mammal () {
    this.isMammal = true;
}
Mammal.prototype = new Animal();

// Cat "Class" (Constructor function + prototype for inheritance)
function Cat (species) {
    this.isCat = true;
    this.species = species
}
Cat.prototype = new Mammal();

// Make an instance object of the Cat "Class"
var tiger = new Cat("tiger");

console.log(tiger);
// The console outputs a Cat object with all the properties from all "classes"

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform);
// Outputs: true true true true

// You can see that all of these "is" properties are available in this object
// We can check to see which properties are really part of the instance object
console.log( "tiger hasOwnProperty: "
    ,tiger.hasOwnProperty("isLifeform") // false
    ,tiger.hasOwnProperty("isAnimal")   // false
    ,tiger.hasOwnProperty("isMammal")   // false
    ,tiger.hasOwnProperty("isCat")      // true
);

// New properties can be added to the prototypes of any
// of the "classes" above and they will be usable by the instance
Lifeform.prototype.A    = 1;
Animal.prototype.B      = 2;
Mammal.prototype.C      = 3;
Cat.prototype.D         = 4;

console.log(tiger.A, tiger.B, tiger.C, tiger.D);
// Console outputs: 1 2 3 4

// Look at the instance object again
console.log(tiger);
// You'll see it now has the "D" property
// The others are accessible but not visible (console issue?)
// In the Chrome console you should be able to drill down the __proto__ chain
// You can also look down the proto chain with Object.getPrototypeOf
// (Equivalent to tiger.__proto__)
console.log( Object.getPrototypeOf(tiger) );  // Mammal 
console.log( Object.getPrototypeOf(Object.getPrototypeOf(tiger)) ); // Animal
// Etc. to get to Lifeform

Ecco un'altra buona risorsa da MDN , e qui c'è un jsfiddle così puoi provarlo .


4

L'ereditarietà di Javascript è leggermente diversa da Java e PHP, perché in realtà non ha classi. Ha invece oggetti prototipo che forniscono metodi e variabili membro. È possibile concatenare questi prototipi per fornire l'ereditarietà degli oggetti. Il modello più comune che ho trovato durante la ricerca di questa domanda è descritto su Mozilla Developer Network . Ho aggiornato il loro esempio per includere una chiamata a un metodo di superclasse e per mostrare il log in un messaggio di avviso:

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  log += 'Shape moved.\n';
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// Override method
Rectangle.prototype.move = function(x, y) {
  Shape.prototype.move.call(this, x, y); // call superclass method
  log += 'Rectangle moved.\n';
}

var log = "";
var rect = new Rectangle();

log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
alert(log);

Personalmente, trovo che l'ereditarietà in Javascript sia scomoda, ma questa è la versione migliore che ho trovato.


3

non puoi (nel senso classico). Javascript è un linguaggio prototipico. Noterai che non dichiari mai una "classe" in Javascript; definisci semplicemente lo stato e i metodi di un oggetto. Per produrre l'eredità, prendi un oggetto e lo prototipi. Il prototipo viene ampliato con nuove funzionalità.


1

Puoi usare .inheritWithe .fastClass libreria . È più veloce delle librerie più popolari e talvolta anche più veloce della versione nativa.

Molto facile da usare:

function Super() {
   this.member1 = "superMember";//instance member
}.define({ //define methods on Super's prototype
   method1: function() { console.log('super'); } //prototype member
}.defineStatic({ //define static methods directly on Super function 
   staticMethod1: function() { console.log('static method on Super'); }
});

var Sub = Super.inheritWith(function(base, baseCtor) {
   return {
      constructor: function() {//the Sub constructor that will be returned to variable Sub
         this.member3 = 'subMember3'; //instance member on Sub
         baseCtor.apply(this, arguments);//call base construcor and passing all incoming arguments
      },
      method1: function() { 
         console.log('sub'); 
         base.method1.apply(this, arguments); //call the base class' method1 function
      }
}

Utilizzo

var s = new Sub();
s.method1(); //prints:
//sub 
//super

1
function Person(attr){
  this.name = (attr && attr.name)? attr.name : undefined;
  this.birthYear = (attr && attr.birthYear)? attr.birthYear : undefined;

  this.printName = function(){
    console.log(this.name);
  }
  this.printBirthYear = function(){
    console.log(this.birthYear);
  }
  this.print = function(){
    console.log(this.name + '(' +this.birthYear+ ')');
  }
}

function PersonExt(attr){
  Person.call(this, attr);

  this.print = function(){
    console.log(this.name+ '-' + this.birthYear);
  }
  this.newPrint = function(){
    console.log('New method');
  }
}
PersonExt.prototype = new Person();

// Init object and call methods
var p = new Person({name: 'Mr. A', birthYear: 2007});
// Parent method
p.print() // Mr. A(2007)
p.printName() // Mr. A

var pExt = new PersonExt({name: 'Mr. A', birthYear: 2007});
// Overwriten method
pExt.print() // Mr. A-2007
// Extended method
pExt.newPrint() // New method
// Parent method
pExt.printName() // Mr. A

1

Dopo aver letto molti post, ho trovato questa soluzione ( jsfiddle qui ). Il più delle volte non ho bisogno di qualcosa di più sofisticato

var Class = function(definition) {
    var base = definition.extend || null;
    var construct = definition.construct || definition.extend || function() {};

    var newClass = function() { 
        this._base_ = base;        
        construct.apply(this, arguments);
    }

    if (definition.name) 
        newClass._name_ = definition.name;

    if (definition.extend) {
        var f = function() {}       
        f.prototype = definition.extend.prototype;      
        newClass.prototype = new f();   
        newClass.prototype.constructor = newClass;
        newClass._extend_ = definition.extend;      
        newClass._base_ = definition.extend.prototype;         
    }

    if (definition.statics) 
        for (var n in definition.statics) newClass[n] = definition.statics[n];          

    if (definition.members) 
        for (var n in definition.members) newClass.prototype[n] = definition.members[n];    

    return newClass;
}


var Animal = Class({

    construct: function() {        
    },

    members: {

        speak: function() {
            console.log("nuf said");                        
        },

        isA: function() {        
            return "animal";           
        }        
    }
});


var Dog = Class({  extend: Animal,

    construct: function(name) {  
        this._base_();        
        this.name = name;
    },

    statics: {
        Home: "House",
        Food: "Meat",
        Speak: "Barks"
    },

    members: {
        name: "",

        speak: function() {
            console.log( "ouaf !");         
        },

        isA: function(advice) {
           return advice + " dog -> " + Dog._base_.isA.call(this);           
        }        
    }
});


var Yorkshire = Class({ extend: Dog,

    construct: function(name,gender) {
        this._base_(name);      
        this.gender = gender;
    },

    members: {
        speak: function() {
            console.log( "ouin !");           
        },

        isA: function(advice) {         
           return "yorkshire -> " + Yorkshire._base_.isA.call(this,advice);       
        }        
    }
});


var Bulldog = function() { return _class_ = Class({ extend: Dog,

    construct: function(name) {
        this._base_(name);      
    },

    members: {
        speak: function() {
            console.log( "OUAF !");           
        },

        isA: function(advice) {         
           return "bulldog -> " + _class_._base_.isA.call(this,advice);       
        }        
    }
})}();


var animal = new Animal("Maciste");
console.log(animal.isA());
animal.speak();

var dog = new Dog("Sultan");
console.log(dog.isA("good"));
dog.speak();

var yorkshire = new Yorkshire("Golgoth","Male");
console.log(yorkshire.isA("bad"));
yorkshire.speak();

var bulldog = new Bulldog("Mike");
console.log(bulldog.isA("nice"));
bulldog.speak();

1

Grazie alla risposta di CMS e dopo aver giocherellato per un po 'con prototype e Object.create e cosa no, sono stato in grado di trovare una soluzione chiara per la mia eredità usando apply come mostrato qui:

var myNamespace = myNamespace || (function() {
    return {

        BaseClass: function(){
            this.someBaseProperty = "someBaseProperty";
            this.someProperty = "BaseClass";
            this.someFunc = null;
        },

        DerivedClass:function(someFunc){
            myNamespace.BaseClass.apply(this, arguments);
            this.someFunc = someFunc;
            this.someProperty = "DerivedClass";
        },

        MoreDerivedClass:function(someFunc){
            myNamespace.DerivedClass.apply(this, arguments);
            this.someFunc = someFunc;
            this.someProperty = "MoreDerivedClass";
        }
    };
})();


1
function Base() {
    this.doSomething = function () {
    }
}

function Sub() {
    Base.call(this); // inherit Base's method(s) to this instance of Sub
}

var sub = new Sub();
sub.doSomething();

2
Per favore, non limitarti a inserire il codice, spiega cosa fa e come risponde alla domanda.
Patrick Hund

1

Classi ES6:

Javascript non ha classi. Le classi in javascript sono solo zucchero sintattico costruito sopra il modello di ereditarietà prototipale che javascript. È possibile utilizzare JS classper applicare l'ereditarietà prototipale, ma è importante rendersi conto che in realtà stai ancora utilizzando le funzioni di costruzione sotto il cofano.

Questi concetti si applicano anche quando estendi da una es6"classe" utilizzando la parola chiave extends. Questo crea solo un collegamento aggiuntivo nella catena del prototipo. Il__proto__

Esempio:

class Animal {
  makeSound () {
    console.log('animalSound');
  }
}

class Dog extends Animal {
   makeSound () {
    console.log('Woof');
  }
}


console.log(typeof Dog)  // classes in JS are just constructor functions under the hood

const dog = new Dog();

console.log(dog.__proto__ === Dog.prototype);   
// First link in the prototype chain is Dog.prototype

console.log(dog.__proto__.__proto__ === Animal.prototype);  
// Second link in the prototype chain is Animal.prototype
// The extends keyword places Animal in the prototype chain
// Now Dog 'inherits' the makeSound property from Animal

Object.create ()

Object.create()è anche un modo per creare ereditarietà in JS in javascript. Object.create()è una funzione che crea un nuovo oggetto, prende un oggetto esistente come argomento. Assegnerà l'oggetto che è stato ricevuto come argomento alla __proto__proprietà dell'oggetto appena creato. Ancora una volta è importante rendersi conto che siamo vincolati al paradigma di ereditarietà prototipale che JS incarna.

Esempio:

const Dog = {
  fluffy: true,
  bark: () => {
      console.log('woof im a relatively cute dog or something else??');
  }
};

const dog = Object.create(Dog);

dog.bark();


0

Non puoi ereditare da una classe in JavaScript, perché non ci sono classi in JavaScript.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.