Cosa sta succedendo in questo codice con gli oggetti Numero che contengono proprietà e incrementano il numero?


246

Un recente tweet conteneva questo frammento di JavaScript.

Qualcuno può spiegare cosa sta accadendo in esso passo dopo passo?

> function dis() { return this }
undefined
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
> five * 5
25
> five.wtf
"potato"
> five++
5
> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
> five
6

In particolare, non mi è chiaro:

  • perché il risultato di dis.call(5)è a Numbercon un qualche tipo di [[PrimitiveValue]]proprietà, ma i risultati di five++e five * 5sembrano essere solo i numeri semplici ( 5e 25non Number)
  • perché la five.wtfproprietà scompare dopo l' five++incremento
  • perché la five.wtfproprietà non è più nemmeno impostabile dopo l' five++incremento, nonostante l' five.wtf = 'potato?'assegnazione apparentemente imposti il ​​valore.

6
Ahah ho visto quel tweet !! È così bizzarro, penso che perché lo moltiplichi, non influisce completamente sull'oggetto, ma ++sembra influenzare il tipo sottostante
Callum Linington,

8
Quale linea non hai capito? Prevs Postincremento? Dopo la moltiplicazione, è di nuovo un Numberoggetto che non ha wtfproprietà ... Ma è comunque un oggetto objectquindi può avere properties..
Rayon,

25
Quando si chiama discon dis.call(5), questo avvolge il numero primitivo 5in un oggetto di tipo Numberche contiene il 5, in modo che questo oggetto possa essere restituito come thisoggetto. Lo ++converte in un numero primitivo che non può contenere proprietà, quindi wtfinizia a non essere definito.
GSerg,


5
@Rayon ... e questo è cambiato con ES6 . Quindi, andando avanti, tutto ciò smetterà di accadere.
GSerg,

Risposte:


278

OP qui. Divertente vederlo su Stack Overflow :)

Prima di approfondire il comportamento, è importante chiarire alcune cose:

  1. Il valore numerico e l' oggetto numerico ( a = 3vs a = new Number(3)) sono molto diversi. Uno è un primitivo, l'altro è un oggetto. Non è possibile assegnare attributi alle primitive, ma è possibile agli oggetti.

  2. La coercizione tra i due è implicita.

    Per esempio:

    (new Number(3) === 3)  // returns false
    (new Number(3) == 3)   // returns true, as the '==' operator coerces
    (+new Number(3) === 3) // returns true, as the '+' operator coerces
  3. Ogni espressione ha un valore di ritorno. Quando REPL legge ed esegue un'espressione, questo è ciò che visualizza. I valori di ritorno spesso non significano ciò che pensi e implicano cose che non sono vere.

Ok, eccoci.

Immagine originale del codice JavaScript

L'impegno.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

Definire una funzione dise chiamarla con 5. Questo eseguirà la funzione con 5come contesto ( this). Qui viene forzato da un valore Numero a un oggetto Numero. È molto importante notare che se fossimo in modalità rigorosa ciò non sarebbe accaduto .

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

Ora impostiamo l'attributo five.wtfsu 'potato', e con cinque come oggetto, abbastanza sicuro che accetta l' assegnazione semplice .

> five * 5
25
> five.wtf
'potato'

Con fivecome oggetto, mi assicuro che può ancora eseguire semplici operazioni aritmetiche. Può. I suoi attributi rimangono ancora? Sì.

La svolta.

> five++
5
> five.wtf
undefined

Ora controlliamo five++. Il trucco con l' incremento postfisso è che l'intera espressione valuterà rispetto al valore originale e quindi incrementerà il valore. Sembra che fivesiano ancora cinque, ma in realtà l'espressione è stata valutata a cinque, quindi impostata fivesu 6.

Non solo è stato fiveimpostato su 6, ma è stato costretto a ritornare in un valore Numero e tutti gli attributi vengono persi. Poiché le primitive non possono contenere attributi, five.wtfnon sono definite.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

Tento di nuovo di riassegnare un attributo wtfa five. Il valore restituito implica che si attacca, ma in realtà non lo fiveè perché è un valore Numero, non un oggetto Numero. L'espressione valuta 'potato?', ma quando controlliamo vediamo che non è stata assegnata.

