繼承:構(gòu)造函數(shù)式

寫在前面:
以前在coding時,經(jīng)常在開發(fā)者面板中見到一個Object中見到__proto__prototype這兩個屬性,也沒有
深入去了解過,這里先給自己掃盲一個。__proto__Object.prototype的一個屬性,它被賦予了一個重要功能——訪問一個對象原型鏈中的屬性和方法。由于它是Object.prototype的屬性,故而在一個類被實例化后,該對象便能訪問到它的__proto__,說到這里就明白了,即是__proto__擁有的屬性和方法,取決于該類的prototype,即該類的原型上定義的屬性和方法。如下:

console.log((new Foo).__proto__ === Foo.prototype); // true
console.log((new Foo).prototype === undefined); // true

參考鏈接:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
http://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript

另外補充一點OO的概念:
何為類:具有相同屬性和方法的一組對象的集合(略拗口),它為屬于該類的全部對象提供了抽象的描述,主要包括了屬性和方法兩個部分。
何為對象(即實例):通過new某一個類所產(chǎn)生的實例instance,叫做該類的一個對象。


接下來進入正題:

方法一:在子類中調(diào)用call/apply

function Parent_1() {
  this.name = 'parent_1';
}
Parent_1.prototype.sayName = function() { // 不能被繼承
  return this.name;
};
function Child_1() {
  // 立即調(diào)用Parent_1,注意這里是直接調(diào)用,并非new
  // 那么就會產(chǎn)生一個問題,即是子類只能繼承到父類構(gòu)造函數(shù)中的屬性和方法,而不能繼承到父類原型中定義的屬性和方法
  Parent_1.apply(this, arguments);
  this.name = 'child_1';
}
// 最后實例化子類
var c1 = new Child_1;

缺點:不能繼承父類原型上的屬性和方法
結(jié)論:父類的屬性和方法,需要寫入其構(gòu)造函數(shù)

方法二:子類的原型指向父類的實例

function Parent_2() {
  this.name = 'parent_2';
}
function Child_2() {
  this.name = 'child_2';
}
Child_2.prototype.sayName = function() { // 將在實例化過程中被清除
  return this.name;
};
// 子類的原型指向父類的實例,which也意味著子類的constructor指向了錯誤的地方,此時子類的constructor指向Parent2
Child_2.prototype = new Parent_2;
// console.log(Child_2.prototype.constructor === Parent_2); // true
// 接下來需要給子類的原型增加一個constructor屬性,使其指向正確的地方,即子類自身
Child_2.prototype.constructor = Child_2;
// 最后實例化子類
var c2 = new Child_2;

缺點:子類原型上的屬性和方法將被清除掉
結(jié)論:子類的屬性和方法,需要寫入其構(gòu)造函數(shù)。同時需要注意的一點是,一定要將子類的constructor指向子類自身,否則將造成繼承鏈混亂

方法三:子類原型直接指向父類的原型

function Parent_3() {
  this.name = 'parent_3';
}
function Child_3() {
  this.name = 'child_3';
}
Child_3.prototype.sayName = function() { // 將在實例化過程中被清除
  return this.name;
};
Child_3.prototype = Parent_3.prototype; // 將子類的原型直接指向父類的原型(原型是對象)
// 這里就會產(chǎn)生一個問題,即子類的原型和父類的原型指向了同一個內(nèi)存地址,自此子類原型中屬性的任何改動,都會同樣改動到父類的原型上(凌亂了)
// 例如:
// Child_3.prototype.constructor = Child_3;
// console.log(Parent_3.prototype.constructor === Child3); // true
// 最后實例化子類
var c3 = new Child_3;

缺點:子類原型上的屬性和方法將被清除掉;不能繼承父類構(gòu)造函數(shù)中的屬性和方法;子類的原型和父類的原型指向了同一個內(nèi)存地址,造成了繼承鏈混亂,無法判斷到底是誰繼承了誰
優(yōu)點:沒有父類實例化的過程,節(jié)省了內(nèi)存占用,提高了程序運行效率
結(jié)論:子類的屬性和方法,需要寫入其構(gòu)造函數(shù)

方法四:利用一個空的構(gòu)造函數(shù)作為“中介”,代替子類的原型,繼承父類的原型(方法三的enhanced版本)

