Javascript之對象組合繼承

感悟: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。

 

總結:組合繼承經過借用構造函數技術和重寫原型對象,可讓一個對象繼承另外一個對象的屬性和方法原型屬性和原型方法。在組合繼承中借用構造函數技術和重寫原型對象都是必要的,缺乏了任何一個都會使繼承不能正常工做。

組合繼承也存在缺點,主要是被繼承的對象的實例屬性的重複調用。

相關文章
相關標籤/搜索