247 votes

Que se passe-t-il dans ce code avec les objets Number qui détiennent des propriétés et qui incrémentent le nombre ?

Un tweet récent contenait cet extrait de JavaScript.

Quelqu'un peut-il expliquer ce qui se passe étape par étape ?

> 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, je n'y vois pas clair :

  • pourquoi le résultat de dis.call(5) est un Number avec une sorte de [[PrimitiveValue]] mais les résultats de la propriété five++ y five * 5 semblent être des chiffres simples 5 y 25 (pas Number s)
  • pourquoi le five.wtf disparaît après que la propriété five++ incrément
  • pourquoi le five.wtf n'est même plus paramétrable après que la propriété five++ Malgré l'augmentation de la five.wtf = 'potato?' l'affectation apparemment la définition de la valeur.

278voto

Matthew Points 2520

OP ici. Il est amusant de voir cela sur Stack Overflow :)

Avant d'aborder le comportement, il est important de clarifier quelques points :

  1. Valeur numérique y Objet numérique ( a = 3 vs a = new Number(3) ) sont très différents. L'un est une primitive, l'autre un objet. Il n'est pas possible d'attribuer des attributs aux primitives, mais on peut le faire pour les 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. Tous les Expression a une valeur de retour. Lorsque le REPL lit et exécute une expression, voici ce qu'il affiche. Les valeurs de retour ne signifient souvent pas ce que vous pensez et impliquent des choses qui ne sont tout simplement pas vraies.

Ok, c'est parti.

Original image of the JavaScript code

L'engagement.

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

Définir une fonction dis y appel avec 5 . Ceci exécutera la fonction avec 5 comme contexte ( this ). Ici, il est forcé de passer d'une valeur numérique à un objet numérique. Il est très important de noter que si nous étions en mode strict cela n'aurait pas eu lieu .

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

Nous définissons maintenant l'attribut five.wtf à 'potato' et, avec cinq comme objet, il accepte bien sûr le Affectation simple .

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

Avec five en tant qu'objet, je m'assure qu'il peut toujours effectuer des opérations arithmétiques simples. Il peut Ses attributs sont-ils toujours valables ? Oui.

Le tour.

> five++
5
> five.wtf
undefined

Nous vérifions maintenant five++ . L'astuce avec incrément postfixe est que l'expression entière sera évaluée par rapport à la valeur d'origine et ensuite incrémenter la valeur. Voici ce que cela donne five est toujours cinq, mais en réalité l'expression a été évaluée à cinq, alors mettre five à 6 .

Non seulement les five est fixé à 6 mais il a été transformé en valeur numérique, et tous les attributs sont perdus. Puisque les primitives ne peuvent pas contenir d'attributs, five.wtf n'est pas défini.

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

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

Le prestige.

> five
6

Depuis l'incrémentation du postfixe, five a été 6 .

78voto

deceze Points 200115

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 manière, sauf qu'ils ont un aspect différent 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 quel {} , tandis que la primitive 5 ne le fait pas :

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

En ce qui concerne la première dis.call(5) veuillez consulter Comment fonctionne le mot-clé "this" ? . Disons simplement que le premier argument de call est utilisée comme valeur de this et que cette opération force le nombre à devenir plus complexe. Number Formulaire d'objet* Plus tard ++ le ramène à la forme primitive, car l'opération d'addition + donne lieu à 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"

A Number accepte de nouvelles propriétés.

> five++

++ aboutit à une nouvelle primitive 6 valeur...

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

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

* Il est à noter qu'en mode strict les this l'argument serait traité différemment et serait pas est converti en un Number . Voir http://es5.github.io/#x10.4.3 pour les détails de la mise en œuvre.

59voto

luisperezphd Points 3220

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.

Cela fait des semaines que j'enquête sur ce sujet. Tout a commencé par une nuit d'orage en octobre dernier. Je suis tombé par hasard sur le Number Je veux dire, pourquoi diable JavaScript avait-il une classe de Number classe ?

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

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

JavaScript espérait que personne ne s'en apercevrait, mais des gens ont signalé des comportements étranges et inattendus, et maintenant, grâce à vous et à votre question, j'ai les preuves dont j'ai besoin pour faire éclater cette affaire au grand jour.

Voici ce que nous avons découvert jusqu'à présent. Je ne sais pas si je devrais même vous dire cela - vous devriez 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 avait l'air bien, et tout allait bien - pour l'instant.

Aucun message d'erreur, juste le mot "undefined" dans la sortie de la console, exactement ce à quoi on pouvait s'attendre. Après tout, il s'agit d'une déclaration de fonction, qui n'est pas censée renvoyer quoi que ce soit.

Mais ce n'était que le début. Personne n'aurait pu prédire ce qui s'est passé ensuite.

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

Oui, je sais, vous vous attendiez à une 5 Mais ce n'est pas ce que vous avez obtenu, n'est-ce pas ? Vous avez obtenu 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'essayais de boire, mais aucune quantité de Mountain Dew ne pouvait me faire oublier. Cela n'avait aucun sens !