function Parent_4() {
  this.name = 'parent_4';
}
function Child_4() {
  this.name = 'child_4';
}
Child_4.prototype.sayName = function() { // 將在實例化過程中被清除
  return this.name;
};
var F = function() {}; // 創(chuàng)建一個空的構(gòu)造函數(shù)
F.prototype = Parent_4.prototype; // 將構(gòu)造函數(shù)的原型指向父類
Child_4.prototype = new F; // 將子類的原型指向空構(gòu)造函數(shù)的實例
Child_4.prototype.constructor = Child_4; // 將子類的constructor指向子類自身

// 接下來為子類設(shè)一個uber屬性,這個屬性直接指向父對象的prototype屬性。
// (uber是一個德語詞,意思是"向上"、"上一層"。)這等于在子類上打開一條通道,可以直接調(diào)用父對象的方法。
// 這一行放在這里,只是為了實現(xiàn)繼承的完備性,純屬備用性質(zhì)。

// 至于具體到應(yīng)用層面上來解釋的話,意思就是說,如果你確切知道一個屬性/方法是繼承自父類的原型的話,
// 就可以直接c4.uber.xxx來訪問該屬性/方法,而不用沿著原型鏈回溯一層一層地查找,提升了查詢效率
Child_4.prototype.uber = Parent_4.prototype;
// 最后實例化子類
var c4 = new Child_4;
// console.log(c4.uber === Parent_4.prototype); // true

缺點:子類原型上的屬性和方法將被清除掉;不能繼承父類構(gòu)造函數(shù)中的屬性和方法
優(yōu)點:子類原型指向了空構(gòu)造函數(shù)的實例,從而解決了方法三中的繼承鏈混亂問題,子類原型的改動不會影響到父類
結(jié)論:子類的屬性和方法,需要寫入其構(gòu)造函數(shù)

方法五:淺拷貝繼承父類

function Parent_5() {
  this.name = 'parent_5';
}
Parent_5.prototype.pSayName = function() {
  return this.name;
};
function Child_5() {
  this.name = 'child_5';
}
Child_5.prototype.cSayName = function() {
  return this.name;
};
// 將父類的原型中的屬性和方法,復(fù)制到子類的原型中
// 問題是,在此過程中,子類原型中的同名屬性/方法會被父類覆蓋
// 注:其實也算不上問題,因為在面向?qū)ο蟮脑O(shè)計階段,就應(yīng)抽象出所有對象的公共屬性和方法,故而子類原型中,
// 不應(yīng)該出現(xiàn)與父類同名的屬性和方法
for(var k in Parent_5.prototype) {
  Child_5.prototype[k] = Parent_5.prototype[k];
}
// 對子類原型上屬性的改動不會影響到父類原型,因為他們的原型相互并沒有引用關(guān)系(對比方法三)
Child_5.prototype.uber = Parent_5.prototype;
// 最后實例化子類
var c5 = new Child_5;

缺點:不能繼承父類構(gòu)造函數(shù)中的屬性和方法
優(yōu)點:由于是key-value方式的復(fù)制,故子類的原型上的屬性和方法不會被清除掉,從而對子類原型的改動不會影響到父類的原型
結(jié)論:父類的屬性和方法,需寫到父類的原型中


以上則是構(gòu)造函數(shù)式繼承的五種方法。

這是Javascript在創(chuàng)造之初,提供了這種模擬式的基于類的模式——偽類模式,這使得它真正的本質(zhì)被掩蓋——這其實是一種基于原型的語言。

總體來看,采用構(gòu)造函數(shù)的方式來實現(xiàn)繼承,都有各種缺點,我們可以采用方法一和方法五結(jié)合使用,即采用方法一來繼承父類構(gòu)造函數(shù)中的屬性和方法,采用方法五來繼承父類原型中的屬性和方法。例如:

function Parent_6(name, age) {
  this.name = name;
  this.age = age;
}
Parent_6.prototype.sayName = function() {
  return this.name;
}
function Child_6() {
  Parent_6.apply(this, arguments);
  this.occupation = arguments[2];
}
// 將父類原型上的屬性/方法淺拷貝至子類
for(var k in Parent_6.prototype) {
  Child_6.prototype[k] = Parent_6.prototype[k];
}
Child_6.prototype.uber = Parent_6.prototype;
// 實例化子類
var c6 = new Child_6('child_6', 18, 'pilot');

歡迎交流,完。兄弟篇——繼承:非構(gòu)造函數(shù)式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容