雖然Object構造函數或對象字面量,或者Object.create()均可以用來建立單個對象,但這些方式有個明顯的缺點:使用同一個接口建立不少對象,會產生大量的重複代碼。java
這種將建立對象的封裝成爲函數,調用函數來建立對象。用函數來封裝以特定接口建立對象的細節.windows
function createPerson(age,name){ var o = {}; o.age = age; o.name = name; o.sayName = function(){ console.log(this.name); } return o; // 不能肯定對象的類型。 } var p = createPerson('zhangsan',25); p.sayName(); // zhangsan
在上面咱們建立的是Person類型的對象p,可是卻不能判斷他的類型是Person。
工廠模式雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題。app
建立自定義的構造函數,從而定義自定義對象類型的屬性和方法。這樣就能夠將實例對象指定爲特定對象了。注意,該模式直接去用this定義屬性和方法,並無顯示的建立對象,且沒有返回值。
改寫前面的例子:函數
function Person(age,name){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name) } } var p = new Person();
var p = new Person();
執行含義是:
1.建立一個新對象
2.將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象);
3.執行構造函數中的代碼(爲這個新對象添加屬性);
4.返回新對象。
注意: 若將構造函數當成普通函數使用,this指向windows.
使用夠函數的弊端就是,方法也會在每一個實例上在建立一次,浪費內存。this
爲了不這空間的浪費,咱們能夠把函數轉移到函數外部。prototype
function sayName(){ console.log(this.name) } function Person(age,name){ this.name = name; this.age = age; this.sayName = sayName; } var p = new Person();
在全局做用域中定義的函數實際上只能被某個對象調用,這讓全局做用域有點名存實亡。而更讓人沒法接受的是:若是對象須要定義不少方法,那麼就要定義不少個全局函數,因而咱們這個自定義的引用類型就絲毫沒有封裝性可言了。code
這種模式的基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象;但從表面上看,這個函數又很像是典型的構造函數。
寄生構造函數模式與構造函數模式有相同的問題,每一個方法都要在每一個實例上從新建立一遍,建立多個完成相同任務的方法徹底沒有必要,浪費內存空間並且使用該模式返回的對象與構造函數之間沒有關係。所以,使用instanceof運算符和prototype屬性都沒有意義。
適用於對象的包裝,相似於java的裝飾器模式。 返回一個包裝對象。
基本樣式:對象
function Person(name, age, job){ var o = new Object(); // 類型是Object。 和Person無關 o.name = name; o.age = age; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person("zhangsan",25); friend.sayName(); //"zhangsan"
應用:讓建立的Array對象擁有新的方法(在不改變原有代碼的基礎上)接口
function SpecialArray() { var value = new Array(); value.push.apply(value,arguments); value.toPipedString = function(){ // 對Array對象的一層包裝 return this.join("|"); } return value; }
穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:一是新建立對象的實例方法不引用 this;二是不使用 new 操做符調用構造函數。
特色: 沒有公共屬性,並且其方法也不引用this的對象,instanceof失效。和寄生構造函數的不一樣在於不使用new來構造函數,同時實例方法不引用this。ip
function Person(name,age,job){ //建立要返回的對象 var o = new Object(); //能夠在這裏定義私有變量和函數 //添加方法 o.sayName = function(){ console.log(name); // 注意這裏訪問的是形參的值, 並非對象身上的name屬性 }; //返回對象 return o; } //在穩妥模式建立的對象中,除了使用sayName()方法以外,沒有其餘方法訪問name的值 var p = Person("zhangsan",25); p.name = 'lisi' // 即便能夠爲這個對象修改了屬性name, 單sayName訪問是形參name p.sayName();//"zhangsan"
使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。換句話說,沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中。
function Person(){}; Person.prototype = { // constructor:Person, // 這樣添加是可枚舉的 name: "zhangsan", age: 25, sayName : function(){ console.log(this.name); } }; // 默認狀況下,原生的constructor屬性是不可枚舉的, 因此最好設置爲不可枚舉的 Object.defineProperty(Person.prototype,'constructor',{ enumerable: false, value: Person }); var person1 = new Person(); person1.sayName();//"bai" console.log(person1.constructor === Person);//true console.log(person1.constructor === Object);//false
上述p和p1對象的屬性和方法都是共享的。原型模式問題在於引用類型值屬性會被全部的實例對象共享並修改。
建立自定義類型的最多見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存。另外,這種混成模式還支持向構造函數傳遞參數,是最適用的一種模式。
function Person(name,age,job){ this.name = name; this.age = age; this.friends = ["zhangsan","lisi"]; } Person.prototype = { constructor: Person, sayName : function(){ console.log(this.name); } } var person1 = new Person("wangwu",29); person1.friends.push("zhaoliu"); console.log(person1.friends);// ["zhangsan","lisi","zhaoliu"]; console.log(person2.friends);// ["zhangsan","lisi"]; console.log(person1.friends === person2.friends);//false console.log(person1.sayName === person2.sayName);//true
動態原型模式將組合模式中分開使用的構造函數和原型對象都封裝到了構造函數中,而後經過檢查方法是否被建立,來決定是否初始化原型對象。
function Person(name,age,job){ // 屬性 this.name = name; this.age = age; //方法 檢查sayName 是否建立 if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ console.log(this.name); }; } } var friend = new Person("zhangsan",25); friend.sayName();//'zhangsan'