C'est à ce moment-là que j'ai découvert ce qui se passait réellement - il s'agissait de 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 plaçant là où ils savaient que personne ne le regarderait - leur site web. la documentation .

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

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

C'était là, en toutes lettres, en police Open Sans. C'était le call() fonction - comment ai-je pu être aussi stupide ?

Mon numéro n'était plus du tout un numéro. Dès que je l'ai transmis à call() il est devenu autre chose. Il est devenu... un objet.

Je n'arrivais pas à y croire au début. Comment cela pouvait-il être vrai ? Mais je ne pouvais pas ignorer les preuves qui s'accumulaient autour de moi. Il suffit de regarder pour s'en rendre compte :

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtf avait raison. Les numéros ne peuvent pas avoir de propriétés personnalisées - nous le savons tous ! C'est la première chose que l'on apprend à l'académie.

Nous aurions dû le savoir dès que nous avons vu la sortie de la console - ce n'était pas le nombre que nous pensions qu'il était. Il s'agissait d'un imposteur, d'un objet qui se faisait passer pour notre doux et innocent numéro.

C'était... new Number(5) .

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

C'était le plan parfait : attendre que personne ne regarde et échanger notre numéro contre un objet qui lui ressemble. Nous obtenons un numéro, la fonction est invoquée et personne ne s'en aperçoit.

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

Voir, ce que call() ne comprenait pas qu'il n'était pas le seul en ville à pouvoir contraindre les chiffres. Après tout, il s'agissait de JavaScript - la coercition était omniprésente.

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

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

J'ai dit five J'ai besoin qu'il devienne 5 fois plus grand - il n'a pas demandé pourquoi et je n'ai pas expliqué. J'ai alors fait ce que tout bon programmeur ferait : J'ai multiplié. Il n'y avait aucune chance qu'il puisse faire semblant de s'en sortir.

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

Bon sang ! Non seulement five se multiplient très bien wtf était toujours là. Maudit soit ce type et sa pomme de terre.

Qu'est-ce qui se passe ? Est-ce que je me suis trompé ? Est-ce que five vraiment un numéro ? Non, il doit me manquer quelque chose, je le sais, il y a quelque chose que je dois oublier, quelque chose de si simple et de si basique que je l'ignore complètement.

J'écrivais cette réponse depuis des heures et je n'étais toujours pas parvenu à faire valoir mon point de vue. Je ne pouvais pas continuer ainsi, les gens finiraient par arrêter de lire, il fallait que je trouve quelque chose et vite.

Attendez, c'est ça ! five n'é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 * 5 rien n'est attribué à quoi que ce soit vous créez simplement un nouveau numéro 25 .

C'est sûrement ce qui se passe ici. D'une manière ou d'une autre, lorsque je multiplie five * 5 , five doit être transformé en un nombre et ce nombre doit être celui utilisé pour la multiplication. C'est le résultat de cette multiplication qui est imprimé sur la console, et non la valeur de five même. five ne se voit jamais attribuer quoi que ce soit - il ne change donc évidemment pas.

Comment puis-je obtenir five de s'attribuer le résultat d'une opération. J'ai compris. Avant five J'ai eu le temps de réfléchir et j'ai crié "++".

> five++
5

Aha ! Je l'ai eu ! Tout le monde le sait 5 + 1 es 6 C'était la preuve dont j'avais besoin pour mettre en évidence le fait que le five n'était pas un numéro ! C'était un imposteur ! Un mauvais imposteur qui ne savait pas compter. Et je pouvais le prouver. Voici comment se comporte un vrai nombre :

> num = 5
5
> num++
5

Attendez, qu'est-ce qui se passe ici ? soupir J'ai été tellement pris par l'envie d'en découdre five que j'oublie le fonctionnement des opérateurs postaux. Lorsque j'utilise l'opérateur ++ à la fin de five Je dis qu'il faut renvoyer la valeur actuelle, puis l'incrémenter five . C'est la valeur avant l'opération se produit et est imprimée sur la console. num était en fait 6 et je pourrais le prouver :

>num
6

Il est temps de voir ce que five était vraiment :

>five
6

...c'était exactement ce qu'il fallait. five était bon - mais j'étais meilleur. Si five était toujours un objet, cela signifierait qu'il aurait toujours la propriété wtf et j'étais prêt à parier tout ce qu'il n'a pas fait.

> five.wtf
undefined

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

Je savais que je le tenais, et je voulais m'assurer qu'il ne pourrait pas se tortiller pour s'en sortir. J'avais un autre test dans ma manche. Si j'avais raison, et five était vraiment un nombre, cela ne fonctionnerait pas :

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

Il n'allait pas me tromper cette fois-ci. Je savais que potato? allait être imprimée sur la console parce que c'est la sortie de l'affectation. La vraie question est de savoir si wtf toujours là ?

> five.wtf
undefined

Comme je m'en doutais, il n'y a rien, car les numéros ne peuvent pas être attribués à des propriétés. Nous avons appris cela la première année à l'académie ;)

Merci Nathan. Grâce au courage dont vous avez fait preuve en posant cette question, je peux enfin mettre tout cela derrière moi et passer à une nouvelle affaire.