Il prestigio.

> five
6

Sin dall'incremento postfisso, lo fiveè stato 6.


70
Stai guardando da vicino?
Waddles,

3
@Nathan Long Bene, in Java, che JavaScript ha preso in prestito pesantemente all'inizio, tutti i primitivi hanno un equivalente di classe. inte Integer, per esempio. Suppongo che sia così che tu possa creare una funzione doSomething(Object)e riuscire comunque a darle delle primitive. In questo caso i primitivi saranno convertiti nelle rispettive classi. Ma a JS non interessano davvero i tipi, quindi la ragione è probabilmente qualcos'altro
Suppen

4
@Eric La prima cosa da ++fare è applicare ToNumber al valore . Confronta un caso simile con le stringhe: se lo hai x="5", x++restituisce il numero 5.
apsillers,

2
Davvero la magia accade dis.call(5), la coercizione per obiettare, non me lo sarei mai aspettato.
Mcfedr,

3
@gman tranne che ci sono molte influenze molto più dettagliate, tra cui la denominazione di grossi blocchi della sua libreria standard, il comportamento dei tipi di oggetti standard e persino il fatto che usi una sintassi di parentesi graffe e punti e virgola (anche se i punti e virgola sono opzionale) deriva dal fatto che è stato progettato per essere familiare ai programmatori Java. Sì, è fonte di confusione per i principianti. Ciò non significa che le influenze non esistano.
Jules,

77

Esistono due modi diversi per rappresentare un numero:

var a = 5;
var b = new Number(5);

Il primo è un primitivo, il secondo un oggetto. A tutti gli effetti, entrambi si comportano allo stesso modo, tranne per il loro aspetto diverso se stampati sulla console. Una differenza importante è che, come oggetto, new Number(5)accetta nuove proprietà come qualsiasi altra pianura {}, mentre la primitiva 5non:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

Per quanto riguarda la dis.call(5)parte iniziale , vedere Come funziona la parola chiave "this"? . Diciamo solo che il primo argomento callè usato come valore di this, e che questa operazione forza il numero nella Numberforma di oggetto più complessa . * Più tardi ++lo forza di nuovo nella forma primitiva, perché l'operazione di addizione +risulta in una nuova primitiva.

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

Un Numberoggetto accetta nuove proprietà.

> five++

++si traduce in un nuovo 6valore primitivo ...

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined

... che non ha e non accetta attributi personalizzati.

* Nota che in modalità rigorosa l' thisargomento verrebbe trattato diversamente e non verrebbe convertito in a Number. Vedi http://es5.github.io/#x10.4.3 per i dettagli di implementazione.


2
@Pharap sicuramente meglio in C / C ++ dove puoi fare #define true false. O in Java, dove puoi semplicemente ridefinire il significato dei numeri. Queste sono buone lingue solide. In verità, ogni lingua ha "trucchi" simili in cui si ottiene un risultato che funziona come previsto ma può sembrare strano.
VLAZ,

1
@Vld Ok, fammi riformulare, ecco perché odio scrivere l'anatra.
Pharap,

59

C'è coercizione nel mondo JavaScript: una storia da detective

Nathan, non hai idea di cosa hai scoperto.

Lo sto studiando da settimane ormai. Tutto è iniziato in una notte di tempesta lo scorso ottobre. Mi sono imbattuto per caso nella Numberclasse - voglio dire, perché nel mondo JavaScript aveva una Numberclasse?

Non ero preparato per quello che avrei scoperto dopo.

Si scopre che JavaScript, senza dirtelo, ha cambiato i tuoi numeri in oggetti e i tuoi oggetti in numeri proprio sotto il tuo naso.

JavaScript sperava che nessuno potesse attecchire, ma le persone hanno segnalato strani comportamenti inaspettati, e ora grazie a te e alla tua domanda ho le prove che devo far esplodere questa cosa.

Questo è quello che abbiamo scoperto finora. Non so se dovrei anche dirtelo - potresti voler disattivare JavaScript.

> function dis() { return this }
undefined

Quando hai creato quella funzione, probabilmente non avevi idea di cosa sarebbe successo dopo. Tutto sembrava a posto, e tutto andava bene, per ora.

