Por que 0 [0] é sintaticamente válido?

119

Por que essa linha é válida em javascript?

var a = 0[0];

Depois disso, aé undefined.

Michael M.
fonte
4
como true[0]ou ""[0]
Hacketo 25/03
24
@CodeAngry Para ser justo, o JavaScript nasceu dentro do HTML, e é o HTML que iniciou o processo "jogue o que quiser de mim e tentarei entender o sentido".
Niet the Dark Absol
8
@NiettheDarkAbsol Para ser justo, você está simplesmente errado, pois a sintaxe faz sentido (mas não tanto). Isso está apenas obtendo a propriedade nomeada "0"de um new Number(0)objeto.
méandre
é uma suposição falsa de que a sempre será indefinida. É perfeitamente possível ter 0[0]retornar um valor
Rune FS
@meandre 0["toString"]Isso é incrível, obrigado por apontar.
Jonathan

Respostas:

169

Quando você faz isso 0[0], o interpretador JS transforma o primeiro 0em um Numberobjeto e tenta acessar a [0]propriedade desse objeto que é undefined.

Não há erro de sintaxe porque a sintaxe de acesso à propriedade 0[0]é permitida pela gramática do idioma neste contexto. Essa estrutura (usando termos da gramática Javascript) é NumericLiteral[NumericLiteral].

A parte relevante da gramática de idiomas da seção A.3 da especificação ES5 ECMAScript é a seguinte:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

MemberExpression :
    PrimaryExpression
    FunctionExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    new MemberExpression Arguments    

Assim, pode-se seguir a gramática através desta progressão:

MemberExpression [ Expression ]
PrimaryExpression [ Expression ]
Literal [ Expression ]
NumericLiteral [ Expression ]

E, da mesma forma, também Expressionpode eventualmente acontecer NumericLiteraldepois de seguir a gramática, vemos que isso é permitido:

NumericLiteral [ NumericLiteral ]

O que significa que 0[0]é uma parte permitida da gramática e, portanto, nenhum SyntaxError.


Em seguida, no tempo de execução, você poderá ler uma propriedade que não existe (ela será lida apenas undefined) desde que a fonte da qual você está lendo seja um objeto ou tenha uma conversão implícita em um objeto. E, um literal numérico realmente tem uma conversão implícita em um objeto (um objeto Number).

Esse é um desses recursos frequentemente desconhecidos do Javascript. Os tipos Number, Booleane Stringem Javascript são normalmente armazenados internamente como primitivos (não objetos full-blown). Trata-se de uma representação de armazenamento compacta e imutável (provavelmente feita dessa maneira para a eficiência da implementação). Mas, o Javascript deseja que você possa tratar essas primitivas como objetos com propriedades e métodos. Portanto, se você tentar acessar uma propriedade ou método que não seja diretamente suportado na primitiva, o Javascript coagirá temporariamente a primitiva em um tipo de objeto apropriado com o valor definido como o valor da primitiva.

Quando você usa uma sintaxe semelhante a objeto em uma primitiva como 0[0], por exemplo , o intérprete reconhece isso como um acesso à propriedade em uma primitiva. Sua resposta a isso é pegar a primeira 0primitiva numérica e coagi-la em um Numberobjeto completo no qual ele pode acessar a [0]propriedade. Nesse caso específico, a [0]propriedade em um objeto Number undefinedé por isso que esse é o valor que você obtém 0[0].

Aqui está um artigo sobre a conversão automática de uma primitiva em um objeto para fins de lidar com propriedades:

A Vida Secreta das Primitivas Javascript


Aqui estão as partes relevantes da especificação do ECMAScript 5.1:

9.10 CheckObjectCoercible

Lança TypeError se o valor for undefinedou null, caso contrário, retorna true.

insira a descrição da imagem aqui

11.2.1 Acessadores de propriedades

  1. Permita que baseReference seja o resultado da avaliação de MemberExpression.
  2. Deixe baseValue ser GetValue (baseReference).
  3. Permita que propertyNameReference seja o resultado da avaliação de Expressão.
  4. Permita que propertyNameValue seja GetValue (propertyNameReference).
  5. Chame CheckObjectCoercible (baseValue).
  6. Permita que propertyNameString seja ToString (propertyNameValue).
  7. Se a produção sintática que está sendo avaliada estiver contida no código de modo estrito, deixe estrito ser verdadeiro, caso contrário, estrito seja falso.
  8. Retorne um valor do tipo Referência cujo valor base é baseValue e cujo nome referenciado é propertyNameString e cujo sinalizador de modo estrito é estrito.

