## 創建對象
雖然 `Object` 構造函數或者對象字面量都可以用來創建單個對象,但這些方式有個明顯的缺點:使用同一個接口創建很多對象,會產生大量的重復性代碼。所以為了解決這種問題,我們使用其他方式進行
### 工廠模式
工廠模式是軟件工程領域一種廣為人知的設計模式,這種模式抽象了創建具體對象的過程。考慮到在ECMAScript中無法創建類,開發人員就發明了一種函數,用函數來封裝以特定接口創建對象的細節,如下所示:
```js
function createPerson (name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return 0;
}
var person1 = createPerson("SpiritLing",22,"web");
var person2 = createPerson("XiaoPeng",21,"前端");
```
函數 `createPerson` 能夠格局接受的參數來構建一個包含所有必要信息的 `person` 對象。可以無數次地調用這個函數,而每次它都會返回一個包含三個屬性一個方法的對象。工廠模式雖然解決了創建多個相似對象的問題,但卻沒有解決對象識別的問題(即怎么知道一個對象的類型)。隨著 JavaScript 的發展,又一個新模式出現
### 構造函數模式
使用構造函數模式將上面例子重寫:
```js
function Person (name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("SpiritLing",22,"web");
var person2 = new Person("XiaoPeng",21,"前端");
```
在上面這個例子中, `Person()` 函數取代了原來的函數。我們注意到,`Person()` 中的代碼除了與 `createPerson` 中相同的部分外,還有一些區別:
1. 沒有顯示地創建對象;
2. 直接將屬性和方法賦值給了 `this` 對象;
3. 沒有 `return` 語句;
此外,還應該注意到函數名的變換,此例中 `Person` 函數首字母是大寫字母。按照慣例,構造函數首字母應該大寫,用于和普通函數分別。
要創建 `Person` 的新實例,必須使用 `new` 操作符。以這種方式調用構造函數實際上會經歷以下4個步驟:
1. 創建一個新對象;
2. 將構造函數的作用域賦值給新對象(因此 `this` 就指向了這個新對象);
3. 執行構造函數中的代碼(為這個新對象添加屬性);
4. 返回新對象;
在前面例子的最后,`person1` 和 `person2` 分別保存著 `person` 的一個不同的實例。這兩個對象都有一個 `constructor` (構造函數)屬性,該屬性指向 `person` ,如下所示:
```js
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true
```
對象的 `constructor` 屬性最初是用于標識對象類型的。但是,提到檢測類型,還是 `instanceof` 操作符更可靠一些。我們在這個例子中創建的所有對象既是 `object` 的實例,同時也是 `person` 的實例,這一點通過操作符 `instanceof` 可以得到確認:
```js
console.log(person1 instanceof Person); //true
console.log(person1 instanceof Object); //true
console.log(person2 instanceof Person); //true
console.log(person2 instanceof Object); //true
```
創建自定義的構造函數意味著將來可以將它的實例標識為一種特定的類型;而這正是構造函數模式勝過工廠模式的地方。在這個例子中,`person1` 和 `person2` 之所以同時是 `Object` 的實例,是因為所有對象均繼承自 `Object`(后續有詳細解釋)。
#### 1. 將構造函數當做函數
> 構造函數與其他函數的唯一區別,就在于調用他它的方式不同。
不過,構造函數畢竟也是函數,不存在定義構造函數的特殊語法。任何函數,只要通過 `new` 操作符來調用,那它就可以作為構造函數;而任何函數,不通過 `new` 操作符來調用,那它跟普通函數也不會有什么兩樣。如下通過兩種方式調用上面的 `Person` 構造函數:
```js
// 當做構造函數使用
var person = new Person("SpiritLing",22,"web");
person.sayName(); // "SpiritLing"
// 作為普通函數調用
Person("SpiritLing",22,"web");
window.sayName(); // "SpiritLing";
// 在另一個對象的作用域中調用
var o = new Object();
Person.call(o,"SpiritLing",22,"web");
o.sayName(); // "SpiritLing"
```
這個例子中的前兩行代碼展示了構造函數的典型用法,即使用 `new` 操作符進行實例化操作,而三四行展示了普通函數調用的方式,而最后一個使用 `call` 來改變作用域指向,在另外一個對象中調用。