Nessun messaggio di errore, solo la parola "indefinita" nell'output della console, esattamente quello che ti aspetteresti. Dopotutto, questa era una dichiarazione di funzione - non dovrebbe restituire nulla.

Ma questo era solo l'inizio. Quello che è successo dopo, nessuno avrebbe potuto prevedere.

> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Sì, lo so, ti aspettavi un 5, ma non è quello che hai, è stato - hai qualcos'altro - qualcosa di diverso.

Mi è successa la stessa cosa.

Non sapevo cosa farsene. Mi ha fatto impazzire. Non riuscivo a dormire, non potevo mangiare, ho provato a berlo via, ma nessuna quantità di Mountain Dew mi avrebbe fatto dimenticare. Non aveva alcun senso!

Fu allora che scoprii cosa stava realmente succedendo: era la coercizione e stava accadendo proprio lì davanti ai miei occhi, ma ero troppo cieco per vederlo.

Mozilla ha cercato di seppellirlo mettendolo dove sapevano che nessuno avrebbe guardato: la loro documentazione .

Dopo ore di lettura, rilettura e rilettura ricorsiva, ho trovato questo:

"... e i valori primitivi verranno convertiti in oggetti."

Era proprio lì come può essere spiegato con il font Open Sans. Era la call()funzione - come potrei essere così stupido ?!

Il mio numero non era più un numero. Nel momento in cui l'ho passato call(), è diventato qualcos'altro. È diventato ... un oggetto.

All'inizio non ci potevo credere. Come potrebbe essere vero? Ma non potevo ignorare le prove che si stavano accumulando intorno a me. È proprio lì se guardi solo:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtfera giusto. I numeri non possono avere proprietà personalizzate: lo sappiamo tutti! È la prima cosa che ti insegnano all'Accademia.

Avremmo dovuto sapere il momento in cui abbiamo visto l'output della console: questo non era il numero che pensavamo fosse. Questo era un impostore - un oggetto che si spacciava come il nostro dolce numero innocente.

Questo è stato ... new Number(5).

Ovviamente! Aveva perfettamente senso. call()aveva un lavoro da svolgere, doveva invocare una funzione, e per farlo doveva popolarlo this, sapeva di non poterlo fare con un numero - aveva bisogno di un oggetto ed era disposto a fare qualsiasi cosa per ottenerlo, anche se ciò significava forzare il nostro numero. Quando ha call()visto il numero 5, ha visto un'opportunità.

Era il piano perfetto: aspettate che nessuno lo guardasse e scambiate il nostro numero con un oggetto che assomigli. Otteniamo un numero, la funzione viene invocata e nessuno sarebbe più saggio.

Era davvero il piano perfetto, ma come tutti i piani, anche quelli perfetti, c'era un buco in esso e stavamo per cadere proprio in esso.

Vedi, quello call()che non capiva era che non era l'unico in città in grado di forzare i numeri. Dopotutto questo era JavaScript: la coercizione era ovunque.

call() ho preso il mio numero e non mi sarei fermato fino a quando non avessi tolto la maschera dal suo piccolo impostore e lo avrei esposto all'intera comunità di Stack Overflow.

Ma come? Avevo bisogno di un piano. Certo sembra un numero, ma so che non lo è, deve esserci un modo per dimostrarlo. Questo è tutto! Esso si presenta come un numero, ma può agire come uno?

Ho detto che fiveho bisogno che diventi 5 volte più grande - non ha chiesto perché e non ho spiegato. Allora ho fatto quello che avrebbe fatto un buon programmatore: ho moltiplicato. Sicuramente non c'era modo di farsene uscire.

> five * 5
25
> five.wtf
'potato'

Accidenti! Non solo fivemoltiplicare, ma andava bene wtf. Accidenti a questo ragazzo e alla sua patata.

Cosa diavolo stava succedendo? Ho sbagliato su tutto questo? È fivedavvero un numero? No, devo mancare qualcosa, lo so, c'è qualcosa che devo dimenticare, qualcosa di così semplice ed essenziale che lo sto completamente trascurando.

Non stava andando bene, avevo scritto questa risposta per ore e non ero ancora più vicino a esprimere il mio punto. Non riuscivo a continuare così, alla fine la gente smetteva di leggere, dovevo pensare a qualcosa e dovevo pensarci velocemente.

