Comment hériter d'une classe en javascript?

99

En PHP / Java, on peut faire:

class Sub extends Base
{
}

Et automatiquement toutes les méthodes, propriétés, champs, etc. publics / protégés de la classe Super deviennent une partie de la classe Sub qui peut être remplacée si nécessaire.

Quel est l'équivalent de cela en Javascript?

Cliquez sur Upvote
la source
1
Jetez un oeil ici: stackoverflow.com/questions/1908443/…
Darin Dimitrov
Est-ce que cette façon Crockford fonctionne toujours? ZParenizor.inherits (Parenizor);
Loren Shqipognja
Voir aussi: Classe d'extension JavaScript
user56reinstatemonica8

Réponses:

80

J'ai changé ma façon de faire maintenant, j'essaie d'éviter d'utiliser les fonctions du constructeur et leurs prototypepropriétés, mais mon ancienne réponse de 2010 est toujours en bas. Je préfère maintenant Object.create(). Object.createest disponible dans tous les navigateurs modernes.

Je dois noter que Object.createc'est généralement beaucoup plus lent que d'utiliser newavec un constructeur de fonction.

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

L'un des grands avantages de l'utilisation d'Object.create est de pouvoir passer un argument defineProperties , ce qui vous donne un contrôle significatif sur la façon dont les propriétés de la classe peuvent être accédées et énumérées, et j'utilise également des fonctions pour créer des instances, celles-ci servent de constructeurs d'une certaine manière, comme vous pouvez le faire à la fin au lieu de simplement renvoyer l'instance.

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

Voici ma réponse originale de 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"
Bjorn
la source
5
Qu'en est-il de la valeur sub.prototype.constructor? Je pense qu'il devrait également être réglé sur une valeur inférieure.
maximus
Outre que vous utilisez des mots clés réservés ('super') comme noms de classe, je n'ai pas pu faire exécuter votre exemple: jsbin.com/ixiyet/8/edit
MOnsDaR
@MOnsDaR Je l'ai renommé Base
Bjorn
Si j'utilise alert()pour voir quels instance.showColor()retours je reçois encore undefined. jsbin.com/uqalin/1
MOnsDaR
1
@MOnsDaR c'est parce que la console enregistre, il ne renvoie rien pour l'alerte à afficher. Voyez-vous une instruction de retour dans showColor?
Bjorn le
190

En JavaScript, vous n'avez pas de classes, mais vous pouvez obtenir l'héritage et la réutilisation du comportement de plusieurs manières:

Héritage pseudo-classique (par prototypage):

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

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

Doit être utilisé avec l' newopérateur:

var subInstance = new Sub();

Application de fonction ou "chaînage de constructeur":

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


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

Cette approche doit également être utilisée avec l' newopérateur:

var subInstance = new Sub();

La différence avec le premier exemple est que lorsque nous applyle Superconstructeur de l' thisobjet à l'intérieur Sub, il ajoute les propriétés assignées à thison Super, directement sur la nouvelle instance, par exemple subInstancecontient les propriétés member1et member2directement ( subInstance.hasOwnProperty('member1') == true;).

Dans le premier exemple, ces propriétés sont atteintes via la chaîne de prototypes , elles existent sur un [[Prototype]]objet interne .

Héritage parasite ou constructeurs de puissance:

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

  return obj;
}

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

Cette approche est basée essentiellement sur "l'augmentation d'objet", vous n'avez pas besoin d'utiliser l' newopérateur, et comme vous pouvez le voir, le thismot-clé n'est pas impliqué.

var subInstance = createSub();

ECMAScript 5e éd. Object.createméthode:

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

La méthode ci-dessus est une technique d'héritage prototypique proposée par Crockford .

Les instances d'objet héritent d'autres instances d'objet, c'est tout.

Cette technique peut être mieux que simple « augmentation de l' objet » , car les propriétés héritées ne sont pas copiées sur toutes les nouvelles instances d'objet, puisque la base de l' objet est défini comme [[Prototype]]de l' étendue objet, dans l'exemple ci - dessus subInstancecontient physiquement que la member3propriété.

