Sto cercando di capire dietro le quinte di Javascript e in qualche modo bloccato nella comprensione della creazione di oggetti incorporati, specialmente Oggetto e Funzione e la relazione tra loro.
È complicato, è facile fraintendere e molti libri Javascript per principianti sbagliano, quindi non fidarti di tutto ciò che leggi.
Sono stato uno degli implementatori del motore JS di Microsoft negli anni '90 e nel comitato di standardizzazione e ho commesso numerosi errori nel mettere insieme questa risposta. (Anche se da quando non ci lavoro più da 15 anni posso forse essere perdonato.) È roba difficile. Ma una volta che hai capito l'eredità del prototipo, tutto ha un senso.
Quando ho letto che tutti gli oggetti incorporati come Array, String etc sono estensioni (ereditate) da Object, ho pensato che Object fosse il primo oggetto incorporato che viene creato e il resto degli oggetti eredita da esso.
Inizia buttando via tutto ciò che sai sull'eredità di classe. JS utilizza l'ereditarietà basata sui prototipi.
Quindi, assicurati di avere una definizione molto chiara nella testa di ciò che significa "eredità". Le persone abituate a linguaggi OO come C # o Java o C ++ pensano che l'ereditarietà significhi sottotipizzazione, ma l'ereditarietà non significa sottotipizzazione. Eredità significa che i membri di una cosa sono anche membri di un'altra cosa . Non significa necessariamente che esiste una relazione di sottotitolo tra queste cose! Tanti equivoci nella teoria dei tipi sono il risultato di persone che non si rendono conto che c'è una differenza.
Ma non ha senso quando si arriva a sapere che gli oggetti possono essere creati solo da funzioni, ma anche le funzioni non sono altro che oggetti di funzione.
Questo è semplicemente falso. Alcuni oggetti non vengono creati chiamando new F
alcune funzioniF
. Alcuni oggetti vengono creati dal runtime JS dal nulla. Ci sono uova che non sono state deposte da alcun pollo . Sono stati appena creati dal runtime all'avvio.
Diciamo quali sono le regole e forse questo aiuterà.
- Ogni istanza di oggetto ha un oggetto prototipo.
- In alcuni casi quel prototipo può essere
null
.
- Se si accede a un membro su un'istanza di oggetto e l'oggetto non ha quel membro, l'oggetto passa al suo prototipo o si interrompe se il prototipo è null.
- Il
prototype
membro di un oggetto in genere non lo è il prototipo dell'oggetto.
- Piuttosto, il
prototype
membro di un oggetto funzione F è l'oggetto che diventerà il prototipo dell'oggetto creato danew F()
.
- In alcune implementazioni, le istanze ottengono a
__proto__
membro che dà davvero il loro prototipo. (Questo è ora deprecato. Non fare affidamento su di esso.)
- Agli oggetti funzione viene assegnato un nuovissimo oggetto predefinito
prototype
momento della loro creazione.
- Il prototipo di un oggetto funzione è, ovviamente
Function.prototype
.
Riassumiamo.
- Il prototipo di
Object
èFunction.prototype
Object.prototype
è l'oggetto prototipo oggetto.
- Il prototipo di
Object.prototype
ènull
- Il prototipo di
Function
è Function.prototype
- questa è una delle rare situazioni in cui Function.prototype
è effettivamente il prototipo di Function
!
Function.prototype
è l'oggetto prototipo di funzione.
- Il prototipo di
Function.prototype
èObject.prototype
Supponiamo di fare una funzione Foo.
- Il prototipo di
Foo
è Function.prototype
.
Foo.prototype
è l'oggetto prototipo Foo.
- Il prototipo di
Foo.prototype
è Object.prototype
.
Supponiamo di dire new Foo()
- Il prototipo del nuovo oggetto è
Foo.prototype
Assicurati che abbia senso. Disegniamolo. Gli ovali sono istanze di oggetti. I bordi __proto__
significano "il prototipo di" o prototype
" prototype
proprietà di".
Tutto ciò che il runtime deve fare è creare tutti quegli oggetti e assegnare le loro varie proprietà di conseguenza. Sono sicuro che puoi vedere come sarebbe fatto.
Ora diamo un'occhiata a un esempio che mette alla prova le tue conoscenze.
function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);
Cosa stampa questo?
Bene, cosa instanceof
significa? honda instanceof Car
significa "è Car.prototype
uguale a qualsiasi oggetto sulla honda
catena di prototipi?"
Sì. honda
Il prototipo è Car.prototype
, quindi abbiamo finito. Questo stampa vero.
E la seconda?
honda.constructor
non esiste quindi consultiamo il prototipo, che è Car.prototype
. Quando l' Car.prototype
oggetto è stato creato, gli è stata automaticamente assegnata una proprietà constructor
uguale a Car
, quindi questo è vero.
E adesso?
var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);
Cosa stampa questo programma?
Ancora una volta, lizard instanceof Reptile
significa "è Reptile.prototype
uguale a qualsiasi oggetto sulizard
catena di prototipi?"
Sì. lizard
Il prototipo èReptile.prototype
, quindi abbiamo finito. Questo stampa vero.
Ora, che dire
print(lizard.constructor == Reptile);
Potresti pensare che anche questo sia vero, dato che è lizard
stato costruito con new Reptile
ma ti sbaglieresti. Ragionare fuori.
- Ha
lizard
unconstructor
proprietà? No. Quindi guardiamo al prototipo.
- Il prototipo di
lizard
è Reptile.prototype
, che èAnimal
.
- Ha
Animal
unconstructor
proprietà? No. Quindi guardiamo al suo prototipo.
- Il prototipo di
Animal
è Object.prototype
ed Object.prototype.constructor
è creato dal runtime ed è uguale a Object
.
- Quindi questo stampa falso.
Avremmo dovuto dirlo Reptile.prototype.constructor = Reptile;
ad un certo punto, ma non ci ricordavamo di!
Assicurati che tutto abbia senso per te. Disegna alcune caselle e frecce se è ancora confuso.
L'altra cosa estremamente confusa è, se console.log(Function.prototype)
stampo una funzione ma quando stampo console.log(Object.prototype)
stampa un oggetto. Perché èFunction.prototype
una funzione quando doveva essere un oggetto?
Il prototipo di funzione è definito come una funzione che, quando viene chiamata, ritorna undefined
. Sappiamo già che Function.prototype
è il Function
prototipo, stranamente. Quindi Function.prototype()
è legale, e quando lo fai, undefined
torni indietro. Quindi è una funzione.
Il Object
prototipo non ha questa proprietà; non è richiamabile. È solo un oggetto.
quando console.log(Function.prototype.constructor)
è di nuovo una funzione.
Function.prototype.constructor
è solo Function
, ovviamente. Ed Function
è una funzione.
Ora come puoi usare qualcosa per crearlo da solo (Mind = blown).
Ci stai pensando troppo . Tutto ciò che serve è che il runtime crei un mucchio di oggetti all'avvio. Gli oggetti sono solo tabelle di ricerca che associano le stringhe agli oggetti. Quando il runtime si avvia, tutto quello che deve fare è creare alcuni oggetti dozzina vuoti, e quindi avviare assegnare il prototype
, __proto__
, constructor
, e così via proprietà di ogni oggetto fino a fanno il grafico che hanno bisogno di fare.
Sarà utile se prendi quel diagramma che ti ho dato sopra e aggiungi constructor
bordi ad esso. Vedrai rapidamente che questo è un grafico a oggetti molto semplice e che il runtime non avrà problemi a crearlo.
Un buon esercizio sarebbe farlo da soli. Ecco, ti inizio. Useremo my__proto__
per indicare "l'oggetto prototipo di" e myprototype
per indicare "la proprietà prototipo di".
var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;
E così via. Puoi compilare il resto del programma per costruire un insieme di oggetti con la stessa topologia degli oggetti "reali" incorporati in Javascript? Se lo fai, scoprirai che è estremamente facile.
Gli oggetti in JavaScript sono solo tabelle di ricerca che associano stringhe con altri oggetti . Questo è tutto! Non c'è magia qui. Ti stai annodando perché stai immaginando vincoli che in realtà non esistono, come se ogni oggetto dovesse essere creato da un costruttore.
Le funzioni sono solo oggetti che hanno una capacità aggiuntiva: essere chiamati. Quindi passa attraverso il tuo piccolo programma di simulazione e aggiungi una .mycallable
proprietà a ogni oggetto che indica se è richiamabile o meno. E 'così semplice.
Function.prototype
può essere una funzione e avere campi interni. Quindi no, non esegui la funzione prototipo quando attraversi la sua struttura. Infine, ricorda che esiste un motore che interpreta Javascript, quindi Object e Function sono probabilmente creati all'interno del motore e non da Javascript e riferimenti speciali comeFunction.prototype
eObject.prototype
potrebbero essere interpretati in modo speciale dal motore.