Aspetta e basta! fivenon era 25, 25 era il risultato, 25 era un numero completamente diverso. Certo, come potrei dimenticare? I numeri sono immutabili. Quando si moltiplica 5 * 5nulla viene assegnato a qualsiasi cosa si crea semplicemente un nuovo numero 25.

Deve essere quello che sta succedendo qui. In qualche modo, quando moltiplico five * 5, fivedevo essere forzato in un numero e quel numero deve essere quello usato per la moltiplicazione. Sono i risultati di quella moltiplicazione che vengono stampati sulla console, non il valore di fivese stesso. fivenon viene mai assegnato nulla, quindi ovviamente non cambia.

Quindi, come posso fiveassegnarmi il risultato di un'operazione? Capito. Prima fiveancora di avere la possibilità di pensare, ho urlato "++".

> five++
5

Aha! L'ho avuto! Tutti sanno 5 + 1è 6, questa è stata la prova che mi serviva per esporre che fivenon era un numero! Era un impostore! Un cattivo impostore che non sapeva contare. E potrei provarlo. Ecco come agisce un numero reale:

> num = 5
5
> num++
5

Aspettare? Cosa stava succedendo qui? sospiro Sono stato così preso dal trambusto fiveche mi dimentico di come funzionano gli operatori postali. Quando uso il ++alla fine fivesto dicendo restituire il valore corrente, quindi incrementare five. È il valore prima dell'operazione che viene stampato sulla console. numera in effetti 6e ho potuto dimostrarlo:

>num
6

È ora di vedere cosa fivefosse davvero:

>five
6

... era esattamente come dovrebbe essere. fiveera buono, ma io ero meglio. Se fivefosse ancora un oggetto, ciò significherebbe che avrebbe ancora la proprietà wtfe io avrei voluto scommettere tutto ciò che non aveva.

> five.wtf
undefined

Aha! Avevo ragione. L'ho avuto! fiveera un numero ora - non era più un oggetto. Sapevo che il trucco della moltiplicazione non l'avrebbe salvato questa volta. Vedere five++è davvero five = five + 1. A differenza della moltiplicazione, l' ++operatore assegna un valore a five. Più specificamente, gli assegna i risultati dei five + 1quali proprio come nel caso della moltiplicazione restituisce un nuovo numero immutabile .

Sapevo di averlo, e solo per essere sicuro che non potesse evitarlo. Ho avuto un altro test nella manica. Se avessi ragione, ed fiveera davvero un numero ora, allora questo non avrebbe funzionato:

> five.wtf = 'potato?'
'potato?'

Questa volta non mi avrebbe ingannato. Sapevo che potato?sarebbe stato stampato sulla console perché è l'output del compito. La vera domanda è: ci sarà wtfancora?

> five.wtf
undefined

Proprio come sospettavo - niente - perché ai numeri non è possibile assegnare proprietà. Abbiamo imparato che il primo anno all'Accademia;)

Grazie Nathan. Grazie al tuo coraggio nel porre questa domanda, posso finalmente lasciarmi tutto alle spalle e passare a un nuovo caso.

Come questo sulla funzione toValue(). Oh caro dio. Nooo!


9
Dimenticalo Jake; è Javascript.
Seth

Perché chiamiamo funzioni di costruzione "classi" in JS?
evolutionxbox,

27
01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01dichiara una funzione dische restituisce l'oggetto di contesto. Ciò che thisrappresenta cambia a seconda che tu stia utilizzando la modalità rigorosa o meno. L'intero esempio ha risultati diversi se la funzione fosse dichiarata come:

> function dis() { "use strict"; return this }

Questo è dettagliato nella sezione 10.4.3 della specifica ES5

  1. Se il codice funzione è un codice rigoroso, impostare ThisBinding su thisArg.
  2. Altrimenti se thisArg è nullo o indefinito, imposta ThisBinding sull'oggetto globale.
  3. Altrimenti, se Type (thisArg) non è Object, imposta ThisBinding su ToObject (thisArg).

02è il valore di ritorno della dichiarazione di funzione. undefineddovrebbe essere autoesplicativo qui.

