Que se passe-t-il dans ce code avec des objets Number contenant des propriétés et incrémentant le nombre?


246

Un tweet récent contenait cet extrait de JavaScript.

Quelqu'un peut-il expliquer ce qui s'y passe pas à pas?

> 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

En particulier, il n'est pas clair pour moi:

  • pourquoi le résultat de dis.call(5)est un Numberavec une sorte de [[PrimitiveValue]]propriété, mais les résultats de five++et five * 5semblent être simplement des nombres en clair 5et 25(pas des Numbers)
  • pourquoi la five.wtfpropriété disparaît après l' five++incrément
  • pourquoi la five.wtfpropriété n'est même plus définissable après l' five++incrément, malgré l' five.wtf = 'potato?'affectation définissant apparemment la valeur.

6
Haha j'ai vu ce tweet !! C'est tellement bizarre, je pense que parce que vous le multipliez, cela n'affecte pas tout à fait l'objet, mais ++semble affecter le type sous-jacent
Callum Linington

8
Quelle ligne vous n'avez pas comprise? Prevs Postincrément? Après multiplication, c'est encore un Numberobjet qui n'a pas de wtfpropriété ... Mais c'est objectquand même un donc il peut avoir properties..
Rayon

25
Lorsque vous appelez disavec dis.call(5), cela enveloppe le numéro primitif 5dans un objet de type Numberqui contient le 5, afin que cet objet puisse être renvoyé en tant thisqu'objet. Le le ++reconvertit en un nombre primitif qui ne peut pas contenir de propriétés, donc wtfcommence à être indéfini.
GSerg


5
@Rayon ... et cela a changé avec ES6 . Donc, à l'avenir, tout cela ne se produira plus.
GSerg

Réponses:


278

OP ici. C'est drôle de voir ça sur Stack Overflow :)

