为什么0 [0]在语法上有效?


119

为什么此行在javascript中有效?

var a = 0[0];

之后aundefined


4
作为true[0]""[0]
Hacketo

24
@CodeAngry为了公平起见JavaScript的诞生中的HTML,它的HTML是启动整个“扔任何你喜欢我,我会尽力做它的意义”的事。
Niet the Dark Absol

8
@NiettheDarkAbsol公平地说,您确实是错的,因为语法确实有意义(但还不是很多)。这是刚开命名属性"0"一个的new Number(0)对象。
Meandre

这是一个错误的假设,即a始终是不确定的。完全有可能0[0]返回一个值
Rune

@meandre 0["toString"]太棒了,谢谢指出。
乔纳森(Jonathan)

Answers:


169

当您这样做时0[0],JS解释器将第一个0变成一个Number对象,然后尝试访问该[0]对象的属性undefined

没有语法错误,因为0[0]在这种情况下语言语法允许属性访问语法。该结构(使用Javascript语法中的术语)为NumericLiteral[NumericLiteral]

ES5 ECMAScript规范的A.3节中的语言语法的相关部分是:

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对象)的隐式转换。

这是Java脚本通常不为人知的功能之一。的类型NumberBooleanString在Javascript通常内部存储为基元(未完全成熟的对象)。这些是紧凑的,不变的存储表示(可能以这种方式实现以提高实现效率)。但是,Javascript希望您能够将这些原语像具有属性和方法的对象一样对待。因此,如果您尝试访问原语不直接支持的属性或方法,则Javascript会暂时将原语强制转换为适当类型的对象,并将其值设置为原语的值。

当您在诸如的基元上使用类似对象的语法时0[0],解释器会将其识别为对基元的属性访问。它对此的响应是将第一个0数字原语带入一个成熟的Number对象中,然后可以访问该对象[0]。在这种特定情况下,[0]Number对象的属性undefined就是为什么这就是您从中获得的值的原因0[0]

这是一篇有关将原语自动转换为对象以处理属性的文章:

Javascript基元的秘密生活


以下是ECMAScript 5.1规范的相关部分:

9.10 CheckObjectCoercible

如果value为undefinedor null,则抛出TypeError ,否则返回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为真,否则使strict为假。
  8. 返回类型为Reference的值,其基值为baseValue,其引用名称为propertyNameString,其strict模式标志为strict。

这个问题的有效部分是上面的步骤5。

8.7.1 GetValue(V)

这描述了当访问的值是属性引用时,它如何调用ToObject(base)以获取任何原语的对象版本。

9.9目的

介绍如何BooleanNumberString图元被转换成与[[PrimitiveValue]]内部属性组相应的对象的形式。


作为一个有趣的测试,如果代码是这样的:

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

它仍然不会扔在解析时一个SyntaxError,因为这在技术上是合法的语法,但是当你运行该代码会抛出在运行时类型错误,因为当上述属性存取逻辑应用到的价值x,它会调用CheckObjectCoercible(x)或致电ToObject(x)其如果xnull或,都将引发TypeError undefined


0[1,2]也有效,是什么意思?(我更新了问题)
Michael M.

而且它不会引发语法错误,因为即使不存在,也可以访问那些不存在nullundefined完全没有问题的属性。
user4642212 2015年

6
@Michael无需更新。那是个逗号运算符,所以0[2]
Amit Joki 2015年

1
逗号操作:评估在这两个1和2 1,2,但回报2
user4642212

2
对于问题的细微差别,这真是一个了不起的答案。
tbh__ 2015年

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使用属性名称result(1)调用result(2)上的操作
  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 2015年

@ jfriend00:这不是问题的全部,而是补充。
乔治,2015年

结果不必是不确定的。可以0[0]返回一个值而不是未定义
Rune FS

9

在某些情况下,您可以在Javascript中有效地对数字进行下标:

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

尽管现在还不清楚为什么要这样做,但Javascript中的下标等效于使用点分表示法(尽管点数表示法将您限制为使用标识符作为键)。


@AmitJoki这与(0).toString(不调用函数)相同。这是数字类型的属性。
user4642212 2015年

@AmitJoki,因为它回答了“为什么这行有效”的问题。
邓肯

@Duncan,但这更多是“什么括号符号”,我想OP知道这一点。,它的解释为Number对象,然后它的事实0属性进行访问,并因为它不存在,undefined更多的在jfriend00解释是正确的。
阿米特·乔基

@AmitJoki这是一个不正确的假设,0[0]将返回未定义。这可能是因为它会,但它并不一定是这样的
符文FS

9

我只想指出,这种有效的语法并不是Javascript独有的。大多数语言都会出现运行时错误或类型错误,但这与语法错误不同。在许多情况下,当另一种语言可能会引发异常,包括对不具有给定名称属性的对象进行下标时,Javascript选择返回未定义。

该语法不知道表达式的类型(即使是简单的表达式,如数字文字),也允许您将任何运算符应用于任何表达式。例如,尝试在Javascript中下标undefinednull引起TypeError。这不是语法错误-如果从不执行此错误(位于if语句的错误一侧),则不会引起任何问题,而语法错误根据定义总是在编译时捕获(eval,Function等) ,全部算作编译)。


8

因为它是有效的语法,甚至需要解释有效的代码。您可以尝试访问任何对象的任何属性(在这种情况下,0将强制转换为数字对象),如果存在,它将为您提供值,否则为未定义。但是,尝试访问未定义的属性不起作用,因此0 [0] [0]将导致运行时错误。但是,这仍将分类为有效语法。有效语法和不会导致运行时/编译时错误的有所不同。


3

语法不仅有效,而且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分配给对象的属性,但是您无法引用该(超然)对象,因此将在nexx GC遍历中将其收集


3

在JavaScript中,一切都是对象,因此,当解释器对其进行解析时,它将0视为对象,并尝试将0作为属性返回。当您尝试访问true或“”(空字符串)的第0个元素时,也会发生相同的情况。

即使您设置0 [0] = 1,它也会在内存中设置属性及其值,但是当您访问0时,它将被视为数字(此处不要混淆为Object和number。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.