03la variabile fiveviene inizializzata con il valore di ritorno di disquando viene chiamata nel contesto del valore di base 5. Poiché disnon è in modalità rigorosa, questa linea è identica alla chiamata five = Object(5).

04Il Number {[[PrimitiveValue]]: 5}valore di ritorno dispari è la rappresentazione dell'oggetto che avvolge il valore di base5

05alla proprietà fivedell'oggetto wtfviene assegnato un valore stringa di'potato'

06 è il valore di ritorno dell'assegnazione e deve essere autoesplicativo.

07la proprietà fivedell'oggetto wtfè in fase di esame

08come five.wtfprecedentemente impostato su 'potato'ritorna 'potato'qui

09l' fiveoggetto viene moltiplicato per il valore primitivo 5. Questo non è diverso da qualsiasi altro oggetto moltiplicato ed è spiegato nella sezione 11.5 della specifica ES5 . Di particolare nota è il modo in cui gli oggetti vengono espressi in valori numerici, che viene trattato in alcune sezioni.

9.3 A numero :

  1. Lascia che primValue sia ToPrimitive (argomento di input, numero suggerimento).
  2. Return ToNumber (primValue).

9.1 ToPrimitive :

Restituisce un valore predefinito per l'oggetto. Il valore predefinito di un oggetto viene recuperato chiamando il metodo interno [[DefaultValue]] dell'oggetto, passando il suggerimento opzionale PreferredType. Il comportamento del metodo interno [[DefaultValue]] è definito da questa specifica per tutti gli oggetti ECMAScript nativi in 8.12.8 .

8.12.8 [[DefaultValue]] :

Sia valueOf il risultato della chiamata del metodo interno [[Get]] dell'oggetto O con l'argomento "valueOf".

  1. Se IsCallable (valueOf) è true,

    1. Sia val il risultato della chiamata al metodo interno [[Call]] di valueOf, con O come questo valore e un elenco di argomenti vuoto.
    2. Se val è un valore primitivo, restituisce val.

Questo è tutto un modo per dire che valueOfviene chiamata la funzione dell'oggetto e che nell'equazione viene utilizzato il valore restituito da quella funzione. Se si dovesse cambiare la valueOffunzione, è possibile modificare i risultati dell'operazione:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10poiché fivela valueOffunzione s è rimasta invariata, restituisce il valore di base spostato in 5modo che five * 5valuti a 5 * 5quali risultati25

11la proprietà fivedell'oggetto wtfviene nuovamente valutata nonostante sia rimasta invariata rispetto a quando è stata assegnata 05.

12 'potato'

13l' incremento operatore Postfix è chiamato five, che ottiene il valore numerico ( 5, abbiamo coperto come in precedenza), memorizza il valore in modo che possa essere restituito, aggiunge 1al valore ( 6), assegna il valore a five, e restituisce il valore memorizzato ( 5)

14 come prima, il valore restituito è il valore prima che fosse incrementato

15si accede alla wtfproprietà del valore primitivo ( 6) memorizzato nella variabile five. La sezione 15.7.5 della specifica ES5 definisce questo comportamento. I numeri ottengono le proprietà da Number.prototype.

16 Number.prototypenon ha una wtfproprietà, quindi undefinedviene restituito

17 five.wtfviene assegnato un valore di 'potato?'. L'assegnazione è definita in 11.13.1 della specifica ES5 . Fondamentalmente il valore assegnato viene restituito ma non memorizzato.

18 'potato?' è stato restituito dall'operatore di assegnazione

19di nuovo five, che ha un valore di 6è accessibile, e di nuovo Number.prototypenon ha una wtfproprietà

20 undefined come spiegato sopra

21 five si accede

22 6 viene restituito come spiegato in 13


17

È abbastanza semplice

function dis () { return this; }

Questo restituisce il thiscontesto. Quindi, se lo fai call(5), stai passando il numero come oggetto.

La callfunzione non fornisce argomenti, il primo argomento che dai è il contesto this. Di solito se lo vuoi sul suo contesto, lo dai {}così dis.call({}), il che significa che thisnella funzione è un vuoto this. Tuttavia, se lo passi 5sembra che verrà convertito in un oggetto. Vedi .call

Quindi il ritorno è object