Avant de passer à travers le comportement, il est important de clarifier certaines choses:

  1. La valeur numérique et l' objet numérique ( a = 3vs a = new Number(3)) sont très différents. L'un est un primitif, l'autre est un objet. Vous ne pouvez pas attribuer d'attributs à des primitives, mais vous le pouvez à des objets.

  2. La coercition entre les deux est implicite.

    Par exemple:

    (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. Chaque expression a une valeur de retour. Lorsque le REPL lit et exécute une expression, c'est ce qu'il affiche. Souvent, les valeurs de retour ne signifient pas ce que vous pensez et impliquent des choses qui ne sont tout simplement pas vraies.

OK allons y.

Image originale du code JavaScript

L'engagement.

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

Définissez une fonction diset appelez -la avec 5. Cela exécutera la fonction avec 5comme contexte ( this). Ici, il est contraint d'une valeur numérique à un objet numérique. Il est très important de noter que si nous étions en mode strict, cela ne serait pas arrivé .

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

Maintenant, nous définissons l'attribut five.wtfsur 'potato', et avec cinq comme objet, bien sûr, il accepte l' attribution simple .

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

Avec fivecomme objet, je m'assure qu'il peut toujours effectuer des opérations arithmétiques simples. Ça peut. Ses attributs persistent-ils? Oui.

Le tour.

> five++
5
> five.wtf
undefined

Maintenant, nous vérifions five++. L'astuce avec l' incrémentation de suffixe est que l'expression entière sera évaluée par rapport à la valeur d'origine , puis incrémentera la valeur. Il semble que ce fivesoit toujours cinq, mais vraiment l'expression évaluée à cinq, puis définie fivesur 6.

Non seulement a-t-il fiveété réglé sur 6, mais il a été contraint à nouveau dans une valeur numérique, et tous les attributs sont perdus. Comme les primitives ne peuvent pas contenir d'attributs, elles ne five.wtfsont pas définies.

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

J'essaie à nouveau de réaffecter un attribut wtfà five. La valeur de retour implique qu'elle colle, mais ce n'est pas le cas, car il fives'agit d'une valeur numérique et non d'un objet numérique. L'expression est évaluée 'potato?', mais lorsque nous vérifions, nous voyons qu'elle n'a pas été affectée.

Le prestige.

> five
6

Depuis l'incrément de suffixe, fivea été 6.


70
Regardez-vous de près?
Waddles

3
@Nathan Long Eh bien, en Java, dont JavaScript a beaucoup emprunté au début, toutes les primitives ont un équivalent de classe. intet Integer, par exemple. Je suppose que c'est pour que vous puissiez créer une fonction doSomething(Object)et pouvoir lui donner des primitives. Les primitives seront converties dans leurs classes correspondantes dans ce cas. Mais JS ne se soucie pas vraiment des types, donc la raison est probablement autre chose
Suppen

4
@Eric La première chose à ++faire est d'appliquer ToNumber à la valeur . Comparez un cas similaire avec des chaînes: si vous en avez x="5", x++retourne le nombre 5.
apsillers

2
Vraiment, la magie opère dis.call(5), la contrainte d'objecter, je ne m'y serais jamais attendu.
mcfedr

3
@gman, sauf qu'il existe de nombreuses influences beaucoup plus détaillées, notamment la dénomination de gros morceaux de sa bibliothèque standard, le comportement des types d'objet standard et même le fait qu'il utilise une syntaxe d'accolades et de points-virgules (même si les points-virgules sont facultatif) découle du fait qu'il a été conçu pour être familier aux programmeurs Java. Oui, c'est déroutant pour les débutants. Cela ne signifie pas que les influences n'existent pas.
Jules

77

Il existe deux façons différentes de représenter un nombre:

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

Le premier est une primitive, le second un objet. À toutes fins utiles, les deux se comportent de la même façon, sauf qu'ils sont différents lorsqu'ils sont imprimés sur la console. Une différence importante est que, en tant qu'objet, new Number(5)accepte de nouvelles propriétés comme n'importe quelle plaine {}, tandis que la primitive 5ne:

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

Quant à la dis.call(5)partie initiale , veuillez consulter Comment fonctionne le mot-clé "this"? . Disons simplement que le premier argument de callest utilisé comme valeur de thiset que cette opération force le nombre dans la Numberforme d'objet plus complexe . * Plus tard, il le ++force à revenir dans la forme primitive, car l'opération d'addition +entraîne une nouvelle primitive.

> 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 Numberobjet accepte de nouvelles propriétés.

> five++

++entraîne une nouvelle 6valeur primitive ...

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

... qui n'a pas et n'accepte pas d'attributs personnalisés.

* Notez qu'en mode strict, l' thisargument serait traité différemment et ne serait pas converti en a Number. Voir http://es5.github.io/#x10.4.3 pour les détails d'implémentation.


2
@Pharap bien sûr mieux en C / C ++ où vous pouvez le faire #define true false. Ou en Java, où vous pouvez simplement redéfinir la signification des nombres. Ce sont de bonnes langues solides. En vérité, chaque langue a des "astuces" similaires où vous obtenez un résultat qui fonctionne comme prévu mais qui peut sembler étrange.
VLAZ

1
@Vld Ok, laissez-moi reformuler cela, c'est pourquoi je déteste taper du canard.
Pharap

59

Il y a de la coercition dans le monde JavaScript - Une histoire de détective

Nathan, tu n'as aucune idée de ce que tu as découvert.

J'enquête depuis des semaines maintenant. Tout a commencé par une nuit orageuse en octobre dernier. Je suis tombé par hasard sur la Numberclasse - je veux dire, pourquoi dans le monde JavaScript avait-il une Numberclasse?

Je n'étais pas préparé à ce que j'allais découvrir ensuite.

Il s'avère que JavaScript, sans vous le dire, a changé vos nombres en objets et vos objets en nombres juste sous votre nez.

JavaScript espérait que personne n'accepterait, mais les gens ont signalé un comportement inattendu étrange, et maintenant grâce à vous et à votre question, j'ai les preuves dont j'ai besoin pour faire exploser cette chose.

C'est ce que nous avons découvert jusqu'à présent. Je ne sais pas si je devrais même vous le dire - vous voudrez peut-être désactiver votre JavaScript.

> function dis() { return this }
undefined

Lorsque vous avez créé cette fonction, vous n'aviez probablement aucune idée de ce qui allait se passer ensuite. Tout allait bien, et tout allait bien - pour l'instant.

Aucun message d'erreur, juste le mot "non défini" dans la sortie de la console, exactement ce que vous attendez. Après tout, c'était une déclaration de fonction - elle n'est pas censée retourner quoi que ce soit.

Mais ce n'était que le début. Que s'est-il passé ensuite, personne n'aurait pu prédire.

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

Oui, je sais, vous vous attendiez à un 5, mais ce n'est pas ce que vous avez obtenu, était-ce - vous avez quelque chose d'autre - quelque chose de différent.

La même chose m'est arrivée.

Je ne savais pas quoi en penser. Cela m'a rendu fou. Je ne pouvais pas dormir, je ne pouvais pas manger, j'ai essayé de le boire, mais aucune quantité de Mountain Dew ne me ferait oublier. Cela n'avait aucun sens!

C'est à ce moment-là que j'ai découvert ce qui se passait vraiment - c'était de la coercition, et cela se passait juste devant mes yeux, mais j'étais trop aveugle pour le voir.

Mozilla a essayé de l'enterrer en le mettant là où ils savaient que personne ne regarderait - leur documentation .

Après des heures de lecture récursive et de relecture et de relecture, j'ai trouvé ceci:

"... et les valeurs primitives seront converties en objets."

C'était clair, comme on peut le dire en police Open Sans. C'était la call()fonction - comment pouvais-je être aussi stupide?!

Mon numéro n'était plus du tout un numéro. Au moment où je l'ai passécall() , c'est devenu autre chose. C'est devenu ... un objet.

Je ne pouvais pas y croire au début. Comment cela pourrait-il être vrai? Mais je ne pouvais pas ignorer les preuves qui s'accumulaient autour de moi. C'est juste là si vous regardez juste:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtfétait juste. Les nombres ne peuvent pas avoir de propriétés personnalisées - nous le savons tous! C'est la première chose qu'ils t'enseignent à l'académie.

Nous aurions dû savoir au moment où nous avons vu la sortie de la console - ce n'était pas le nombre que nous pensions qu'il était. C'était un imposteur - un objet se faisant passer pour notre doux numéro innocent.

Ce fut ... new Number(5).

Bien sûr! C'était parfaitement logique. call()avait un travail à faire, il devait invoquer une fonction, et pour ce faire, il avait besoin de remplir this, il savait qu'il ne pouvait pas le faire avec un nombre - il avait besoin d'un objet et il était prêt à tout pour l'obtenir, même si cela signifiait contraindre notre numéro. Quand il a call()vu le nombre 5, il a vu une opportunité.

C'était le plan parfait: attendez que personne ne regarde et échangez notre numéro contre un objet qui lui ressemble. Nous obtenons un nombre, la fonction est invoquée et personne ne serait plus sage.

C'était vraiment le plan parfait, mais comme tous les plans, même parfaits, il y avait un trou dedans, et nous étions sur le point de tomber dedans.

Vous voyez, ce call()qui ne comprenait pas, c'est qu'il n'était pas le seul en ville à pouvoir contraindre les numéros. C'était du JavaScript après tout - la coercition était partout.

call() a pris mon numéro, et je n'allais pas m'arrêter avant d'avoir retiré le masque de son petit imposteur et de l'exposer à toute la communauté Stack Overflow.

Mais comment? J'avais besoin d'un plan. Bien sûr, cela ressemble à un nombre, mais je sais que ce n'est pas le cas, il doit y avoir un moyen de le prouver. C'est tout! Il ressemble à un nombre, mais peut-il agir comme tel?

J'ai dit que fivej'avais besoin qu'il devienne 5 fois plus gros - il n'a pas demandé pourquoi et je n'ai pas expliqué. J'ai ensuite fait ce que tout bon programmeur ferait: je me suis multiplié. Il n'y avait sûrement aucun moyen de faire semblant de s'en sortir.

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

Bon sang! Non seulement fivemultiplier très bien wtfétait toujours là. Merde ce gars et sa pomme de terre.

Que diable se passait-il? Avais-je tort sur tout ça? Est-ce fivevraiment un nombre? Non, je dois manquer quelque chose, je le sais, il y a quelque chose que je dois oublier, quelque chose de si simple et basique que je l'oublie complètement.

Cela n'avait pas l'air bien, j'avais écrit cette réponse pendant des heures et je n'étais pas encore plus près de faire valoir mon point de vue. Je ne pouvais pas continuer ainsi, finalement les gens cessaient de lire, je devais penser à quelque chose et je devais y penser rapidement.

Attendez c'est tout! fiven'était pas 25, 25 était le résultat, 25 était un nombre complètement différent. Bien sûr, comment pourrais-je oublier? Les nombres sont immuables. Lorsque vous multipliez 5 * 5rien n'est attribué à quoi que ce soit, vous créez simplement un nouveau numéro 25.

Ce doit être ce qui se passe ici. D'une manière ou d'une autre, lorsque je multiplie five * 5, je fivedois être contraint à un nombre et ce nombre doit être celui utilisé pour la multiplication. Ce sont les résultats de cette multiplication qui sont imprimés sur la console, pas la valeur d' fiveelle - même. fivene se voit jamais rien attribuer - donc bien sûr, cela ne change pas.

Alors, comment puis-je fivem'attribuer le résultat d'une opération. J? ai compris. Avant fivemême d'avoir eu la chance de réfléchir, j'ai crié "++".

> five++
5

Ah! Je l'ai eu! Tout le monde sait 5 + 1est 6, ce fut la preuve que je devais exposer ce fiven'était pas un numéro! C'était un imposteur! Un mauvais imposteur qui ne savait pas compter. Et je pourrais le prouver. Voici comment agit un nombre réel:

> num = 5
5
> num++
5

Attendre? Que se passait-il ici? soupir, je me suis tellement retrouvé pris au piège fiveque j'oublie comment fonctionnent les opérateurs de poste. Lorsque j'utilise le ++à la fin de fiveje dis retourner la valeur actuelle, puis incrémenter five. C'est la valeur avant l'opération qui est imprimée sur la console. numétait en fait 6et je pourrais le prouver:

>num
6

Il était temps de voir ce fivequi était vraiment:

>five
6

... c'était exactement ce que ça devait être. fiveétait bon - mais j'étais mieux. Si fivec'était encore un objet, cela signifierait qu'il aurait toujours la propriété wtfet j'étais prêt à parier tout ce qu'il n'avait pas.

> five.wtf
undefined

Ah! J'avais raison. Je l'ai eu! fiveétait un nombre maintenant - ce n'était plus un objet. Je savais que l'astuce de multiplication ne la sauverait pas cette fois. Voir five++c'est vraiment five = five + 1. Contrairement à la multiplication, l' ++opérateur attribue une valeur à five. Plus précisément, il lui attribue les résultats five + 1dont tout comme dans le cas de la multiplication renvoie un nouveau nombre immuable .

Je savais que je l'avais, et juste pour m'assurer qu'il ne pouvait pas se frayer un chemin pour en sortir. J'ai eu un test de plus dans ma manche. Si j'avais raison et que j'étais fivevraiment un nombre maintenant, alors cela ne fonctionnerait pas:

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

Il n'allait pas me tromper cette fois. Je savais que ça potato?allait être imprimé sur la console parce que c'est la sortie de l'affectation. La vraie question est, sera wtftoujours là?

> five.wtf
undefined

Tout comme je le soupçonnais - rien - car les numéros ne peuvent pas être affectés à des propriétés. Nous avons appris que la première année à l'académie;)

Merci Nathan. Grâce à votre courage en posant cette question, je peux enfin mettre tout cela derrière moi et passer à un nouveau cas.

Comme celui-ci sur la fonction toValue(). Oh mon Dieu. Nooon!


9
Oubliez ça Jake; c'est Javascript.
Seth

Pourquoi appelons-nous les fonctions constructeurs "classes" dans 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

01déclare une fonction disqui renvoie l'objet contextuel. Ce qui thisreprésente des changements selon que vous utilisez ou non le mode strict. L'exemple entier a des résultats différents si la fonction a été déclarée comme:

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

Ceci est détaillé dans la section 10.4.3 de la spécification ES5

  1. Si le code de fonction est un code strict, définissez ThisBinding sur thisArg.
  2. Sinon, si thisArg est nul ou non défini, définissez ThisBinding sur l'objet global.
  3. Sinon, si Type (thisArg) n'est pas Object, définissez ThisBinding sur ToObject (thisArg).

02est la valeur de retour de la déclaration de fonction. undefineddevrait être explicite ici.

03la variable fiveest initialisée avec la valeur de retour dislorsqu'elle est appelée dans le contexte de la valeur primitive 5. Parce qu'elle disn'est pas en mode strict, cette ligne est identique à l'appel five = Object(5).

04La Number {[[PrimitiveValue]]: 5}valeur de retour impair est la représentation de l'objet qui enveloppe la valeur primitive5

05la propriété de l' fiveobjet se wtfvoit attribuer une valeur de chaîne de'potato'

06 est la valeur de retour de l'affectation et doit être explicite.

07la propriété de l' fiveobjet wtfest en cours d'examen

08comme il five.wtfétait précédemment défini, 'potato'il revient 'potato'ici

09l' fiveobjet est multiplié par la valeur primitive 5. Ceci n'est pas différent de tout autre objet en cours de multiplication et est expliqué dans la section 11.5 de la spécification ES5 . Il est particulièrement important de savoir comment les objets sont convertis en valeurs numériques, ce qui est couvert dans quelques sections.

9.3 ToNumber :

  1. Soit primValue ToPrimitive (argument d'entrée, indice Number).
  2. Retournez àNumber (primValue).

9.1 ToPrimitive :

Renvoie une valeur par défaut pour l'objet. La valeur par défaut d'un objet est récupérée en appelant la méthode interne [[DefaultValue]] de l'objet, en passant l'indicateur facultatif PreferredType. Le comportement de la méthode interne [[DefaultValue]] est défini par cette spécification pour tous les objets ECMAScript natifs en 8.12.8 .

8.12.8 [[DefaultValue]] ::

Soit valueOf le résultat de l'appel de la méthode interne [[Get]] de l'objet O avec l'argument "valueOf".

  1. Si IsCallable (valueOf) est vrai, alors,

    1. Soit val le résultat de l'appel de la méthode interne [[Call]] de valueOf, avec O comme valeur this et une liste d'arguments vide.
    2. Si val est une valeur primitive, retournez val.

C'est tout un moyen détourné de dire que la valueOffonction de l'objet est appelée et que la valeur de retour de cette fonction est utilisée dans l'équation. Si vous deviez changer la valueOffonction, vous pourriez changer les résultats de l'opération:

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

10comme la fonction fives valueOfest restée inchangée, elle retourne la valeur primitive encapsulée de 5façon à ce qu'elle five * 5évalue les 5 * 5résultats dans25

11la propriété de l' fiveobjet wtfest à nouveau évaluée même si elle n'a pas été modifiée depuis son affectation 05.

12 'potato'

13l' opérateur d'incrémentation Postfix est appelé five, qui obtient la valeur numérique ( 5, nous avons expliqué comment précédemment), stocke la valeur afin qu'elle puisse être renvoyée, ajoute 1à la valeur ( 6), attribue la valeur à fiveet renvoie la valeur stockée ( 5)

14 comme précédemment, la valeur renvoyée est la valeur avant son incrémentation

15on accède à la wtfpropriété de la valeur primitive ( 6) stockée dans la variable five. La section 15.7.5 de la spécification ES5 définit ce comportement. Les nombres obtiennent les propriétés de Number.prototype.

16 Number.prototypen'a pas de wtfpropriété, undefinedest donc retourné

17 five.wtfse voit attribuer une valeur de 'potato?'. L'affectation est définie au 11.13.1 de la spécification ES5 . Fondamentalement, la valeur attribuée est renvoyée mais pas stockée.

18 'potato?' a été retourné par l'opérateur d'affectation

19encore une fois five, qui a une valeur de 6est accessible, et encore une fois Number.prototypen'a pas de wtfpropriété

20 undefined comme expliqué ci-dessus

21 five est accessible

22 6 est retourné comme expliqué dans 13


17

C'est assez simple.

function dis () { return this; }

Cela renvoie le thiscontexte. Donc, si vous le faitescall(5) vous passez le numéro en tant qu'objet.

La callfonction ne fournit pas d'arguments, le premier argument que vous donnez est le contexte de this. En général , si vous le voulez sur c'est le contexte, vous lui donnez {}donc dis.call({}), ces moyens thisdans la fonction est un vide this. Cependant, si vous réussissez, 5il semble qu'il sera converti en objet. Voir .call

Donc, le retour est object

Lorsque vous le faites five * 5, JavaScript voit l'objet fivecomme le type primitif, est donc équivalent à 5 * 5. Fait intéressant, faites-le '5' * 5, c'est toujours égal 25, donc JavaScript est clairement en train de couler sous le capot. Aucune modification du fivetype sous-jacent n'est effectuée sur cette ligne

Mais lorsque vous le faites, ++il convertira l'objet en numbertype primitif , supprimant ainsi la .wtfpropriété. Parce que vous affectez le type sous-jacent


Le retour est Number .
GSerg

++le convertit et le réattribue. ++est égal à variable = variable + 1. Alors tu perdswtf
Rajesh

Le *vs ++ne me confond pas du tout. Avoir une fonction qui n'attend aucun argument et retourne this, avoir cette fonction accepter un argument de toute façon et retourner quelque chose qui est comme, mais qui n'est pas exactement, l'argument - cela n'a aucun sens pour moi.
Nathan Long

Mis à jour @NathanLong pour montrer ce qui se dis.call()passe.
Callum Linington

5 ++ se comporte de la même manière qu'en langage C, donc rien de surprenant. thisest simplement un pointeur vers un objet afin que les types primitifs soient convertis implicitement. Pourquoi une fonction sans argument ne pourrait pas avoir au moins un contexte? Le 1er argument de callor bindest utilisé pour définir le contexte. En outre, les fonctions sont des fermetures, ce qui signifie qu'elles ont accès à plus que justearguments
Rivenfall

10

Les valeurs primitives ne peuvent pas avoir de propriété. Mais lorsque vous essayez d'accéder à une propriété sur une valeur primitive, il transtype de manière transparente en un objet Number temporaire.

Alors:

> 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

Déclarez la fonction dis. La fonction renvoie son contexte

function dis() { return this }
undefined

Appelez disavec le contexte 5. Les valeurs primitives sont encadrées lorsqu'elles sont passées en tant que contexte en mode strict ( MDN ). Alors fivemaintenant, c'est l'objet (numéro encadré).

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

Déclarez la wtfpropriété sur la fivevariable

five.wtf = 'potato'
"potato"

Valeur de five.wtf

five.wtf
"potato"

fiveest encadré 5, c'est donc le nombre et l'objet en même temps (5 * 5 = 25). Cela ne change pas five.

five * 5
25

Valeur de five.wtf

five.wtf
"potato"

Déballage fiveici. fiveest maintenant tout simplement primitif number. Il imprime 5, puis ajoute 1à five.

five++
5

fiveest un nombre primitif 6maintenant, il n'y a aucune propriété.

five.wtf
undefined

les primitives ne peuvent pas avoir de propriétés, vous ne pouvez pas définir cela

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

vous ne pouvez pas lire ceci, car il n'a pas été défini

five.wtf
undefined

fiveest à 6cause de l'incrémentation ci-dessus

five
6

7

Tout d'abord, il semble que cela soit exécuté via la console nodejs.

1.

    function dis() { return this }

crée la fonction dis (), mais comme elle n'était pas définie en tant que, varil n'y avait aucune valeur à renvoyer, tout undefinedcomme la sortie, même si elle dis()était définie. Sur un sidenote, thisn'a pas été renvoyé car la fonction n'a pas été exécutée.

2.

    five = dis.call(5)

Ceci retourne javascript Numberl'objet que vous définissez juste la fonction dis()de la thisvaleur à cinq primitive.

3.

   five.wtf = 'potato'

La première renvoie "potato"car vous venez de définir la propriété wtfde fiveà 'potato'. Javascript retourne la valeur d'une variable que vous définissez, le rendant facile à la chaîne plusieurs variables et les mettre à la même valeur comme ceci: a = b = c = 2.

4.

    five * 5

Cela revient 25parce que vous venez de multiplier le nombre primitif 5à five. La valeur de a fiveété déterminée par la valeur de l' Numberobjet.

5.

    five.wtf

J'ai sauté cette ligne avant parce que je la répéterais ici. Il renvoie simplement la valeur de la propriété wtfque vous avez définie ci-dessus.

6.

    five++

Comme l'a dit @Callum, ++convertira le type à numberpartir de la même valeur de l'objet Number {[[PrimitiveValue]]: 5}}.

Maintenant, parce que fiveest un number, vous ne pouvez plus lui attribuer de propriétés tant que vous n'avez pas fait quelque chose comme ceci:

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

ou

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

Notez également que la deuxième méthode aura un comportement différent de celui de la première méthode, car elle définit un objet générique au lieu de l' Numberobjet créé auparavant.

J'espère que cela aide, javascript aime assumer les choses, donc cela peut devenir déroutant lors du passage de l' Numberobjet à une primitive number. Vous pouvez vérifier le type de quelque chose en utilisant le typeofmot - clé, en écrivant typeof five après l'avoir initialisé 'object'et après l'avoir five++fait 'number'.

@deceze décrit extrêmement bien la différence entre l'objet Number et le nombre primitif.


6

Les étendues JavaScript sont constituées de contextes d'exécution. Chaque contexte d'exécution a un environnement lexical (valeurs de portée externe / globale), un environnement variable (valeurs de portée locale) et une cette liaison .

La présente liaison est une partie très importante du contexte d'exécution. L'utilisation callest une façon de modifier cette liaison , et cela créera automatiquement un objet avec lequel remplir la liaison.

Function.prototype.call () (de MDN)

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

thisArg
La valeur de ceci a fourni l'appel à l'amusement. Notez que cela peut ne pas être la valeur réelle vue par la méthode: si la méthode est une fonction en code de mode non strict, null et undefined seront remplacés par l'objet global et les valeurs primitives seront converties en objets . (c'est moi qui souligne)

