【讀書筆記】《JavaScript權威指南》第6章對象

對象簡述(瞭解)

  對象是JavaScript的基本數據類型。對象是一種複合值:它將很多值(原始值或者其他對象)聚合在一起,可通過名字訪問這些值。對象也可看做是屬性的無序集合,每個屬性都是一個名/值對。屬性名是字符串,因此我們可以把對象看成是從字符串到值的映射。這種基本數據結構還有很多種叫法,有些我們已然非常熟悉,比如“散列”(hash)、
“散列表”(hashtable)、“字典”(dictionary)、“關聯數組”(associative array)。
  然而對象不僅僅是字符串到值的映射,除了可以保持自有的屬性,JavaScript對象還可以從一個稱爲原型的對象繼承屬性。對象的方法通常是繼承的屬性。這種“原型式繼承”(prototypal inheritance)是JavaScript的核心特徵。
  屬性包括名字和值。屬性名可以是包含空字符串在內的任意字符串,但對象中不能存在兩個同名的屬性。值可以是任意JavaScript值,或者(在ECMAScript5中)可以是一個getter或setter函數(或兩者都有)。
  除了名字和值之外,每個屬性還有一些與之相關的值,稱爲“屬性特性”(property attribute):

  • 可寫(writable attribute),表明是否可以設置該屬性的值。
  • 可枚舉(enumerable attribute),表明是否可以通過for/in循環返回該屬性。
  • 可配置(configurable attribute),表明是否可以刪除或修改該屬性。

  在ECMAScript5之前,通過代碼給對象創建的所有屬性都是可寫的、可枚舉的和可配置的。在ECMAScript5中則可以對這些特性加以配置。
  除了包含屬性之外,每個對象還擁有三個相關的對象特性(object attribute):

  • 對象的原型(prototype)指向另外一個對象,本對象的屬性繼承自它的原型對象。
  • 對象的類(class)是一個標識對象類型的字符串。
  • 對象的擴展標記(extensible flag)指明瞭(在ECMAScript5中)是否可以向該對象添加新屬性。

  最後,我們用下面這些術語來對三類JavaScript對象和兩類屬性作區分:

  • 內置對象(native object)是由ECMAScript規範定義的對象或類。例如,數組、函數、日期和正則表達式都是內置對象。
  • 宿主對象(host object)是由JavaScript解釋器所嵌入的宿主環境(比如Web瀏覽器)定義的。客戶端JavaScript中表示網頁結構的HTMLElement對象均是宿主對象。既然宿主環境定義的方法可以當成普通的JavaScript函數對象,那麼宿主對象也可以當成內置對象。
  • 自定義對象(user-defined object)是由運行中的JavaScript代碼創建的對象。
  • 自有屬性(own property)是直接在對象中定義的屬性。
  • 繼承屬性(inherited property)是在對象的原型對象中定義的屬性。

創建對象(掌握)

對象直接量

  對象直接量是由若干名/值對組成的映射表,名/值對中間用冒號分隔,名/值對之間用逗號分隔,整個映射表用花括號括起來。屬性名可以是JavaScript標識符也可以是字符串直接量(包括空字符串)。屬性的值可以是任意類型的JavaScript表達式。

var empty={};//沒有任何屬性的對象
var point={x:0,y:0};//兩個屬性
var pointz={x:point.x,y:point.y+1};//更復雜的值
var book={
    "main title":"Javascript",//屬性名字裏有空格,必須用字符串表示
    'sub-title':"The Definitive Guide",//屬性名字裏有連字符,必須用字符串表示
    "for":"all audiences",//"for"是保留字,因此必須用引號
    author:{
        firstname:"David",//注意,這裏的屬性名都沒有引號
        surname:"Flanagan"        
    }

};

  在ECMAScript5(以及ECMAScript3的一些實現)中,保留字可以用做不帶引號的屬性名。然而對於ECMAScript3來說,使用保留字作爲屬性名必須使用引號引起來。
  在ECMAScript5中,對象直接量中的最後一個屬性後的逗號將忽略,且在ECMAScript3的大部分實現中也可以忽略這個逗號,但在IE中則報錯。
  對象直接量是一個表達式,這個表達式的每次運算都創建並初始化一個新的對象。每次計算對象直接量的時候,也都會計算它的每個屬性的值。也就是說,如果在一個重複調用的函數中的循環體內使用了對象直接量,它將創建很多新對象,並且每次創建的對象的屬性值也有可能不同。

