web-dev-qa-db-fra.com

Classe d'extension JavaScript

J'ai une classe de base:

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

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

Que je veux étendre et créer une autre classe avec:

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
}

J'ai fait pas mal de recherches et il semble y avoir beaucoup de solutions compliquées pour le faire en JavaScript. Quel serait le moyen le plus simple et le plus fiable d’accomplir cela dans JS?

64
Lucas Penney

Mis à jour ci-dessous pour ES6

Mars 2013 et ES5

Ce document MDN décrit bien l’extension des classes:

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

En particulier, voici maintenant qu'ils s'en occupent:

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

Notez que Object.create() n'est pas pris en charge par certains navigateurs plus anciens, y compris IE8:

Object.create browser support

Si vous êtes en mesure de les prendre en charge, le document MDN lié suggère d'utiliser un remplissage multiple ou l'approximation suivante:

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

Il est préférable d'utiliser Student.prototype = createObject(Person.prototype) comme ceci plutôt que new Person() car elle évite d'appeler la fonction constructeur du parent } lors de l'héritage du prototype et n'appelle le constructeur parent que lorsque le constructeur de l'héritier est appelé.

Mai 2017 et ES6

Heureusement, les concepteurs JavaScript ont entendu notre appel à l'aide et ont adopté une approche plus appropriée de ce problème.

MDN a un autre bon exemple sur l'héritage de classe ES6, mais je montrerai exactement le même ensemble de classes que ci-dessus, reproduit dans 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

Propre et compréhensible, comme nous le souhaitons tous. Gardez à l'esprit que si ES6 est assez commun, il s'agit de non pris en charge partout :

ES6 browser support

130
Oliver Spryn

ES6 vous donne maintenant la possibilité d’utiliser class & includes keywords: 

Ensuite, votre code sera: 

Vous avez une classe de base:

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

}

Que vous voulez étendre et créer une autre classe avec:

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
        }

}
12
Abdennour TOUMI

Essaye ça:

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

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

Edit: Je mets une brève démonstration ici http://jsbin.com/anekew/1/edit . Notez que extends est un mot réservé dans JS et que vous pouvez recevoir des avertissements lorsque vous filtrez votre code, vous pouvez simplement le nommer inherits, c'est ce que je fais habituellement.

Avec cet assistant en place et en utilisant un objet props comme seul paramètre, l'héritage dans JS devient un peu plus simple:

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"
10
elclanrs

Si vous n'aimez pas l'approche du prototype, car elle ne se comporte pas vraiment de manière pratique, vous pouvez essayer ceci:

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

Utilisation de la bibliothèque légère (2k minifiée): https://github.com/haroldiedema/joii

6
Harold

Ceci est une extension (excusez le jeu de mots) de la solution elclanrs pour inclure des détails sur les méthodes d'instance, ainsi que pour adopter une approche extensible de cet aspect de la question; Je reconnais pleinement que cela a été mis en place grâce à "JavaScript: Le Guide définitif" de David Flanagan (partiellement adapté à ce contexte). Notez que ceci est clairement plus détaillé que d’autres solutions, mais qu’il en bénéficierait probablement à long terme.

Nous utilisons d’abord la simple fonction "extend" de David, qui copie les propriétés dans un objet spécifié:

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

Ensuite, nous implémentons son utilitaire de définition de sous-classe:

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

Pour la dernière préparation, nous améliorons notre prototype Function avec le nouveau jiggery-pokery de David:

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

Après avoir défini notre classe Monster, nous procédons comme suit (ce qui est réutilisable pour toutes les nouvelles classes que nous voulons étendre/hériter):

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

Je peux proposer une variante, il suffit de lire dans le livre, il semble le plus simple:

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
Yarik

Pour l'extension traditionnelle, vous pouvez simplement écrire superclass en tant que fonction constructeur, .__, puis appliquer ce constructeur à votre classe héritée.

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

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

Exemple sur 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');
    }
  }])
0
Dan Key

Pour les autodidactes:

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

Créez une "instance" avec getter et setter:

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

Résumé:

Le problème de l’extension d’une fonction constructeur avec un prototype en Javascript peut être résolu de multiples façons. Laquelle de ces méthodes est la "meilleure" solution est basée sur l'opinion. Cependant, voici deux méthodes fréquemment utilisées pour étendre le prototype de fonction d'un constructeur.

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

L’approche ci-dessus consistant à utiliser les classes ES 2015 n’est rien de plus que du sucre syntaxique par rapport au modèle d’héritage prototype en javascript. Ici, dans le premier journal où nous évaluons typeof Monster, nous pouvons observer qu’il s’agit d’une fonction. En effet, les classes ne sont que des fonctions de constructeur placées sous le capot. Néanmoins, vous pouvez aimer cette façon de mettre en œuvre un héritage prototype et l’apprendre définitivement. Il est utilisé dans les principaux cadres tels que ReactJS et Angular2+.

Fonction d'usine utilisant 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);

Cette méthode utilise la méthode Object.create() qui prend un objet qui sera le prototype du nouvel objet créé qu’elle renvoie. Par conséquent, nous créons d'abord l'objet prototype dans cette fonction, puis appelons Object.create() qui renvoie un objet vide avec la propriété __proto__ définie sur l'objet Monster. Après cela, nous pouvons initialiser toutes les propriétés de l'objet. Dans cet exemple, nous affectons le compte banan à l'objet nouvellement créé. 

0
Willem van der Veen

la version absolument minimale (et correcte, contrairement à la plupart des réponses ci-dessus) est la suivante:

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

C'est tout. Vous pouvez lire ici l'explication la plus longue

0
gdanov