對象就是一組沒有特定順序的值,對象的每個屬性或者方法都可由一個名稱來標識,這個名稱映射到一個值。可以把對象想象成一張散列表,其中的內容就是一組名值對,值可以是數據或者函數
創建自定義對象的通常方式是創建Object的一個新實例,然后再給它添加屬性和方法。早期開發者頻繁使用這種方式創建新對象,幾年后,對象字面量變成了更流行的方式
ES使用了一些內部特性來描述屬性的特征,這些特征是由為JS實現引擎的規范定義的,因此,開發者不能在JS中直接訪問這些特性。為了將某個特性標識為內部特性,規范會用兩個中括號把特性的名稱括起來
屬性分兩種:數據屬性和訪問器屬性
數據屬性
數據屬性包含一個保存數據值的位置,值會從這個位置讀取,也會寫入到這個位置。
數據屬性有4個特性描述它們的行為:
- [[Configurable]]:表示屬性是否可以通過delete刪除并重新定義,是否可以修改它的特性,以及是否可以把它改為訪問器屬性。默認情況下,所有直接定義在對象上的屬性的這個特性都是true
- [[Enumerable]]:表示屬性是否可以通過for-in循環返回,默認情況下,所有直接定義在對象上的屬性的這個特性都是true
- [[Writable]]:表示屬性的值是否可以被修改。默認情況下,所有直接定義在對象上的屬性的這個特性都是true
- [[Value]]:包含屬性實際的值,這個特性默認值為undefined
將屬性添加到對象之后[[Configurable]]、[[Enumerable]]、[[Writable]]都會被設置為true,而[[Value]]特性會被設置為指定的值
要修改屬性的默認特性,就必須使用Object.defineProperty()方法,這個方法接收3個參數:要給其添加屬性的對象、屬性的名稱和一個描述符對象。最后一個參數,即描述符對象上的屬性可以包含:configurable、enumerable、writable和value,跟相關特性的名稱一一對應。根據要修改的特性,可以設置其中一個或多個值
在調用Object.defineProperty()時,如果描述符對象上的屬性的值都不指定,則都默認為false
訪問器屬性
訪問器屬性不包含數據值,相反,他們包含一個獲取函數和一個設置函數,不過這兩個函數不是必須的,在讀取訪問器屬性時,會調用獲取函數,這個函數的責任就是返回一個有效的值,在寫入訪問器屬性時,會調用設置函數并傳入新值,這個函數必須決定對數據做出什么修改。
訪問器屬性有4個特性描述它們的行為:
- [[Configurable]]:表示屬性是否可以通過delete刪除并重新定義,是否可以修改它的特性,以及是否可以把它改為訪問器屬性。默認情況下,所有直接定義在對象上的屬性的這個特性都是true
- [[Enumerable]]:表示屬性是否可以通過for-in循環返回,默認情況下,所有直接定義在對象上的屬性的這個特性都是true
- [[Get]]:獲取函數,在讀取屬性時調用,默認值為undefined
- [[Set]]:設置函數,在寫入屬性時調用,默認值為undefined
訪問器屬性是不能直接定義的,必須使用Object.defineProperty()
獲取函數和設置函數不一定都要定義,只定義獲取函數意味著屬性是只讀的,嘗試修改屬性會被忽略,嚴格模式下會報錯,類似的,只有一個設置函數的屬性是不能讀取的,非嚴格模式下讀取會返回undefined,嚴格模式下會報錯
Object.defineProperties()方法可以通過多個描述符一次性定義多個屬性,它接受兩個參數:要為之添加或修改屬性的對象和另一個描述符對象,其屬性與要添加或修改的屬性一一對應
使用Object.getOwnPropertyDescriptor()方法可以取得指定屬性的屬性描述符,這個方法接收兩個參數:屬性所在的對象和要取其描述符的屬性名。返回值是一個對象
ES8新增了Object.getOwnPropertyDescriptors()靜態方法。這個方法實際上會在每個自有屬性上調用Object.getOwnPropertyDescriptor()并在一個新對象中返回它們
合并兩個對象:把源對象所有的本地屬性一起復制到目標對象上,這種操作也被稱為混入,因為目標對象通過混入源對象的屬性得到了增強
ES6專門為合并對象提供了Object.assign()方法,這個方法接受一個目標對象和一個或多個源對象作為參數,然后將每個源對象中可枚舉和自有屬性復制到目標對象,以字符串和符號為鍵的屬性會被復制,對每個符合條件的屬性,這個方法會使用源對象上的[[Get]]取得屬性的值,然后使用目標對象上的[[Set]]設置屬性的值
Object.assign()實際上對每個源對象執行的是淺復制,如果多個源對象都有相同的屬性,則使用最后一個復制的值。此外,從源對象訪問器屬性取得的值,比如獲取函數,會作為一個靜態值賦給目標對象。換句話說,不能在兩個對象間轉移獲取函數和設置函數
如果賦值期間出錯,則操作會中止并退出,同時拋出錯誤。Object.assign()沒有回滾之前賦值的概念,因此它是一個盡力而為,可能只會完成部分復制的方法
ES6規范新增了Object.is()方法,這個方法和===很像,但同時也考慮到了一些邊界情形
在給對象添加變量的時候,開發者經常會發現屬性名和變量名是一樣的,簡寫屬性名只要使用變量名就會自動被解釋為同名的屬性鍵,如果沒有找到同名變量就會報錯
在引入可計算屬性之前,如果想使用變量的值作為屬性,那么必須先聲明對象,然后使用中括號語法來添加屬性。換句話說,不能在對象字面量中直接動態命名屬性
有了可計算屬性,就可以在對象字面量中完成動態屬性賦值。中括號包圍的對象屬性鍵告訴運行時將其作為JS表達式而不是字符串來求值
可計算屬性表達式中拋出任何錯誤都會中斷對象創建,不能回滾
在給對象定義方法時,通常都要寫一個方法名、冒號,然后再引用一個匿名函數表達式,新的簡寫方法的語法遵循同樣的模式,但開發者要放棄給函數表達式命名,這樣可以明顯縮短方法聲明。簡寫方法名對獲取和設置函數也是適用的。簡寫方法名與可計算屬性鍵相互兼容
對象解構語法可以在一條語句中使用嵌套數據實現一個或多個賦值操作,簡單地說,對象解構就是使用與對象匹配的結構來實現對象屬性賦值
使用解構可以在一個類似對象字面量的結構中,聲明多個變量,同時執行多個賦值操作。如果想讓變量直接使用屬性的名稱,那么可使用簡寫語法。
解構賦值不一定與對象的屬性匹配,賦值的時候可以忽略某些屬性,而如果引用的屬性不存在,則該變量的值就是undefined。也可以在解構賦值的同時定義默認值。
解構在內部使用函數toObject()把源數據結構轉換為對象,這意味著對象解構的上下文中,原始值會被當成對象,這也意味著,null和undefined不能被解構,否則會報錯
解構并不要求變量必須在解構表達式中聲明,不過,如果是給事先聲明的變量賦值,則賦值表達式必須包含在一對括號中
解構對于引用嵌套的屬性或目標沒有限制,為此,可以通過解構來復制對象屬性,解構賦值可以使用嵌套解構,以匹配嵌套的屬性,在外層屬性沒有定義的情況下不能使用嵌套解構,無論源對象還是目標對象都一樣
涉及多個屬性的解構賦值是一個輸出無關的順序化操作,如果一個解構表達式涉及多個賦值,開始的賦值成功而后面的賦值出錯,則整個解構賦值只會完成一部分
在函數參數列表中也可以進行解構賦值,對參數的解構賦值不會影響arguments對象,但可以在函數簽名中聲明在函數體內使用局部變量