通過new創建對象

  new運算符創建並初始化一個新對象。關鍵字new後跟隨一個函數調用。這裏的函數稱做構造函數(constructor),構造函數用以初始化一個新創建的對象。JavaScript語言核心中的原始類型都包含內置構造函數。

var o=new Object();//創建一個空對象,和{}一樣
var a= new Array();//創建一個空數組,和[]一樣
var d=new Date();//創建一個表示當前時間的Date對象
var r=new RegExp("js");//創建一個可以進行模式匹配的EegExp對象

Object.create()

原型

  在講述第三種對象創建技術之前,我們應當首先解釋一下原型。每一個JavaScript對象(nu11除外)都和另一個對象相關聯。“另一個”對象就是我們熟知的原型,每一個對象都從原型繼承屬性。
  所有通過對象直接量創建的對象都具有同一個原型對象,並可以通過JavaScript代碼object.prototype獲得對原型對象的引用。
  通過關鍵字new和構造函數調用創建的對象的原型就是構造函數的prototype屬性的值。因此,同使用{}創建對象一樣,通過new Object()創建的對象也繼承自object.prototype。同樣,通過new Array()創建的對象的原型就是Array.prototype,通過new Date()創建的對象的原型就是Date.prototype。
  沒有原型的對象爲數不多,Object.prototype就是其中之一。它不繼承任何屬性。其他原型對象都是普通對象,普通對象都具有原型。
  所有的內置構造函數(以及大部分自定義的構造函數)都具有一個繼承自object.prototype的原型。例如,Date.prototype的屬性繼承自Object.prototype,因此由new Date()創建的Date對象的屬性同時繼承自Date.prototype和Object.prototype。這一系列鏈接的原型對象就是所謂的“原型鏈”(prototype chain)。

Object.create()

  ECMAScript 5定義了一個名爲Object.create()的方法,它創建一個新對象,其中第一個參數是這個對象的原型。Object.create()提供第二個可選參數,用以對對象的屬性進行進一步描述。後面會詳細講述第二個參數。
  Object.create()是一個靜態函數,而不是提供給某個對象調用的方法。使用它的方法很簡單,只須傳入所需的原型對象即可:

var o1=Object.create({x:1,y:2});//o1繼承了屬性x和y

  可以通過傳入參數nu11來創建一個沒有原型的新對象,但通過這種方式創建的對象不會繼承任何東西,甚至不包括基礎方法,比如toString(),也就是說,它將不能和“+”
運算符一起正常工作。
  如果想創建一個普通的空對象(比如通過{}或new 0bject()創建的對象),需要傳入Object.prototype:

var o3=Object.create(Object.prototype);//o3和{}和new Object()一樣

  通過原型繼承創建一個新對象

//1inherit()返回了一個繼承自原型對象p的屬性的新對象
//這裏使用ECMAScript 5中的Object.create()函數(如果存在的話)
//如果不存在Object.create(),則退化使用其他方法
function inherit(p){
    if(p==null)throw TypeError();//p是一個對象,但不能是null
    if(Object.create)//如果0bject.create()存在
        return Object.create(p);//直接使用它
    var t=typeof p;//否則進行進一步檢測
    if(t l=="object"&&t!=="function")throw TypeError();
    function f(){};//定義一個空構造函數
    f.prototype=p;//將其原型屬性設置爲p 
    return new f();/使用f()創建p的繼承對象
}

  注意,inherit()並不能完全代替0bject.create(),它不能通過傳入nul1原型來創建對象,而且不能接收可選的第二個參數。
  inherit()函數的其中一個用途就是防止庫函數無意間(非惡意地)修改那些不受你控制的對象。當函數讀取繼承對象的屬性時,實際上讀取的是繼承來的值。如果給繼承對象的屬性賦值,則這些屬性只會影響這個繼承對象自身,而不是原始對象:

屬性的查詢和設置(掌握)

  可以通過點(.)或方括號([])運算符來獲取屬性的值,當使用方括號時,我們說方括號內的表達式必須返回字符串。其實更嚴格地講,表達式必須返回字符串或返回一個可以轉換爲字符串的值。

作爲關聯數組的對象.

object.property
object["property"]

  使用方括號和一個字符串,看起來更像數組,只是這個數組元素是通過字符串索引而不是數字索引。這種數組就是我們所說的關聯數組(associative array),也稱做散列、映射或字典(dictionary)。JavaScript對象都是關聯數組。