CMS
la source
3
n'utilisez pas d'instances pour l'héritage - utilisez ES5 Object.create()ou une clone()fonction personnalisée (par exemple mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js ) pour hériter directement de l'objet prototype; voir les commentaires sur stackoverflow.com/questions/1404559/… pour une explication
Christoph
Merci @Christoph, j'étais sur le point de mentionner la Object.createméthode :)
CMS
1
Ce n'est pas un héritage approprié, car vous aurez des membres d'instance de Super sur le prototype de Sub. Par conséquent, toutes les instances du Sub partageront la même member1variable, ce qui n'est pas du tout souhaitable. Bien sûr, ils peuvent le réécrire, mais cela n'a tout simplement aucun sens. github.com/dotnetwise/Javascript-FastClass est une meilleure solution de sucre.
Adaptabi
Bonjour @CMS, pourriez-vous expliquer s'il vous plaît, pourquoi dois-je créer une instance de la classe parente dans le premier exemple pour configurer l'héritage pour la sous-classe? Je parle de cette ligne: Sub.prototype = new Super();. Et si les deux classes ne seront jamais utilisées pendant l'exécution du script? Cela ressemble à un problème de performances. Pourquoi dois-je créer une classe parent si la classe enfant n'est pas réellement utilisée? Pouvez-vous préciser s'il vous plaît? Voici la simple démonstration du problème: jsfiddle.net/slavafomin/ZeVL2 Merci!
Slava Fomin II
Dans tous les exemples - sauf le dernier - il y a une "classe" pour Super et une "classe" pour Sub, puis vous créez une instance du Sub. Pouvez-vous ajouter un exemple comparable pour l'exemple Object.create?
Luke
49

Pour ceux qui atteignent cette page en 2019 ou après

Avec la dernière version du standard ECMAScript (ES6) , vous pouvez utiliser le mot-clé class.

Notez que la définition de classe n'est pas une définition régulière object; il n'y a donc pas de virgule entre les membres de la classe. Pour créer une instance d'une classe, vous devez utiliser le newmot - clé. Pour hériter d'une classe de base, utilisez 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'

Pour hériter d'une classe de base, utilisez 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

À partir de la classe dérivée, vous pouvez utiliser super de n'importe quel constructeur ou méthode pour accéder à sa classe de base:

  • Pour appeler le constructeur parent, utilisez super().
  • Pour appeler un autre membre, utilisez, par exemple super.getName(),.

Il y a plus à utiliser des classes. Si vous souhaitez approfondir le sujet, je vous recommande « Classes in ECMAScript 6 » du Dr Axel Rauschmayer. *

la source

Merlin
la source
1
Sous le capot, classet extendsc'est du sucre de syntaxe (ultra utile) pour la chaîne prototype: stackoverflow.com/a/23877420/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
juste pour votre information 'instance.name' ici 'mycar.name' renverra le nom de la classe. Il s'agit d'un comportement par défaut d'ES6 et d'ESnext. Ici pour mycar.name retournera 'Vehicle'
Shiljo Paulson
7

Eh bien, en JavaScript, il n'y a pas "d'héritage de classe", il n'y a que "héritage de prototype". Donc, vous ne créez pas une classe «camion» pour ensuite la marquer comme une sous-classe de «automobile». Au lieu de cela, vous créez un objet "Jack" et dites qu'il utilise "John" comme prototype. Si John sait combien "4 + 4" est, alors Jack le sait aussi.

Je vous suggère de lire l'article de Douglas Crockford sur l'héritage prototypique ici: http://javascript.crockford.com/prototypal.html Il montre également comment vous pouvez faire en sorte que JavaScript ait un héritage "similaire" comme dans d'autres langages OO et explique ensuite que signifie en fait casser javaScript d'une manière qu'il n'était pas censé être utilisé.

