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!
++semble affecter le type sous-jacent