JS中的对象创建、属性访问

一条面试题

公司最近在招web fronter,免不了需要进行面试,于是大家在群里讨论一些题目。其中一题如下:

var a = new Object;
var b = new Object;
var c = new Object;
c[a]=a;
c[b]=b;
alert(c[a]===a); //输出什么
 

这个题目还是很有意思的,第一反应往往会心中涌起一丝疑惑,这难道不是true么(我就是的= =!)事实上还真输出了一个false...这样也许看不出什么端倪,但是如果稍微变动一下代码:

var a = new Object;
var b = new Object;
var c = new Object;
c[a]=a;
c[b]=b;
alert(c[a]===b); //输出true
alert(c[b]===b); //输出true
 

从结果可以明显的推断出一个事实:当运行了c[b]=b这条语句之后,原先的属性c[a]被覆盖了。也就是说,其实c[a]和c[b]指向的是同一个东西。

 

创建JS对象

现在需要深究一下,JS对象的属性究竟是怎么回事...一般而言,我们会认为JS中的对象就是一个无序的属性集,这个属性集中的每个属性都由键值对构成,就好比一种散列表类似的数据结构。对象里属性的值可以用来存放JS中原型的值、对象的引用、函数的引用。这在Ecmascript-262标准中可以找到理论依据:

Ecmascript-262 3rd
An object is a member of the type Object. It is an unordered collection of properties each of which
contains a primitive value, object, or function. A function stored in a property of an object is called a method.

 

JS中有两种创建对象的方式,一种是通过new运算符,还有一种是通过字面量的方式。前文的面试题中就是利用了new Object来创建一个貌似是空的对象。下面来分别分析一下这两种创建对象的方式:

 

1.字面量

字面量创建的方式很简单,根据Ecma标准语法:

Ecma 262 10.1.5
ObjectLiteral :
     { }
     { PropertyNameAndValueList }
PropertyNameAndValueList :
     PropertyName : AssignmentExpression
     PropertyNameAndValueList , PropertyName : AssignmentExpression
PropertyName :
     Identifier
     StringLiteral
     NumericLiteral

根据描述,如果创建的不是空对象,而是一个带有Name和Value的对象,那么Name可以是JS中的标识符、字符串或者数字。具体的创建过程是可以描述成:

1)var obj = {} 就等于var obj = new Object()

The production ObjectLiteral : {} is evaluated as follows:
1. Create a new object as if by the expression new Object().
2. Return Result(1).
 

2)var obj = { PropertyNameAndValueList }

如果是这种带了键值对的对象,首先还是调用new Object()来创建一个新的对象,然后会计算PropertyName、AssignmentExpression,利用GetValue方法获取AssignmentExpression的值,最后调用被新创建对象的[[Put]] 方法(obj的put方法是内部方法,外部无法调用),具体细节如下:

The production ObjectLiteral : { PropertyNameAndValueList } is evaluated as follows:
1. Evaluate PropertyNameAndValueList.
2. Return Result(1);
The production PropertyNameAndValueList : PropertyName : AssignmentExpression is evaluated as follows:
1. Create a new object as if by the expression new Object().
2. Evaluate PropertyName.
3. Evaluate AssignmentExpression.
4. Call GetValue(Result(3)).
5. Call the [[Put]] method of Result(1) with arguments Result(2) and Result(4).
6. Return Result(1).

这里的GetValue和[[Put]]方法都可以暂且不管,因为它们对于程序员并不可见。进一步看一下Evaluate PropertyName的过程:

The production PropertyName : Identifier is evaluated as follows:
1. Form a string literal containing the same sequence of characters as the Identifier.
2. Return Result(1).
The production PropertyName : StringLiteral is evaluated as follows:
1. Return the value of the StringLiteral.
The production PropertyName : NumericLiteral is evaluated as follows:
1. Form the value of the NumericLiteral.
2. Return ToString(Result(1)).

可以发现,在利用字面量创建对象的时候:如果属性的name用JS中的标识符表示,那么name会被转成值相同的字符串;如果属性的name是number,那么会调用ToString来计算该number的字符串表示,这儿的ToString也是JS内部的方法。

 

2.利用new Object()

调用new Object()也是一种创建对象的方法,如果不传参数进去,本质上来说跟直接使用字面量{}没有区别。实际上new Object的过程还是有些复杂的:

