ECMAScript實現繼承的方式:程序員
1.原型鏈ide
利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。函數
function Father(){ this.name = 'father' } function Son(){ } //繼承Father,將Father的實例賦給Son的原型 Son.prototype = new Father() var son1 = new Son() son1.name // father 繼承了Father的name屬性
以前在《淺談建立對象》中,咱們提出 原型模式建立對象 是能夠修改原型的,以下this
1 function Father(){ 2 this.name = 'father' 3 } 4 5 function Son(){ 6 } 7 8 //給Son添加原型屬性 9 Son.prototype.age = '1' 10 11 //重寫Son原型對象,繼承Father 12 Son.prototype = new Father() 13 14 //建立Son實例,能夠放在修改原型以前,修改原型可以從實例上當即反映出來 15 var son1 = new Son() 16 17 Son.prototype.job = '程序員' 18 19 son1.age // error 20 //在重寫Son原型對象以前 給Son原型添加屬性是沒用的,重寫會切斷與以前原型對象的聯繫 21 22 son1.job // 程序員 23 24 Son.prototype = { 25 height: 1.70 26 } 27 28 son1.height // error 上述方法也是屬於重寫原型對象
相似於原型模式,單獨使用原型鏈實現繼承的問題:spa
a.原型鏈中的引用類型的值會被全部實例共享,並能夠修改,而修改後會反映到全部的實例上,這樣實例就不能擁有私有的引用類型的屬性。prototype
b.在建立子類型實例時,咱們沒法在不影響其餘實例的同時向父類型的構造函數中傳遞參數。3d
2.借用構造函數code
function Father(name){ this.name = name; this.colors = ['red','green','blue'] } function Son(name){ //在子類型構造函數中調用父類型的構造函數,經過call函數等強制綁定做用域 Father.call(this,name) } var son1 = new Son('張三') son1.name //張三 son1.colors.push('yellow') var son2 = new Son('李四') son2.name //李四 son1.colors // ['red','green','blue','yellow'] son2.colors // ['red','green','blue']
相對原型鏈而言,借用構造函數能夠向父類型的構造函數傳參。對象
相似於構造函數模式,單獨使用借用構造函數模式實現繼承的問題:blog
a.方法都在構造函數中定義,每次建立對象就會從新實例化一次方法屬性
b.父類型的原型屬性,子類型是訪問不到的。
3.組合繼承
使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。
1 function Father(name){ 2 this.name = name; 3 this.colors = ['red','green','blue'] 4 } 5 Father.prototype.sayName = function (){ 6 alert(this.name) 7 } 8 9 function Son(name,age){ 10 Father.call(this,name); 11 this.age = age; 12 } 13 14 //重寫Son的原型實現繼承,這種重寫會致使Son的實例的constructor屬性都變成父類型的構造函數Father,Son.constructor //Father 而不是Son 15 Son.prototype = new Father() 16 //對於某些對constructor屬性比較在乎的場景,能夠手動的將Son的constructor屬性設置成Son,Son.constructor //Son 17 Son.prototype.constructor = Son; 18 Son.prototype.sayAge = function(){ 19 alert(this.age) 20 } 21 22 var son1 = new Son('張三',20) 23 son1.colors.push('yellow') 24 son1.sayName(); //張三 25 son1.sayAge(); //20 26 27 var son2 = new Son('李四',21) 28 son2.sayName(); //李四 29 son2.sayAge(); //21 30 31 son1.colors // ['red','green','blue','yellow'] 32 son2.colors // ['red','green','blue']
不管什麼狀況下,都會調用2次父類型的構造函數,
第一次在建立子類型原型的時候,Son.prototype = new Father(),
第二次在子類型構造函數內部,Fahter.call(this,name)
4.原型式繼承
1 var Father = { 2 name:'father', 3 friends:['張三','李四'] 4 } 5 //只傳入一個參數的狀況下,這2種寫法的行爲同樣的 6 var son1 = Object(Father) 7 son1.name = '張三' 8 var son2 = Object.create(Father) 9 son2.friends.push('王五') 10 11 son1.name // 張三 12 son2.name // 張三 ,使用object.create 相似於原型鏈的繼承方式,超類原型的全部屬性都是共享的 13 son1.friends // ['張三','李四'] 14 son2.friends // ['張三','李四','王五'] 15 16 var son3 = Object.create(Father,{ 17 name:{ 18 value:'王五' 19 } 20 }) 21 son3.name // 王五 22 son3.friends // ['張三','李四','王五']
在不想建立構造函數,只是想一個對象與另外一個對象保持相似的狀況下,原型式繼承是能夠勝任的,不過他們的引用類型的屬性仍是全部實例共享的。
5.寄生式繼承
function createSon(Father){ var Son = Object(Father); //在這裏能夠添加屬於子類型的屬性 Son.sayHi = function(){ alert('hi') } return Son }
和原型式繼承同樣,在不考慮自定義類型和構造函數的狀況下,只想返回一個相似的新對象時,寄生式繼承能夠作到,不過和構造函數模式相似,方法都在函數中定義,每次建立對象就會從新實例化一次方法屬性,全部的實例的sayHi方法都是一個新的實例
6.寄生組合式繼承
爲了解決組合繼承調用2次父類型構造函數的問題
首先咱們要搞清楚爲何組合繼承會調用2次父類型的構造函數,組合繼承的2次調用分別是 子構造函數中的 借用構造函數 和 原型鏈繼承的實現。
其實多餘的一次調用就是 原型鏈繼承的實現中,咱們必需要拿到一個父類型的實例用來做爲子類型的原型,這樣修改子類型的原型只會修改父類型的實例原型,而不會影響父類型的其餘實例。
這就是爲何 在原型鏈繼承中,咱們用 Son.prototype = new Father() //獲取父類型的實例重寫子類型的原型 而不是 Son.prototype = Father.prototype // 這樣寫的話,咱們在Son.prototype上新添加一個屬性,會影響到Father的其餘實例
怎麼才能只得到一個與父類型prototype類似的對象而不調用父類型的構造函數呢?
在不考慮自定義類型和構造函數的狀況下,只想返回一個相似的新對象時,寄生式繼承能夠作到
fucntion expendPrototype(Son,Father){ //建立父類型的原型副本 var prototype = Obejct(Father.prototype); //彌補重寫原型而失去的默認的constructor屬性 prototype.constructor = Son; //重寫子類型的原型 Son.prototype = prototype; }
這樣 咱們用這個方法去代替以前的 Son.prototype = new Father(),從而解決了多一次無用的構造函數調用.