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

时间:2015-03-25 08:42:31

标签: javascript

为什么这行在javascript中有效?

var a = 0[0];

之后,aundefined

7 个答案:

答案 0 :(得分:168)

执行0[0]时,JS解释器会将第一个0转换为Number对象,然后尝试访问该对象的[0]属性,即{ {1}}。

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

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

NumericLiteral[NumericLiteral]

因此,人们可以通过这个进程跟随语法:

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])转换为对象。并且,数字文字确实具有对对象的隐式转换(Number对象)。

这是Javascript常常未知的功能之一。 Javascript中的类型undefinedNumberBoolean通常在内部存储为基元(不是完整的对象)。这些是一个紧凑的,不可变的存储表示(可能以这种方式实现了实现效率)。但是,Javascript希望您能够将这些原语视为具有属性和方法的对象。因此,如果您尝试访问基元上不直接支持的属性或方法,则Javascript将暂时将基元强制转换为适当类型的对象,并将值设置为基元的值。

当您在String等基元上使用类似对象的语法时,解释器会将此识别为对基元的属性访问。它对此的响应是获取第一个0[0]数字原语并将其强制转换为一个完整的0对象,然后它可以访问Number属性。在这种特定情况下,Number对象上的[0]属性为[0],这就是您从undefined获得的值的原因。

这是一篇关于将基元自动转换为对象以处理属性的文章:

The Secret Life of Javascript Primitives


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

9.10 CheckObjectCoercible

如果值为0[0]undefined,则抛出TypeError,否则返回null

enter image description here

11.2.1 Property Accessors

  
      
  1. 让baseReference成为评估MemberExpression的结果。
  2.   
  3. 让baseValue为GetValue(baseReference)。
  4.   
  5. 让propertyNameReference成为评估Expression的结果。
  6.   
  7. 让propertyNameValue为GetValue(propertyNameReference)。
  8.   
  9. 调用CheckObjectCoercible(baseValue)。
  10.   
  11. 让propertyNameString为ToString(propertyNameValue)。
  12.   
  13. 如果正在评估的句法生成包含在严格中   模式代码,让严格为真,否则严格为假。
  14.   
  15. 返回类型为Reference的值,其基值为baseValue且其值为   引用的名称是propertyNameString,其严格模式标志是   严格。
  16.   

此问题的执行部分是上面的第5步。

8.7.1 GetValue (V)

这描述了当访问的值是属性引用时,它调用true来获取任何原语的对象版本。

9.9 ToObject

这描述了如何将ToObject(base)BooleanNumber基元转换为对象形式,并相应地设置了[[PrimitiveValue]]内部属性。


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

String

它仍然不会在分析时抛出一个SyntaxError,因为这是技术上合法的语法,但是当你运行代码时它会在运行时抛出一个TypeError,因为当上面的Property Accessors逻辑应用于{{1}的值时},它会调用var x = null; var a = x[0]; 或调用x,如果CheckObjectCoercible(x)ToObject(x)x,则会引发TypeError。

答案 1 :(得分:19)

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

JS grammar的相关部分是

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

由于0[0]符合这些规则,因此它被视为有效表达式。是否正确(例如,不会在运行时抛出错误)是另一个故事,但是是的。这就是JS如何评估像someLiteral[someExpression]这样的表达式:

  1. 评估someExpression(可以是任意复杂的)
  2. 将文字转换为相应的对象类型(数字文字=> Number,字符串=> String等)
  3. 使用属性名称result(1)
  4. 调用result(2)上的get property操作
  5. 丢弃结果(2)
  6. 所以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无法转换为对象)并引发运行时错误。

答案 2 :(得分:9)

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

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

虽然没有立即明白你为什么要这样做,但是在Javascript中下标相当于使用点分表示法(尽管点符号限制了你使用标识符作为键)。

答案 3 :(得分:9)

我只是想注意,这是有效的语法对Javascript来说并不是唯一的。大多数语言都会出现运行时错误或类型错误,但这与语法错误不同。 Javascript选择在其他语言可能引发异常的许多情况下返回undefined,包括在订阅没有给定名称属性的对象时。

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

答案 4 :(得分:8)

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

答案 5 :(得分:3)

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

即使你设置0 [0] = 1,它也会在内存中设置属性及其值,但是当你访问0时,它会被视为一个数字(不要在此处理为对象和数字之间混淆。)

答案 6 :(得分:3)

语法不仅有效,结果也不一定是undefined,尽管在大多数情况下,如果不是所有理智的情况下都会如此。 JS是最纯粹的面向对象语言之一。大多数所谓的OO语言都是面向类的,在某种意义上说,你不能改变一旦创建的对象的形式(它与类相关),只改变对象的状态。在JS中,您可以更改对象的状态和形式,这比您想象的更频繁。如果您滥用它,这种能力会产生一些相当模糊的代码。数字是不可变的,所以你不能改变对象本身,不是它的状态,也不是它的形式,所以你可以做到

0[0] = 1;

这是一个有效的赋值表达式,它返回1但实际上没有赋值,数字0是不可变的。这本身有点奇怪。您可以拥有一个有效且正确(可执行)的assingment表达式,它不会分配任何内容(*)。但是,数字的类型是一个可变对象,因此您可以改变类型,并且更改将级联原型链。

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赋给对象的属性,但是你无法引用那个(transcient)对象,因此它将在nexx GC传递中被收集