Impossibile risolvere il mistero delle funzioni in Javascript


16

Sto cercando di capire dietro le quinte di Javascript e in qualche modo bloccato nella comprensione della creazione di oggetti incorporati, specialmente degli oggetti e Funzione e la relazione tra loro.

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. 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. In un certo senso ha iniziato a sembrare il dilemma di gallina e pollo.

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?

Inoltre, secondo la documentazione di Mozilla, ogni javascript functionè un'estensione Functiondell'oggetto ma quando console.log(Function.prototype.constructor)è di nuovo una funzione. Ora come puoi usare qualcosa per crearlo da solo (Mind = blown).

L'ultima cosa, Function.prototypeè una funzione ma posso accedere alla constructorfunzione usando Function.prototype.constructorsignifica che Function.prototypeè una funzione che restituisce l' prototypeoggetto


poiché una funzione è un oggetto, ciò significa che Function.prototypepuò 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 come Function.prototypee Object.prototypepotrebbero essere interpretati in modo speciale dal motore.
Walfrat,

1
A meno che tu non stia cercando di implementare un compilatore JavaScript conforme agli standard, non devi davvero preoccuparti di queste cose. Se stai cercando di fare qualcosa di utile, sei fuori rotta.
Jared Smith,

5
Cordiali saluti, la solita frase in inglese per "il dilemma di gallina e pollo" è "il problema del pollo e delle uova", vale a dire "quale è venuto prima, il pollo o l'uovo?" (Naturalmente la risposta è l'uovo. Gli animali ovipari erano in giro da milioni di anni prima dei polli.)
Eric Lippert

Risposte:


32

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 Falcune 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 prototypemembro di un oggetto in genere non lo è il prototipo dell'oggetto.
  • Piuttosto, il prototypemembro 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" prototypeproprietà di".

inserisci qui la descrizione dell'immagine

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 instanceofsignifica? honda instanceof Carsignifica "è Car.prototypeuguale a qualsiasi oggetto sulla hondacatena di prototipi?"

Sì. hondaIl prototipo è Car.prototype, quindi abbiamo finito. Questo stampa vero.

E la seconda?

honda.constructornon esiste quindi consultiamo il prototipo, che è Car.prototype. Quando l' Car.prototypeoggetto è stato creato, gli è stata automaticamente assegnata una proprietà constructoruguale 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 Reptilesignifica "è Reptile.prototypeuguale a qualsiasi oggetto sulizard catena di prototipi?"

Sì. lizardIl 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 è lizardstato costruito con new Reptilema ti sbaglieresti. Ragionare fuori.

  • Ha lizardunconstructor proprietà? No. Quindi guardiamo al prototipo.
  • Il prototipo di lizardè Reptile.prototype, che èAnimal .
  • Ha Animalunconstructor proprietà? No. Quindi guardiamo al suo prototipo.
  • Il prototipo di Animalè Object.prototypeed 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 Functionprototipo, stranamente. Quindi Function.prototype()è legale, e quando lo fai, undefinedtorni indietro. Quindi è una funzione.

Il Objectprototipo 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 myprototypeper 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 .mycallableproprietà a ogni oggetto che indica se è richiamabile o meno. E 'così semplice.


9
Finalmente una breve, concisa, facile comprensione di JavaScript! Eccellente! Come può qualcuno di noi essere confuso? :) Anche se in tutta serietà, l'ultimo aspetto degli oggetti come tabelle di ricerca è davvero la chiave. C'è un metodo per la follia --- ma è ancora follia ...
Greg Burghardt

4
@GregBurghardt: concordo che all'inizio sembra complesso, ma la complessità è la conseguenza di semplici regole. Ogni oggetto ha un __proto__. Il __proto__prototipo dell'oggetto è nullo. Il __proto__di new X()è X.prototype. Tutti gli oggetti funzione hanno il prototipo di funzione ad __proto__eccezione del prototipo di funzione stesso. Objecte Functione il prototipo di funzione sono funzioni. Queste regole sono tutte semplici e determinano la topologia del grafico degli oggetti iniziali.
Eric Lippert,

6

Hai già molte risposte eccellenti, ma voglio solo dare una risposta breve e chiara alla tua risposta su come funziona tutto questo, e quella risposta è:

MAGIA!!!

Davvero, tutto qui.

Le persone che implementano motori di esecuzione ECMAScript devono implementare regole di ECMAScript, ma non rispettare da loro nel loro attuazione.

La specifica ECMAScript afferma che A eredita da B ma B è un'istanza di A? Nessun problema! Creare prima A con un puntatore prototipo di NULL, creare B come istanza di A, quindi correggere il puntatore prototipo di A per puntare successivamente a B. Vai tranquillo.