繼承

  JavaScript對象具有“自有屬性”(own property),也有一些屬性是從原型對象繼承而來的。假設要查詢對象o的屬性x,如果o中不存在x,那麼將會繼續在o的原型對象中查詢屬性x。如果原型對象中也沒有x,但這個原型對象也有原型,那麼繼續在這個原型對象的原型上執行查詢,直到找到x或者查找到一個原型是nul1的對象爲止。可以看到,對象的原型屬性構成了一個“鏈”,通過這個“鏈”可以實現屬性的繼承。
  在JavaScript中,只有在查詢屬性時纔會體會到繼承的存在,而設置屬性則和繼承無關,這是JavaScript的一個重要特性,該特性讓程序員可以有選擇地覆蓋(override)繼承的屬性。
  屬性賦值要麼失敗,要麼創建一個屬性,要麼在原始對象中設置屬性,但有一個例外,如果o繼承自屬性x,而這個屬性是一個具有setter方法的accessor屬性,那麼這時將調用setter方法而不是給o創建一個屬性x。需要注意的是,setter方法是由對象o調用的,而不是定義這個屬性的原型對象調用的。因此如果setter方法定義任意屬性,這個操作只是針對o本身,並不會修改原型鏈。

屬性訪問錯誤

  查詢一個不存在的屬性並不會報錯,如果在對象0自身的屬性或繼承的屬性中均未找到屬性x,屬性訪問表達式o.x返回undefined。但是,如果對象不存在,那麼試圖查詢這個不存在的對象的屬性就會報錯。nu11和undefined值都沒有屬性,因此查詢這些值的屬性會報錯。
  當然,給nu11和undefined設置屬性也會報類型錯誤。給其他值設置屬性也不總是成功,有一些屬性是隻讀的,不能重新賦值,有一些對象不允許新增屬性,但讓人頗感意外的是,這些設置屬性的失敗操作不會報錯:這是一個歷史遺留問題,這個bug在ECMAScript5的嚴格模式中已經修復。在嚴格模式中,任何失敗的屬性設置操作都會拋出一個類型錯誤異常。

//內置構造函數的原型是隻讀的
Object.prototype=o;//賦值失敗,但沒報錯,Object.prototype沒有修改

刪除屬性(掌握)

  delete運算符可以刪除對象的屬性。它的操作數應當是一個屬性訪問表達式。讓人感到意外的是,delete只是斷開屬性和宿主對象的聯繫,而不會去操作屬性中的屬性:
  a={p:{x:1}};b=a.p;delete a.p;執行這段代碼之後b.x的值依然是1。由於已經刪除的屬性的引用依然存在,因此在JavaScript的某些實現中,可能因爲這種不嚴謹的代碼而造成內存泄漏。所以在銷燬對象的時候,要遍歷屬性中的屬性,依次刪除。
  delete運算符只能刪除自有屬性,不能刪除繼承屬性(要刪除繼承屬性必須從定義這個屬性的原型對象上刪除它,而且這會影響到所有繼承自這個原型的對象)。
  當delete表達式刪除成功或沒有任何副作用(比如刪除不存在的屬性)時,它返回true。如果delete後不是一個屬性訪問表達式,delete同樣返回true:

0={x:1};//o有一個屬性x,並繼承屬性tostring
delete o.x;//刪除x,返回true
delete o.x;//什麼都沒做(x已經不存在了),返回true
delete o.toString;//什麼也沒做(tostring是繼承來的),返回true
delete 1;//無意義,返回true

  delete不能刪除那些可配置性爲fa1se的屬性(儘管可以刪除不可擴展對象的可配置屬性)。某些內置對象的屬性是不可配置的,比如通過變量聲明和函數聲明創建的全局對象的屬性。在嚴格模式中,刪除一個不可配置屬性會報一個類型錯誤。在非嚴格模式中(以及ECMAScript3中),在這些情況下的delete操作會返回false:

delete object.prototype;//不能刪除,屬性是不可配置的
Var x=1;//聲明一個全局變量
delete this.x;//不能刪除這個屬性
function f(){}//聲明一個全局函數
delete this.f;//也不能刪除全局函數

  在嚴格模式中,delete後跟隨一個非法的操作數(比如x),則會報一個語法錯誤,因此必須顯式指定對象及其屬性。

this.x=1;//創建一個可配置的全局屬性(沒有用var)
delete x;//將它刪除,在嚴格模式下報錯

