建立對象方式:html
工廠模式:使用簡單的函數建立對象,爲對象添加屬性和方法,而後返回對象;數組
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; return o; } var person1 = createPerson("Nicholas",20,"soft"); var person2 = createPerson("Greg",27,"IT"); //優勢:可以無數次調用該函數,生成相同屬性的對象。 // 缺點:但卻沒有解決對象識別的問題。
構造函數模式:安全
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Nicholas",20,"soft"); var person2 = new Person("Greg",27,"IT"); 在這個實例中,Person()函數取代了createPerson函數。咱們注意到,Person()中的代碼除了與createPerson中相同的部分外,還存在如下不一樣之處: 1 沒有顯示的建立對象 2 直接將屬性和方法賦給了this對象 3 沒有return語句 要建立Person的新實例,必須使用new 操做符。以這種方式調用構造函數實際上會經歷如下4個步驟 (1) 建立一個新對象 (2) 將構造函數的做用域賦給新對象 (3) 執行構造函數中的代碼 (4) 返回新對象 前面例子的最後,person1 、 person2 分別保存着Person的一個不一樣的實例。這兩個對象都有一個constructor(構造函數)屬性,該屬性指向Person console.log(person1.constructor == Person); //true console.log(person2.constructor == Person); //true 最初標識對象類型的方式:constructor屬性。可是,提到檢測檢測類型,仍是 instancof 操做符更靠譜些。 console.log(person1 instanceof Person); //true console.log(person2 instanceof Person); //true console.log(person1 instanceof Object); //true console.log(person2 instanceof Object); //true 建立自定義的構造函數意味着未來能夠將它的實例標識爲一種特定的類型;而這正是構造函數模式賽過工廠模式的地方。 在這個例子中,person1 和 person2 之因此同時是Object 的實例,是由於全部對象均繼承自Object; 1 構造函數用法 將構造函數看成函數 構造函數與其它函數的區別,就是調用的方式不同。不過構造函數也是函數,也不存其定義方式不同。 任何函數經過new操做符來調用,那他就能夠做爲構造函數。若是任何函數不一樣過new來調用,則和普通函數沒有啥區別; <1> 看成爲構造函數調用時 var person1 = new Person("Nicholas",20,"soft"); person1.sayName();// Nicholas <2> 看成爲普通函數調用時 == window Person("Nicholas",20,"soft"); sayName();//Nicholas <3> 在另一個對象做用域中調用 var o = new Object(); Person.cell(o,"Nicholas",20,"soft"); o.sayName(); //Nicholas 2 構造函數問題 每一個函數在每一個實例上多建立了一遍; person1 與 person2 都有一個名爲 sayName 的方法,但那兩個方法不是相同的實例; function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)") } 在ECMScript中函數也是對象,所以定義一個函數也就是實例化了一個對象(Person.sayName); 從這個角度來看每一個實例對象是存在不一樣的Function實例的本質。 建立兩個徹底相同的Function實例確實沒有必要;何況還有this,不要代碼執行前就把函數綁定到特定的對象上面。所以大能夠像這樣 把函數轉移到外部方式來解決這個問題; function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } 這樣的方式來確實解決一個問題是,解決兩個函數作同一件事情,可是當一個對象存在多個方法的時候,這對於自定的函數來講絲毫沒有封裝可言。好在這些問題能夠經過原型來解決。
原型模式:app
1 每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。 2 若是按照字面意思來理解,那麼prototype就是經過調用構造函數而建立的那個對象實例的原型對象。 3 4 實例的原型對象, 5 原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。換句話說,沒必要在構造函數中定義對象實例的信息,而是能夠直接將這些信息直接添加到原型對象中,以下 6 7 function Person(){} 8 9 Person.prototype.name = "Nicholas"; 10 Person.prototype.age = 29; 11 Person.prototype.job = "Software Engineer"; 12 Person.prototype.sayName = function(){ 13 alert(this.name); 14 } 15 16 var person1 = new Person(); 17 person1.sayName(); //Nicholas 18 19 var person2 = new Person(); 20 person2.sayName(); //Nicholas 21 22 alert(person1.sayName == person2.sayName); //true 23 24 1 理解原型對象 25 不管何時,只要建立一個新函數,就會根據一組特定的規則爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。 26 在默認狀況下,全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。 27 就拿前面來講 Person.prototype.constructor 指向 person。而經過這個構造函數,咱們還能夠繼續爲原型對象。 28 而經過這個構造函數,咱們還可繼續爲原型對象添加其餘屬性和方法。 29 30 建立自定義的構造函數以後,其原型對象默認只會取得constructor屬性;至於其它方法,則都是從Object 繼承而來的。 31 32 當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。ECMA-262第5版中管這個指針叫[[Prototype]]. 33 34 雖然腳本中沒有標準的 方式訪問,但 Firefox\ Safari\ Chrome 在每一個對象上都支持一個屬性__proto__ 而在其它實現中,這個屬性對象腳本則是徹底不可見的。 35 36 不過,要明確的 [[Prototype]] 鏈接存在於實例與構造函數的原型對象之間,而不是存在於實例與構造函數之間。 37 38 雖然在全部實現過程當中沒法訪問到[[Potottype]],但能夠經過isPrototypeOf()方法來肯定對象與原型之間的關係。 39 從本質講,若是[[Potottype]]指向調用isPrototypeOf()方法的對象(Person.property),那麼這個方法就返回true 以下 40 console.log(Person.prototype.isPrototypeOf(person1)); //true 41 42 ECMScript5增長一個新方法 43 /* 44 @param1 對象 45 @return 對象關聯的原型對象 46 IE9+ Firefox3.5+,Safari5+,Opera 12+ 和 Chrome 47 */ 48 Object.getPrototypeOf() 49 50 每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。 51 1 搜索首先從對象實例開始 找到則返回 否 繼續 52 2 從指針指向的原型對象中搜索 53 54 注意:雖然能夠經過對象實例訪問保存在原型中的值,但卻不能經過對象實例重寫原型中的值。 若是在實例中添加一個屬性,而該屬性與實例原型中的一個屬性同名, 55 那咱們就在實例中建立該屬性,該屬性將會屏蔽原型中的那個屬性。 56 57 function Person(){} 58 59 Person.prototype.name = "Nicholas"; 60 Person.prototype.age = 29; 61 Person.prototype.job = "Software Engineer"; 62 Person.prototype.sayName = function(){ 63 alert(this.name); 64 } 65 66 var person1 = new Person(); 67 var person2 = new Person(); 68 person1.name = "Greg"; 69 console.log(person1.name); //Greg --- 來自實例 70 console.log(person2.name); //Nicholas --- 來自原型 71 72 該實例說明一個問題:實例中添加與原型中聲明同名的變量,只會阻止其訪問其原型。並不會修改那個屬性。 即便將這個屬性設置爲null,也只會在實例中設置這個屬性,而不會回覆其指向原型的鏈接。 73 不過delete 操做符則能夠徹底刪除實例屬性,從而讓咱們可以從新訪問原型中的屬性, 74 75 function Person(){ 76 } 77 Person.prototype.name = "Nicholas"; 78 Person.prototype.age = 29; 79 Person.prototype.job = "Software Engineer"; 80 Person.prototype.sayName = function(){ 81 alert(this.name); 82 } 83 84 var person1 = new Person(); 85 person1.name = "Greg"; 86 delete person1.name; 87 console.log(person1.name); //Nicholas --- 來自原型 88 89 注意:判斷一個屬性是存在於實例中,仍是存在於原型中。這個方法只在給定屬性存在於對象實例中時,纔會返回true。來看下面這個例子 90 91 function Person(){} 92 Person.prototype.name = "Nicholas"; 93 Person.prototype.age = 29; 94 Person.prototype.job = "Software Engineer"; 95 Person.prototype.sayName = function(){ 96 alert(this.name); 97 } 98 99 var person1 = new Person(); 100 var person2 = new Person(); 101 person1.hasOwnProperty("name"); //false 102 103 person1.name = "WJ"; 104 person1.hasOwnProperty("name"); //true 105 106 2 原型與in 操做符 107 108 function Person(){} 109 110 Person.prototype.name = "Nicholas"; 111 Person.prototype.age = 29; 112 Person.prototype.job = "Software Engineer"; 113 Person.prototype.sayName = function(){ 114 alert(this.name); 115 } 116 117 var person1 = new Person(); 118 var person2 = new Person(); 119 console.log(person1.hasOwnProperty("name")); //false 120 console.log("name" in person1) // true 121 122 person1.name = "Greg"; 123 console.log(person1.name); // 來自實例 124 console.log(person1.hasOwnProperty("name")); //true 125 console.log("name" in person1); //true 126 127 delete person1.name 128 console.log(person1.name); // Nicholas 來自原型 129 console.log(person1.hasOwnProperty("name")); // false 130 console.log("name" in person1) // true 131 132 133 從上面能夠看出要麼是從對象,要麼是從原型中訪問到的。所以,調用「name」 in person1 始終都返回true,不管該屬性存在於實例中仍是存在於原型中。 134 同時使用 hasOwnProperty()方法和in操做符,就能夠肯定該屬性到底存在於對象中,仍是存在於原型中,以下所示 135 136 function hasPrototypeProperty(object,name){ 137 return !object.hasOwnProperty(name) && (name in object); 138 } 139 function Person(){} 140 Person.prototype.name = "Nicholas"; 141 Person.prototype.age = 29; 142 Person.prototype.job = "Software Engineer"; 143 Person.prototype.sayName = function(){ 144 alert(this.name); 145 } 146 147 var person1 = new Person(); 148 hasPrototypeProperty(person1,"name"); // true 原型 149 person1.name = "Greg"; // 實例 150 hasPrototypeProperty(person1,"name"); // false 151 152 該屬性顯示存在於原型中時 hasPrototypeProperty 返回true 153 當第二次的時返回false 由於實例中存在該同名屬性時,就不要原型中的同名屬性 154 155 156 注意:在使用for-in循環時,返回的是全部可以經過對象訪問的、可枚舉的屬性,其中既包括存在於實例中的屬性,也包括存在於原型中的屬性。屏蔽了原型中不可枚舉的屬性(即將[[Enumerable]]) 157 標記的屬性)的實例實例屬性也會在for-in循環中返回,由於根據規定,全部開發人員定義的屬性是可枚舉的----只有在IE8及更好版本中例外。 158 159 IE早起版本的實現中存在一個bug,既屏蔽不可枚舉屬性的實例屬性不會出如今for-in循環中 160 161 例 162 var o = { 163 toString:function(){ 164 return "My Object"; 165 } 166 } 167 168 for(var prop in o){ 169 if(prop == "toString"){ 170 alert("Found toString"); //在IE中不會顯示 171 } 172 }; 173 174 175 要得到對象上的可枚舉屬性,能夠利用 ECMScript5 增長一個新方法 Object.keys(); 176 177 /* 178 @param object 179 */ 180 Object.keys(); 181 182 function Person(){ 183 } 184 185 Person.prototype.name = "Nicholas"; 186 Person.prototype.age = 29; 187 Person.prototype.job = "Software"; 188 Person.prototype.sayName = function(){ 189 alert(this.name); 190 }; 191 var keys = Object.keys(Person.prototype); 192 alert(keys); //"name,age,job,sayName" 193 194 var p1 = new Person(); 195 p1.name = "Rob"; 196 p1.age = 31; 197 var p1keys = Object.keys(p1); 198 alert(p1keys); // name age 199 200 201 若是想得到全部實例屬性,不管它是否可枚舉,均可以使用Object.getOwnPropertyNames()方法。 202 var keys = Object.getOwnPropertyNames(Person.prototype); 203 alert(keys); // constructor name age job sayName 204 205 keys()\getOwnPropertyNames Export: IE9+ Firefox4+ Safari5+ Opera12+ Chrome 206 207 3 更簡單的原型語法 208 209 讀者大概注意到了,前面例子中每添加一個屬性和方法就要敲一遍 Person.prototype. 爲減小沒必要要的輸入,也爲了從視覺上更好的封裝原型的功能, 210 更常見的作法是用一個包含全部屬性和方法的對象自面量來重寫整個原型對象,以下 211 212 function Person(){} 213 Person.prototype = { 214 name:"Nicholas", 215 age:29, 216 job:"Safari5", 217 sayName:function(){ 218 alert(this.name); 219 } 220 }; 221 222 在上面的代碼中,咱們將Person.prototype 設置爲等於一個以對象自面量形式建立的新對象。最終結果相同,但有一個例外:constructor 屬性再也不指向Person了。在這裏本質上徹底重寫了默認的prototype對象, 224 所以constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數),再也不指向Person函數。 225 此時, instanceof 操做符還能返回正確結果,但經過constructor已經沒法肯定對象的類型了。 226 227 var friend = new Person(); 228 229 alert(friend instanceof Object); //true 230 alert(friend instanceof Person); //true 231 232 alert(friend.constructor == Object); //true 233 alert(friend.constructor == Person); //false 234 235 在此,用 instanceof 操做符測試 Object 和 Person 任然返回 true, 但 constructor 屬性則等於 Object 而不等於Person 了。若是constructor的值真的很重要,能夠像 236 下面這樣特地將它設置回適當的值。 237 function Person(){} 238 Person.prototype = { 239 constructor:Person, 240 name:"Nicholas", 241 age:29, 242 job:"Safari5", 243 sayName:function(){ 244 alert(this.name); 245 } 246 }; 247 248 以上代碼特地包含了一個constructor屬性,並將它的值設置爲Person,從而確保了經過該屬性可以訪問到適當的值。 249 可是這種方式設置constructor屬性的[Enumerable]特性被設置爲true。默認狀況下,原生的constructor 屬性是不可枚舉的,所以若是你使用兼容ECMAScript5 的JavaScript引擎,能夠試一試 250 Object.defineProperty().或 Object.defineProperties() 更改屬性基本信息.
優勢是屬性函數共用;缺點:<1> 省略了構造函數的傳參,生成全部實例在默認狀況下都取得相同的屬性值。<2> 最大問題仍是其共用本質引發來的函數
<1> 就是其共享本質問題 原型中全部屬性是本不少實例共享的,這種共享對於函數很是合適。對於包含引用類型值得屬性來講,問題就比較突出了。 function Person(){ } Person.prototype = { constructor:Person, name:"Nicholas", age:29, job:"Software", friends:["Shelby","Court"], sayName:function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); alert(person2.friends); alert(person1.friends === person2.friends); 從上面的一個實例看出來,person1與perosn2共用一個字符串數組,其中一方做修改都會反映出來。假如咱們的初衷是兩個對象實例共享一個數組 的話,那是沒有問題;但是,實例通常是要由屬於本身的所有屬性,而這個問題正是咱們不多有人會單獨使用原型;
組合使用構造函數模式和原型模式性能
1 組合使用構造函數和原型模式 2 3 1 構造函數定義實例屬性 4 2 原型定義方法和共享的屬性 5 每一個實例都有本身的實例屬性的副本,但同時又共享着對象的引用,最大限度地節省了內存。 另外這種混成模式還支持想構造函數傳遞參數;可謂是集兩種模式之長。 6 7 function Person(name,age,job){ 8 this.name = name; 9 this.age = age; 10 this.job = job; 11 this.friends = ["Shelby",Court]; 12 } 13 14 Person.prototype ={ 15 constructor:Person, 16 sayName:function(){ 17 alert(this.name); 18 } 19 } 20 var perosn1 = new Person("Nicholas",29,"Software"); 21 var perosn1 = new Person("Greg",27,"Software"); 22 23 person1.friends.push("Van"); 24 alert(person1.friends); //"Shelby,Count,Van" 25 alert(person2.friends); //"Shelby,Count" 26 alert(person1.friends === person2.friends); // false 27 alert(person1..sayName === person2.sayName); // true 28 29 這種方式,使用最多,最廣,認同度最高的一種建立自定義類型的方式。
動態原型模式測試
1 對於OO語言經驗的人來講,因爲構造函數、原型相互獨立的時候,會感到很是的困惑。動態原型模式正是致力於解決這個問題的一個方案,它把全部信息封裝在一個構造 2 函數中,而經過在構造函數中初始化原型(僅在必要的狀況下),又保持了同時使用構造函數和原型的有點。換句話說,能夠經過檢查某個應該存在的方法是否有效,來決定是否 3 須要初始化原型。 4 5 function Person(){ 6 if(typeof this.sayName !="function"){ 7 Person.prototype.sayName = function(){ 8 alert(this.name); 9 } 10 } 11 } 12 13 var friend = new Person(); 14 friend.sayName(); 15 16 這種方式主要是:只有當sayName方法不存在的狀況下,纔會將它添加到原型中。
寄生構造函數模式this
1 一般是前面幾種方式不適合的狀況下,可使用這種方式。 2 基本思想: 3 建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象; 4 5 function Person(name,age,job){ 6 var o = new Object(); 7 o.name = name; 8 o.age = age; 9 o.job = job; 10 o.sayName = function(){ 11 alert(this.name); 12 } 13 return o; 14 } 15 var friend = new Person("Nicholas",29,"Software"); 16 friend.sayName(); //"Nicholas" 17 18 在這個實例中,Person 建立了一個新對象,並以相應的屬性和方法初始化該對象,而後又返回這個對象。除了使用new操做符並把使用包裝函數叫作構造函數以外,這個模式跟工廠模式實際上是如出一轍的。 19 構造函數在不返回值的狀況下,默認會返回新對象實例。而經過在構造函數的未添加一個 return語句,能夠重寫構造的返回值。 20 21 例如使用場景:當但願建立一個具備額外方法的特殊數組。因爲不能直接修改Array構造函數,所以可使用這個模式。 22 23 function SpercialArray(){ 24 //建立數組 25 var values = new Array(); 26 values.push.apply(values,arguments); 27 28 values.toPipedString = function(){ 29 return this.join("|"); 30 } 31 return values; 32 } 33 var colors = new SpercialArray("red","blue","green"); 34 alert(colors.toPipedString()); //"red|blue|green" 35 關於寄生構造函數模式,有一點須要說明:首先返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係; 36 也就是不能經過 instanceof 操做符來肯定對象類型。 若是可使用其它模式不推薦使用。
穩妥結構函數模式spa
所謂穩妥對象,指的是沒有公共屬性,並且其方法也不引用this的對象;使用場合在一些安全的環境中,或者在防止數據被其餘應用程序改動時使用。穩妥構造函數遵循與寄生構造函數相似的模式,但有兩點不一樣:一:建立的新對象的實例不引用this;二:不是用new操做符調用構造函數。上Person構造函數改寫以下prototype
1 function Person(name,age,job){ 2 var o = new Object(); 3 4 o.sayName = function(){ 5 alert(name); 6 } 7 return o; 8 }
注意:在以這種模式建立的對象中,除了使用sayName()方法以外,沒有其餘辦法訪問name的值。能夠向下面使用的Person構造函數。
var friend = Person("Nicholas",29,"Software Engineer");
friend.sayName(); // "Nicholas"
這樣變量friend中保存的是一個穩妥對象,而除了調用sayName()方法外,沒有別的方式訪問其餘數據成員。即便有其餘代碼給這個對象添加方法或者數據成員,但也不可能有別的辦法訪問傳入到構造函數中的原始數據。
穩妥構造函數模式提供安全性,使得它很是適合在某些安全執行環境中
繼承模式
原型鏈
1 2 實現原型鏈有一種基本模式,其代碼大體以下 3 4 function SuperType(){ 6 this.property = true; 7 } 8 9 SuperType.prototype.getSuperValue = function(){ 10 return this.prototype; 11 } 12 function SubType(){ 13 this.subproperty = false; 14 } 15 16 //繼承了SuperType 17 18 SubType.prototype = new SuperType(); 19 20 SubType.prototype.getSubValue = function(){ 21 return this.subproperty; 22 } 23 24 var instance = new SubType(); 25 alert(instance.getSuperValue()); 26 27 28 29 30 function SuperType(){ 31 this.property = true; 32 } 33 34 SuperType.prototype.getSuperValue = function(){ 35 return this.property; 36 } 37 38 function SubType(){ 39 this.subproperty = false; 40 } 41 SubType.prototype = new SuperType(); 42 43 SubType.property = { 44 getSubValue:function(){ 45 return this.subproperty; 46 }, 47 someOtherMethod:function(){ 48 return false; 49 } 50 } 51 52 var instance = new SubType(); 53 alert(instance.getSuperValue()); 54 55 56 既在經過原型進行繼承時,不能使用字面量的方法建立原型內容,由於這樣會重寫原型鏈 57 58 原型中存在的最大問題是: 59 1 引用類型值得共用問題 60 61 2 用原型實現的繼承,因爲屬性共用,在建立子類型實例時,不能向超類型的構造函數中傳遞參數。
借用構造函數
借用構造函數 call() apply()
在解決原型中包含引用類型值所帶來問題的過程當中 可使用一種叫借用構造函數的技術(有時候也叫作僞造對象或經典繼承)。這種技術的基本思想至關簡單,既在子類型構造函數的內部調用超類型構造函數。別忘了,函數 只不過是在特定環境中執行代碼的對象,所以經過使用apply()和 call()方法也能夠在新建立的對象上執行構造函數, function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ SuperType.call(this); } var instance = new SubType(); instance.colors.push("black"); alert(instance.colors); //red blue green black var instance1 = new SubType(); alert(instance1.colors); // red blue green 借用構造函數相比原型模式的優勢: 傳遞參數 例如: function SuperType(name){ this.name = name; } function SubType(){ SuperType.call(this,"Nicholas"); this.age = 20; } var instance = new SubType(); alert(instance.name); // Nicholas alert(instance.age); //20 借用構造函數的問題:
若是僅僅是借用構造函數,那麼也將沒法避免構造函數模式存在的問題------ 方法都在構造函數中定義,所以函數複用就無從談起了。
組合模式繼承: 原型模式和借構造函數兩種模式的組合,取它們兩的有點, 原型來定義實例的共用屬性和方法,構造函數來定義實例屬性
組合繼承(combination inheritance) 原型與借用構造函數兩種方法取其長的一種組合方式 主要思想: 利用原型對屬性和方法的繼承 而經過借用構造函數來實現對實例屬性的繼承。 這樣既然繼承原型中屬性,又能保證明例擁有本身的屬性。 function SuperType(name){ this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ //繼承 SuperType.call(this,name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("Nicholas",20); instance1.colors.push("black"); alert(instance1.colors); instance1.sayName(); instance1.sayAge(); var instance2 = new SubType("Greg",27); alert(instance2.colors); instance2.sayName(); instance2.sayAge
原型式繼承
寄生式繼承
寄生組合式繼承
無論是那種模式,最終都是爲了建立對象。