Comme celle concernant la fonction toValue() . Oh, mon Dieu. Noooon !

28voto

zzzzBov Points 62084
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

01 déclare une fonction dis qui renvoie l'objet contextuel. Ce que this représente des changements selon que vous utilisez le mode strict ou non. L'ensemble de l'exemple donne des résultats différents si la fonction est déclarée comme :

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

Ce point est détaillé dans le document section 10.4.3 de la spécification ES5

  1. Si le code de la fonction est un code strict, définir le ThisBinding sur thisArg.
  2. Dans le cas contraire, si thisArg est nul ou indéfini, le ThisBinding est défini comme étant l'objet global.
  3. Sinon, si Type(thisArg) n'est pas Object, définir ThisBinding à ToObject(thisArg).

02 est la valeur de retour de la déclaration de la fonction. undefined devrait s'expliquer d'elle-même.

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

04 L'étrange Number {[[PrimitiveValue]]: 5} la valeur de retour est la représentation de l'objet qui enveloppe la valeur primitive 5

05 les five de l'objet wtf se voit attribuer une valeur de chaîne de caractères de 'potato'

06 est la valeur de retour de l'affectation et devrait s'expliquer d'elle-même.

07 les five de l'objet wtf le bien est en cours d'examen

08 comme five.wtf était précédemment fixé à 'potato' il renvoie 'potato' aquí

09 les five est multiplié par la valeur primitive 5 . Ce phénomène n'est pas différent de celui de la multiplication de tout autre objet et est expliqué dans le document section 11.5 de la spécification ES5 . La façon dont les objets sont transformés en valeurs numériques est particulièrement intéressante et sera abordée dans quelques sections.

9.3 ToNumber :

  1. Let primValue be ToPrimitive(input argument, hint Number).
  2. Retourner ToNumber(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'indication facultative PreferredType. Le comportement de la méthode interne [[DefaultValue]] est défini par la présente spécification pour tous les objets ECMAScript natifs en 8.12.8 .

8.12.8 [[Valeur par défaut]] :

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 à la méthode interne [[Call]] de valueOf, avec O comme valeur et une liste d'arguments vide.
    2. Si val est une valeur primitive, retourner val.

Il s'agit d'une manière détournée de dire que l'objet valueOf est appelée et la valeur de retour de cette fonction est utilisée dans l'équation. Si vous deviez changer la fonction valueOf vous pourriez modifier les résultats de l'opération :

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

10 comme five s valueOf était inchangée, elle renvoie la valeur primitive enveloppée 5 de sorte que five * 5 s'évalue à 5 * 5 qui se traduit par 25

11 les five de l'objet wtf est évaluée à nouveau bien qu'elle soit restée inchangée par rapport à la date de son attribution le 05 .

12 'potato'

13 les Opérateur d'incrémentation postfixe est appelé sur five qui obtient la valeur numérique ( 5 (nous avons vu plus haut comment), stocke la valeur pour qu'elle puisse être renvoyée, ajoute 1 à la valeur ( 6 ), attribue la valeur à five et renvoie la valeur stockée ( 5 )

14 comme précédemment, la valeur retournée est la valeur avant qu'elle ne soit incrémentée

15 les wtf de la valeur primitive ( 6 ) stockée dans la variable five est accessible. Section 15.7.5 de la spécification ES5 définit ce comportement. Les numéros obtiennent les propriétés de Number.prototype .

16 Number.prototype n'a pas de wtf donc undefined est renvoyée

17 five.wtf se voit attribuer une valeur de 'potato?' . L'affectation est définie au point 11.13.1 de la spécification ES5 . En principe, la valeur attribuée est renvoyée mais n'est pas stockée.

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

19 à nouveau five qui a une valeur de 6 est consulté, et à nouveau Number.prototype n'a pas de wtf propriété

20 undefined comme expliqué ci-dessus

21 five est accessible

22 6 est renvoyée comme expliqué dans 13

18voto

No1_Melman Points 1516

C'est très simple.

function dis () { return this; }

Cette opération renvoie le this contexte. Ainsi, si vous faites call(5) vous transmettez le nombre en tant qu'objet.

En call ne fournit pas d'arguments, le premier argument que vous donnez est le contexte de la fonction this . Habituellement, si vous voulez qu'il soit dans le contexte, vous le donnez. {} donc dis.call({}) ce qui signifie this dans la fonction est un this . Toutefois, si vous passez 5 il semble qu'il sera converti en objet. Voir aussi .appel

Le rendement est donc de object

Lorsque vous le faites five * 5 JavaScript voit l'objet five comme type primitif, ce qui équivaut à 5 * 5 . Il est intéressant de noter que les '5' * 5 , il est toujours égal à 25 Le JavaScript est donc bien présent sous le capot. Pas de changement au niveau du sous-jacent five se fait sur cette ligne

Mais lorsque vous le faites ++ il convertira l'objet en une primitive number supprimant ainsi le type .wtf propriété. Parce que vous affectez le type sous-jacent

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X