Quando lo fai five * 5, JavaScript vede l'oggetto fivecome tipo primitivo, quindi è equivalente a 5 * 5. È interessante notare che '5' * 5è ancora uguale 25, quindi JavaScript è chiaramente sotto il cofano. Su questa riga non viene apportata alcuna modifica al fivetipo sottostante

Ma quando lo fai ++convertirà l'oggetto nel numbertipo primitivo rimuovendo così la .wtfproprietà. Perché stai influenzando il tipo sottostante


Il ritorno è Numero .
GSerg,

++converte e lo riassegna. ++è uguale a variable = variable + 1. Quindi perdiwtf
Rajesh,

Il *vs ++non mi confonde affatto. Avere una funzione che non si aspetta argomenti e non ritorna this, avere quella funzione accettare comunque un argomento e restituire qualcosa che è simile, ma non è esattamente, l'argomento - che non ha senso per me.
Nathan Long,

Aggiornato @NathanLong per mostrare cosa dis.call()fa.
Callum Linington,

5 ++ si comporta come nel linguaggio C, quindi nulla di sorprendente. thisè semplicemente un puntatore a un oggetto, quindi i tipi primitivi vengono convertiti implicitamente. Perché una funzione senza argomenti non può avere almeno un contesto? Il primo argomento di callo bindviene utilizzato per impostare il contesto. Inoltre, le funzioni sono chiusure, il che significa che hanno accesso a più di un semplicearguments
Rivenfall

10

I valori primitivi non possono avere proprietà. Ma quando si tenta di accedere a una proprietà con un valore di base, si trasforma in modo trasparente in un oggetto Numero temporaneo.

Così:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6

8

Dichiara funzione dis. La funzione restituisce il suo contesto

function dis() { return this }
undefined

Chiama discon il contesto 5. I valori primitivi vengono inscatolati quando passati come contesto in modalità rigorosa ( MDN ). Quindi fiveora è oggetto (numero in scatola).

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Dichiara wtfproprietà su fivevariabile

five.wtf = 'potato'
"potato"

Valore di five.wtf

five.wtf
"potato"

fiveè inscatolato 5, quindi è il numero e l'oggetto allo stesso tempo (5 * 5 = 25). Non cambia five.

five * 5
25

Valore di five.wtf

five.wtf
"potato"

Unboxing fivequi. fiveora è solo primitivo number. Stampa 5, quindi aggiunge 1a five.

five++
5

fiveè il numero primitivo 6ora, non ci sono proprietà in esso.

five.wtf
undefined

le primitive non possono avere proprietà, non è possibile impostare questo

five.wtf = 'potato?'
"potato?"

non puoi leggere questo, perché non è stato impostato

five.wtf
undefined

fiveè a 6causa dell'incremento del post sopra

five
6

7

Prima di tutto sembra che questo venga eseguito attraverso la console nodejs.

1.

    function dis() { return this }

crea la funzione dis (), ma poiché non era impostato come varvalore non c'era alcun valore da restituire, così undefinedcome l'output, anche se è dis()stato definito. Su un sidenote, thisnon è stato restituito perché la funzione non è stata eseguita.

2.

    five = dis.call(5)

Questo restituisce l' Numberoggetto javascript perché hai appena impostato il valore della funzione dis()sui thiscinque primitivi.

3.

   five.wtf = 'potato'

Il primo ritorna "potato"perché hai appena impostato la proprietà wtfdi fivea 'potato'. Javascript restituisce il valore di una variabile impostata, semplificando il concatenamento di più variabili e impostandole sullo stesso valore in questo modo:a = b = c = 2 .

4.

    five * 5

Questo ritorna 25perché hai appena moltiplicato il numero primitivo 5su five. Il valore di è fivestato determinato dal valore Numberdell'oggetto.

5.

    five.wtf

Ho saltato questa riga prima perché l'avrei ripetuta qui. Restituisce semplicemente il valore della proprietà wtfimpostata in precedenza.

6.

    five++

Come ha detto @Callum, ++il tipo verrà convertito numbernello stesso valore dell'oggetto Number {[[PrimitiveValue]]: 5}}.

Ora, poiché fiveè un number, non puoi più impostarne le proprietà finché non fai qualcosa del genere:

    five = dis.call(five)
    five.wtf = "potato?"