檢測屬性(掌握)

  JavaScript對象可以看做屬性的集合,我們經常會檢測集合中成員的所屬關係——判斷某個屬性是否存在於某個對象中。可以通過in運算符、hasOwnPreperty()和propertyIsEnumerable()方法來完成這個工作,甚至僅通過屬性查詢也可以做到這一點。

in運算符

  in運算符的左側是屬性名(字符串),右側是對象。如果對象的自有屬性或繼承屬性中包含這個屬性則返回true。

var o={x:1}
"x" in o;//true:"x“是o的屬性
"y" in o;//false:"y"不是o的屬性
"tostring" in o;//true:o繼承toString屬性

hasOwnPreperty()

  對象的hasownProperty()方法用來檢測給定的名字是否是對象的自有屬性。對於繼承屬性它將返回false:

var o={x:1}
o.hasownProperty(“x");//true:o有一個自有屬性x
o.hasownProperty("y");//false:o中不存在屬性y
o.hasownProperty("tostring");//false:toString是繼承屬性

propertyIsEnumerable()

  propertyIsEnumerable()是hasOwnProperty()的增強版,只有檢測到是自有屬性且這個屬性的可枚舉性(enumerable attribute)爲true時它才返回true。某些內置屬性是不可枚舉的。通常由JavaScript代碼創建的屬性都是可枚舉的,除非在ECMAScript5中使用一個特殊的方法來改變屬性的可枚舉性,隨後會提到:

var o=inherit({y:2});
o.x=1;
o.propertyIsEnumerable("x");//true:o有一個可枚舉的自有屬性x
o.propertyIsEnumerable("y");//false:y是繼承來的
object.prototype.propertyIsEnumerable("toString");//false:不可枚舉

  除了使用in運算符之外,另一種更簡便的方法是使用“l==”判斷一個屬性是否是undefined:

var o={x:1}
o.x !==undefined;//true:o中有屬性x
o.y !==a undefined;//false:o中沒有屬性y 
o.toString !==undefined;//true:o繼承了toString屬性

  然而有一種場景只能使用in運算符而不能使用上述屬性訪問的方式。in可以區分不存在的屬性和存在但值爲undefined的屬性。

var o={x:undefined}//屬性被顯式賦值爲undefined
o.x!==undefined//false:屬性存在,但值爲undefined o.
y !== undefined//false:屬性不存在
"x” in o//true:屬性存在
"y" in o//false:屬性不存在
delete o.x;//刪除了屬性x
“x” in o//false:屬性不再存在

枚舉屬性(掌握)

for/in循環

  for/in循環可以在循環體中遍歷對象中所有可枚舉的屬性(包括自有屬性和繼承的屬性),把屬性名稱賦值給循環變量。對象繼承的內置方法不可枚舉的,但在代碼中給對象添加的屬性都是可枚舉的(除非用下文中提到的一個方法將它們轉換爲不可枚舉的)。

var o={x:1,y:2,z:3};//三個可枚舉的自有屬性
o.propertyIsEnumerable("toString")//=>false,不可枚舉
for(p in o)//遍歷屬性
console.log(p);//輸出x、y和z,不會輸出tostring

  有許多實用工具庫給0bject.prototype添加了新的方法或屬性,這些方法和屬性可以被所有對象繼承並使用。然而在ECMAScript 5標準之前,這些新添加的方法是不能定義爲不可枚舉的,因此它們都可以在for/in循環中枚舉出來。爲了避免這種情況,需要過濾for/in循環返回的屬性,下面兩種方式是最常見的:

for(p in o){
    if(lo.hasownProperty(p))continue;//跳過繼承的屬性
}
for(p in o){
    if(typeof o[p]==="function")continue;//跳過方法
}

Object.keys()

  除了for/in循環之外,ECMAScript5定義了兩個用以枚舉屬性名稱的函數。第一個是0bject.keys(),它返回一個數組,這個數組由對象中可枚舉的自有屬性的名稱組成。

Object.getownPropertyNames()

  ECMAScript 5中第二個枚舉屬性的函數是0bject.getownPropertyNames(),它和0jbect.keys()類似,只是它返回對象的所有自有屬性的名稱,而不僅僅是可枚舉的屬性。在ECMAScript3中是無法實現的類似的函數的,因爲ECMAScript3中沒有提供任何方法來獲取對象不可枚舉的屬性。

屬性getter和setter(掌握)

  在ECMAScript 5中,屬性值可以用一個或兩個方法替代,這兩個方法就是getter和setter。由getter和setter定義的屬性稱做“存取器屬性”(accessor property),它不同於“數據屬性”(data property),數據屬性只有一個簡單的值。
  當程序查詢存取器屬性的值時,JavaScript調用getter方法(無參數)。這個方法的返回值就是屬性存取表達式的值。
  當程序設置一個存取器屬性的值時,JavaScript調用setter方法,將賦值表達式右側的值當做參數傳入setter。從某種意義上講,這個方法負責“設置”屬性值。可以忽略setter方法的返回值。
  和數據屬性不同,存取器屬性不具有可寫性(writable attribute)。如果屬性同時具有getter和setter方法,那麼它是一個讀/寫屬性。如果它只有getter方法,那麼它是一個只讀屬性。如果它只有setter方法,那麼它是一個只寫屬性(數據屬性中有一些例外),讀取只寫屬性總是返回undefined。

varo={
    //普通的數據屬性
    data_prop:value,//存取器屬性都是成對定義的函數
    get accessor_prop(){/*這裏是函數體*/},
    set accessor_prop(value){/*這裏是函數體*/}
};

  存取器屬性定義爲一個或兩個和屬性同名的函數,這個函數定義沒有使用function關鍵字,而是使用get和(或)set。注意,這裏沒有使用冒號將屬性名和函數體分隔開,但在函數體的結束和下一個方法或數據屬性之間有逗號分隔。和數據屬性一樣,存取器屬性是可以繼承的。

屬性的特性(掌握)

  除了包含名字和值之外,屬性還包含一些標識它們可寫、可枚舉和可配置的特性。在ECMAScript3中無法設置這些特性,所有通過ECMAScript3的程序創建的屬性都是可寫的、可枚舉的和可配置的,且無法對這些特性做修改。
  可以認爲一個屬性包含一個名字和4個特性。
  數據屬性的4個特性分別是它的值(value)、可寫性(writable)、可枚舉性(enumerable)和可配置性(configurable)。
  存取器屬性不具有值(value)特性和可寫性,它們的可寫性是由setter方法存在與否決定的。因此存取器屬性的4個特性是讀取(get)、寫入(set)、可枚舉性和可配置性。
  爲了實現屬性特性的查詢和設置操作,ECMAScript5中定義了一個名爲“屬性描述符”(property descriptor)的對象,這個對象代表那4個特性。
  數據屬性的描述符對象的屬性有value、writable、enumerable和configurable。存取器屬性的描述符對象則用get屬性和set屬性代替value和writable。
  其中writable、enumerable和configurable都是布爾值,當然,get屬性和set屬性是函數值。

Object.getOwnPropertyDescriptor()

讀取

  通過調用0bject.getownPropertyDescriptor()可以獲得某個對象特定屬性的屬性描述符:

//返回{value:1,writable:true,enumerable:true,configurable:true}
Object.getownPropertyDescriptor({x:1},"x");
//返回{get:/*func*/,set:undefned,enumerable:true,configurable:true}
Object.getownPropertyDescriptor(random,"octet");
//對於繼承屬性和不存在的屬性,返回undefined
Object.getownPropertyDescriptor({},"x");//undefined,沒有這個屬性
Object.getownPropertyDescriptor({},"tostring");//undefined,繼承屬性

  從函數名字就可以看出,Object.getownPropertyDescriptor()只能得到自有屬性的描述符。要想獲得繼承屬性的特性,需要遍歷原型鏈(Object.getPrototypeof())。

設置

Object.defineProperty()

  要想設置屬性的特性,或者想讓新建屬性具有某種特性,則需要調用Object.definePeoperty(),傳入要修改的對象、要創建或修改的屬性的名稱以及屬性描述符對象:

var o={};//創建一個空對象
//添加一個不可枚舉的數據屬性x,並賦值爲1
Object.defineProperty(o,"x",{value:1,writable:true,enumerable:false,configurable:true});
//屬性是存在的,但不可枚舉
o.x;//=>1
Object.keys(o)//=>[]
//現在對屬性x做修改,讓它變爲只讀
Object.defineproperty(o,"x",{writable:false});
//試圖更改這個屬性的值
o.x=2;//操作失敗但不報錯,而在嚴格模式中拋出類型錯誤異常
o.x//=>1
//屬性依然是可配置的,因此可以通過這種方式對它進行修改:
Object.defineproperty(o,"x",{value:2});
o.x//=>2
//現在將x從數據屬性修改爲存取器屬性
Object.defneProperty(o,"x",{get:function(){return o;}});o.x//=>0

  傳入object.defineProperty()的屬性描述符對象不必包含所有4個特性。對於新創建的屬性來說,默認的特性值是false或undefined。對於修改的已有屬性來說,默認的特性值沒有做任何修改。注意,這個方法要麼修改已有屬性要麼新建自有屬性,但不能修改繼承屬性。

Object.defineProperties()

  如果要同時修改或創建多個屬性,則需要使用0bject.defineProperties()。第一個參數是要修改的對象,第二個參數是一個映射表,它包含要新建或修改的屬性的名稱,以及它們的屬性描述符,例如:

var p=Object. defineProperties({),{
    x:{ value:1, writable: true, enumerable: true, configurable: true},
    y:{ value:1, writable: true, enumerable: true, configurable: true}, r:{
    get: function(){ return Math. sqrt(this.x* this.x+ this.y* this.y)}, 
    enumerable: true, 
    configurable: true
    }
));

  對於那些不允許創建或修改的屬性來說,如果用Object.defineProperty()和Object.defineProperties()對其操作(新建或修改)就會拋出類型錯誤異常,比如,給一個不可擴展的對象新增屬性就會拋出類型錯誤異常。
  下面是完整的規則,任何對0bject.defineProperty()或Object.defineProperties()違反規則的使用都會拋出類型錯誤異常:

  • 如果對象是不可擴展的,則可以編輯已有的自有屬性,但不能給它添加新屬性。
  • 如果屬性是不可配置的,則不能修改它的可配置性和可枚舉性。
  • 如果存取器屬性是不可配置的,則不能修改其getter和setter方法,也不能將它轉換爲數據屬性。
  • 如果數據屬性是不可配置的,則不能將它轉換爲存取器屬性。
  • 如果數據屬性是不可配置的,則不能將它的可寫性從false修改爲true,但可以從true修改爲false。
  • 如果數據屬性是不可配置且不可寫的,則不能修改它的值。然而可配置但不可寫屬性的值是可以修改的(實際上是先將它標記爲可寫的,然後修改它的值,最後轉換爲不可寫的)。

  複製屬性的特性

/*
*給Object.prototype添加一個不可枚舉的extend()方法
*這個方法繼承自調用它的對象,將作爲參數傳入的對象的屬性一一複製
*除了值之外,也複製屬性的所有特性,除非在目標對象中存在同名的屬性,
*參數對象的所有自有對象(包括不可枚舉的屬性)也會一一複製。
*/
Object.defineProperty(object.prototype,
    “extend",定義Object.prototype.extend 
    {
        writable:true,enumerable:false,//將其定義爲不可枚舉的
        configurable:true,
        value:function(o){//值就是這個函數
            //得到所有的自有屬性,包括不可枚舉屬性
            var names=Object.getownPropertyNames(o);
            //遍歷它們
            for(var i=o;i<names.length;i++){
            //如果屬性已經存在,則跳過
                if(names[i]in this)continue;
                //獲得0中的屬性的描述符
                var desc=Object.getOwnPropertyDescriptor(o,names[i]);
                //用它給this創建一個屬性
                Object.defineProperty(this,names[i],desc);
            }
        }
    });

getter和setter的老式API

  可以通過對象直接量語法給新對象定義存取器屬性,但不能查詢屬性的getter和setter方法或給已有的對象添加新的存取器屬性。在ECMAScript5中,可以通過Object.getOwnPropertyDescriptor()和0bject.defineproperty()來完成這些工作。
  在ECMAScript5標準被採納之前,大多數JavaScript的實現(IE瀏覽器除外)已經可以支持對象直接量語法中的get和set寫法。這些實現提供了非標準的老式API用來查詢和設置getter和setter。這些API由4個方法組成,所有對象都擁有這些方法。
_lookupGetter_()和_lookupSetter_()用以返回一個命名屬性的getter和setter方法。defineGetter()和defineSetter()用以定義getter和setter,這兩個函數的第一個參數是屬性名字,第二個參數是getter和setter方法。這4個方法都是以兩條下劃線作前綴,兩條下劃線作後綴,以表明它們是非標準的方法。

對象的三個屬性(掌握)

原型屬性

  對象的原型屬性是用來繼承屬性的,這個屬性如此重要,以至於我們經常把“o的原型屬性”直接叫做“o的原型”。
  原型屬性是在實例對象創建之初就設置好的,通過對象直接量創建的對象使用Object.prototype作爲它們的原型。通過new創建的對象使用構造函數的prototype屬性作爲它們的原型。通過Object.create()創建的對象使用第一個參數(也可以是nul1)作爲它們的原型。
  在ECMAScript5中,將對象作爲參數傳入Object.getPrototypeOf()可以查詢它的原型。在ECMAScript3中,則沒有與之等價的函數,但經常使用表達式o.constructor.prototype來檢測一個對象的原型。通過new表達式創建的對象,通常繼承一個constructor屬性,這個屬性指代創建這個對象的構造函數。
  後面還解釋了使用這種方法來檢測對象原型的方式並不可靠的原因。注意,通過對象直接量或Object.create()創建的對象包含一個名爲constructor的屬性,這個屬性指代Object()構造函數。因此,constructor.prototype纔是對象直接量的真正的原型,但對於通過Object.create()創建的對象則往往不是這樣。
  要想檢測一個對象是否是另一個對象的原型(或處於原型鏈中),請使用isPrototypeof()方法。例如,可以通過p.isPrototypeOf(o)來檢測p是否是o的原型,isPrototypeOf()函數實現的功能和instanceof運算符非常類似:

var p={x:1};/定義一個原型對象
var o=Object.create(p);//使用這個原型創建一個對象
p.isPrototype0f(o)//=>true:o繼承自p
Object.prototype.isPrototypeOf(o)//=>true:p繼承自object.prototype

  Mozilla實現的JavaScript(包括早些年的Netscape)對外暴露了一個專門命名爲_proto_的屬性,用以直接查詢/設置對象的原型。但並不推薦使用_proto_,因爲儘管Safari和Chrome的當前版本都支持它,但IE和Opera還未實現它(可能以後也不會實現)。實現了ECMAScript 5的Firefox版本依然支持_proto,但對修改不可擴展對象的原型做了限制。

類屬性

  對象的類屬性(class attribute)是一個字符串,用以表示對象的類型信息。ECMAScript
3和ECMAScript5都未提供設置這個屬性的方法,並只有一種間接的方法可以查詢它。
默認的toString()方法(繼承自Object.prototype)返回瞭如下這種格式的字符串:

[object class]

  因此,要想獲得對象的類,可以調用對象的tostring()方法,然後提取已返回字符串的第8個到倒數第二個位置之間的字符。不過讓人感覺棘手的是,很多對象繼承的tostring()方法重寫了,爲了能調用正確的tostring()版本,必須間接地調用Function.ca11()方法。
  classof()函數可以返回傳遞給它的任意對象的類:

function classof(o){
    if(o ===null) return "Null";
    if(o === undefined) return "Undefined";
    return Object.prototype.toString.cal1(o).slice(8,-1);
}

  通過內置構造函數(比如Array和Date)創建的對象包含“類屬性”(class atribute),它與構造函數名稱相匹配。宿主對象也包含有意義的“類屬性”,但這和具體的JavaScript實現有關。通過對象直接量和Object.create創建的對象的類屬性是“Object”,那些自定義構造函數創建的對象也是一樣,類屬性也是“Object”,因此對於自定義的類來說,沒辦法通過類屬性來區分對象的類:

classof(nul1)//=>"Null"
classof(1)//=>"Number"
c1assof("")//=>"String"
classof(false)//=>"Boolean"
classof({})//=>"Object"
classof([])//=>"Array"
classof(/./)//=>"Regexp"
classof(new Date())//=>"Date".
classof(window)//=>"Window”(這是客戶端宿主對象)
function f(){};//定義一個自定義構造函數
classof(new f());//=>"Object"

可擴展性

  對象的可擴展性用以表示是否可以給對象添加新屬性。所有內置對象和自定義對象都是顯式可擴展的,宿主對象的可擴展性是由JavaScript引擎定義的。除非將它們轉換爲不可擴展的。
  ECMAScript5定義了用來查詢和設置對象可擴展性的函數。通過將對象傳入Object.esExtensible(),來判斷該對象是否是可擴展的。如果想將對象轉換爲不可擴展的,需要調用Object.preventExtensions(),將待轉換的對象作爲參數傳進去。注意,一旦將對象轉換爲不可擴展的,就無法再將其轉換回可擴展的了。同樣需要注意的是,preventExtensions()隻影響到對象本身的可擴展性。如果給一個不可擴展的對象的原型添加屬性,這個不可擴展的對象同樣會繼承這些新屬性。
  可擴展屬性的目的是將對象“鎖定”,以避免外界的干擾。對象的可擴展性通常和屬性的可配值性與可寫性配合使用。
  Object.seal()和Object.preventExtensions()類似,除了能夠將對象設置爲不可擴展的,還可以將對象的所有自有屬性都設置爲不可配置的。也就是說,不能給這個對象添加新屬性,而且它已有的屬性也不能刪除或配置,不過它已有的可寫屬性依然可以設置。對於那些已經封閉(sealed)起來的對象是不能解封的。可以使用Object.isSealed()來檢測對象是否封閉。
  Object.freeze()將更嚴格地鎖定對象——“凍結”(frozen)。除了將對象設置爲不可擴展的和將其屬性設置爲不可配置的之外,還可以將它自有的所有數據屬性設置爲只讀(如果對象的存取器屬性具有setter方法,存取器屬性將不受影響,仍可以通過給屬性賦值調用它們)。使用Object.isFrozen()來檢測對象是否凍結。
  Object.preventExtensions()、Object.seal()和Object.freeze()都返回傳入的對象,也就是說,可以通過函數嵌套的方式調用它們:

//創建一個封閉對象,包括一個凍結的原型和一個不可枚舉的屬性
var o=object.seal(Object.create(Object.freeze({x:1}),
                        {y:{value:2,writable:true}}));

序列化對象(掌握)

  對象序列化(serialization)是指將對象的狀態轉換爲字符串,也可將字符串還原爲對象。ECMAScript 5提供了內置函數]S0N.stringify()和]S0N.parse()用來序列化和還原JavaScript對象。這些方法都使用JSON作爲數據交換格式,JSON的全稱是“JavaScript Object Notation”——JavaScript對象表示法,它的語法和JavaScript對象與數組直接量的語法非常相近:

o={x:1,y:{z:[false,null,“]]}};//定義一個測試對象
s=JS0N.stringify(o);//s是{"x":1,"y":{"z":[false,mull,""]}}
p=JS0N.parse(s);//p是o的深拷貝

  NaN、Infinity和-Infinity序列化的結果是null,日期對象序列化的結果是ISO格式的日期字符串(參照Date.toJSON()函數),但JSON.parse()依然保留它們的字符串形態,而不會將它們還原爲原始日期對象。函數、RegExp、Error對象和undefined值不能序列化和還原。JSON.stringify()只能序列化對象可枚舉的自有屬性。對於一個不能序列化的屬性來說,在序列化後的輸出字符串中會將這個屬性省略掉。
  JSON.stringify()和JSON.parse()都可以接收第二個可選參數,通過傳入需要序列化或還原的屬性列表來定製自定義的序列化或還原操作。

對象方法(掌握)

toString()方法

  toString()方法沒有參數,它將返回一個表示調用這個方法的對象值的字符串。默認的toString()方法的返回值帶有的信息量很少,例如,下面這行代碼的計算結果爲字符串“[object Object]”:

vars={x:1,y:1}.tostring();

  由於默認的toString()方法並不會輸出很多有用的信息,因此很多類都帶有自定義的toString()。例如,當數組轉換爲字符串的時候,結果是一個數組元素列表,只是每個元素都轉換成了字符串,再比如,當函數轉換爲字符串的時候,得到函數的源代碼。

