[toc]
#### 創建對象
- 對象直接量
```javascript
var a = {},
b = {x: 0, y: 1},
c = {x: b.x, y: b.y},
d = {
name: 'UMEI',
year: 2,
member: {
'CEO': 'xu',
'CTO': 'yonnie'
}
}
```
- **new**
> 通過new運算符創建一個對象,這里稱作構造函數,構造函數用以初始化一個新創建的對象
```javascript
var a = new Object(),
b = new Array(),
c = new Date()
```
- **原型**
**每一個javascript對象(null除外)都和一個原型對象相關聯,每一個對象都從原型繼承屬性。**
所有通過對象直接量創建的對象都具有同一個原型對像--**Object.prototype**。
通過new創建的對象的原型就是構造函數的prototype屬性的值。因此通過new Object()創建的對象繼承Object.prototype,通過new Array()創建的對象繼承Array.prototype,通過new Date()創建的繼承Date.prototype。
Object.prototype不繼承任何屬性,沒有原型對象。
所有的其他的對象、原型對像都繼承自Object.prototype。因此new Date()創建的對象同時繼承自Date.prototype和Object.prototype。這一系列鏈接的原型對象就是所謂的**“原型鏈”**
- Object.create()
Object.create()可以傳入兩個參數,第一個參數是這個原型的對象,還有第二個可選參數。
```javascript
var a = Object.create({x: 1, y: 1});
a繼承了x和y屬性
```
可以通過傳入null創建一個沒有原型的新對象,但通過這種方式創建的不會繼承任何方法和屬性。
如果想創建一個普通的空對象,需要傳入Object.prototype
可以通過任務原型創建新對象(可以使任意對象可繼承)
```javascript
//inherit()返回了一個繼承自原型對像p的屬性的新對象
//這里使用es5中的Object.create()函數
//如果不存在Object.create()則使用其他方法
function inherit(p){
if(p == null) throw TypeError(); //p如果是null或者undefined 拋出類型錯誤
if(Object.create) //如果存在create方法,則使用create
return Object.create(p);
var t = typeof p;
if(t !== "object" && t !== "function"){
throw TypeError();
}
function f(){};
f.prototype = p;
return new f();
}
```
inherit()函數的作用就是防止庫函數無意間修改那些不受你控制的對象。當函數讀取繼承對象的屬性時,實際上讀取的是繼承來的值。如果給繼承對象的屬性賦值,不會影響原始對象
---
#### 屬性的查詢和設置
> 可以通過(.)和([])來獲取屬性的值。如果一個對象的屬性名是保留字,必須使用[]來訪問
##### 繼承
> Javascript對象具有“自有屬性”,也有一些屬性是從原型對象繼承而來。
查詢 對象o的屬性x,如果o中不存在x,那么就會沿著o的原型鏈查,直到找到x或者找到一個原型是null的對象位置。
```javascript
var o = {};
o.x = 1;
var p = inherit(o);
p.y = 2;
var q = inherit(p);
p.z = 3;
var s = q.toString();//toString繼承自Object.prototype
q.x + q.y; //--> 3, x和y分別繼承自o和p
q.x = 4; //會覆蓋q繼承的x屬性,o的x屬性不會變
```
屬性賦值操作首先檢查原型鏈。例如,如果o繼承自一個只讀屬性x。那么賦值操作是不允許的。如果允許賦值操作,它也總是在原始對象上創建屬性或對已有的屬性賦值,不會去修改原型鏈。
在js中,只有在查詢屬性時才會體會到繼承的存在,而設置屬性則和繼承無關。
##### 屬性訪問錯誤
訪問一個不存在的屬性不會報錯,單數如果訪問一個不存在的對象的屬性就會報錯
內置構造函數的原型是只讀的。
```javascript
Object.prototype = 1; //不會報錯,但是沒被修改為1
```
在嚴格模式中,任何失敗的屬性設置操作都會拋出一個類型錯誤。
#### 刪除屬性
delete運算符可以刪除對象的屬性,delete只是斷開屬性和宿主對象的聯系,而不會去操作屬性中的屬性。
```javascript
var a = {p: {x: 1}},
b = a.p;
delete a.p;
console.log(b.x); //--> 刪除了a.p屬性,但是b.x的值還是1
```
已經刪除的屬性的引用依然存在,delete只能刪除自有屬性。
delete不能刪除那些可配置性為false的屬性。某些內置對象的屬性是不可配置的,比如通過變量聲明和函數聲明創建的全局對象的屬性。在嚴格模式中,刪除一個不可配置屬性會報一個類型錯誤。在非嚴格模式中,delete操作失敗會返回false
```javascript
delete Object.prototype //不能刪除,屬性不可配置
var x = 1;
delete this.x; //返回false
delete x; //返回false
function f(){
}
delete this.f; //不能刪除全局函數
```
#### 檢測屬性
> 可以通過in運算符、hasOwnPreperty() 和 propertyInEnumerable()方法來判斷某個屬性是否存在于某個對象中。
in 運算符,如果對象的自由屬性或繼承屬性中包含這個屬性則返回true
```javascript
var o = {x: 1};
'x' in o; //true
'y' in o; //false
'toString' in o; //true 繼承的屬性
```
hasOwnProperty()方法用來檢測是否是自有屬性,如果是繼承的屬性會返回false
```javascript
var o = {x: 1}
o.hasOwnProperty('x'); //true
o.hasOwnProperty('y'); //false
o.hasOwnProperty('toString'); //false
```
propertyIsEnumerable()是hasOwnProperty()的增強版,只有檢測到自有屬性且這個屬性可枚舉時才返回true。有些內置屬性時不可枚舉的
```javascript
var o = inherit({y: 2});
o.x = 1;
o.propertyIsEnumerable('y'); //false
o.propertyIsEnumerable('x'); //true
Object.prototype.propertyIsEnumerable('toString'); //false toString不可枚舉
```
有一種情況只能用in運算符來判斷,就是存在并且值為undefined的屬性
```javascript
var o = {x: 1, y: undefined};
'x' in o; //true
'y' in o; //true
'z' in o; //false
delete o.x
'x' in o; //false
```
#### 枚舉屬性
> for/in 循環可以在循環體中遍歷對象中所有可枚舉的屬性(**包括自有和繼承的**),把屬性名稱賦值給循環變量。**對象繼承的內置方法不可枚舉**,但在代碼中給對象添加的屬性都是可枚舉的。
```javascript
var o = {
x: 1,
y: 2,
z: 3,
test: function(){
console.log('test')
}
},
p = Object.create(o);
for(i in p){
console.log(i); //--> x,y,z,test; 繼承自o的屬性都是可枚舉的, 沒有toString
}
```
在es5之前,在Object.prototype添加的方法會被枚舉出來。
定義一個extend()函數來枚舉屬性
```javascript
function extend(o, p){
for (prop in p){
o[prop] = p[prop]; //遍歷p中的所有屬性,將屬性添加至o中
}
return o;
}
/*
*將p中的可枚舉屬性復制到o中,并返回o
*如果o和p中有同名的屬性,o中的屬性將不受影響。
*這個函數并不處理getter和setter以及復制屬性
*
*/
function merge(o, p){
for(prop in p){
if(o.hasOwnProperty(prop)) continue; //過濾掉已經在o中存在的屬性
o[prop] = p[prop]
}
return o
}
/*
*如果o中的屬性在p中沒有同名屬性,則從o中刪除這個 屬性
*返回o
*/
function restrict(o, p){
for(prop in o){
if(!(prop in p)) delete o[prop]; //如果在p中不存在,則刪除之
}
return o;
}
/*
*如果o中的屬性在p中存在同名屬性,則從o中刪除這個屬性
*返回o
*/
function(o, p){
for(prop in p){
delete o[prop]
}
return o;
}
/*
*返回一個新對象,這個對象同時擁有o的屬性和p的屬性
*如果o和p有重名屬性,使用p中的屬性值
*/
function union(o, p){
return extend(extend({}, o), p);
}
/*
*返回一個新對象, 這個對象擁有同時再o和p中豎線的屬性
*很想求o和p的交集,但p中屬性的值被忽略
*/
function intersection(o, p){
return restrict(extend({}, o), p);
}
/*
*返回一個數組,這個數組包含的是o中可枚舉的自有屬性的名字
*/
function keys(o){
if(typeof o !== 'object') throw TypeError();
var result = [];
for(var prop in o){
if(o.hasOwnProperty(prop))
result.push(prop);
}
return result;
}
```
##### 屬性getter和setter
> 由getter和setter定義的屬性稱作“存取器屬性”,不同于數據屬性,數據屬性只有一個簡單的值
當程序查詢存取器屬性的值時,js調用getter方法。當設置存取器屬性的值時,調用setter方法。從某種意義上來看,這個方法負責“設置”屬性值,可以忽略setter 方法的返回值。
存取器屬性和數據屬性不同,它不具有可寫性。如果只有getter方法,那么是一個只讀屬性。如果只有setter方法,那么是只寫屬性,讀取時總是返回undefined。
定義存取器屬性最簡單的方法是使用對象直接量語法的一種擴展寫法:
```javascript
var o = {
//普通的數據屬性
data_drop: 1,
//存取器屬性
get accessor_prop(){
//函數體
},
set accessor_prop(value){
//函數體
}
}
```
存取器屬性定義為一個或兩個和屬性同名的函數,這個函數定義**沒有用function關鍵字**, 而是用了set、get,沒有使用冒號。 注意,與下一個方法或者屬性之間有逗號分隔。
```javascript
var p = {
x: 1.0,
y: 1.0,
get r(){
return Math.sqrt(this.x*this.x + this.y*this.y)
},
set r(new_value){
var old_value = this.x*this.x + this.y*this.y,
ratio = new_value/old_value;
this.x *= ratio;
this.y *= ratio;
},
get theta(){
return Math.atan2(this.y, this.x);
}
}
```
跟數據屬性一樣,存取器屬性也是可以繼承的
```javascript
var q = inherit(p);
q.x = 1, q.y = 1;
console.log(q.r); //可以使用繼承的存取器屬性
```
#### 屬性的特性
- 值
- 可寫性
- 可枚舉性
- 可配置性
存取器屬性不具有值特性和可寫性,他們的可寫性由setter方法決定。因此存取器屬性的4個特性是讀取、寫入、可枚舉性和可配置性
為了實現屬性特性的查詢和設置操作,es5中定義了一個“屬性描述符”的對象。
```javascript
//返回{value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor({x: 1}, 'x');
var a = {
get random(){
return Math.random()
}
}
//{get: function random(), set: undefined, configurable: true, enumerable: true}
Object.getOwnPropertyDescriptor(a, 'random');
//對于繼承屬性沒返回undefined
Object.getOwnPropertyDescriptor({}, 'toString'); //undefined
```
Object.getOwnPropertyDescriptor()只能得到自由屬性的描述符,如果想獲得繼承屬性的特性,則需要遍歷原型鏈。
我們可以調用Object.defineProperty()來設置屬性的特性,傳入要修改的對象、要創建或者修改的屬性的名稱以及屬性描述符對象:
```javascript
var o = {};
Object.defineProperty(o, 'x', {
value: 1,
writable: true,
enumerable: false,
configurable: true
});
o.x; //-->1 屬性存在
Object.keys(o); //--> [] 但是不可枚舉
//設置屬性只讀
Object.defineProperty(o, 'x', {
writable: false
});
o.x = 2 //操作失敗,在嚴格模式中會報錯
//雖然屬性只讀,但是依然可以通過配置來修改屬性的值
Object.defineProperty(o, 'x', {
value: 2
});
o.x; //2
//將x從數據屬性修改成存取器屬性
Object.defineProperty(o, 'x', {
get: function(){
return 0;
}
});
```
如果需要同時修改或者創建多個屬性則需要使用Object.defineProperties()。第一個參數是對象,第二個參數是一個映射表。
```javascript
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
}
})
```
- 如果對象不可擴展,則只能編輯已有的自有屬性,不能添加新屬性
- 如果屬性時不可配置的,則不能修改可配置性和可枚舉性
- 如果存取器屬性不可配置,則不能修改getter和setter方法,也不能轉為數據屬性
- 如果數據屬性不可配置,則不能轉為存取器屬性,不能將它的可寫性從false改為true,但是可以從true改為false
- 如果數據屬性是不可配置且不可寫的,則不能修改它的值。如果是可配置的,那么可以通過defineProperty()方法修改值
#### 對象的三個屬性
- 原型屬性
通過對象直接量創建的對象使用Object.prototype作為原型。
通過new創建的對象使用率構造函數的prototype屬性作為原型。
使用Object.create()創建的對象使用第一個參數(也可以是null)作為原型。
在es5中將對象作為參數傳入Object.getPrototypeof()可以查詢它的原型。在es3中,經常用表達式o.constructor.prototype 來檢測一個對象的原型。通過new表達式創建的對象,通常繼承一個constructor屬性,這個屬性指代創建這個函數的構造函數。(更多的細節再[9.2節](#)進一步討論)
通過對象直接量或者Object.create()創建的對象包含一個名為constructor的屬性,這個屬性指代Object()的構造函數,constructor.prototype才是真正的原型。要想檢測一個對象是否是另一個對象的原型(或者處于原型鏈中),可以使用isPrototypeOf()方法
```javascript
var p = {x: 1},
o = Object.create(p);
p.isPrototypeOf(o); //true
Object.prototype.isPrototypeOf(o) //true
```
對象還有一個__proto__屬性,但是ie和opera還不支持
- 類屬性
es3和es5都沒有設置這個屬性的方法,只有toString()方法會返回對應的字符串
[object class]
- 可擴展性
對象的可擴展性用以表示是否可以給對象添加新屬性。所有內置對象和自定義對象都是顯式可擴展的。
es5定義了一個方法--Object.esExtensible(),來判斷對象是否是可擴展的。
Object.preventExtensions()可以將對象轉換為不可擴展的。一旦轉換成不可擴展的對象,無法再轉換回可擴展的了!!
preventExtensions()只影響對象本身的可擴展性,如果給它的原型添加屬性,這個不可擴展的對象同樣能繼承新屬性。
Object.seal()和preventExtensions()類型,除了能將對象設置為不可擴展的還能降對象的所有自有屬性設置成不可配置的,不能給這個屬性添加新屬性,已有的屬性不能刪除或者配置,但是已有的可寫屬性依然可以設置。可以用Object.isSealed()來檢測對象是否封閉
Object.freeze()可以“凍結”對象。它將對象設置為不可擴展的,屬性設置為不可配置外,還能將對象的所有數據屬性設置為只讀的。可以通過Object.isFrozen()來檢測對象是否凍結。
#### 序列化對象
NaN、Infinity和-Infinity序列化的結果是null,日期對象的序列化結果是ISO格式的日期字符串,但JSON.parse()依然會保留他們的字符串形態,而不是還原成原始的日期對象。
函數、RegExp、Error對象和undefined值不能序列化和還原。
JSON.stringify()只能序列化對象可枚舉的自有屬性。
JSON.stringify()和JSON.parse()都能接受第二個可選參數,通過傳入需要序列化或還原的屬性列表來定制自定義的序列化或還原操作。Javascript核心參考中有詳細文檔
#### 對象方法
- toString()方法
- toLocaleString()方法
- toJSON()方法
- valueOf()方法