new Object ( [ value ] )
When the Object constructor is called with no arguments or with one argument value, the following steps are taken:
1. If value is not supplied, go to step 8.
2. Ifthetypeof value isnotObject,gotostep5.
3. If the value is a native ECMAScript object, do not create a new object but simply return value.
4. If the value is a host object, then actions are taken and a result is returned in an implementation-dependent manner that may depend on the host object.
5. Ifthetypeof value is String, return ToObject(value).
6. Ifthetypeof value is Boolean, return ToObject(value).
7. Ifthetypeof value is Number, return ToObject(value).
8. (The argument value was not supplied or its type was Null or Undefined.)Create a new native ECMAScript object.
The [[Prototype]] property of the newly constructed object is set to the Object prototype object.
The [[Class]] property of the newly constructed object is set to "Object".
The newly constructed object has no [[Value]] property.
Return the newly created native object

很显然,如果是不传参数,那么会创建一个 native ECMAScript object,随后会给这个object添加一系列的内部属性 ,比如将 [[Prototype]]设置为Object prototype object(即Object.prototype),将[[Class]]设置为字符串“Object”。注意,在Object.prototype中已经包含了一些方法:

        1.toString ( )

        2.toLocaleString ( )

        3.valueOf ( )

        4.hasOwnProperty (V)

        5.isPrototypeOf (V)

        6.propertyIsEnumerable (V)

利用new Object新创建的对象不会有除了上面之外别的方法。

 

对象的属性访问、赋值

对JS中的对象进行属性访问同样也是两种形式,一种是利用“[ ]”运算符,还有一种是利用“.”运算符。即:

CallExpression. Identifier 或CallExpression[ Expression ]

注意就是利用“.”的时候,只能后接一个合法的Identifie。但是如果是利用“[ ]”却可以包含一个表达式进来,本文刚开始的题目中就是利用这种形式去访问obj的属性。其实CallExpression. Identifier也会按照CallExpression[ <Identifier-string>]去执行。

CallExpression[ Expression ] is evaluated as follows:
1. Evaluate CallExpression.
2. Call GetValue(Result(1)).
3. Evaluate Expression.
4. Call GetValue(Result(3)).
5. Call ToObject(Result(2)).
6. Call ToString(Result(4)).
7. Return a value of type Reference whose base object is Result(5) and whose property name is Result(6).

尤其要注意第6行, 所有方括号中的Expression的值要经过ToString这一步。

 

对于赋值,具体的计算过程如下:

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
1. Evaluate LeftHandSideExpression.
2. Evaluate AssignmentExpression.
3. Call GetValue(Result(2)).
4. Call PutValue(Result(1), Result(3)).

CallExpression就是一种 LeftHandSideExpression。

 

题目详解:

下面来拆分题目,逐行来推敲具体的执行过程,首先是三条创建对象的语句:

//创建一个native Ecmascript object
//[[Prototype]]指向Object.prototype
//[[Class]]设为"Object"
var a = new Object;
//同a
var b = new Object;
//同a
var c = new Object;

注意,一个obj里并非只有 [[Prototype]]和[[Class]]两个内部属性,具体的内部属性以及内部方法可以参考Ecma262的8.6章节。

 

在创建完对象后,又是两条属性赋值语句。

//c[a]会按照CallExpression[ Expression ] 的7个步骤来计算,
//其中的GetValue较为复杂,可以参考http://www.w3help.org/zh-cn/causes/SD9028的注释2
//注意第6步,ToString(a)会调用到ToPrimitive(a),进而调用a的[[DefaultValue]]方法,具体参考Ecma规范
//这里最终会调用到a.toString方法,根据Object.prototype.toString的描述,会返回[object Object]
//即最终相当于c["[object Object]"]=a;
c[a]=a;
//即最终相当于c["[object Object]"]=b;
c[b]=b;
alert(c[a]===a);

 

稍微变个形

var a = {};
var b = {};
var c = {};
c.a=a;
c.b=b;
alert(c[a]===a);

你可能会立马想到,c.a是怎么处理的,很显然这是利用了“.”运算符访问属性,而不是“[ ]”。根据上面的描述, CallExpression. Identifier会按照CallExpression[ <Identifier-string>]去执行,那么这里的c.a就相当于c["a"]。因此最后结果肯定是true了?

 

真的是这样么?别忘了,在alert语句中还有c[a]的存在,如前文所述,c[a]完全就相当于c["[object Object]"],因此这段代码相当于:

var a = {};
var b = {};
var c = {};
c["a"]=a;
c["b"]=b;
alert(c["[object Object]"]===a);

真是相当的恶心....

 

再来变一变

这次c的属性直接在声明里写好了。

var a = {};
var b = {};
var c = {
	a:a,
	b:b
};
alert(c.a===a);

这回是利用了字面量创建对象的方式。根据上面的描述,如果键值对的name是一个合法的JS标识符,那么name就是将该标识符变成直接字符串,简单来说,就是两边加上引号。

因此,上面这段代码相当于:

var a = {};
var b = {};
var c = {};
c["a"]=a;
c["b"]=b;
alert(c.a===a);

终于输出了true....

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值