Почему 0 [0] синтаксически допустимо?

119

Почему эта строка действительна в javascript?

var a = 0[0];

После этого aесть undefined.

Майкл М.
источник
4
как true[0]или ""[0]
Hacketo
24
@CodeAngry Честно говоря, JavaScript родился в HTML, и именно с HTML началось все, что «бросай мне все, что хочешь, и я попытаюсь разобраться в этом».
Niet the Dark Absol
8
@NiettheDarkAbsol Честно говоря, вы просто ошибаетесь, поскольку синтаксис имеет смысл (но не настолько). Это просто получить свойство с именем "0"из new Number(0)объекта.
меандре
это ложное предположение, что всегда будет неопределенным. Вполне возможно 0[0]вернуть значение
Rune FS
@meandre 0["toString"]Это круто , спасибо, что указали на это.
Джонатан

Ответы:

169

Когда вы это сделаете 0[0], интерпретатор JS превратит первый объект 0в Numberобъект, а затем попытается получить доступ к [0]свойству этого объекта, которое есть undefined.

Нет синтаксической ошибки, потому что синтаксис доступа к свойству 0[0]разрешен грамматикой языка в этом контексте. Эта структура (используя термины грамматики Javascript) имеет вид NumericLiteral[NumericLiteral].

Соответствующая часть грамматики языка из раздела A.3 спецификации ES5 ECMAScript следующая:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

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

Итак, можно проследить за грамматиком в этой прогрессии:

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

И точно так же Expressionможет в конечном итоге быть NumericLiteralтак, следуя грамматике, мы видим, что это разрешено:

NumericLiteral [ NumericLiteral ]

Это означает, что 0[0]это разрешенная часть грамматики и, следовательно, без SyntaxError.


Затем во время выполнения вам разрешено читать свойство, которое не существует (оно будет просто считываться как undefined), пока источник, из которого вы читаете, либо является объектом, либо имеет неявное преобразование в объект. И числовой литерал действительно имеет неявное преобразование в объект (объект Number).

Это одна из часто неизвестных функций Javascript. Типы Number, Booleanи Stringв JavaScript , как правило , хранится в виде примитивов (не распустившиеся объекты). Это компактное неизменяемое представление хранилища (вероятно, сделано таким образом для эффективности реализации). Но Javascript хочет, чтобы вы могли обрабатывать эти примитивы как объекты со свойствами и методами. Итак, если вы попытаетесь получить доступ к свойству или методу, которые напрямую не поддерживаются примитивом, Javascript временно преобразует примитив в соответствующий тип объекта со значением, установленным на значение примитива.

Когда вы используете объектно-подобный синтаксис для примитива, такого как 0[0], интерпретатор распознает это как доступ к свойству примитива. Его ответ на это - взять первый 0числовой примитив и преобразовать его в полноценный Numberобъект, который затем может получить доступ к [0]свойству. В этом конкретном случае [0]свойство объекта Number - undefinedвот почему это значение, которое вы получаете 0[0].

Вот статья об автоматическом преобразовании примитива в объект для работы со свойствами:

Тайная жизнь примитивов Javascript


Вот соответствующие части спецификации ECMAScript 5.1:

9.10 CheckObjectCoercible

Выбрасывает TypeError, если значение равно undefinedили null, в противном случае возвращает true.

введите описание изображения здесь

11.2.1 Средства доступа к свойствам

  1. Пусть baseReference будет результатом вычисления MemberExpression.
  2. Пусть baseValue будет GetValue (baseReference).
  3. Пусть propertyNameReference будет результатом вычисления Expression.
  4. Пусть propertyNameValue будет GetValue (propertyNameReference).
  5. Вызовите CheckObjectCoercible (baseValue).
  6. Пусть propertyNameString будет ToString (propertyNameValue).
  7. Если синтаксическая продукция, которая оценивается, содержится в коде строгого режима, пусть strict будет true, иначе пусть strict будет false.
  8. Возвращает значение типа Reference, базовым значением которого является baseValue, имя, на которое ссылается, - propertyNameString, а флаг строгого режима - strict.

Оперативная часть этого вопроса - шаг № 5 выше.

8.7.1 GetValue (V)

Это описывает, как, когда значение, к которому осуществляется доступ, является ссылкой на свойство, он вызывает, ToObject(base)чтобы получить версию объекта любого примитива.

9.9 ToObject

Здесь описывается Boolean, Numberи Stringпримитивы преобразуются в форме объекта с [[PrimitiveValue]] внутреннее свойство множества соответственно.


В качестве интересного теста, если бы код был таким:

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

Он по-прежнему не будет генерировать SyntaxError во время синтаксического анализа, поскольку это технически допустимый синтаксис, но он вызовет ошибку TypeError во время выполнения, когда вы запустите код, потому что, когда вышеупомянутая логика доступа к свойствам применяется к значению x, он будет вызывать CheckObjectCoercible(x)или вызывать ToObject(x)который оба будут вызывать TypeError, если xесть nullили undefined.

jfriend00
источник
0[1,2]тоже действительно, что это значит? (Я обновляю вопрос)
Майкл М.
И это не вызывает синтаксической ошибки, потому что доступ к свойствам чего-либо, что не является nullили undefinedсовершенно нормальным, даже если эти свойства не существуют.
user4642212
6
@Michael не нужно обновлять. Это оператор запятой, так что это просто0[2]
Амит Джоки
1
Оператор запятая: оценивает как 1, так и 2 в, 1,2но возвращает 2.
user4642212
2
Это отличный ответ на нюанс вопроса.
tbh__
20

Как и большинство языков программирования, JS использует грамматику для анализа вашего кода и преобразования его в исполняемую форму. Если в грамматике нет правила, которое можно применить к определенному фрагменту кода, он выдает ошибку SyntaxError. В противном случае код считается действительным, независимо от того, имеет ли он смысл или нет.