Dici, ma aspetta, non c'è modo di cambiare il puntatore del prototipo in ECMAScript! Ma ecco il punto: questo codice non è in esecuzione sul motore ECMAScript, questo codice è il motore ECMAScript. Ha accesso agli interni degli oggetti di cui non dispone il codice ECMAScript in esecuzione sul motore. In breve: può fare quello che vuole.

A proposito, se vuoi davvero, devi farlo solo una volta: in seguito, ad esempio, puoi scaricare la memoria interna e caricare questo dump ogni volta che avvii il tuo motore ECMAScript.

Si noti che tutto ciò vale ancora, anche se lo stesso motore ECMAScript è stato scritto in ECMAScript (come nel caso di Mozilla Narcissus, ad esempio). Anche allora, il codice ECMAScript che implementa il motore ha ancora pieno accesso al motore che sta implementando , sebbene ovviamente non abbia accesso al motore su cui è in esecuzione .


3

Dalle specifiche ECMA 1

ECMAScript non contiene classi appropriate come quelle in C ++, Smalltalk o Java, ma piuttosto supporta costruttori che creano oggetti eseguendo codice che alloca spazio di archiviazione per gli oggetti e inizializza tutto o parte di essi assegnando i valori iniziali alle loro proprietà. Tutte le funzioni, inclusi i costruttori, sono oggetti, ma non tutti gli oggetti sono costruttori.

Non vedo come potrebbe essere più chiaro !!! </sarcasm>

Più in basso vediamo:

Prototipo Un prototipo è un oggetto utilizzato per implementare l'ereditarietà di struttura, stato e comportamento in ECMAScript. Quando un costruttore crea un oggetto, quell'oggetto fa implicitamente riferimento al prototipo associato del costruttore allo scopo di risolvere i riferimenti di proprietà. Il prototipo associato del costruttore può essere referenziato dall'espressione di programma constructor.prototype e le proprietà aggiunte al prototipo di un oggetto sono condivise, tramite ereditarietà, da tutti gli oggetti che condividono il prototipo.

Quindi possiamo vedere che un prototipo è un oggetto, ma non necessariamente un oggetto funzione.

Inoltre, abbiamo questo interessante titbit

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

Il costruttore Object è l'oggetto intrinseco% Object% e il valore iniziale della proprietà Object dell'oggetto globale.

e

Il costruttore della funzione è l'oggetto intrinseco% Function% e il valore iniziale della proprietà Function dell'oggetto globale.


Adesso lo fa. ECMA6 consente di creare classi e creare istanze di oggetti da esse.
ncmathsadist,

2
Le classi ES6 di @ncmathsadist sono solo uno zucchero sintattico, la semantica è la stessa.
Hamza Fatmi,

1
Il tuo sarcasmmoniker altrimenti, questo testo è davvero piuttosto opaco per un principiante.
Robert Harvey,

vero, aggiungo di più in seguito, ho bisogno di scavare
Ewan

1
erm? sottolineare che non è chiaro dal documento
Ewan

1

I seguenti tipi comprendono tutti i valori in JavaScript:

  • boolean
  • number
  • undefined(che include il valore singolo undefined)
  • string
  • symbol ("cose" uniche astratte che vengono confrontate per riferimento)
  • object

Ogni oggetto (cioè tutto) in JavaScript ha un prototipo, che è una specie di oggetto.

Il prototipo contiene funzioni, che sono anche una specie di oggetto 1 .

Gli oggetti hanno anche un costruttore, che è una funzione e quindi un tipo di oggetto.

nidificato

È tutto ricorsivo, ma l'implementazione è in grado di farlo automagicamente perché, a differenza del codice JavaScript, può creare oggetti senza dover chiamare funzioni JavaScript (poiché gli oggetti sono solo memoria che l'implementazione controlla).

La maggior parte dei sistemi di oggetti in molti linguaggi tipizzati dinamicamente è circolare 2 in questo modo. Ad esempio, in Python, le classi sono oggetti e la classe di classi lo è type, quindi typeè quindi un'istanza di se stessa.

L'idea migliore è quella di utilizzare solo gli strumenti forniti dalla lingua e non pensare troppo a come ci sono arrivati.

1 Le funzioni sono piuttosto speciali perché sono richiamabili e sono gli unici valori che possono contenere dati opachi (il loro corpo e possibilmente una chiusura).

2 In realtà è più un tortuoso nastro ramificato piegato all'indietro su se stesso, ma "circolare" è abbastanza vicino.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.