Uma parte operacional para esta pergunta é a etapa 5 acima.

8.7.1 GetValue (V)

Isso descreve como quando o valor que está sendo acessado é uma referência de propriedade, ele chama ToObject(base)para obter a versão do objeto de qualquer primitiva.

9.9 ToObject

Este descreve como Boolean, Numbere Stringprimitivas são convertidos para uma forma de objecto com o [[PrimitiveValue]] propriedade interno conjunto em conformidade.


Como um teste interessante, se o código fosse assim:

var x = null;
var a = x[0];

Ainda não jogaria um SyntaxError no momento da análise, pois isso é tecnicamente sintaxe legal, mas seria jogar um TypeError em tempo de execução quando você executar o código, pois quando o acima da lógica Propriedade Accessors é aplicada ao valor de x, ele irá chamar CheckObjectCoercible(x)ou chamada ToObject(x)que ambos lançarão um TypeError se xfor nullou undefined.

jfriend00
fonte
0[1,2]também é válido, o que isso significa? (I atualizar a questão)
Michael M.
E isso não gera um erro de sintaxe, porque acessar propriedades em qualquer coisa que não seja nullou undefinedesteja totalmente correta, mesmo que essas propriedades não existam.
user4642212
6
@ Michael não precisa atualizar. Isso é um operador vírgula por isso é apenas0[2]
Amit Joki
1
Operador de vírgula: avalia 1 e 2 em, 1,2mas retorna 2. #
user4642212 25/15/15
2
Essa é uma resposta impressionante para a nuance de uma pergunta.
tbh__ 31/03
20

Como a maioria das linguagens de programação, o JS usa uma gramática para analisar seu código e convertê-lo em um formato executável. Se não houver uma regra na gramática que possa ser aplicada a um pedaço específico de código, ele lançará um SyntaxError. Caso contrário, o código é considerado válido, não importa se faz sentido ou não.

As partes relevantes da gramática JS são

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Como 0[0]está em conformidade com essas regras, é considerada uma expressão válida . Se está correto (por exemplo, não gera um erro no tempo de execução) é outra história, mas sim. É assim que o JS avalia expressões como someLiteral[someExpression]:

  1. avaliar someExpression(que pode ser complexo arbitrário)
  2. converter o literal em um tipo de objeto correspondente (literais numéricos => Number, strings => Stringetc)
  3. chame a get propertyoperação no resultado (2) com o nome da propriedade resultado (1)
  4. descartar resultado (2)

Então 0[0]é interpretado como

index = 0
temp = Number(0)
result = getproperty(temp, index) // it's undefined, but JS doesn't care
delete temp
return result

Aqui está um exemplo de uma expressão válida , mas incorreta :

null[0]

É analisado corretamente, mas no tempo de execução, o intérprete falha na etapa 2 (porque nullnão pode ser convertido em um objeto) e gera um erro em tempo de execução.

georg
fonte
1
Há mais do que isso. var x = null; var a = x[0];não gera um erro de sintaxe, mas gera um TypeError em tempo de execução.
jfriend00
@ jfriend00: não era disso que se tratava a questão, mas acrescentou.
Georg
o resultado não precisa ser indefinido. É possível obter 0[0]para devolver um valor em vez de indefinida
Rune FS
9

Há situações em que você pode subscrever validamente um número em Javascript:

-> 0['toString']
function toString() { [native code] }

Embora não seja imediatamente aparente o motivo pelo qual você deseja fazer isso, a inscrição em Javascript é equivalente ao uso de notação pontilhada (embora a notação de ponto limite o uso de identificadores como chaves).