Соответствующие части грамматики JS :

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Поскольку 0[0]соответствует этим правилам, оно считается допустимым выражением. Правильно ли это (например, не выдает ли ошибку во время выполнения) - другой вопрос, но да, это так. Вот как JS оценивает такие выражения, как someLiteral[someExpression]:

  1. оценить someExpression(что может быть произвольно сложным)
  2. преобразовать литерал в соответствующий тип объекта (числовые литералы => Number, строки => и Stringт. д.)
  3. вызвать get propertyоперацию над результатом (2) с именем свойства result (1)
  4. отменить результат (2)

Так 0[0]интерпретируется как

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

Вот пример правильного , но неправильного выражения:

null[0]

Он разбирается нормально, но во время выполнения интерпретатор не работает на шаге 2 (потому что nullне может быть преобразован в объект) и выдает ошибку времени выполнения.

Georg
источник
1
Это еще не все. var x = null; var a = x[0];не генерирует синтаксическую ошибку, но выдает ошибку TypeError во время выполнения.
jfriend00
@ jfriend00: вопрос был не в этом, а добавил.
георг
результат не обязательно должен быть неопределенным. Можно получить 0[0]значение вместо неопределенного
Rune FS
9

Бывают ситуации, когда вы можете правильно указать индекс в Javascript:

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

Хотя не сразу понятно, зачем вам это нужно, индексирование в Javascript эквивалентно использованию точечной нотации (хотя точечная нотация ограничивает вас использованием идентификаторов в качестве ключей).

Дункан
источник
@AmitJoki Это то же самое (0).toString(без вызова функции). Это свойство числового типа.
user4642212
@AmitJoki, потому что он отвечает на вопрос, почему эта строка верна.
Дункан
@Duncan, но это больше похоже на «что такое скобки», и я полагаю, что OP это знает. Тот факт , что это интерпретируется как объект Number , а затем его 0свойство получено и , поскольку она не существует, undefinedявляется более правильным , как объяснено в jfriend00.
Амит Джоки
@AmitJoki это неверное предположение, 0[0]которое вернет undefined. Вполне вероятно, что так и будет, но это не обязательно
Rune FS
9

Я просто хотел бы отметить, что этот действительный синтаксис никоим образом не уникален для Javascript. Большинство языков будут иметь ошибку времени выполнения или ошибку типа, но это не то же самое, что ошибка синтаксиса. Javascript предпочитает возвращать undefined во многих ситуациях, когда другой язык может вызвать исключение, в том числе при индексировании объекта, который не имеет свойства с заданным именем.

Синтаксис не знает тип выражения (даже простого выражения, такого как числовой литерал), и позволяет применять любой оператор к любому выражению. Например, попытка присвоить индекс undefinedили nullвызвать TypeErrorв Javascript. Это не синтаксическая ошибка - если она никогда не выполняется (находится на неправильной стороне оператора if), это не вызовет никаких проблем, тогда как синтаксическая ошибка по определению всегда обнаруживается во время компиляции (eval, Function и т. Д. , все считается компиляцией).

Random832
источник
8

Потому что это валидный синтаксис и даже валидный код для интерпретации. Вы можете попытаться получить доступ к любому свойству любого объекта (и в этом случае 0 будет приведен к числовому объекту), и он выдаст вам значение, если оно существует, в противном случае - неопределенное. Однако попытка доступа к свойству undefined не работает, поэтому 0 [0] [0] приведет к ошибке выполнения. Однако это все равно будет классифицироваться как допустимый синтаксис. Есть разница в том, какой синтаксис является допустимым, а какой не вызывает ошибок времени выполнения / компиляции.

CLOX
источник
3

Действителен не только синтаксис, но и результат не обязательно должен быть, undefinedхотя в большинстве, если не во всех разумных случаях, будет. JS - один из самых чистых объектно-ориентированных языков. Большинство так называемых объектно-ориентированных языков ориентированы на классы в том смысле, что вы не можете изменить форму (она привязана к классу) однажды созданного объекта, а только состояние объекта. В JS вы можете изменять как состояние, так и форму объекта, и это вы делаете чаще, чем вы думаете. Эта способность делает код довольно непонятным, если вы используете его неправильно. Цифры неизменяемы, поэтому вы не можете изменить сам объект, ни его состояние, ни его форму, поэтому вы можете сделать

0[0] = 1;

которое является допустимым выражением присваивания, которое возвращает 1, но на самом деле ничего не присваивает. Числовое значение 0неизменяемо. Что само по себе несколько странно. У вас может быть действительное и правильное (исполняемое) выражение присваивания, которое ничего не присваивает (*). Однако тип числа является изменяемым объектом, поэтому вы можете изменить тип, и изменения будут каскадно распространяться по цепочке прототипов.

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]); 

конечно, это далеко от категории разумного использования, но язык указан для этого, потому что в других сценариях расширение возможностей объектов действительно имеет большой смысл. Вот как плагины jQuery подключаются к объекту jQuery, чтобы дать пример.

(*) Он фактически присваивает значение 1 свойству объекта, однако вы не можете ссылаться на этот (временный) объект, и, таким образом, он будет собран на проходе nexx GC.

Руна FS
источник
3

В JavaScript все является объектом, поэтому, когда интерпретатор анализирует его, он обрабатывает 0 как объект и пытается вернуть 0 как свойство. То же самое происходит, когда вы пытаетесь получить доступ к 0-му элементу со значением true или "" (пустая строка).

Даже если вы установите 0 [0] = 1, он установит свойство и его значение в памяти, но пока вы получаете доступ к 0, он обрабатывается как число (не путайте здесь обработку как объекта и числа).

Лаксмикант Данге
источник