Une fois qu'il est évident que 5 est en cours de conversion new Number(5), le reste devrait être assez évident. Notez que d'autres exemples fonctionneront également tant qu'ils sont des valeurs primitives.

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" />

entrez la description de l'image ici


4

Quelques concepts expliquent ce qui se passe

5 est un nombre, une valeur primitive

Number {[[PrimitiveValue]]: 5} est une instance de Number (appelons-le wrapper d'objet)

Chaque fois que vous accédez à une propriété / méthode sur une valeur primitive, le moteur JS crée un wrapper d'objet du type approprié ( Numberpour 5, Stringpour 'str'et Booleanpour true) et résout l'appel d'accès à la propriété / méthode sur ce wrapper d'objet. C'est ce qui se passe quand on le fait true.toString()par exemple.

Lors de l'exécution d'opérations sur des objets, ils sont convertis en valeurs primitives (en utilisant toStringou valueOf) afin de résoudre ces opérations - par exemple lors de l'exécution

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

stringcontiendra la concaténation de chaînes de mystret obj.toString()et numbermaintiendra l'addition de 3et obj.valueOf().

Maintenant, pour tout mettre ensemble

five = dis.call(5)

dis.call(5)se comporte comme (5).dis()s'il 5avait réellement la méthode dis. Afin de résoudre l'appel de méthode, l'encapsuleur d'objet est créé et l'appel de méthode est résolu sur celui-ci. À ce stade, cinq points sur un wrapper d'objet autour de la valeur primitive 5.

five.wtf = 'potato'

Définir une propriété sur un objet, rien de spécial ici.

five * 5

Il s'agit en fait d' five.valueOf() * 5obtenir la valeur primitive du wrapper d'objet. fivepointe toujours sur l'objet initial.

five++

C'est en fait five = five.valueOf() + 1. Avant cette ligne fivecontient le wrapper objet autour de la valeur 5, tandis qu'après ce point fivecontient la valeur primitive6 .

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

fiven'est plus un objet. Chacune de ces lignes crée une nouvelle instance de Number afin de résoudre l' .wtfaccès à la propriété. Les instances sont indépendantes, donc définir une propriété sur l'une ne sera pas visible sur une autre. Le code est complètement équivalent à celui-ci:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.