### ECMAScript中的對象其實就是一組數據和功能的集合,Object的每個實例都具有下列的屬性和方法。
#### 1、定義:無序屬性的集合,其屬性可以包含基本值、對象或者函數。
#### 2、聲明對象的方法
```
// 1、實例聲明,添加屬性
let obj = new Object()
obj.name = 'zhang'
// 2、對象字面量聲明
let obj2 = { name: 'wang' }
```
#### 3、屬性類型
1)數據屬性:包含一個數據值的位置,這個位置可以讀取和寫入值。有4個描述行為的特性。
* [[configurable]]:表示能都通過delete刪除屬性重新定義屬性,能否修改特性,能否把屬性修改為訪問器屬性。一旦定義為false,就不能再定義configurable和configurable特性,否則會報錯。
* [[enumerable]]:表示能否用for-in循環返回屬性。
* [[writable]]:表示能否修改屬性的值。
* [[value]]:包含這個屬性的數據值。
注:使用Object.defineProperty()方法,未設置configurable、writable、enumerable的時候默認為false
```
// 1、configurable,定義為false后,不能再定義改特性和enumerable特性,如果重新定義會報錯
let obj0 = { name: 'zhang' }
Object.defineProperty(obj0, 'name', { configurable: false })
Object.defineProperty(obj0, 'name', { configurable: true }) // Error
Object.defineProperty(obj0, 'name', { enumerable: false } // Error
Object.defineProperty(obj0, 'name', { writable: false }) // 正常執行
Object.defineProperty(obj0, 'name', { value: 'wang' }) // 正常執行
delete obj0.name // 嚴格模式報錯,非嚴格模式不執行
// 2、enumerable
let obj1 = { name: 'zhang', sex: 'man' }
for (let key in obj1) { console.log(key) } // name sex 正常返回
Object.defineProperty(obj, name, { enumerable: false })
for (let key in obj1) { console.log(key) } // sex 只返回最后一個屬性
// 3、writable
let obj2 = { name: 'zhang' }
obj2.name = 'wang'
Object.defineProperty(obj2, 'name', { writable: false })
obj2.name = 'li' // 嚴格模式報錯,非嚴格模式不執行
console.log(obj2.name) // wang
4、value
let obj3 = { name: 'zhang' }
Object.defineProperty(obj3, 'name', { value: 'wang' })
console.log(obj3) // { name: 'wang' }
```
2)訪問器屬性:不包含數據屬性[[writable]]和[[value]]特性,但是包含getter和setter函數(不過不是必需的)。讀取訪問器屬性時會調用getter函數,寫入時調用setter函數。訪問器屬性不能直接定義,必須使用Object.defineProperty()來定義。有4個特性。
* [[configurable]]:表示能都通過delete刪除屬性重新定義屬性,能否修改特性,能否把屬性修改為數據屬性。一旦定義為false,就不能再定義configurable和configurable特性,否則會報錯。
* [[enumerable]]:表示能否用for-in循環返回屬性。
* [[get]]:讀取屬性時調用的函數,默認值為undefined
* [[set]]:寫入屬性時調用的函數,默認值為undefined
```
let book = {
_year: 2004,
edition: 1
}
Object.defineProperty(book, 'year', {
get: function () {
return this._year
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue
this.edition = 2
}
}
})
book.year = 2003 // 沒有效果
book.year = 2005
console.log(book) // {_year: 2005, edition: 2, year: 2005}
```
注:_year前面的下劃線是一種常見的記號,用于表示只能通過對象方法訪問的屬性。
```
// __defineGetter__和__defineSetter__方法
let book = { year: 2004, edition: 1 }
book.__defineGetter__('year', function () {
return this._year
})
book.__defineSetter__('year', function (newVaule) {
if (newValue > 2005) {
this._year = newValue
this.edition = 2
}
})
book.year = 2003 // 沒有效果
book.year = 2005
console.log(book) // {_year: 2005, edition: 2, year: 2005}
```
定義多個屬性
```
let book = {}
Object.defineProperties(book, {
_year: {
writable: true,
value: 2004
},
edition: {
writable: true,
value: 1
},
year: {
get: function () {
return this._year
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newVaule
this.edition = 2
}
}
}
})
book.year = 2003 // 沒有效果
book.year = 2005
console.log(book) // {_year: 2005, edition: 2, year: 2005}
```
#### 4、讀取屬性的特性
使用Object.getOwnPropertyDescriptor(target, key)方法,可以獲取給定屬性的特性,返回一個對象。如果是數據屬性,這個對象包含configurable、writable、enumerable、value四個屬性;如果是訪問器屬性,這個對象包含configurable、enumerable、get、set四個屬性。
```
let book = {}
Object.defineProperties(book, {
_year: {
writable: true,
value: 2004
},
edition: {
writable: true,
value: 1
},
year: {
get: function () {
return this._year
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue
this.edition = 2
}
}
}
})
let descriptor0 = Object.getOwnPropertyDescriptor(book, '_year')
console.log(descriptor0) // { configurable: false, enumerable: false, writable: true, value: 2004 }
let descriptor1 = Object.getOwnPropertyDescriptor(book, 'year')
console.log(descriptor1.enumerable) // false
console.log(typeof descriptor1.set) // function
```
#### 5、工廠模式
對然Object構造函數或對象字面量都可以用來創建單個對象,但這種方法有明顯的缺點,使用同一個接口創建很多對象的時候,會產生大量重讀代碼。這是后產生了工廠模式,一種函數,用函數來封裝以特定接口創建對象的細節。
```
// 函數createPerson()能夠接收參數來構建一個包含所有必要信息的Person對象。可以無數次調用這個函數創建一個包含兩個屬性和一個方法的對象。工廠模式雖然解決了創建多個相似對象的問題,卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
function createPerson (name, sex) {
var o = new Object()
o.name = name
o.sex = sex
o.sayName = function () {
alert(this.name)
}
return o
}
```
#### 6、構造函數模式
構造函數創建實例用new操作符,這種方式調用構造函數會經歷4步:1、創建一個新對象。2、將構造函數的作用域賦值給新對象(因此this就指向了新對象)。3、執行構造函數的代碼(為新對象添加屬性)。4、返回新對象。
```
function Person (name, sex) {
this.name = name
this.sex = sex
this.sayName = function () {
alert(this.name)
}
}
let person0 = new Person('zhang', 'man')
let person0 = new Person('wang', 'woman')
// constructor屬性標識當前構造函數
console.log(person0.constructor === Person) // true
// 也可以用instanceof檢測對象類型
console.log(person0 instanceof Object) // true
console.log(person0 instanceof Person) // true
// 構造函數模式的問題:每個方法都要在每個實例上重新創建一遍,Person0與Person1的sayName方法不是同一個方法。
console.log(person0.sayName === person1.sayName) // false
```
注:創建自定義構造函數意味著將來可以將它的實例標識為一種特定的類型,這正是構造函數模式勝過工廠模式的地方。