JS对象创建详解

转自http://driftcloudy.iteye.com/blog/949318

下面是JS的一个面试题

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

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

答案已在注释中指出。此处分析原因,主要是要理解JS创建对象的过程。
JS中有两种创建对象的方式,一种是通过new运算符,还有一种是通过字面量的方式。
一、利用字面量
ECMA标准语法如下:

Ecma 262 10.1.5
ObjectLiteral :
{ }
{ PropertyNameAndValueList }
PropertyNameAndValueList :
PropertyName : AssignmentExpression
PropertyNameAndValueList , PropertyName : AssignmentExpression
PropertyName :
Identifier
String Literal
Numeric Literal

根据描述,如果创建的不是空对象,而是一个带有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内部的方法。

二、利用new Object()
ECMA标准语法如下:

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. If the type of value is not Object,go to step 5.
  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. If the type of value is String, return To Object(value).
  6. If the type of value is Boolean, return To Object(value).
  7. If the type of value is Number, return To Object(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....

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容