建立對象的模式:設計模式
1.工廠模式數組
1 function createPerson(name,age){ 2 var obj = new Object(); 3 obj.name = name; 4 obj.age = age; 5 obj.sayName = function(){ 6 alert(this.name) 7 } 8 return obj; 9 } 10 11 var person1 = createPerson("張三",20) 12 var person2 = createPerson("李四",21)
軟件工程領域廣爲人知的設計模式,抽象了建立具體對象的過程,在ECMAScript中沒法建立類,用函數來封裝以特定接口建立對象的細節。閉包
工廠模式的問題app
工廠模式能夠接受參數無數次構建一個包含全部必要信息的自定義對象,雖然解決了建立多個類似對象的問題,但不能解決對象識別的問題,由於父親原型都是Object,ide
那麼新建一個createDog的工廠,構建的對象 和 createPerson 工廠構造的對象 類型實際上是一致的。函數
2.構造函數模式this
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 this.sayname = function(){ 5 alert(this.name) 6 } 7 } 8 9 var person1 = new Person("張三",20) 10 var person2 = new Person("李四",21)
構造函數模式相比較工廠模式spa
沒有顯示的建立對象,直接將屬性和方法賦值給了this對象,沒有return語句.prototype
構造函數也是函數,只不過能夠建立對象而已,建立對象必須使用new操做符,經歷下面4個步驟:設計
a.建立一個新對象
b.將構造函數的做用域賦給新對象(this就指向這個新對象了)
c.執行構造函數中的代碼(爲新對象添加屬性)
d.返回新對象
使用構造函數模式支持使用person1.constructor == Person //true person1.constructor == Object //true
以前的工廠模式只會 person1.constructor == Person //false person1.constructor == Object //true
建立自定義的構造函數能夠將它的實例標識爲一種特定的類型,這是構造函數模式賽過工廠模式的地方。
構造函數的使用方式:
a.new操做符做爲構造函數使用
b.不使用new操做符
Person('李四',20);//將做用域賦給window window.name = '李四'
var o = new Object(); Person.call(o,'張三',20); //將做用域賦給o,這裏做用域傳遞具體請看 「淺談this」,o.name = '張三'
構造函數的問題
主要問題:在構造函數中的方法屬性也是一個函數,也就須要在每一個構造函數實例中都要從新建立一遍。即,使用構造函數構造兩個對象,好比這2個對象的方法屬性都有個sayName的方法,這兩個方法都是從新建立出來的,也是2個不一樣的實例。所以每次構造一個對象,都會從新初始化 屬於這個對象的方法屬性的實例,咱們能夠來想象下,若是一個對象有10個方法屬性,那麼就是很可怕的。原本都是和業務無關的完成一樣任務的方法,結果卻要和對象進行綁定。
alert(person1.sayName == person2.sayName) //false
既然this對象在,咱們不必把重複使用的方法函數綁定到this上,能夠經過把函數定義轉移到構造函數外部解決問題。
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 this.sayName = sayName; 5 } 6 7 function sayName(){ 8 alert(this.name) 9 }
這樣一來,person1和person2對象就共享了在全局做用域中定義的同一個sayName方法。
每當解決一個問題的時候就會出現另外一個問題。
若是對象須要定義不少的方法,那麼就須要定義不少個全局函數,那麼這個自定義的引用類型就絲毫沒有封裝可言了。
3.原型模式
1 function Person(){} 2 3 Person.prototype.name = "張三" 4 Person.prototype.age = 20 5 6 Person.prototype.sayName = function(){ 7 alert(this.name) 8 } 9 10 var person1 = new Person() 11 person1.sayName();//張三 12 var person2 = new Person() 13 person2.sayName();//張三 14 alert(person1.sayName == person2.sayName) // true
咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可讓特定類型的全部實例共享的屬性和方法。
和構造函數不一樣,咱們將sayName方法直接添加到了Person的prototype屬性中,仍然能夠經過調用構造函數來建立新對象,而且新對象還會具備這個prototype屬性中包含的全部屬性。
alert(person1.sayName == person2.sayName) //true
原型模式的問題
a.省略了爲構造函數傳遞初始化參數的過程,結果全部實例默認狀況下取得相同的屬性值。
b.原型中全部屬性是被不少實例共享的,適合函數,對於那些包含基本值的屬性也勉強說得過去,可是對於引用類型值的屬性就出現問題了。
原型模式下的實例沒法擁有屬於實例的私有引用類型屬性,好比數組等。由於共享,因此別的實例也能夠進行修改這個引用類型屬性的值。
這也是咱們不多看到有人單獨使用原型模式的緣由。
4.組合使用構造函數模式和原型模式
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 this.friends = ["王五"] 5 } 6 Person.prototype = { 7 constructor:Person, 8 sayName :function(){ 9 alert(this.name) 10 } 11 } 12 13 var person1 = new Person('張三',20) 14 var person2 = new Person('李四',21) 15 16 person1.friends.push('王六') 17 alert(person1.friends) // 王五,王六 18 alert(person2.friends) // 王五 19 20 person1.friends == person2.friends //false 21 person1.sayName == person2.sayName //true
建立自定義類型的最經常使用方式,就是組合使用構造函數模式與原型模式。
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性,結果每一個實例都會有本身的一份實例屬性的副本但同時又享用着對方法的引用,
最大限度的節省了內存,還支持想構造函數傳遞參數。
5.動態原型模式
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 if(typeof this.sayname != 'function'){ 5 Person.prototype.sayName = function(){ 6 alert(this.name) 7 } 8 } 9 }
動態原型模式修改了同時使用構造函數模式和原型模式的獨立風格,把全部的信息都封裝到構造函數中,在構造函數中初始化原型,又保持了同時使用構造函數模式和原型模式的優勢。
6.寄生構造模式
1 function SpecialArray(){ 2 var values = new Array(); 3 values.push.apply(values, arguments); 4 values.toPipedString = function(){ 5 return this.join("|"); 6 } 7 return values; 8 } 9 var a = new SpecialArray(2,6,8,9,4); 10 a.toPipedString(); 11 var b = SpecialArray(2,6,8,9,4); 12 b.toPipedString();
「寄生器構造函數」能夠在構造函數不適應的狀況使用,好比要建立一個數組類型,像上面代碼同樣(由於構造函數只能建立對象類型)。爲了讓人一看就知道是在構造一個新的對象類型的實例,因此雖然它寫的和工廠模式同樣,可是建立時用了new,所以使得實現的過程不同,(可是實現過程不重要)。
具體做用,好比建立具備額外方法的已有類型(如數組,Date類型等),可是又不污染原有的類型。
因此就算沒有new也同樣,只不過加上new讓人清楚這是新對象類型的實例,也是「寄生器構造函數」裏有個「構造函數」的緣由。
7.穩妥構造函數模式
function Person(name, age, job) { var o = new Object(); // private members var nameUC = name.toUpperCase(); // public members o.sayName = function() { alert(name); }; o.sayNameUC = function() { alert(nameUC); }; return o; } var person = Person("Nicholas", 32, "software Engineer"); person.sayName(); // "Nicholas" person.sayNameUC(); // "NICHOLAS" alert(person.name); // undefined alert(person.nameUC); // undefined