web-dev-qa-db-fra.com

Bon exemple de l'héritage basé sur un prototype de JavaScript

Je programme avec les langues OOP depuis plus de 10 ans, mais j'apprends le JavaScript maintenant et c'est la première fois que je rencontre un héritage basé sur un prototype. J'ai tendance à apprendre plus rapidement en étudiant un bon code Qu'est-ce qu'un exemple bien écrit d'une application (ou d'une bibliothèque) JavaScript qui utilise correctement l'héritage prototypique? Et pouvez-vous décrire (brièvement) comment/où l'héritage prototypique est utilisé, afin que je sache par où commencer la lecture?

88
Alex Reisner

Douglas Crockford a une belle page sur Héritage prototypal JavaScript :

Il y a cinq ans, j'ai écrit Classical Inheritance en JavaScript. Il a montré que JavaScript est un langage prototype sans classes et qu'il possède un pouvoir expressif suffisant pour simuler un système classique. Mon style de programmation a évolué depuis, comme tout bon programmeur devrait le faire. J'ai appris à adopter pleinement le prototypalisme et à me libérer des limites du modèle classique.

Dean Edward's Base.js , Classe de Mootools ou Simple héritage de John Resig les travaux sont des façons de faire héritage classique en JavaScript .

48
Gregory Pakosz

Comme mentionné, les films de Douglas Crockford donnent une bonne explication sur le pourquoi et le comment. Mais pour le mettre dans quelques lignes de JavaScript:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

Le problème avec cette approche est qu’il va recréer l’objet chaque fois que vous en créez un. Une autre approche consiste à déclarer vos objets sur la pile de prototypes, comme suit:

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = ​function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

L'introspection présente un léger inconvénient. Dumping testOne donnera des informations moins utiles. De plus, la propriété privée "privateVariable" dans "testOne" est partagée dans tous les cas, également mentionnée utilement dans les réponses de shesek.

76
Dynom
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);
26
Vlad Bezden

Je voudrais jeter un oeil à YUI , et à la bibliothèque Base de Dean Edward: http://dean.edwards.name/weblog/2006/03/base /

Pour YUI, vous pouvez jeter un coup d’œil sur le module de lang , en particulier. la méthode YAHOO.lang.extend . Et ensuite, vous pouvez parcourir le code source de certains widgets ou utilitaires et voir comment ils utilisent cette méthode.

14
Roland Bouman

Il existe également la bibliothèque Ajax de ASP.NET , http://www.asp.net/ajax/ .

Il existe également de nombreux bons articles MSDN, notamment Création d'applications Web avancées à l'aide de techniques orientées objet.

5
Kevin Jones

ES6 class et extends

ES6 class et extends ne sont que du sucre syntaxique pour les manipulations de chaînes de prototypes possibles, et donc sans doute la configuration la plus canonique.

Commencez par en apprendre davantage sur la chaîne de prototypes et sur . propriété à consulter à: https://stackoverflow.com/a/23877420/895245

Déconstruis maintenant ce qui se passe:

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// https://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Diagramme simplifié sans tous les objets prédéfinis:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype

C’est l’exemple le plus clair que j’ai trouvé, tiré du Node book ( http://book.mixu.net/node/ch6.html) =) de Mixu:

Je privilégie la composition à la succession:

Composition - La fonctionnalité d'un objet est constituée d'un agrégat de différentes classes contenant des instances d'autres objets. Héritage - La fonctionnalité d'un objet est composée de sa propre fonctionnalité et de la fonctionnalité de ses classes parentes. Si vous devez avoir un héritage, utilisez tout simplement JS

Si vous devez implémenter l'héritage, évitez au moins d'utiliser une autre fonction d'implémentation/magique non standard. Voici comment implémenter un fac-similé raisonnable d'héritage dans ES3 pur (tant que vous suivez la règle de ne jamais définir de propriétés sur des prototypes):

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

Ce n’est pas la même chose que l’héritage classique - mais c’est un langage Javascript standard, compréhensible, qui possède la fonctionnalité que recherchent le plus souvent: des constructeurs chaînables et la possibilité d’appeler des méthodes de la superclasse.

3
supershnee

Je suggère de regarder Class.create de PrototypeJS:
Line 83 @ http://prototypejs.org/assets/2009/8/31/prototype.js

2
Tomasz Durka

Les meilleurs exemples que j'ai vus sont ceux de Douglas Crockford JavaScript: les bonnes parties . Cela vaut vraiment la peine d'acheter pour vous aider à avoir une vision équilibrée de la langue.

Douglas Crockford est responsable du format JSON et travaille chez Yahoo en tant que guru JavaScript.

1
Chris S

Ajout d'un exemple d'héritage basé sur un prototype en Javascript.

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6 utilise une implémentation beaucoup plus facile de l'héritage avec l'utilisation de constructor et de super-mots-clés.

0
Vinit Khandelwal

Il existe un extrait héritage basé sur un prototype JavaScript avec des implémentations spécifiques à la version ECMAScript. Il choisira automatiquement laquelle utiliser entre les implémentations ES6, ES5 et ES3 en fonction de l'exécution actuelle.

0
fuweichin