先來講下最簡單的兩種建立對象的方式:經過Object構造函數建立以及對象字面量方式。javascript
var person = new Object() person.name = 'youyang' person.age = 18 person.sayName = function () { console.log(this.name) }
var person = { name : 'youyang', age : 18, sayName : function () { console.log(this.name) } }
以上兩種方式在建立單個對象時沒有問題,但若是建立多個相似對象的話,就會產生大量重複代碼,爲了解決這一問題,出現了工廠模式建立對象的方法。java
工廠模式是軟件工程領域一種廣爲人知的設計模式,用函數來封裝建立一個具體對象的過程,而後就能夠屢次調用這個函數以建立類似的對象。設計模式
function createPerson (name, age) { var obj = new Object() obj.name = name obj.age = age obj.sayName = function () { console.log(this.name) } return obj } var p1 = createPerson('youyang', 18) var p2 = createPerson('xiaoqu', 28)
這種方式雖然解決了建立多個類似對象的問題,可是這種方式建立的對象沒法判斷對象的類型。函數
ECMAScript中的構造函數可用來建立特定類型的對象。所以自定義類型對象能夠像下面這樣建立:this
function Person (name, age) { this.name = name this.age = age this.sayName = function () { console.log(this.name) } } var p1 = new Person('youyang', 18) var p2 = new Person('youyang', 20)
爲了區分構造函數和普通函數,構造函數的首字母要大寫,不過構造函數自己也是一個函數,只不過是用來建立對象而已。同時使用構造函數建立實例對象,必須使用new操做符,以這種方式調用構造函數實際上會經歷下面4個步驟:.net
console.log(p1 instanceof Person) // true console.log(p2 instanceof Person) // true
另外再次說明,構造函數也是函數,它和其餘函數的不一樣僅僅在於能夠用new來調用建立一個實例對象,當構造函數不用new來調用時那它就和普通函數就沒有區別。prototype
構造函數解決了對象的類型檢測問題,可是它依然仍是存在缺陷的,觀察構造函數建立對象的代碼,每建立一個對象,就會執行一次Person構造函數,而在函數內部有一個sayName方法,那麼每執行一次Person,就會建立一個sayName方法,也就是p1實例的sayName方法和p2實例的sayName方法不是一個方法。設計
console.log(p1.sayName == p2.sayName) // false
那麼當建立多個對象時就會相應的建立多個函數,而一樣功能的方法,建立多個是沒有必要的。指針
每一個函數都有一個prototype屬性,一樣每一個構造函數都有一個prototype,這個屬性是一個指針,它指向一個對象,這個對象就是原型對象。而原型對象上的屬性是能夠被全部由該構造函數建立的實例共享的。
理解原型模式的工做原理必須先理解原型對象,建議先看下上篇文章:理解原型對象
下面是原型模式建立對象的例子:code
function Person () {} Person.prototype = { constructor: Person, name : 'youyang', age : 18, colors : ['red', 'yellow', 'blue'] sayName : function () { console.log(this.name) } } var p1 = new Person() var p2 = new Person()
而原型模式的缺點一方面,它沒辦法像構造函數那樣傳參的方式動態獲取不一樣的屬性值,全部的對象實例共享了原型上的屬性,它們默認取得的是相同的屬性值,另外一方面而=原型模式的共享本質致使了更加嚴重的問題,就是全部實例共享的引用類型屬性會相互影響。
p1.colors.push('green') console.log(p2.colors) // ['red', 'yellow', 'blue', 'green']
可見改變了p1實例的colors屬性,然而p2實例受到了影響,這是咱們不但願看到的。
建立自定義類型最多見的方式就是組合使用構造函數模式與原型模式,構造函數模式用於定義實例屬性,而原型模式用於定義共享的方法和屬性。
function Person (name, age) { this.name = name this.age = age this.colors = ['red', 'yellow', 'blue'] } Person.prototype = { constructor: Person, sayName : function () { console.log(this.name) } } var p1 = new Person('youyang', 18) var p2 = new Person('xiaoqu', 20) p1.colors.push('green') console.log(p1.colors) // ['red', 'yellow', 'blue', 'green'] console.log(p2.colors) // ['red', 'yellow', 'blue'] console.log(p1,sayName === p2.sayName) // true
因而可知,原型模式與構造函數模式組合使用,既能動態的定義每一個實例對象本身的屬性,並且引用類型屬性互不影響,同時還共享了原型上的方法。
這種構造函數與原型混合的模式是javascript中使用最普遍認同度最高的一種建立自定義類型的方法。
除了上面介紹的集中建立對象的方式,還有動態原型模式,寄生構造函數模式和穩妥構造函數模式,其中動態原型模式是在組合模式(構造函數模式和原型模式組合使用)的基礎上再進一步修改得來,將原型的初始化操做也一同封裝進構造函數中。寄生構造函數模式和穩妥構造函數模式也都有相應的應用場景,可是不多使用到,就不一一介紹了。