2167 votes

Comment fonctionne JavaScript .prototype ?

Je ne suis pas vraiment fan des langages de programmation dynamiques mais j'ai écrit ma part de code JavaScript. Je n'ai jamais vraiment compris ce concept de programmation basée sur le prototype, est-ce que quelqu'un sait comment cela fonctionne?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Je me souviens de nombreuses discussions que j'ai eues avec des gens il y a quelque temps (je ne suis pas tout à fait sûr de ce que je fais) mais à ma compréhension, il n'y a pas de concept de classe. C'est juste un objet, et les instances de ces objets sont des clones de l'original, non?

Mais quel est exactement le but de cette propriété ".prototype" en JavaScript? Comment est-elle liée à l'instanciation des objets?

Mise à jour : façon correcte

var obj = new Object(); // pas un objet fonctionnel
obj.prototype.test = function() { alert('Hello?'); }; // ce n'est pas correct!

function MyObject() {} // un objet fonctionnel de première classe
MyObject.prototype.test = function() { alert('OK'); } // OK

Aussi, ces diapositives ont vraiment beaucoup aidé.

83 votes

John Resig a quelques diapositives sur les prototypes de fonction qui m'ont été utiles lorsque j'ai étudié le sujet (vous pouvez également apporter des modifications au code et voir ce qui se passe...) http://ejohn.org/apps/learn/#64

5 votes

Super matériel de référence, dans le but de maintenir cette question informative, placez peut-être quelques commentaires du site de John sur votre réponse au cas où son site serait modifié de manière à ce que votre lien ne soit plus disponible. De toute façon +1, cela m'a aidé.

96 votes

+1 pour votre lien vers diapositive Ninja JavaScript de John Resig #64. À partir de là, cela a vraiment été utile, et j'ai l'impression de bien comprendre les prototypes.

1832voto

stivlo Points 28997

Dans un langage implémentant l'héritage classique comme Java, C# ou C++, vous commencez par créer une classe - un modèle pour vos objets - puis vous pouvez créer de nouveaux objets à partir de cette classe ou vous pouvez étendre la classe, en définissant une nouvelle classe qui augmente la classe d'origine.

En JavaScript, vous créez d'abord un objet (il n'y a pas de concept de classe), puis vous pouvez augmenter votre propre objet ou créer de nouveaux objets à partir de celui-ci. Ce n'est pas difficile, mais un peu étrange et difficile à assimiler pour quelqu'un habitué à la méthode classique.

Exemple:

//Définir un objet fonctionnel pour contenir des personnes en JavaScript
var Person = function(name) {
  this.name = name;
};

//Ajouter dynamiquement un nouveau getter à l'objet déjà défini
Person.prototype.getName = function() {
  return this.name;
};

//Créer un nouvel objet de type Person
var john = new Person("John");

//Essayer le getter
alert(john.getName());

