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

为什么这一行在 javascript 中有效?

var a = 0[0];

在那之后,a就是 undefined

6836 次浏览

当您执行 0[0]时,JS 解释器将把第一个 0转换成一个 Number对象,然后尝试访问该对象的 [0]属性,即 undefined

没有语法错误,因为此上下文中的语言语法允许属性访问语法 0[0]。这个结构(使用 Javascript 语法中的术语)是 NumericLiteral[NumericLiteral]

ES5 ECMAScript 规范中 答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 ]

Which means that 0[0] is an allowed part of the grammar and thus no SyntaxError.


然后,在运行时,您可以读取一个不存在的属性(它将被读取为 undefined) ,只要您正在读取的源是一个对象或者有一个隐式转换到一个对象。而且,数值文字确实有一个隐式的对象转换(Number 对象)。

This is one of those often unknown features of Javascript. The types Number, Boolean and String in Javascript are usually stored internally as primitives (not full-blown objects). These are a compact, immutable storage representation (probably done this way for implementation efficiency). But, Javascript wants you to be able to treat these primitives like objects with properties and methods. So, if you try to access a property or method that is not directly supported on the primitive, then Javascript will temporarily coerce the primitive into an appropriate type of object with the value set to the value of the primitive.

当您对基元(如 0[0])使用类似对象的语法时,解释器将其识别为对基元的属性访问。它对此的响应是获取第一个 0数值原语并强制它成为一个完整的 Number对象,然后它可以访问该对象上的 [0]属性。在这个特定的示例中,Number 对象上的 [0]属性是 undefined,这就是为什么您从 0[0]获得这个值的原因。

下面是一篇关于为了处理属性而将原语自动转换为对象的文章:

Javascript 原语的秘密生活


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

9.10检查对象强制性

如果值为 undefinednull,则抛出 TypeError,否则返回 true

enter image description here

11.2.1属性访问器

  1. 让 baseReference 成为计算 MemberExpression 的结果。
  2. 让 baseValue 成为 GetValue (baseReference)。
  3. 让 PropertyNameReference 成为计算 Expression 的结果。
  4. 让 PropertyNameValue 成为 GetValue (PropertyNameReference)。
  5. 调用 CheckObjectCoercel (baseValue)。
  6. 让 PropertyNameString 成为 ToString (PropertyNameValue)。
  7. 如果正在计算的句法生成包含在严格的 模式代码,则为 true,否则为 false。
  8. 返回一个引用类型的值,其基值为 baseValue,并且其 所引用的名称是 PropertyNameString,其严格模式标志为 严格。

这个问题的一个关键部分是上面的第5步。

8.7.1 GetValue (V)

This describes how when the value being accessed is a property reference, it calls ToObject(base) to get the object version of any primitive.

9.9对象

这描述了如何将 BooleanNumberString原语转换为具有相应的[[ PrimitiveValue ]]内部属性集的对象形式。


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

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

它仍然不会在解析时抛出一个 SyntaxError,因为这在技术上是合法的语法,但是它会在运行时抛出一个 TypeError,因为当上面的属性访问器逻辑应用到 x的值时,它会调用 CheckObjectCoercible(x)或者 ToObject(x),如果 xnull或者 undefined,它都会抛出一个 TypeError。

因为它是有效的语法,甚至是要解释的有效代码。您可以尝试访问任何对象的任何属性(在这种情况下,0将被强制转换为 Number-object) ,如果它存在,它将给出值,否则不定义该值。但是,尝试访问未定义的属性不起作用,因此0[0][0]将导致运行时错误。但是这仍然被归类为有效的语法。什么是有效语法和什么不会导致运行时/编译时错误是有区别的。

有些情况下,你可以在 Javascript 中有效地下标一个数字:

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

虽然您不知道为什么要这样做,但是在 Javascript 中下标等同于使用虚线表示法(尽管点表示法限制您使用标识符作为键)。

像大多数编程语言一样,JS 使用语法来解析代码并将其转换为可执行形式。如果语法中没有可以应用于特定代码块的规则,它将抛出 SyntaxError。否则,无论代码是否有意义,都被认为是有效的。

JS 语法的有关部分包括

Literal ::
NumericLiteral
...


PrimaryExpression :
Literal
...


MemberExpression :
PrimaryExpression
MemberExpression [ Expression ]
...

因为 0[0]符合这些规则,所以它被认为是 有效表达式。是否是 correct(例如在运行时不抛出错误)是另外一回事,但是是的。这就是 JS 计算像 someLiteral[someExpression]这样的表达式的方法:

  1. 评估 someExpression(它可以是任意复杂的)
  2. 将文字转换为对应的对象类型(数值文字 = > Number,字符串 = > String等)
  3. 使用属性名 result (1)对 result (2)调用 get property操作
  4. 弃牌结果(2)

So 0[0] is interpreted as

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无法转换为对象)并抛出运行时错误。

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

该语法不知道表达式的类型(即使是像数值文字这样的简单表达式) ,并且允许您对任何表达式应用任何运算符。例如,尝试下标 undefinednull会导致 Javascript 中出现 TypeError。这不是语法错误——如果这个错误从来没有执行过(在 if 语句的错误一边) ,它不会引起任何问题,而语法错误根据定义总是在编译时被捕获(eval、 Function 等等,都算作编译)。

In JavaScript, everything is object, so when interpreter parse it, it treats 0 as a object and tries to return 0 as a property. The same thing happens when you try to access 0th element of true or ""(empty string).

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

不仅语法有效,结果也不一定是 undefined,尽管在大多数情况下,如果不是所有正常的情况下,结果都是 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]);

of course it's a far cry from the sane use category but the language is specified to allow for this because in other scenarios, extending the objects capabilities actually makes a lot of sense. It's how jQuery plugins hook into the jQuery object to give an example.

(*)它确实将值1赋给一个对象的属性,但是您无法引用这个(先验的)对象,因此它将在 nexx GC 传递中被收集