A palestra 'Wat' para o CodeMash 2012 basicamente aponta algumas peculiaridades bizarras com Ruby e JavaScript.
Eu fiz um JSFiddle dos resultados em http://jsfiddle.net/fe479/9/ .
Os comportamentos específicos do JavaScript (como eu não conheço Ruby) estão listados abaixo.
Descobri no JSFiddle que alguns dos meus resultados não correspondiam aos do vídeo e não sei por que. No entanto, estou curioso para saber como o JavaScript está lidando com o trabalho nos bastidores em cada caso.
Empty Array + Empty Array
[] + []
result:
<Empty String>
Estou bastante curioso sobre o +
operador quando usado com matrizes em JavaScript. Isso corresponde ao resultado do vídeo.
Empty Array + Object
[] + {}
result:
[Object]
Isso corresponde ao resultado do vídeo. O que está acontecendo aqui? Por que isso é um objeto? O que o +
operador faz?
Object + Empty Array
{} + []
result:
[Object]
Isso não corresponde ao vídeo. O vídeo sugere que o resultado é 0, enquanto eu recebo [Objeto].
Object + Object
{} + {}
result:
[Object][Object]
Isso também não corresponde ao vídeo, e como a saída de uma variável resulta em dois objetos? Talvez meu JSFiddle esteja errado.
Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
Fazendo wat + 1 resultados em wat1wat1wat1wat1
...
Eu suspeito que este é apenas um comportamento direto que tentar subtrair um número de uma string resulta em NaN.
fonte
Array(16).join("wat" - 1) + " Batman!"
{} + {}
.Respostas:
Aqui está uma lista de explicações para os resultados que você está vendo (e deveria estar vendo). As referências que estou usando são do padrão ECMA-262 .
[] + []
Ao usar o operador de adição, os operandos esquerdo e direito são convertidos primeiro em primitivos ( §11.6.1 ). Conforme §9.1 , a conversão de um objeto (neste caso, uma matriz) para uma primitiva retorna seu valor padrão, que para objetos com um
toString()
método válido é o resultado da chamadaobject.toString()
( §8.12.8 ). Para matrizes, é o mesmo que chamararray.join()
( §15.4.4.2 ). A união de uma matriz vazia resulta em uma cadeia vazia, portanto, a etapa 7 do operador de adição retorna a concatenação de duas cadeias vazias, que é a cadeia vazia.[] + {}
Semelhante
[] + []
, ambos os operandos são convertidos em primitivos primeiro. Para "Objetos de objeto" (§15.2), esse é novamente o resultado da chamadaobject.toString()
, que para objetos não nulos e indefinidos é"[object Object]"
( §15.2.4.2 ).{} + []
O
{}
aqui não é analisado como um objeto, mas como um bloco vazio ( §12.1 , pelo menos enquanto você não estiver forçando essa declaração a ser uma expressão, mas mais sobre isso posteriormente). O valor de retorno de blocos vazios está vazio, portanto, o resultado dessa instrução é o mesmo que+[]
. O+
operador unário ( §11.4.6 ) retornaToNumber(ToPrimitive(operand))
. Como já sabemos,ToPrimitive([])
é a string vazia e, de acordo com §9.3.1 ,ToNumber("")
é 0.{} + {}
Semelhante ao caso anterior, o primeiro
{}
é analisado como um bloco com valor de retorno vazio. Novamente,+{}
é o mesmo queToNumber(ToPrimitive({}))
eToPrimitive({})
é"[object Object]"
(consulte[] + {}
). Então, para obter o resultado+{}
, precisamos aplicarToNumber
a string"[object Object]"
. Ao seguir as etapas do §9.3.1 , obtemosNaN
como resultado:Array(16).join("wat" - 1)
Conforme §15.4.1.1 e §15.4.2.2 ,
Array(16)
cria uma nova matriz com o comprimento 16. Para obter o valor do argumento para ingressar, as seções # 11.6.2 # 5 e # 6 mostram que precisamos converter os dois operandos em um número usandoToNumber
.ToNumber(1)
é simplesmente 1 ( §9.3 ), enquanto queToNumber("wat")
novamente éNaN
como §9.3.1 . O passo 7 de §11.6.2 , §11.6.3 determina queEntão o argumento para
Array(16).join
éNaN
. Seguindo § 15.4.4.5 (Array.prototype.join
), temos que invocarToString
o argumento, que é"NaN"
( §9.8.1 ):Após o passo 10 de §15.4.4.5 , obtemos 15 repetições da concatenação
"NaN"
e da sequência vazia, que é igual ao resultado que você está vendo. Ao usar em"wat" + 1
vez de"wat" - 1
como argumento, o operador de adição converte1
em uma cadeia de caracteres em vez de converter"wat"
em um número, portanto efetivamente chamaArray(16).join("wat1")
.Quanto ao motivo pelo qual você está vendo resultados diferentes para o
{} + []
caso: Ao usá-lo como argumento de função, você está forçando a instrução a ser uma ExpressionStatement , o que torna impossível analisar{}
como bloco vazio; portanto, é analisado como um objeto vazio literal.fonte
[]+1
segue basicamente a mesma lógica que[]+[]
, apenas com o1.toString()
operando rhs. Para[]-1
ver a explicação do"wat"-1
ponto 5. Lembre-se de queToNumber(ToPrimitive([]))
é 0 (ponto 3).Isso é mais um comentário do que uma resposta, mas, por algum motivo, não posso comentar sua pergunta. Eu queria corrigir o seu código JSFiddle. No entanto, eu postei isso no Hacker News e alguém sugeriu que eu o republicasse aqui.
O problema no código JSFiddle é que
({})
(abrir chaves entre parênteses) não é o mesmo que{}
(abrir chaves como o início de uma linha de código). Então, quando você digita,out({} + [])
está forçando a{}
ser algo que não é quando você digita{} + []
. Isso faz parte do 'wat'-ness geral do Javascript.A ideia básica era simples: o JavaScript queria permitir os dois formulários:
Para isso, foram feitas duas interpretações da chave de abertura: 1. não é necessária e 2. pode aparecer em qualquer lugar .
Esta foi uma jogada errada. O código real não tem uma chave de abertura aparecendo no meio do nada, e o código real também tende a ser mais frágil quando usa a primeira forma e não a segunda. (Uma vez a cada dois meses no meu último emprego, eu era chamado para a mesa de um colega de trabalho quando as modificações no meu código não estavam funcionando, e o problema era que eles adicionaram uma linha ao "se" sem adicionar encaracolados Eu acabei de adotar o hábito de sempre usar chaves, mesmo quando você está escrevendo apenas uma linha.)
Felizmente, em muitos casos, o eval () replicará toda a watidez do JavaScript. O código JSFiddle deve ler:
[Também é a primeira vez que escrevo document.writeln em muitos e muitos anos, e me sinto um pouco suja escrevendo qualquer coisa que envolva document.writeln () e eval ().]
fonte
This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere
- Eu discordo (tipo de): Eu tenho muitas vezes nos últimos blocos usados como este para variáveis de escopo em C . Esse hábito foi adquirido por um tempo atrás ao fazer C incorporado, onde variáveis na pilha ocupam espaço; portanto, se não forem mais necessárias, queremos que o espaço seja liberado no final do bloco. No entanto, o ECMAScript apenas escopo dentro dos blocos function () {}. Portanto, embora eu discorde que o conceito está errado, concordo que a implementação em JS está ( possivelmente ) errada.let
para declarar variáveis com escopo de bloco.Eu segundo solução @ Ventero. Se desejar, você pode entrar em mais detalhes sobre como
+
converte seus operandos.Primeiro passo (§9.1): converter os dois operandos de primitivas (valores primitivos são
undefined
,null
, booleanos, números, cordas; todos os outros valores são objectos, incluindo matrizes e funções). Se um operando já é primitivo, você está pronto. Caso contrário, é um objetoobj
e as seguintes etapas são executadas:obj.valueOf()
. Se retornar um primitivo, você está pronto. Instâncias diretasObject
e matrizes retornam a si mesmas, portanto você ainda não terminou.obj.toString()
. Se retornar um primitivo, você está pronto.{}
e[]
ambos retornam uma string, e pronto.TypeError
.Para datas, as etapas 1 e 2 são trocadas. Você pode observar o comportamento da conversão da seguinte maneira:
Interação (
Number()
primeiro converte em primitivo e depois em número):Segunda etapa (§11.6.1): Se um dos operandos for uma string, o outro operando também será convertido em string e o resultado será produzido pela concatenação de duas strings. Caso contrário, ambos os operandos são convertidos em números e o resultado é produzido adicionando-os.
Explicação mais detalhada do processo de conversão: “ O que é {} + {} em JavaScript? "
fonte
Podemos nos referir à especificação e isso é ótimo e mais preciso, mas a maioria dos casos também pode ser explicada de uma maneira mais compreensível com as seguintes declarações:
+
e-
operadores trabalham apenas com valores primitivos. Mais especificamente+
(adição) funciona com cadeias ou números e+
(unário) e-
(subtração e unário) funciona apenas com números.valueOf
outoString
, que estão disponíveis em qualquer objeto. Essa é a razão pela qual essas funções ou operadores não lançam erros quando invocados em objetos.Então, podemos dizer que:
[] + []
é o mesmoString([]) + String([])
que'' + ''
. Eu mencionei acima que+
(adição) também é válido para números, mas não há representação numérica válida de uma matriz em JavaScript, portanto, a adição de cadeias é usada.[] + {}
é o mesmoString([]) + String({})
que é o mesmo que'' + '[object Object]'
{} + []
. Este merece mais explicações (veja a resposta Ventero). Nesse caso, os chavetas não são tratadas como um objeto, mas como um bloco vazio; portanto, é o mesmo que+[]
. O Unary+
funciona apenas com números, portanto, a implementação tenta obter um número[]
. Primeiro, tentavalueOf
que, no caso de matrizes, retorne o mesmo objeto; depois, tenta o último recurso: a conversão de umtoString
resultado em um número. Podemos escrever como+Number(String([]))
qual é o mesmo+Number('')
que é o mesmo que+0
.Array(16).join("wat" - 1)
subtração-
funciona apenas com números; portanto, é o mesmo que:,Array(16).join(Number("wat") - 1)
pois"wat"
não pode ser convertido em um número válido. Nós recebemosNaN
, e qualquer operação aritmética emNaN
resultados comNaN
, por isso temos:Array(16).join(NaN)
.fonte
Para reforçar o que foi compartilhado anteriormente.
A causa subjacente desse comportamento é parcialmente devida à natureza do JavaScript de tipo fraco. Por exemplo, a expressão 1 + "2" é ambígua, pois há duas interpretações possíveis com base nos tipos de operando (int, string) e (int int):
Assim, com diferentes tipos de entrada, as possibilidades de saída aumentam.
O algoritmo de adição
As primitivas do JavaScript são string, número, nulo, indefinido e booleano (o símbolo estará disponível em breve no ES6). Qualquer outro valor é um objeto (por exemplo, matrizes, funções e objetos). O processo de coerção para converter objetos em valores primitivos é descrito assim:
Se um valor primitivo for retornado quando object.valueOf () for chamado, retorne esse valor, caso contrário, continue
Se um valor primitivo for retornado quando object.toString () for chamado, retorne esse valor, caso contrário, continue
Lançar um TypeError
Nota: Para valores de data, o pedido é chamar toString antes de valueOf.
Se qualquer valor de operando for uma sequência, faça uma concatenação de sequência
Caso contrário, converta os dois operandos em seu valor numérico e adicione esses valores
Conhecer os vários valores de coerção dos tipos no JavaScript ajuda a tornar as saídas confusas mais claras. Veja a tabela de coerção abaixo
Também é bom saber que o operador + do JavaScript é associativo à esquerda, pois isso determina quais serão os resultados que envolverão mais de uma operação.
Alavancar o Thus 1 + "2" fornecerá "12" porque qualquer adição que envolva uma string sempre será padronizada para a concatenação da string.
Você pode ler mais exemplos nesta postagem do blog (isenção de responsabilidade que eu escrevi).
fonte