o

    five = { value: 6, wtf: "potato?" }

Si noti inoltre che il secondo modo avrà un comportamento diverso rispetto all'utilizzo del primo metodo perché sta definendo un oggetto generico invece Numberdell'oggetto creato in precedenza.

Spero che questo aiuti, a javascript piace assumere le cose, quindi può diventare confuso quando si Numberpassa dall'oggetto a una primitiva number. Puoi controllare di che tipo si tratta utilizzando la typeofparola chiave, scrivendo il tipo di cinque dopo che l'inizializzazione ritorna 'object'e dopo che lo five++fa ritorna 'number'.

@deceze descrive molto bene la differenza tra l'oggetto Numero e il numero primitivo.


6

Gli ambiti JavaScript sono costituiti da contesti di esecuzione. Ogni contesto di esecuzione ha un lessicale ambiente (/ valori ambito globale esterni), un ambiente variabile (valori con ambito a livello locale), ed un questo legame .

Il questo legame è una parte molto importante del contesto di esecuzione. L'utilizzo callè un modo per alterare questo legame , e farlo creerà automaticamente un oggetto con cui popolare il legame.

Function.prototype.call () (da MDN)

Sintassi
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
Il valore di questo fornito per la chiamata al divertimento. Si noti che questo potrebbe non essere il valore effettivo visto dal metodo: se il metodo è una funzione in codice di modalità non rigorosa, null e indefinito verranno sostituiti con l'oggetto globale e i valori primitivi verranno convertiti in oggetti . (enfatizzare il mio)

Una volta evidente che 5 viene convertito in new Number(5), il resto dovrebbe essere piuttosto ovvio. Si noti che anche altri esempi funzioneranno fintanto che sono valori primitivi.

function primitiveToObject(prim){
  return dis.call(prim);
}
function dis(){ return this; }

//existing example
console.log(primitiveToObject(5));

//Infinity
console.log(primitiveToObject(1/0));

//bool
console.log(primitiveToObject(1>0));

//string
console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />

inserisci qui la descrizione dell'immagine


4

Un paio di concetti spiegano cosa succede

5 è un numero, un valore primitivo

Number {[[PrimitiveValue]]: 5} è un'istanza di Number (chiamiamola wrapper di oggetti)

Ogni volta che si accede a una proprietà / metodo su un valore di base, il motore JS creerà un wrapper di oggetti del tipo appropriato ( Numberfor 5, Stringfor 'str'e Booleanfor true) e risolverà la chiamata di accesso / metodo della proprietà su quel wrapper di oggetti. Questo è ciò che accade quando lo fai true.toString()ad esempio.

Quando eseguono operazioni su oggetti, vengono convertiti in valori primitivi (utilizzando toStringo valueOf) per risolvere tali operazioni, ad esempio quando si eseguono

var obj = { a : 1 };
var string = 'mystr' + obj;
var number = 3 + obj;

stringconterrà la concatenazione di stringhe di mystre obj.toString()e numberconterrà l'aggiunta di 3e obj.valueOf().

Ora per mettere tutto insieme

five = dis.call(5)

dis.call(5)si comporta proprio come (5).dis()se 5avesse effettivamente il metodo dis. Per risolvere la chiamata del metodo, viene creato il wrapper di oggetti e la chiamata del metodo viene risolta su di esso. A questo punto cinque indicano un wrapper di oggetti attorno al valore di base 5.

five.wtf = 'potato'

Impostare una proprietà su un oggetto, niente di speciale qui.

five * 5

Questo sta effettivamente five.valueOf() * 5ottenendo il valore di base dal wrapper di oggetti. fivepunta ancora all'oggetto iniziale.

five++

Questo è in realtà five = five.valueOf() + 1. Prima di questa riga fivecontiene il wrapper dell'oggetto attorno al valore 5, mentre dopo questo punto fivecontiene il valore di base6 .

five.wtf
five.wtf = 'potato?'
five.wtf

fivenon è più un oggetto. Ciascuna di queste righe crea una nuova istanza di Number per risolvere l' .wtfaccesso alla proprietà. Le istanze sono indipendenti, quindi l'impostazione di una proprietà su una non sarà visibile su un'altra. Il codice è completamente equivalente a questo:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
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.