naïfs
la source
Supposons que le prototype de Jack soit John. Pendant l'exécution, j'ai ajouté une propriété / un comportement à John. Vais-je obtenir cette propriété / ce comportement de Jack?
Ram Bavireddi
Vous le ferez certainement. Par exemple, c'est ainsi que les gens ajoutent généralement la méthode "trim ()" à tous les objets string (ce n'est pas intégré) Voir un exemple ici: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ …
naïfs
6

Je trouve cette citation la plus éclairante:

En substance, une "classe" JavaScript est simplement un objet Function qui sert de constructeur plus un objet prototype attaché. ( Source: Guru Katz )

J'aime utiliser des constructeurs plutôt que des objets, donc je suis partisan de la méthode "d'héritage pseudo-classique" décrite ici par CMS . Voici un exemple d' héritage multiple avec une chaîne prototype :

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

Voici une autre bonne ressource de MDN , et voici un jsfiddle pour que vous puissiez l'essayer .

Luke
la source
4

L'héritage Javascript est un peu différent de Java et PHP, car il n'a pas vraiment de classes. Au lieu de cela, il a des objets prototypes qui fournissent des méthodes et des variables membres. Vous pouvez chaîner ces prototypes pour fournir l'héritage d'objet. Le modèle le plus courant que j'ai trouvé lors de la recherche de cette question est décrit sur le Mozilla Developer Network . J'ai mis à jour leur exemple pour inclure un appel à une méthode de superclasse et pour afficher le journal dans un message d'alerte:

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

Personnellement, je trouve l'héritage en Javascript gênant, mais c'est la meilleure version que j'ai trouvée.

Don Kirkby
la source
3

vous ne pouvez pas (au sens classique du terme). Javascript est un langage prototypique. Vous remarquerez que vous ne déclarez jamais une "classe" en Javascript; vous définissez simplement l'état et les méthodes d'un objet. Pour produire l'héritage, vous prenez un objet et vous le prototypez. Le prototype est étendu avec de nouvelles fonctionnalités.

DC
la source
1

Vous pouvez utiliser .inheritWithet .fastClass bibliothèque . Il est plus rapide que la plupart des bibliothèques populaires et parfois même plus rapide que la version native.

Utilisation très simple:

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

Usage

var s = new Sub();
s.method1(); //prints:
//sub 
//super
Adaptabi
la source
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
nnattawat
la source
1

Après avoir lu de nombreux articles, j'ai trouvé cette solution ( jsfiddle ici ). La plupart du temps, je n'ai pas besoin de quelque chose de plus sophistiqué

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();
chauwel
la source
1

Grâce à la réponse de CMS et après avoir tripoté pendant un certain temps prototype et Object.create et que sais-je encore, j'ai pu trouver une solution soignée pour mon héritage en utilisant apply comme indiqué ici:

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";
        }
    };
})();
pasx
la source
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();
Kai Hartmann
la source
2
Veuillez ne pas simplement publier du code, expliquer ce qu'il fait et comment il répond à la question.
Patrick Hund
1

Classes ES6:

Javascript n'a pas de classes. Les classes en javascript ne sont que du sucre syntaxique qui s'ajoute au modèle d' héritage prototypique que javascript. Vous pouvez utiliser le JS classpour appliquer l'héritage prototypique, mais il est important de réaliser que vous utilisez encore des fonctions de constructeur sous le capot.

Ces concepts s'appliquent également lorsque vous étendez une es6«classe» à l'aide du mot clé extend. Cela crée simplement un lien supplémentaire dans la chaîne de prototypes. le__proto__

Exemple:

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()est également un moyen de créer un héritage dans JS en javascript. Object.create()est une fonction qui crée un nouvel objet, et prend un objet existant comme argument. Il attribuera l'objet qui a été reçu comme argument au__proto__ propriété de l'objet nouvellement créé. Encore une fois, il est important de réaliser que nous sommes liés au paradigme d'héritage prototypique que JS incarne.

Exemple:

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

const dog = Object.create(Dog);

dog.bark();

Willem van der Veen
la source
0

Vous ne pouvez pas hériter d'une classe en JavaScript, car il n'y a pas de classes en JavaScript.

Jörg W Mittag
la source