Duncan
fonte
@AmitJoki É o mesmo que (0).toString(sem chamar a função). É uma propriedade do tipo de número.
user4642212
@AmitJoki porque responde à pergunta 'por que essa linha é válida'?
Duncan
@ Duncan, mas isso é mais do que "notação de suporte" e suponho que o OP saiba disso. O fato de ser interpretado como objeto Number e, em seguida, sua 0propriedade ser acessada e, uma vez que não existe, undefinedé mais correto, conforme explicado em jfriend00.
Amit Joki
@AmitJoki, é uma suposição incorreta que 0[0]retornará indefinida. É provável que ele vai, mas ele não tem que ser o caso
Rune FS
9

Gostaria apenas de observar que essa sintaxe válida não é de forma alguma exclusiva do Javascript. A maioria dos idiomas terá um erro de tempo de execução ou um erro de tipo, mas isso não é o mesmo que um erro de sintaxe. O Javascript escolhe retornar indefinido em muitas situações em que outro idioma pode gerar uma exceção, inclusive ao inscrever um objeto que não possui uma propriedade com o nome fornecido.

A sintaxe não conhece o tipo de uma expressão (mesmo uma expressão simples como um literal numérico) e permitirá que você aplique qualquer operador a qualquer expressão. Por exemplo, tentar subscrever undefinedou nullcausar um TypeErrorem Javascript. Não é um erro de sintaxe - se isso nunca for executado (estando no lado errado de uma instrução if), não causará problemas, enquanto um erro de sintaxe é, por definição, sempre capturado no tempo de compilação (eval, Function, etc. , todos contam como compilação).

Random832
fonte
8

Porque é uma sintaxe válida e até mesmo um código válido para ser interpretado. Você pode tentar acessar qualquer propriedade de qualquer objeto (e, neste caso, 0 será convertido em um objeto Number) e fornecerá o valor, se existir, caso contrário, indefinido. Tentar acessar uma propriedade de undefined não funciona, no entanto, 0 [0] [0] resultaria em um erro de tempo de execução. Isso ainda seria classificado como sintaxe válida. Há uma diferença entre o que é sintaxe válida e o que não causará erros de tempo de execução / compilação.

Clox
fonte
3

Não apenas a sintaxe é válida, mas o resultado não precisa ser o undefinedmesmo na maioria, se não em todos os casos sãos. JS é uma das linguagens orientadas a objetos mais puras. A maioria das linguagens OO são orientadas a classes, no sentido de que você não pode alterar a forma (está ligada à classe) do objeto uma vez criado, apenas o estado do objeto. Em JS, você pode alterar o estado e a forma do objeto, e isso é feito com mais frequência do que você pensa. Essa capacidade cria um código bastante obscuro, se você o usar incorretamente. Os numerais são imutáveis, então você não pode alterar o objeto em si, nem o estado nem a forma, para que você possa fazer

0[0] = 1;

que é uma expressão de atribuição válida que retorna 1, mas na verdade não atribui nada. O numeral 0é imutável. O que por si só é um tanto estranho. Você pode ter uma expressão de avaliação válida e correta (executável), que não atribua nada (*). No entanto, o tipo do numeral é um objeto mutável, para que você possa alterar o tipo, e as alterações entrarão em cascata na cadeia de protótipos.

Number[0] = 1;
//print 1 to the console
console.log(0[0]);
//will also print 1 to the console because all integers have the same type
console.log(1[0]); 

é claro que está muito longe da categoria de uso racional, mas o idioma é especificado para permitir isso, porque em outros cenários, estender os recursos dos objetos faz muito sentido. É como os plugins do jQuery se conectam ao objeto jQuery para dar um exemplo.

(*) Na verdade, atribui o valor 1 à propriedade de um objeto, no entanto, não há como referenciar esse objeto (transcendente) e, portanto, ele será coletado na passagem do GC da nexx

Rune FS
fonte
3

No JavaScript, tudo é objeto; portanto, quando o intérprete o analisa, ele trata 0 como um objeto e tenta retornar 0 como uma propriedade. O mesmo acontece quando você tenta acessar o 0º elemento de true ou "" (string vazia).

Mesmo se você definir 0 [0] = 1, ele definirá a propriedade e seu valor na memória, mas enquanto você acessa 0, ele trata como um número (não se confunda entre tratar como Objeto e número aqui.)

Laxmikant Dange
fonte