感悟:java
最近看了一些關於Javascript對象繼承的知識,發現本身以前雖然看了一些書,可是不少知識都忘了。雖然不少東西都忘了,但再次看的過程當中對這些東西不會再向剛接觸時那麼陌生,並且理解起來也比以前順暢和透徹多了。數組
充分說明:多看書是有意義的。app
————————————————————————————————————————————————————————————————————————————————————————————碎碎念函數
關於對象之間的繼承,在Javascript中主要是經過原型對象鏈來實現的,這一點與java這種基於類的面嚮對象語言有明顯的不一樣,Javascript是基於原型的面嚮對象語言(大部分人說是基於對象的語言兩種說法的觀點不一樣)。this
下面來具體說一下繼承的實現方式:spa
1、組合繼承prototype
1.組合繼承將原型鏈和借用構造函數技術組合在一塊兒。經過使用apply或者是call借用構造函數,借用對象能夠獲得被借用對象的實例屬性。首先來看一個栗子:code
function People(){ this.species = "人類"; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; } function Person(name, sex){ People.apply(this,arguments);
//此處是 Person 的實例屬性,固然也能夠添加一些實例方法 this.name = name; this.sex = sex; } var person1 = new Person("二狗","男"); alert(person1.species);//人類 alert(person1.nationality);//undefined alert(person1.showSpecies());//Uncaught TypeError: person1.showSpecies is not a function
經過結果能夠看到,person1 是構造函數 Person 的一個實例,由於構造函數 Person 使用了借用構造函數技術 對象
People.apply(this,arguments);
Person 就能夠得到 People 的實例屬性 species;可是 Person 沒法得到 People 的原型屬性:nationality 和原型方法:showSpecies();blog
若是想讓 Person 得到 People 的原型屬性和原型方法,須要讓 Person 得到 People 的原型對象(隱式)上的屬性和方法。
本質上講:就是要重寫 Person 的原型對象。
a. 一種簡單實用的作法是直接將 People 的原型屬性和原型方法複製給 Person:(也稱之爲拷貝繼承)
function People(){ this.species = "人類"; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; } function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } //遍歷並複製 for(var i in People.prototype){ Person.prototype[i] = People.prototype[i]; } var person1 = new Person("二狗","男"); alert(person1.species);//人類 alert(person1.nationality);中國 alert(person1.showSpecies());人類
注意:上面這種複製是將 People.prototype 複製給了 Person.prototype。Person 擁有了和 People 同樣的隱式原型對象。經過對原型對象上的數組進行操做能夠證明:
function People(){ this.species = "人類"; } People.prototype.colorArray = ["red", "blue"]; function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } for(var i in People.prototype){ Person.prototype[i] = People.prototype[i]; } var person1 = new Person("二狗","男"); alert(person1.colorArray.push("green"));//3 var person2 = new People("二毛","男"); alert(person2.colorArray.push("black"));//4
兩個不一樣構造函數實例化獲得的對象,他們操做原型對象上的數組是同一個,說明這種原型對象上的原型屬性和原型方法的複製是遵循通常的 Javascript 複製規則的。
能夠對這個隱式的原型對象作一些別的操做,好比:
修改隱式對象對某個已引用方法:
function People(){ this.species = "人類"; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; }; People.prototype.cheers = function(){ return "中國加油!"; }; function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } for(var i in People.prototype){ Person.prototype[i] = People.prototype[i]; } Person.prototype.cheers = function(){ return "中國必勝!"; }; var person1 = new Person("二狗","男"); alert(person1.species);//人類 alert(person1.nationality);//中國 alert(person1.showSpecies());//人類 alert(person1.cheers());//中國必勝! var person2 = new People("二毛","男"); alert(person2.cheers());//中國加油!
能夠看到:person1 利用修改構造函數對應的原型對象中的方法,引用了一個新的方法。也能夠向下面的樣子先引用一個方法,再經過繼承覆蓋以前的引用,固然,這樣作沒什麼意義:
function People(){ this.species = "人類"; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; }; People.prototype.cheers = function(){ return "中國加油!"; }; function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } Person.prototype.cheers = function(){ return "中國必勝!"; }; for(var i in People.prototype){ Person.prototype[i] = People.prototype[i]; } var person1 = new Person("二狗","男"); alert(person1.species);//人類 alert(person1.nationality);//中國 alert(person1.showSpecies());//人類 alert(person1.cheers());//中國加油! var person2 = new People("二毛","男"); alert(person2.cheers());//中國加油!
b. 將 People 的實例賦值給 Person 的原型對象,同時修改 Person 原型對象的 constructor 屬性值爲自身。
若是隻是單單將 People 的實例賦值給 Person 的原型對象, 而不修改 Person 原型對象的 constructor 屬性會怎麼樣?
來看看修改 Person 原型對象先後 Person 構造函數的 Person.prototype.constructor 屬性:
function People(){ this.species = "人類"; } function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } alert(Person.prototype.constructor);//Person這個完整的函數 Person.prototype = new People(); alert(Person.prototype.constructor);//People這個完整的函數
能夠看到在將 People 實例賦值給 Person 先後,Person 的構造函數變了,若是這個時候什麼都不作,那麼再對 Person 實例化:
function People(){ this.species = "人類"; } function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } alert(Person.prototype.constructor);//Person這個完整的函數 Person.prototype = new People(); alert(Person.prototype.constructor);//People這個完整的函數
var person1 = new Person("二狗","男"); alert(person1.constructor);//People這個完整的函數
會發現構造函數 Person 的實例: person1 的構造函數竟然不是 Person 而是 People,這顯然不對。
也就是說構造函數 People 在將其實例賦值給 Person 的原型對象時,同時也將 Person 的原型對象的屬性 constructor 也更換了(很明顯,由於 constructor 是 prototype 的屬性,皮之不存,毛將焉附?),
而 prototype.constructor 的值表示由當前構造函數對象 實例化的 函數對象的構造函數(簡單點說 Person.prototype.constructor 就是告訴構造函數對象 Person 的實例,他們是誰構造的,
而 person1.constructor 讓做爲實例對象的 person1 直指它本身的構造函數)。——能夠看出,正常狀況下 Person.prototype.constructor === person1.constructor 應該成立。
function People(){ this.species = "人類"; } function Person(name, sex){ People.apply(this,arguments); this.name = name; this.sex = sex; } alert(Person.prototype.constructor);//Person這個完整的函數 Person.prototype = new People(); Person.prototype.constructor = Person; var person1 = new Person("二狗","男"); alert(person1.constructor === Person.prototype.constructor);//true
經過上面講到的組合繼承的兩種實現方式,雖然可以實現屬性和方法、原型屬性和原型方法的繼承,可是存在一個不足:不管在什麼狀況下,被繼承的函數對象都會被調用兩次。
先來看一個有趣的地方,假如:在組合繼承中不使用 借用構造函數技術 而直接重寫原型對象,會發生什麼?
function People(){ this.species = "人類"; this.arr = [1]; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; }; function Person(name, sex){ // People.apply(this,arguments); this.name = name; this.sex = sex; } Person.prototype = new People();//讓People的實例屬性變成Person的原型屬性 Person.prototype.constructor = Person; var person1 = new Person("二狗","男"); person1.arr.push(2);//1,2 alert(person1.species);//人類 alert(person1.arr); alert(person1.nationality);//中國 alert(person1.showSpecies());//人類 var person2 = new People("二毛","男"); person2.arr.push(3); alert(person2.arr);//1,3 alert(person1.arr);//1,2
這樣看來在構造函數 People 和 構造函數 Person 的實例對象中都能正常使用 People 的實例屬性。若是是單個構造函數 Person 的多個 實例對象呢?
function People(){ this.species = "人類"; this.arr = [1]; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; }; function Person(name, sex){ // People.apply(this,arguments); this.name = name; this.sex = sex; } Person.prototype = new People();//讓People的實例屬性變成Person的原型屬性 Person.prototype.constructor = Person; var person1 = new Person("二狗","男"); var person3 = new Person("三狗","男"); person1.arr.push(2); alert(person1.arr);//1,2 person3.arr.push(4); alert(person3.arr);//1,2,4 alert(person1.arr);//1,2,4 var person2 = new People("二毛","男"); person2.arr.push(3); alert(person2.arr);//1,3 alert(person1.arr);//1,2,4 var person4 = new People("四毛","男"); person4.arr.push(5); alert(person4.arr);//1,5 alert(person2.arr);//1,3
注意看構造函數 People 和構造函數 Person 的實例經過相同操做後結果的差別。
構造函數 People 的實例會各自擁有本身的數組 arr, 各自的操做間是不會相互影響的,可是構造函數 Person 的實例都擁有相同的數組 arr 引用,他們操做的是同一個數組。那麼實例化獲得的不一樣對象沒法正常使用各自的 arr。
因此在組合繼承中,借用構造函數和原型鏈缺一不可。
這裏的 person1.__proto__ 是幫助咱們拿到 person1 的構造函數 Person 的原型對象。能夠看到,上面直接將 People 的實例屬性轉化爲 Person 的原型屬性,而 People 的原型屬性和原型方法也成爲 Person 的原型屬性和原型方法。
可是,組合繼承也存在缺點,最明顯的就是:不管在什麼狀況下,只要實例化了使用組合繼承後的對象,被繼承的對象都會調用兩次。
function People(){ this.species = "人類"; } People.prototype.nationality = "中國"; People.prototype.showSpecies = function(){ return this.species; }; function Person(name, sex){ People.apply(this,arguments);//第二次調用People() this.name = name; this.sex = sex; } Person.prototype = new People();//第一次調用People() Person.prototype.constructor = Person; var person1 = new Person("二狗","男");
第一次發生在將 People 的實例對象潛複製給 Person 的原型對象(能夠是直接複製,也能夠是經過 People 的實例 new People() );
第二次發生在調用構造函數 Person 時。
在第一次調用的時候,其實 Person 就已經拿到了 People 的實例化屬性 species,而第二次調用發生在實例化 Person 的時候,經過借用構造函數,Person 又一次的調用了 People,會在 Person1 上建立實例屬性 species。
總結:組合繼承經過借用構造函數技術和重寫原型對象,可讓一個對象繼承另外一個對象的屬性和方法、原型屬性和原型方法。在組合繼承中借用構造函數技術和重寫原型對象都是必要的,缺乏了任何一個都會使繼承不能正常工做。
組合繼承也存在缺點,主要是被繼承的對象的實例屬性的重複調用。