为什么这行在javascript中有效?
var a = 0[0];
之后,a
为undefined
。
答案 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中的类型undefined
,Number
和Boolean
通常在内部存储为基元(不是完整的对象)。这些是一个紧凑的,不可变的存储表示(可能以这种方式实现了实现效率)。但是,Javascript希望您能够将这些原语视为具有属性和方法的对象。因此,如果您尝试访问基元上不直接支持的属性或方法,则Javascript将暂时将基元强制转换为适当类型的对象,并将值设置为基元的值。
当您在String
等基元上使用类似对象的语法时,解释器会将此识别为对基元的属性访问。它对此的响应是获取第一个0[0]
数字原语并将其强制转换为一个完整的0
对象,然后它可以访问Number
属性。在这种特定情况下,Number对象上的[0]
属性为[0]
,这就是您从undefined
获得的值的原因。
这是一篇关于将基元自动转换为对象以处理属性的文章:
The Secret Life of Javascript Primitives
以下是ECMAScript 5.1规范的相关部分:
如果值为0[0]
或undefined
,则抛出TypeError,否则返回null
。
- 让baseReference成为评估MemberExpression的结果。
- 让baseValue为GetValue(baseReference)。
- 让propertyNameReference成为评估Expression的结果。
- 让propertyNameValue为GetValue(propertyNameReference)。
- 调用CheckObjectCoercible(baseValue)。
- 让propertyNameString为ToString(propertyNameValue)。
- 如果正在评估的句法生成包含在严格中 模式代码,让严格为真,否则严格为假。
- 返回类型为Reference的值,其基值为baseValue且其值为 引用的名称是propertyNameString,其严格模式标志是 严格。
醇>
此问题的执行部分是上面的第5步。
这描述了当访问的值是属性引用时,它调用true
来获取任何原语的对象版本。
这描述了如何将ToObject(base)
,Boolean
和Number
基元转换为对象形式,并相应地设置了[[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]
这样的表达式:
someExpression
(可以是任意复杂的)Number
,字符串=> String
等)get property
操作
所以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,包括在订阅没有给定名称属性的对象时。
语法不知道表达式的类型(即使是像数字文字这样的简单表达式),并且允许您将任何运算符应用于任何表达式。例如,尝试下标undefined
或null
会导致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传递中被收集