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

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

var a = 0[0];

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

Ответ 1

Когда вы выполняете 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] является допустимой частью грамматики и, следовательно, не имеет синтаксического эффекта.


Затем во время выполнения вы можете прочитать свойство, которое не существует (оно будет просто считано как 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.

enter image description here

11.2.1 Аксессоры свойств

  • Пусть baseReference будет результатом оценки MemberExpression.
  • Пусть baseValue будет GetValue (baseReference).
  • Пусть свойствоNameReference является результатом вычисления выражения.
  • Пусть свойствоNameValue будет GetValue (propertyNameReference).
  • Вызов CheckObjectCoercible (baseValue).
  • Пусть свойствоNameString - ToString (propertyNameValue).
  • Если синтаксическое производство, которое оценивается, содержится в строгом mode, пусть strict будет true, иначе пусть strict будет false.
  • Возвращает значение типа Reference, базовым значением которого является baseValue и чья ссылочным именем является свойствоNameString, а флаг строгого режима - строго.

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

8.7.1 GetValue (V)

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

9.9 ToObject

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


Как интересный тест, если код был примерно таким:

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

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

Ответ 2

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

Соответствующие части JS-грамматика заключаются в

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

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

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

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

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

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

null[0]

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

Ответ 3

Существуют ситуации, когда вы можете достоверно индексировать число в Javascript:

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

Мало того, что синтаксис действителен, результат не должен быть undefined, хотя в большинстве случаев, если не все нормальные случаи, он будет. JS является одним из самых чистых объектно-ориентированных языков. Большинство так называемых языков OO ориентированы на классы, в том смысле, что вы не можете изменить форму (привязанную к классу) объекта, когда-то созданного, только состояние объекта. В 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 свойству объекта, однако вы не можете ссылаться на этот (трансформированный) объект, и поэтому он будет собран на проходе GC GC nexx