[TOC]
# **Object 類型**
  在 ECMAScript 規范中,引用類型除 Object 本身外,Date、Array、RegExp 也屬于引用類型 。
  引用類型也即對象類型,ECMA262 把對象定義為:**無序屬性的集合,其屬性可以包含基本值、對象或者函數**。 也就是說,對象是一組沒有特定順序的值 。由于其值的大小會改變,所以不能將其存放在棧中,否則會降低變量查詢速度。因此,對象的值存儲在堆(heap)中,而存儲在變量處的值,是一個指針,指向存儲對象的內存處,即按址訪問。具備這種存儲結構的,都可以稱之為引用類型 。
<br>
## 7.1 **對象拷貝**
  由于引用類型的變量只存指針,而對象本身存儲在堆中 。因此,當把一個對象賦值給多個變量時,就相當于把同一個對象地址賦值給了每個變量指針 。這樣,每個變量都指向了同一個對象,當通過一個變量修改對象,其他變量也會同步更新。
```
var obj = {name:'Jack'};
var obj2 = obj;
obj2.name ='Tom'
console.log(obj.name,obj2.name);//Tom,Tom
```
  ES6 提供了一個原生方法用于對象的拷貝,即 Object.assign() 。
```
var obj = {name:'Jack'};
var obj2 = Object.assign({},obj);
obj2.name ='Tom'
console.log(obj.name,obj2.name);//Jack Tom
```
  需要注意的是,Object.assign() 拷貝的是屬性值。當屬性值是基本類型時,沒有什么問題 ,但如果該屬性值是一個指向對象的引用,它也只能拷貝那個引用值,而不會拷貝被引用的那個對象。
```
var obj = {base:{name:'Jack'}};
var obj2 = Object.assign({},obj);
obj2.base.name ='Tom'
console.log(obj.base.name,obj2.base.name);//Tom Tom
```
  從結果可以看出,obj 和 obj2 的屬性 base 指向了同一個對象的引用。因此,Object.assign 僅僅是拷貝了一份對象指針作為副本 。這種拷貝被稱為 “一級拷貝”?或 “淺拷貝”**。**
  如果要徹底的拷貝一個對象作為副本,兩者之間的操作相互不受影響,則可以通過 JSON 的序列化和反序列化方法來實現 。
```
var obj = {base:{name:'Jack'}};
var obj2 = JSON.parse(JSON.stringify(obj))
obj2.base.name ='Tom'
console.log(obj.base.name,obj2.base.name);//Jack Tom
```
  這種拷貝被稱為 “多級拷貝”?或 “深拷貝” 。?
<br>
## 7.2 **屬性類型**
  ECMA-262 第 5 版定義了一些內部特性(attribute),用以描述對象屬性(property)的各種特征。ECMA-262 定義這些特性是為了實現 JavaScript 引擎用的,因此在 JavaScript 中不能直接訪問它們。為了表示特性是內部值,該規范把它們放在了兩對兒方括號中,例如\[\[Enumerable\]\]。 這些內部特性可以分為兩種:數據屬性 和 訪問器屬性?。
<br>
### 【1】**數據屬性**
  數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值 。數據屬性有4個描述其行為的內部特性:
* \[\[Configurable\]\]:能否通過 delete 刪除屬性從而重新定義屬性,或者能否把屬性修改為訪問器屬性。該默認值為 true。
* \[\[Enumerable\]\]:表示能否通過 for-in 循環返回屬性。默認值為 true。
* \[\[Writable\]\]:能否修改屬性的值。默認值為 true。
* \[\[Value\]\]:包含這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。默認值為 undefined 。
  要修改屬性默認的特性,必須使用 ECMAScript 5 的 Object.defineProperty() 方法。這個方法接收三個參數:屬性所在的對象、屬性的名字和一個描述符對象。其中,描述符(descriptor)對象的屬性必須是:configurable、enumerable、writable 和 value。設置其中的一或多個值,可以修改對應的特性值。例如:
```
var person = {};
Object.defineProperty(person,"name", {
writable:false, value:"Nicholas"
});
console.log(person.name);//"Nicholas"
person.name ="Greg";
console.log(person.name);//"Nicholas"
```
  在調用 Object.defineProperty() 方法時,如果不指定 configurable、enumerable 和?writable 特性,其默認值都是 false 。
### **【2】訪問器屬性**
  訪問器屬性不包含數據值,它們包含一對 getter 和 setter 函數(不過,這兩個函數都不是必需的)。在讀取訪問器屬性時,會調用getter 函數,這個函數負責返回有效的值;在寫入訪問器屬性時,會調用setter 函數并傳入新值,這個函數負責決定如何處理數據。訪問器屬性有如下4 個特性。
* \[\[Configurable\]\]:表示能否通過 delete 刪除屬性從而重新定義屬性,或者能否把屬性修改為數據屬性。默認值為true 。
* \[\[Enumerable\]\]:表示能否通過 for-in 循環返回屬性。默認值為 true。
* \[\[Get\]\]:在讀取屬性時調用的函數。默認值為 undefined 。
* \[\[Set\]\]:在寫入屬性時調用的函數。默認值為 undefined 。
訪問器屬性不能直接定義,也必須使用 Object.defineProperty() 來定義。請看下面的例子:
```
var book = {
_year: 2004
};
Object.defineProperty(book,"year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
console.log('set new value:'+ newValue)
}
}
});
book.year = 2005;//set new value:2005
```
## ?**Object 新增 API**
  ECMA-262 第 5 版對 Object 對象進行了增強,包括?defineProperty 在內,共定義了 9 個新的 API:
* create(prototype\[,descriptors\]):用于原型鏈繼承。創建一個對象,并把其 prototype 屬性賦值為第一個參數,同時可以設置多個 descriptors 。
* defineProperty(O,Prop,descriptor) :用于定義對象屬性的特性。
* defineProperties(O,descriptors) :用于同時定義多個屬性的特性。
* getOwnPropertyDescriptor(O,property):獲取 defineProperty 方法設置的 property 特性。
* getOwnPropertyNames:獲取所有的屬性名,不包括 prototy 中的屬性,返回一個數組。
* keys():和 getOwnPropertyNames 方法類似,但是獲取所有的可枚舉的屬性,返回一個數組。
* preventExtensions(O) :用于鎖住對象屬性,使其不能夠拓展,也就是不能增加新的屬性,但是屬性的值仍然可以更改,也可以把屬性刪除。
* Object.seal(O) :把對象密封,也就是讓對象既不可以拓展也不可以刪除屬性(把每個屬性的 configurable 設為 false),單數屬性值仍然可以修改。
* Object.freeze(O) :完全凍結對象,在 seal 的基礎上,屬性值也不可以修改(每個屬性的 wirtable 也被設為 false)。