Há coerção no mundo do JavaScript - uma história de detetive
Nathan, você não tem ideia do que descobriu.
Estou investigando isso há semanas. Tudo começou em uma noite de tempestade em outubro passado. Eu acidentalmente tropecei na Numberaula - quero dizer, por que diabos o JavaScript tinha uma Numberaula?
Eu não estava preparado para o que iria descobrir a seguir.
Acontece que o JavaScript, sem dizer a você, alterou seus números para objetos e seus objetos para números logo abaixo do seu nariz.
O JavaScript esperava que ninguém entendesse, mas as pessoas têm relatado um comportamento estranho e inesperado, e agora, graças a você e à sua pergunta, tenho a evidência de que preciso para estourar tudo isso.
Foi isso que descobrimos até agora. Eu não sei se eu deveria estar dizendo isso a você. Você pode desativar o JavaScript.
> function dis() { return this }
undefined
Quando você criou essa função, provavelmente não tinha ideia do que aconteceria a seguir. Tudo parecia bem, e tudo estava bem - por enquanto.
Nenhuma mensagem de erro, apenas a palavra "indefinido" na saída do console, exatamente o que você esperaria. Afinal, essa era uma declaração de função - não deveria retornar nada.
Mas isso foi apenas o começo. O que aconteceu depois, ninguém poderia ter previsto.
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
Sim, eu sei, você esperava um 5, mas não foi isso que você conseguiu, foi - você conseguiu outra coisa - algo diferente.
A mesma coisa aconteceu comigo.
Eu não sabia o que fazer disso. Isso me deixou louco. Eu não conseguia dormir, não conseguia comer, tentei beber, mas nenhuma quantidade de Mountain Dew me faria esquecer. Simplesmente não fazia sentido!
Foi quando eu descobri o que realmente estava acontecendo - era coerção, e estava acontecendo bem diante dos meus olhos, mas eu estava cego demais para vê-lo.
A Mozilla tentou enterrá-lo, colocando-o onde eles sabiam que ninguém iria olhar - sua documentação .
Após horas lendo e relendo e relendo recursivamente, encontrei o seguinte:
"... e valores primitivos serão convertidos em objetos."
Estava bem claro, como pode ser escrito na fonte Open Sans. Era a call()função - como eu poderia ser tão estúpido ?!
Meu número não era mais um número. No momento em que passei call(), tornou-se outra coisa. Tornou-se ... um objeto.
Eu não pude acreditar no começo. Como isso pode ser verdade? Mas eu não podia ignorar as evidências que estavam se acumulando ao meu redor. Está aí se você apenas olhar:
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
wtfestava certo. Os números não podem ter propriedades personalizadas - todos sabemos isso! É a primeira coisa que eles ensinam na academia.
Deveríamos saber o momento em que vimos a saída do console - este não era o número que pensávamos. Era um impostor - um objeto que se passava como nosso doce número inocente.
Este foi ... new Number(5).
Claro! Fazia todo o sentido. call()tinha um trabalho a fazer, tinha que invocar uma função e, para fazer isso, precisava preencher this, sabia que não podia fazer isso com um número - precisava de um objeto e estava disposto a fazer qualquer coisa para obtê-lo, até se isso significava coagir nosso número. Quando call()viu o número 5, viu uma oportunidade.
Era o plano perfeito: espere até que ninguém olhe e troque nosso número por um objeto parecido com ele. Nós obtemos um número, a função é invocada e ninguém seria o mais sábio.
Realmente era o plano perfeito, mas como todos os planos, mesmo os perfeitos, havia um buraco nele, e estávamos prestes a cair nele.
Veja, o call()que não entendia era que ele não era o único na cidade que conseguia coagir números. Afinal, isso era JavaScript - coerção estava em toda parte.
call() peguei meu número e não pararia até tirar a máscara de seu pequeno impostor e expô-lo a toda a comunidade Stack Overflow.
Mas como? Eu precisava de um plano. Claro que parece um número, mas sei que não, tem que haver uma maneira de provar isso. É isso aí! Ele parece como um número, mas pode agir como um?
Eu disse fiveque precisava que ele ficasse 5 vezes maior - ele não perguntou o porquê e eu não expliquei. Fiz então o que qualquer bom programador faria: eu me multipliquei. Certamente não havia como ele conseguir escapar disso.
> five * 5
25
> five.wtf
'potato'
Droga! Não só fivemultiplicar muito bem wtfainda estava lá. Maldito esse cara e sua batata.
Que diabos estava acontecendo? Eu estava errado sobre essa coisa toda? É fiverealmente um número? Não, devo estar perdendo alguma coisa, eu sei, há algo que devo estar esquecendo, algo tão simples e básico que estou completamente ignorando.
Isso não parecia bom, eu estava escrevendo essa resposta por horas e ainda não estava mais perto de fazer o meu argumento. Eu não conseguia continuar, eventualmente as pessoas paravam de ler, eu tinha que pensar em alguma coisa e tinha que pensar rápido.
Espere, é isso! fivenão foi 25, 25 foi o resultado, 25 foi um número completamente diferente. Claro, como eu poderia esquecer? Os números são imutáveis. Quando você multiplica, 5 * 5nada é atribuído a nada, basta criar um novo número 25.
Deve ser o que está acontecendo aqui. De alguma forma, quando eu multiplico five * 5, fivedeve estar sendo coagido a um número e esse número deve ser o usado para a multiplicação. São os resultados dessa multiplicação impressa no console, não o valor em fivesi. fivenunca recebe nada - então é claro que isso não muda.
Então, como faço para me fiveatribuir o resultado de uma operação. Deixa comigo. Antes fivemesmo de ter a chance de pensar, eu gritei "++".
> five++
5
Aha! Eu o peguei! Todo mundo sabe que 5 + 1é 6, essa era a evidência que eu precisava para expor que fivenão era um número! Foi um impostor! Um impostor ruim que não sabia contar. E eu pude provar isso. Veja como um número real age:
> num = 5
5
> num++
5
Esperar? O que estava acontecendo aqui? suspiro , fiquei tão preso em flagras fiveque esqueço como os operadores de correios funcionam. Quando eu uso o ++no final do fiveque estou dizendo, retorne o valor atual e depois aumente five. É o valor antes da operação que é impresso no console. numfoi de fato 6e eu pude provar:
>num
6
Hora de ver o que fiverealmente era:
>five
6
... era exatamente o que deveria ser. fivefoi bom - mas eu estava melhor. Se fiveainda fosse um objeto, isso significaria que ainda teria a propriedade wtfe eu estava disposto a apostar tudo que não tinha.
> five.wtf
undefined
Aha! Eu tinha razão. Eu o peguei! fiveera um número agora - não era mais um objeto. Eu sabia que o truque da multiplicação não salvaria desta vez. Ver five++é realmente five = five + 1. Diferentemente da multiplicação, o ++operador atribui um valor a five. Mais especificamente, atribui a ele os resultados dos five + 1quais, como no caso da multiplicação, retorna um novo número imutável .
Eu sabia que o tinha, e apenas para ter certeza de que ele não conseguiria se esquivar disso. Eu tinha mais um teste na manga. Se eu estivesse certo e fiverealmente fosse um número agora, isso não funcionaria:
> five.wtf = 'potato?'
'potato?'
Ele não ia me enganar dessa vez. Eu sabia potato?que seria impresso no console porque isso é resultado da tarefa. A verdadeira questão é: wtfainda estará lá?
> five.wtf
undefined
Assim como eu suspeitava - nada - porque números não podem ser atribuídos a propriedades. Aprendemos que no primeiro ano na academia;)
Obrigado Nathan. Graças à sua coragem em fazer esta pergunta, finalmente posso deixar tudo isso para trás e seguir para um novo caso.
Como este sobre a função toValue(). Oh meu Deus. Nããão!
++parece afetar o tipo subjacente