//Si je modifie maintenant Person, également John obtiendra les mises à jour
Person.prototype.sayMyName = function() {
  alert('Bonjour, je m'appelle ' + this.getName());
};

//Appeler la nouvelle méthode sur john
john.sayMyName();

Jusqu'à présent, j'ai étendu l'objet de base, maintenant je crée un autre objet et ensuite j'hérite de Person.

//Créer un nouvel objet de type Client en définissant son constructeur. Ce n'est pas 
//lié à Person pour le moment.
var Customer = function(name) {
    this.name = name;
};

//Maintenant je lie les objets et pour cela, nous lions le prototype de Cliente à 
//une nouvelle instance de Person. Le prototype est la base qui sera utilisée pour 
//construire toutes les nouvelles instances et également, modifier dynamiquement 
//tous les objets déjà construits car en JavaScript les objets conservent un pointeur 
//vers le prototype
Customer.prototype = new Person();     

//Maintenant je peux appeler les méthodes de Person sur le Client, essayons, tout d'abord 
//je dois créer un Client.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//Si j'ajoute de nouvelles méthodes à Person, elles seront ajoutées à Client, mais si j'ajoute
//de nouvelles méthodes à Client, elles ne seront pas ajoutées à Person. Exemple:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Essayons:
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

var Person = function (name) {
    this.name = name;
};
Person.prototype.getName = function () {
    return this.name;
};
var john = new Person("John");
alert(john.getName());
Person.prototype.sayMyName = function () {
    alert('Bonjour, je m'appelle ' + this.getName());
};
john.sayMyName();
var Customer = function (name) {
    this.name = name;
};
Customer.prototype = new Person();

var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();
Customer.prototype.setAmountDue = function (amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function () {
    return this.amountDue;
};
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

Comme mentionné, je ne peux pas appeler setAmountDue(), getAmountDue() sur une Personne.

//La déclaration suivante génère une erreur.
john.setAmountDue(1000);

1 votes

Bien que j'apprécie la réponse détaillée, cette question comporte déjà plusieurs bonnes réponses et une réponse acceptée, donc je suis quelque peu surpris de voir ce genre de chose apparaître maintenant. J'ai cependant une question pour vous. Customer.prototype = new Person(); Est-ce que l'opérateur new est vraiment nécessaire ici ? ou est-ce que vous le laisseriez parfois intentionnellement de côté ? Je pense que cela fonctionnerait toujours sans l'opérateur new mais les modifications apportées au prototype du client affecteront désormais l'objet fonctionnel Personne.

357 votes

Je pense que les réponses sur stackoverflow ne sont pas seulement intéressantes pour l'auteur original, mais aussi pour une grande communauté d'autres personnes qui surveillent ou viennent de recherches. Et j'ai été l'un d'entre eux et j'avais bénéficié de vieux messages. Je pense que je pourrais contribuer aux autres réponses en ajoutant quelques exemples de code. À propos de votre question : si vous supprimez le nouveau, cela ne fonctionne pas. lorsque j'appelle myCustomer.sayMyName(), cela renvoie "myCustomer.sayMyName n'est pas une fonction". Le moyen le plus simple est d'expérimenter avec firebug et de voir ce qui se passe.

2 votes

Mais pourquoi ? La valeur est toujours un objet ? n'est-ce pas le point ici que les objets et les fonctions devraient être indiscernables ? en plus du fait que vous ne pouvez pas invoquer sur un objet.

1061voto

Christoph Points 64389

Chaque objet JavaScript possède un "emplacement" interne appelé [[Prototype]] dont la valeur est soit null, soit un object. Vous pouvez considérer un emplacement comme une propriété sur un objet, interne au moteur JavaScript, cachée du code que vous écrivez. Les crochets autour de [[Prototype]] sont délibérés et sont une convention de spécification ECMAScript pour indiquer des emplacements internes.

La valeur pointée par le [[Prototype]] d'un objet est communément appelée "le prototype de cet objet".

Si vous accédez à une propriété via la notation pointée (obj.nomPropriété) ou entre crochets (obj['nomPropriété']), et que l'objet n'a pas directement une telle propriété (c'est-à-dire une propriété propre, vérifiable via obj.hasOwnProperty('nomPropriété')), le moteur recherche alors une propriété portant ce nom sur l'objet référencé par le [[Prototype]] à la place. Si le [[Prototype]] n'a également pas une telle propriété, son [[Prototype]] est vérifié à son tour, et ainsi de suite. De cette manière, la chaîne de prototypes de l'objet original est parcourue jusqu'à ce qu'une correspondance soit trouvée, ou que son extrémité soit atteinte. Au sommet de la chaîne de prototypes se trouve la valeur null.

Les implémentations JavaScript modernes permettent l'accès en lecture et/ou en écriture au [[Prototype]] de différentes manières :

  1. L'opérateur new (configure la chaîne de prototypes sur l'objet par défaut renvoyé par une fonction constructeur),
  2. Le mot-clé extends (configure la chaîne de prototypes lors de l'utilisation de la syntaxe de classe),
  3. Object.create définira l'argument fourni comme étant le [[Prototype]] de l'objet résultant,
  4. Object.getPrototypeOf et Object.setPrototypeOf (obtenir/définir le [[Prototype]] après la création de l'objet), et
  5. La propriété d'accesseur normalisée (c'est-à-dire getter/setter) nommée __proto__ (similaire à 4.)

Object.getPrototypeOf et Object.setPrototypeOf sont préférés à __proto__, en partie parce que le comportement de o.__proto__ est inhabituel lorsqu'un objet a un prototype de null.

Le [[Prototype]] d'un objet est initialement défini lors de la création de l'objet.

Si vous créez un nouvel objet via new Func(), le [[Prototype]] de l'objet sera, par défaut, défini sur l'objet référencé par Func.prototype.

Remarquez donc que, toutes les classes, et toutes les fonctions qui peuvent être utilisées avec l'opérateur new, ont une propriété nommée .prototype en plus de leur propre emplacement interne [[Prototype]]. Cette double utilisation du mot "prototype" est la source de confusion sans fin chez les nouveaux venus dans le langage.

Utiliser new avec des fonctions constructeur nous permet de simuler l'héritage classique en JavaScript ; bien que le système d'héritage de JavaScript soit - comme nous l'avons vu - basé sur des prototypes et non sur des classes.

Avant l'introduction de la syntaxe de classe en JavaScript, les fonctions constructeur étaient le seul moyen de simuler des classes. Nous pouvons considérer les propriétés de l'objet référencé par la propriété .prototype de la fonction constructeur comme étant des membres partagés ; c'est-à-dire des membres qui sont les mêmes pour chaque instance. Dans les systèmes basés sur des classes, les méthodes sont implémentées de la même manière pour chaque instance, donc les méthodes sont ajoutées conceptuellement à la propriété .prototype ; cependant, les champs d’un objet sont spécifiques à l'instance et sont donc ajoutés à l'objet lui-même lors de la construction.

Sans la syntaxe de classe, les développeurs devaient configurer manuellement la chaîne de prototypes pour obtenir des fonctionnalités similaires à l'héritage classique. Cela a conduit à une pléthore de façons différentes d'y parvenir.

Voici une façon :

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

...et voici une autre façon :

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

La syntaxe de classe introduite en ES2015 simplifie les choses, en fournissant extends comme "unique moyen" pour configurer la chaîne de prototypes afin de simuler l'héritage classique en JavaScript.

Ainsi, similaire au code ci-dessus, si vous utilisez la syntaxe de classe pour créer un nouvel objet de la sorte :

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... le [[Prototype]] de l'objet résultant sera défini sur une instance de Parent, dont le [[Prototype]], à son tour, est Parent.prototype.

Enfin, si vous créez un nouvel objet via Object.create(foo), le [[Prototype]] de l'objet résultant sera défini sur foo.

1 votes

Alors, est-ce que je fais quelque chose de mal en définissant de nouvelles propriétés sur la propriété prototype dans mon petit extrait de code?

3 votes

Je pense que cela signifie avoir des objets de fonction en tant que citoyens de première classe.

8 votes

Je déteste les choses non standard, en particulier dans les langages de programmation, pourquoi y a-t-il même un proto quand ce n'est clairement pas nécessaire ?

198voto

Mehran Hatami Points 4493

Il s'agit d'un modèle d'objet très simple, basé sur un prototype, qui serait considéré comme un échantillon pendant l'explication, sans commentaire pour l'instant :

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

Il y a quelques points cruciaux que nous devons prendre en compte avant de passer au concept du prototype.

1- Comment les fonctions JavaScript fonctionnent réellement :

Pour faire le premier pas, nous devons comprendre comment les fonctions JavaScript fonctionnent réellement, comme une fonction de classe en utilisant this ou simplement comme une fonction normale avec ses arguments, ce qu'elle fait et ce qu'elle renvoie.

Disons que nous voulons créer un Person modèle objet. mais dans cette étape, je vais essayer de faire exactement la même chose sans utiliser prototype y new mot-clé .

Donc, dans cette étape functions , objects y this mot-clé, sont tout ce que nous avons.

La première question serait comment this pourrait être utile sans utiliser le mot-clé new mot-clé .

Donc pour répondre à cette question, disons que nous avons un objet vide, et deux fonctions comme :

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

et maintenant sans utiliser new mot-clé comment nous pourrions utiliser ces fonctions. JavaScript a donc 3 façons différentes de le faire :

a. La première façon est d'appeler la fonction comme une fonction normale :

Person("George");
getName();//would print the "George" in the console

dans ce cas, il s'agit de l'objet contexte actuel, qui est généralement l'objet global window dans le navigateur ou GLOBAL en Node.js . Cela signifie que nous aurions, window.name dans le navigateur ou GLOBAL.name dans Node.js, avec "George" comme valeur.

b. Nous pouvons joindre à un objet, comme ses propriétés

- Le moyen le plus simple pour ce faire est de modifier le fichier vide person objet, comme :

person.Person = Person;
person.getName = getName;

de cette façon nous pouvons les appeler comme :

person.Person("George");
person.getName();// -->"George"

et maintenant le person est comme un objet :

Object {Person: function, getName: function, name: "George"}

- L'autre façon d'attacher une propriété à un objet est d'utiliser le prototype de cet objet qui peut être trouvé dans n'importe quel objet JavaScript portant le nom de __proto__ et j'ai essayé de l'expliquer un peu dans la partie résumé. Nous pourrions donc obtenir le même résultat en faisant :

person.__proto__.Person = Person;
person.__proto__.getName = getName;

Mais de cette façon, ce que nous faisons réellement c'est modifier le Object.prototype car chaque fois que nous créons un objet JavaScript en utilisant des littéraux ( { ... } ), il est créé sur la base de Object.prototype ce qui signifie qu'il est attaché à l'objet nouvellement créé comme un attribut nommé __proto__ Par conséquent, si nous le modifions, comme nous l'avons fait dans notre extrait de code précédent, tous les objets JavaScript seront modifiés, ce qui n'est pas une bonne pratique. Alors quelle pourrait être la meilleure pratique maintenant :

person.__proto__ = {
    Person: Person,
    getName: getName
};

et maintenant d'autres objets sont en paix, mais ça ne semble toujours pas être une bonne pratique. Nous avons donc encore une solution, mais pour l'utiliser, nous devons revenir à cette ligne de code où person a été créé ( var person = {}; ) puis changez-le comme suit :

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

ce qu'il fait est de créer un nouveau JavaScript Object et attachez le propertiesObject au __proto__ attribut. Donc pour être sûr que vous pouvez le faire :

console.log(person.__proto__===propertiesObject); //true

Mais le point délicat ici est que vous avez accès à toutes les propriétés définies dans __proto__ au premier niveau de la person (lire la partie résumé pour plus de détails).


comme vous le voyez en utilisant l'un de ces deux moyens this indiquerait exactement le person objet.

c. JavaScript a une autre façon de fournir la fonction avec this qui utilise appelez o appliquer pour invoquer la fonction.

La méthode apply() appelle une fonction avec une valeur donnée et des arguments fournis sous la forme d'un tableau (ou d'un objet de type tableau). arguments fournis sous forme de tableau (ou d'un objet de type tableau).

y

La méthode call() appelle une fonction avec une valeur donnée et les arguments fournis individuellement.

de cette façon qui est ma préférée, nous pouvons facilement appeler nos fonctions comme :

Person.call(person, "George");

o

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

ces 3 méthodes sont les étapes initiales importantes pour comprendre la fonctionnalité du .prototype.


2- Comment le new le travail sur les mots-clés ?

c'est la deuxième étape pour comprendre le .prototype fonctionnalité.C'est ce que j'utilise pour simuler le processus :

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

dans cette partie, je vais essayer de suivre toutes les étapes de JavaScript, sans utiliser la fonction new mot-clé et prototype lorsque vous utilisez new mot-clé. donc quand on fait new Person("George") , Person sert de constructeur. Voici ce que fait JavaScript, un par un :

a. Tout d'abord, il crée un objet vide, en fait un hash vide :

var newObject = {};

b. la prochaine étape que JavaScript prend est de joindre tous les objets prototypes à l'objet nouvellement créé

nous avons my_person_prototype ici similaire à l'objet prototype.

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

Ce n'est pas la façon dont JavaScript attache les propriétés qui sont définies dans le prototype. La manière réelle est liée au concept de chaîne de prototypes.


a. & b. Au lieu de ces deux étapes, vous pouvez obtenir exactement le même résultat en faisant :

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

maintenant nous pouvons appeler le getName dans notre my_person_prototype :

newObject.getName();

c. puis il donne cet objet au constructeur,

nous pouvons le faire avec notre échantillon comme :

Person.call(newObject, "George");

o

Person.apply(newObject, ["George"]);

alors le constructeur peut faire ce qu'il veut, parce que este à l'intérieur de ce constructeur est l'objet qui vient d'être créé.

maintenant le résultat final avant de simuler les autres étapes : Objet {nom : "George"}


Résumé :

Fondamentalement, lorsque vous utilisez le nouveau sur une fonction, vous faites appel à celle-ci et cette fonction sert de constructeur, donc quand vous dites :

new FunctionName()

JavaScript crée en interne un objet, un hash vide, puis il donne cet objet au constructeur, qui peut alors faire ce qu'il veut, car este à l'intérieur de ce constructeur est l'objet qui vient d'être créé et ensuite il vous donne cet objet bien sûr si vous n'avez pas utilisé l'instruction de retour dans votre fonction ou si vous avez mis un return undefined; à la fin du corps de votre fonction.

Ainsi, lorsque JavaScript cherche une propriété sur un objet, la première chose qu'il fait est de la chercher sur cet objet. Et puis il y a une propriété secrète [[prototype]] que nous avons habituellement comme __proto__ et cette propriété est ce que JavaScript regarde ensuite. Et quand il regarde dans le __proto__ Dans la mesure où il s'agit à nouveau d'un autre objet JavaScript, il possède sa propre fonction __proto__ attribut, il monte et monte jusqu'à ce qu'il arrive au point où le prochain __proto__ est nulle. Le point est le seul objet en JavaScript dont la fonction __proto__ est nul est Object.prototype objet :

console.log(Object.prototype.__proto__===null);//true

et c'est ainsi que l'héritage fonctionne en JavaScript.

The prototype chain

En d'autres termes, lorsque vous avez une propriété prototype sur une fonction et que vous appelez un new sur cette fonction, après que JavaScript ait fini de rechercher les propriétés de l'objet nouvellement créé, il va regarder le fichier .prototype et il est également possible que cet objet ait son propre prototype interne. et ainsi de suite.

6 votes

A) S'il vous plaît ne pas expliquer les prototypes en copiant les propriétés b) Le réglage de l'interne [[prototype]] se fait avant que la fonction constructeur ne soit appliquée sur l'instance, veuillez changer cet ordre c) jQuery est totalement hors sujet dans cette question

1 votes

@Bergi: merci d'avoir signalé, je vous serais reconnaissant de me faire savoir si c'est bon maintenant.

7 votes

Pouvez-vous s'il vous plaît le rendre plus simple? Vous avez raison sur tous les points, mais les étudiants qui lisent cette explication peuvent être vraiment confus pour la première fois. Prenez un exemple plus simple et laissez le code s'expliquer lui-même ou ajoutez une tonne de commentaires pour clarifier ce que vous voulez dire.

83voto

Ramesh Points 6909

prototype permet de créer des classes. Si vous n'utilisez pas prototype, alors cela devient statique.

Voici un exemple court.

var obj = new Object();
obj.test = function() { alert('Bonjour ?'); };

Dans le cas ci-dessus, vous avez l'appel de la fonction statique test. Cette fonction ne peut être accédée que par obj.test où vous pouvez imaginer obj comme une classe.

alors que dans le code ci-dessous

function obj()
{
}

obj.prototype.test = function() { alert('Bonjour?'); };
var obj2 = new obj();
obj2.test();

Le obj est devenu une classe qui peut maintenant être instanciée. Plusieurs instances de obj peuvent exister et elles ont toutes la fonction test.

Ce qui précède est ma compréhension. Je le rends collaboratif afin que les gens puissent me corriger si je me trompe.

13 votes

-1: prototype est une propriété des fonctions constructeur, pas des instances, c'est-à-dire que votre code est incorrect! Peut-être vouliez-vous dire la propriété non standard __proto__ des objets, mais c'est une toute autre histoire...

0 votes

@Christoph - Merci de l'avoir signalé. J'ai mis à jour le code d'exemple.

3 votes

Il y a tellement plus à cela... De plus, JavaScript n'est pas un langage basé sur les classes - il gère l'héritage via des prototypes, vous devez couvrir les différences de manière plus détaillée !

69voto

rockXrock Points 853

Après avoir lu ce fil, je me sens confus avec la chaîne de prototype JavaScript, puis j'ai trouvé ces graphiques

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance *[[protytype]]* and <code>prototype</code> property of function objects

c'est un graphique clair pour montrer l'héritage JavaScript par la chaîne de prototype

et

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

celui-ci contient un exemple avec du code et plusieurs beaux diagrammes.

la chaîne de prototype finit par remonter à Object.prototype.

la chaîne de prototype peut techniquement être étendue autant que vous le souhaitez, chaque fois en définissant le prototype de la sous-classe égal à un objet de la classe parente.

J'espère que cela vous aide également à comprendre la chaîne de prototype JavaScript.

1 votes

Est-il possible d'avoir une héritage multiple en Javascript?

0 votes

Est-ce que Foo est un objet littéral ici ou un objet de fonction ? Si c'est un objet littéral, je crois que Foo.prototype ne pointera pas de nouveau vers Foo via le constructeur.

1 votes

@user3376708 JavaScript ne supporte que l'héritage simple (source)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X