toLocaleString()方法

  除了基本的toString()方法之外,對象都包含tolocaleString()方法,這個方法返回一個表示這個對象的本地化字符串。0bject中默認的toLocaleString()方法並不做任何本地化自身的操作,它僅調用toString()方法並返回對應值。Date和Number類對toLocaleString()方法做了定製,可以用它對數字、日期和時間做本地化的轉換。Array類的toLocalestring()方法和toString()方法很像,唯一的不同是每個數組元素會調用tolocalestring()方法轉換爲字符串,而不是調用各自的toString()方法。

toJSON()方法

  Object.prototype實際上沒有定義toJSON()方法,但對於需要執行序列化的對象來說,JSON.stringify()方法會調用toJSON()方法。如果在待序列化的對象中存在這個方法,則調用它,返回值即是序列化的結果,而不是原始的對象。具體示例參見Date.toJSON()。

valueOf()方法

  valueOf()方法和toString()方法非常類似,但往往當JavaScript需要將對象轉換爲某種原始值而非字符串的時候纔會調用它,尤其是轉換爲數字的時候。如果在需要使用原始值的上下文中使用了對象,JavaScript就會自動調用這個方法。默認的valueOf()方法不足爲奇,但有些內置類自定義了valueOf()方法(比如Date.valueOf())。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章