Чому синтаксично діє 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)об'єкта.
meandre

це помилкове припущення, що а завжди буде невизначеним. Цілком можливо 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), доки джерело, з якого ви читаєте, є об'єктом або має неявне перетворення на об'єкт. І числовий буквал дійсно має неявне перетворення в об'єкт (об'єкт Число).

Це одна з тих часто невідомих особливостей Javascript. Типи Number, Booleanі Stringв JavaScript , як правило , зберігається у вигляді примітивів (не розпустить об'єкти). Це компактне, незмінне представлення пам’яті (можливо, це зроблено таким чином для ефективності впровадження). Але Javascript хоче, щоб ви могли ставитись до цих примітивів, як до об'єктів із властивостями та методами. Отже, якщо ви спробуєте отримати доступ до властивості чи методу, який не підтримується безпосередньо на примітиві, Javascript тимчасово примусить примітив у відповідний тип об'єкта зі значенням, встановленим значенням примітиву.

Коли ви використовуєте об'єктоподібний синтаксис на примітиві, такому як 0[0], інтерпретатор розпізнає це як доступ до властивості примітиву. Його відповідь на це полягає в тому, щоб прийняти перший 0числовий примітив і примусити його до повномасштабного Numberоб'єкта, до якого він може отримати доступ до [0]властивості. У цьому конкретному випадку [0]властивість об’єкта «Число» - 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. Нехай властивістьNameReference є результатом оцінки виразів.
  4. Нехай propertyNameValue буде GetValue (propertyNameReference).
  5. Виклик CheckObjectCoercible (baseValue).
  6. Нехай propertyNameString буде ToString (propertyNameValue).
  7. Якщо синтаксичне виробництво, яке оцінюється, міститься в строгому режимі коду, нехай строгий буде істинним, інакше нехай строгий буде хибним.
  8. Поверніть значення типу 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 під час виконання під час запуску коду, оскільки коли вищевказана логіка властивостей властивостей застосовується до значення x, воно буде викликати CheckObjectCoercible(x)або викликати ToObject(x)який обидва кинуть TypeError, якщо xє nullабо undefined.


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) з результатом імені властивості (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його неможливо перетворити на об'єкт) і видає помилку часу виконання.


1
Тут є більше, ніж це. var x = null; var a = x[0];не генерує синтаксичну помилку, але видає TypeError під час виконання.
jfriend00

@ jfriend00: про це не було питання, а додалося.
georg

результат не повинен бути визначеним. Можна 0[0]повернути значення замість невизначеного
Rune FS

9

Є ситуації, коли ви можете дійсно підписати номер у Javascript:

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

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


@AmitJoki Це те саме, що (0).toString(без виклику функції). Це властивість типу номера.
user4642212

@AmitJoki тому, що він відповідає на питання "чому цей рядок дійсний".
Дункан

@Duncan, але це більше "нотація дужки", і я думаю, що ОП це знає. Те, що він інтерпретується як об'єкт Number, а потім 0доступ до його властивості, а оскільки він не існує, undefinedє більш правильним, як пояснено у jfriend00.
Аміт Джокі

@AmitJoki це неправильне припущення, яке 0[0]повернеться невизначеним. Цілком імовірно, що це буде, але це не повинно бути так
Rune FS

9

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

Синтаксис не знає тип виразу (навіть простий вираз на зразок числового літералу), і дозволить застосувати будь-який оператор до будь-якого виразу. Наприклад, спроба підписати undefinedабо nullвикликати TypeErrorв JavaScript. Це не синтаксична помилка - якщо вона ніколи не виконується (перебуваючи на неправильній стороні if-заяви), це не спричинить жодних проблем, тоді як помилка синтаксису за визначенням завжди виявляється під час компіляції (eval, Function тощо) , усі вважаються компілюючими).


8

Тому що це дійсний синтаксис і навіть дійсний код для інтерпретації. Ви можете спробувати отримати доступ до будь-якого властивості будь-якого об’єкта (і в цьому випадку 0 буде передано на об'єкт Число), і він дасть вам значення, якщо воно існує, інакше не визначене. Спроба отримати доступ до властивості невизначеного не працює, тому 0 [0] [0] призведе до помилки виконання. Це все ще буде класифіковано як дійсний синтаксис. Існує відмінність того, що є дійсним синтаксисом, а що не спричинить помилки виконання / компіляції.


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


3

У JavaScript все є об'єктом, тому, коли інтерпретатор розбирає його, він трактує 0 як об'єкт і намагається повернути 0 як властивість. Те ж саме відбувається, коли ви намагаєтеся отримати доступ до 0-го елемента true або "" (порожній рядок).

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.