J'essaie de comprendre les coulisses de Javascript et je suis un peu coincé dans la compréhension de la création d'objets intégrés, spécialement Object and Function et la relation entre eux.
C'est compliqué, il est facile de se méprendre, et un grand nombre de livres Javascript pour débutants se trompent, alors ne faites pas confiance à tout ce que vous lisez.
J'ai été l'un des implémenteurs du moteur JS de Microsoft dans les années 1990 et membre du comité de normalisation, et j'ai fait un certain nombre d'erreurs en préparant cette réponse. (Même si je n'ai pas travaillé là-dessus depuis plus de 15 ans, je peux peut-être être pardonné.) C'est un truc délicat. Mais une fois que vous comprenez l'héritage des prototypes, tout devient logique.
Quand j'ai lu que tous les objets intégrés comme Array, String, etc. sont une extension (héritée) d'Object, j'ai supposé que Object était le premier objet intégré créé et le reste des objets en héritait.
Commencez par jeter tout ce que vous savez sur l'héritage basé sur les classes. JS utilise l'héritage basé sur un prototype.
Ensuite, assurez-vous d'avoir une définition très claire dans votre tête de ce que signifie «héritage». Les gens habitués aux langages OO comme C # ou Java ou C ++ pensent que l'héritage signifie le sous-typage, mais l'héritage ne signifie pas le sous-typage. L'héritage signifie que les membres d'une chose sont également membres d'une autre chose . Cela ne signifie pas nécessairement qu'il existe une relation de sous-typage entre ces choses! Tant de malentendus dans la théorie des types sont le résultat de personnes ne réalisant pas qu'il y a une différence.
Mais cela n'a pas de sens lorsque vous apprenez que les objets ne peuvent être créés que par des fonctions, mais alors les fonctions ne sont rien d'autre que des objets de fonction.
C'est tout simplement faux. Certains objets ne sont pas créés en appelant new F
une fonction F
. Certains objets sont créés par le runtime JS à partir de rien du tout. Il y a des œufs qui n'ont été pondus par aucun poulet . Ils ont juste été créés par le runtime au démarrage.
Disons quelles sont les règles et peut-être que cela aidera.
- Chaque instance d'objet a un objet prototype.
- Dans certains cas, ce prototype peut être
null
.
- Si vous accédez à un membre sur une instance d'objet et que l'objet n'a pas ce membre, alors l'objet passe à son prototype ou s'arrête si le prototype est nul.
- Le
prototype
membre d'un objet n'est généralement pas le prototype de l'objet.
- Au contraire, le
prototype
membre d'un objet fonction F est l'objet qui deviendra le prototype de l'objet créé par new F()
.
- Dans certaines implémentations, les instances obtiennent un
__proto__
membre qui donne vraiment leur prototype. (Ceci est désormais obsolète. Ne comptez pas dessus.)
- Les objets fonction reçoivent un tout nouvel objet par défaut affecté
prototype
lors de leur création.
- Le prototype d'un objet fonction est, bien sûr
Function.prototype
.
Résumons.
- Le prototype de
Object
estFunction.prototype
Object.prototype
est l'objet prototype d'objet.
- Le prototype de
Object.prototype
estnull
- Le prototype de
Function
est Function.prototype
- c'est l'une des rares situations où Function.prototype
est en fait le prototype de Function
!
Function.prototype
est l'objet prototype de la fonction.
- Le prototype de
Function.prototype
estObject.prototype
Supposons que nous faisons une fonction Foo.
- Le prototype de
Foo
is Function.prototype
.
Foo.prototype
est l'objet prototype Foo.
- Le prototype de
Foo.prototype
is Object.prototype
.
Supposons que nous disions new Foo()
- Le prototype du nouvel objet est
Foo.prototype
Assurez-vous que cela a du sens. Dessinons-le. Les ovales sont des instances d'objets. Les arêtes __proto__
signifient soit "le prototype de", soit prototype
"la prototype
propriété de".
Le runtime n'a qu'à créer tous ces objets et à affecter leurs différentes propriétés en conséquence. Je suis sûr que vous pouvez voir comment cela se ferait.
Voyons maintenant un exemple qui teste vos connaissances.
function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);
Qu'est-ce que cette impression?
Eh bien, qu'est-ce que cela instanceof
signifie? honda instanceof Car
signifie "est Car.prototype
égal à n'importe quel objet sur honda
la chaîne prototype de?"
Oui, ça l'est. honda
le prototype est Car.prototype
, donc nous avons terminé. Cela s'imprime vrai.
Et le deuxième?
honda.constructor
n'existe pas donc nous consultons le prototype, qui l'est Car.prototype
. Lorsque l' Car.prototype
objet a été créé, il lui a été automatiquement attribué une propriété constructor
égale à Car
, c'est donc vrai.
Et maintenant?
var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);
Qu'est-ce que ce programme imprime?
Encore une fois, lizard instanceof Reptile
signifie "est Reptile.prototype
égal à n'importe quel objet sur lizard
la chaîne prototype de?"
Oui, ça l'est. lizard
le prototype est Reptile.prototype
, donc nous avons terminé. Cela s'imprime vrai.
Et maintenant
print(lizard.constructor == Reptile);
Vous pourriez penser que cela s'imprime également, car a lizard
été construit avec new Reptile
mais vous vous trompez. Raisonnez-le.
- A
lizard
une constructor
propriété? Non. Par conséquent, nous examinons le prototype.
- Le prototype de
lizard
is Reptile.prototype
, qui est Animal
.
- A
Animal
une constructor
propriété? Non. Donc on regarde son prototype.
- Le prototype de
Animal
est Object.prototype
, et Object.prototype.constructor
est créé par le runtime et est égal à Object
.
- Donc, cela est faux.
Nous aurions dû dire Reptile.prototype.constructor = Reptile;
à un moment donné, mais nous ne nous en souvenions pas!
Assurez-vous que tout a du sens pour vous. Dessinez des cases et des flèches si cela prête à confusion.
L'autre chose extrêmement déroutante est que si console.log(Function.prototype)
j'imprime une fonction, mais lorsque j'imprime, console.log(Object.prototype)
elle imprime un objet. Pourquoi Function.prototype
une fonction alors qu'elle était censée être un objet?
Le prototype de fonction est défini comme une fonction qui, lorsqu'elle est appelée, retourne undefined
. Nous savons déjà que Function.prototype
c'est le Function
prototype, curieusement. C'est donc Function.prototype()
légal et quand vous le faites, vous undefined
revenez. C'est donc une fonction.
Le Object
prototype n'a pas cette propriété; ce n'est pas appelable. Ce n'est qu'un objet.
quand vous console.log(Function.prototype.constructor)
c'est encore une fonction.
Function.prototype.constructor
est juste Function
, évidemment. Et Function
c'est une fonction.
Maintenant, comment pouvez-vous utiliser quelque chose pour le créer vous-même (Mind = blown).
Vous pensez trop à cela . Tout ce qui est requis, c'est que le runtime crée un tas d'objets au démarrage. Les objets ne sont que des tables de recherche associant des chaînes à des objets. Lorsque l'exécution démarre, tout ce qu'il a à faire est de créer quelques dizaines d' objets en blanc, puis commencer à attribuer le prototype
, __proto__
, constructor
et ainsi sur les propriétés de chaque objet jusqu'à ce qu'ils fassent le graphique qu'ils doivent faire.
Il sera utile de prendre le diagramme que je vous ai donné ci-dessus et d'y ajouter des constructor
bords. Vous verrez rapidement qu'il s'agit d'un graphe d'objet très simple et que le runtime n'aura aucun problème à le créer.
Un bon exercice serait de le faire vous-même. Ici, je vais commencer. Nous utiliserons my__proto__
pour signifier "l'objet prototype de" et myprototype
pour signifier "la propriété prototype de".
var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;
Etc. Pouvez-vous remplir le reste du programme pour construire un ensemble d'objets qui ont la même topologie que les "vrais" objets intégrés Javascript? Si vous le faites, vous constaterez que c'est extrêmement facile.
Les objets en JavaScript ne sont que des tables de recherche qui associent des chaînes à d'autres objets . C'est ça! Il n'y a pas de magie ici. Vous vous faites nouer parce que vous imaginez des contraintes qui n'existent pas réellement, comme si chaque objet devait être créé par un constructeur.
Les fonctions ne sont que des objets qui ont une capacité supplémentaire: être appelés. Parcourez donc votre petit programme de simulation et ajoutez une .mycallable
propriété à chaque objet qui indique s'il peut être appelé ou non. C'est aussi simple que ça.
Function.prototype
peut être une fonction et avoir des champs internes. Donc non, vous n'exécutez pas la fonction prototype lorsque vous parcourez sa structure. Enfin, n'oubliez pas qu'il existe un moteur interprétant Javascript, donc l'objet et la fonction sont probablement créés dans le moteur et non à partir de Javascript et de références spéciales commeFunction.prototype
etObject.prototype
pourraient simplement être interprétés de manière spéciale par le moteur.