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閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容