一.建立對象
雖然Object構造函數或對象字面量能夠用來建立單個對象,但有個明顯缺點:使用同一個接口建立不少對象會產生大量重複代碼。於是你們開始探索其餘方式。
1.工廠模式es6
function createPerson (name, age) { var o = new Object(): o.name = name; o.age = age; o.sayName = function () { console.log(this.name); } return o; } var person = createPerson('Lily', 12);
工廠模式特色:雖然解決了建立多個類似對象的問題,但卻沒法識別一個對象的類型。(instance of)
2.構造函數模式數組
function Person (name, age) { this.name = name; this.age = age; this.sayName = function () { console.log(this.name); } } var person = new Person('Lily', 12); //使用構造函數模式建立實例,必須使用new操做符。
構造函數模式特色:能夠將它的實例標識爲一種特定類型,但每一個方法都要在實力上從新建立一遍。
3.原型模式函數
function Person () {} Person.prototype.name = 'Lily'; Person.prototype.age = 12; Person.prototype.sayName = function () { console.log(this.name); } var person = new Person(); //使用hasOwnProperty方法能夠檢測一個屬性存在於實例仍是原型。 console.log(person.hasOwnProperty('name')); //false person.name = 'Tom'; //來自實例 console.log(person.hasOwnProperty('name')); //true
更簡單的原型語法:this
function Person () {} Person.prototype = { name: 'Lily', age: 12, sayName: function () { console.log(this.name); } } //這樣寫會致使constructor屬性再也不指向Person了。(不管什麼時候建立一個新函數A,都會爲它建立一個prototype屬性,指向函數的原型對象,默認狀況下,全部原型對象都會得到一個constructor屬性,包含一個指向A的指針。)
於是可更改成:prototype
function Person () {} Person.prototype = { constructor: Person, //增長constructor屬性 name: 'Lily', age: 12, sayName: function () { console.log(this.name); } } //這種寫法要注意,建立實例必定要在定義原型以後,由於重寫原型對象就切斷了構造函數與最初原型的聯繫。
原型模式的特色:實現了讓全部實例共享原型對象所包含的屬性和方法。但缺點也在於這種共享對於包含引用類型值的屬性而言,存在一些問題,即全部實例會共享一個數組或者對象。
4.組合構造函數模式和原型模式指針
function Person (name, age) { this.name = name; this.age = age; this.friends = ['Tom', 'Bob']; } Person.prototype = { constructor: Person, sayName: function () { console.log(this.name); } }
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。這是目前最普遍,認同度最高的方法。
5.動態原型模式
有其餘OO語言經驗的開發人員看到獨立的構造函數和原型,可能會困惑。動態原型模式則是致力於解決此問題的一個方案,它把全部信息都封裝在了構造函數中,在必要狀況下,經過在構造函數中初始化原型,保持了組合使用構造函數和原型的優勢。code
function Person (name, age) { this.name = name; this.age = age; this.friends = ['Tom', 'Bob']; if (type of this.sayName != 'function') { Person.prototype.sayName = function () { console.log(this.name); } } //if語句只需檢查一個初始化應該存在的共享屬性或方法便可。 }
使用動態原型模式要記得不能用對象字面量重寫原型,由於若是在建立了實例的狀況下重寫原型。那麼就會切斷現有實例與新原型的聯繫。
關於寄生構造模式和穩妥構造模式,在工程實踐用的很少且稍顯過期,就不贅述了。有時間能夠了解es6的class。
二.繼承
1.原型鏈對象
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType(){ this.property = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.property; }
須要注意的是,在經過原型鏈實現繼承時,不能用對象字面量建立原型方法,由於這樣會重寫原型鏈。以下所示:繼承
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType(){ this.property = false; } SubType.prototype = new SuperType(); SubType.prototype = { getSubValue: function () { return this.subproperty; }, someOtherMethod: function () { return false; } } //這樣會致使SubType的實例沒法訪問到getSuperValue()方法了。
原型鏈繼承的問題:
1.共享實例屬性,若是SupeType中定義一個數組colors,當subType經過原型鏈繼承SuperType後,它也會擁有一個colors屬性(就像專門建立了一個subType.prototype.colors屬性同樣),結果是SubType全部實例都會共享這個屬性。對其中一個實例.colors屬性會影響到全部其餘實例。
2.在建立子類型的實例時,沒辦法在不影響全部對象實例的狀況下,向超類型的構造函數傳遞參數。
所以實踐中不多單獨使用原型鏈繼承。
2.借用構造函數接口
function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'yellow']; } function SubType () SuperType.call(this, 'Lily'); this.age = 12; } //使用這種方式就能夠向超類型的構造函數傳參啦。
借用構造函數的問題:仍是和構造函數建立對象同樣,方法都在構造函數定義,函數複用就無從談起了。
3.組合繼承
使用原型鏈實現對原型屬性和方法的繼承,而經過構造函數實現對實例屬性的繼承。
function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'yellow']; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType (name, age) // 繼承實例屬性 SuperType.call(this, name); //第二次調用SuperType() this.age = age; } //繼承方法 SubType.prototype = new SuperType(); //第一次調用SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); } var instance1 = new SubType('Lily', 12); instance1.colors.push('green'); console.log(instance1.colors); // "red,blue,yellow,green" var instance2 = new SubType('Lucy', 22); console.log(instance1.colors); // "red,blue,yellow"
組合繼承避免了原型鏈和借用構造函數的缺陷,是一種較爲流行的繼承方式。可是它還存在一個缺點:在第一次調用superType時,SubType.prototype會獲得兩個屬性:name和colors,當調用SubType的構造函數時又會再調用一次SuperType構造函數,在對象實例上建立了實例屬性name和colors屏蔽了SubType原型中的同名屬性。
寄生組合式繼承
寄生組合式是組合式繼承的改進,思路是沒必要爲了指定子類的原型而調用超類的構造函數,僅複製超類的原型便可。
function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'yellow']; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType (name, age) // 繼承實例屬性 SuperType.call(this, name); this.age = age; } //繼承方法 SubType.prototype = Object.create(SuperType.prototype); //Object.create(obj)至關於 function F(){}; F.prototype = obj;return new F(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); }
開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承方法。