1. 程式人生 > >javascript面向物件-繼承

javascript面向物件-繼承

複習原型物件:
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.sayName = function(){
   alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

在這裡插入圖片描述

6.3.1 原型鏈

原型鏈:基本思想是利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。 假如我們讓原型物件等於另一個型別的例項,原型物件將包含一個指向另一個原型的指標,相應地,另一個原型中也包含著一個指向另一個建構函式的指標。假如另一個原型又是另一個型別的例項,那麼上述關係依然成立,如此層層遞進,就構成了實 例與原型的鏈條。

function SuperType(){
	this.property = true;
}
SuperType.prototype.getSuperValue = function(){
	return this.property;
};
function SubType(){
	this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
	return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true

在這裡插入圖片描述 每個型別分別有一個屬性和一個方法。它們的主要區別是 SubType 繼承了 SuperType,而繼承是通過建立 SuperType 的例項,並將該例項賦給SubType.prototype 實現的。實現的本質是重寫原型物件,代之以一個新型別的例項。

在通過原型鏈實現繼承的情況下,搜尋過程就得以沿著原型鏈繼續向上。就拿上面的例子來說,呼叫instance.getSuperValue()會經歷三個搜尋步驟: 1)搜尋例項; 2)搜尋 SubType.prototype;3)搜尋 SuperType.prototype,最後一步才會找到該方法。

  1. 預設的原型 所有函式的預設原型都是 Object 的例項,因此預設原型都會包含一個內部指標,指向 Object.prototype。這也正是所有自定義型別都會繼承 toString()、valueOf()等預設方法的根本原因。
  2. 確定原型和例項的關係 可以通過兩種方式來確定原型和例項之間的關係。第一種方式是使用 instanceof 操作符,只要用這個操作符來測試例項與原型鏈中出現過的建構函式,結果就會返回 true。第二種方式是使用 isPrototypeOf()方法。同樣,只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生的例項的原型,因此 isPrototypeOf()方法也會返回 true。
  3. 謹慎地定義方法 子型別有時候需要重寫超型別中的某個方法,或者需要新增超型別中不存在的某個方法。但不管怎樣,給原型新增方法的程式碼一定要放在替換原型的語句之後。
  4. 原型鏈的問題 最主要的問題來自包含引用型別值的原型。想必大家還記得,我們前面介紹過包含引用型別值的原型屬性會被所有例項共享;而這也正是為什麼要在建構函式中,而不是在原型物件中定義屬性的原因。在通過原型來實現繼承時,原型實際上會變成另一個型別的例項。於是,原先的例項屬性也就順理成章地變成了現在的原型屬性了。 原型鏈的第二個問題是:在建立子型別的例項時,不能向超型別的建構函式中傳遞引數。

6.3.2 借用建構函式

借用建構函式(constructor stealing)的技術(有時候也叫做偽造物件或經典繼承)。這種技術的基本思想相當簡單,即在子型別建構函式的內部呼叫超型別建構函式。

function SubType(){
	//繼承了 SuperType
	SuperType.call(this);
}

通過使用 apply()和 call()方法也可以在(將來)新建立的物件上執行建構函式

  1. 傳遞引數
function SubType(){
//繼承了 SuperType,同時還傳遞了引數
SuperType.call(this, "Nicholas");
	//例項屬性
	this.age = 29;
}

2.借用建構函式的問題 如果僅僅是借用建構函式,那麼也將無法避免建構函式模式存在的問題——方法都在建構函式中定義,因此函式複用就無從談起了。而且,在超型別的原型中定義的方法,對子型別而言也是不可見的,結果所有型別都只能使用建構函式模式。

6.3.3 組合繼承

組合繼承(combination inheritance),有時候也叫做偽經典繼承,指的是將原型鏈借用建構函式的技術組合到一塊,從而發揮二者之長的一種繼承模式。

6.3.4 原型式繼承

這種方法並沒有使用嚴格意義上的建構函式。他的想法是藉助原型可以基於已有的物件建立新物件,同時還不必因此建立自定義型別。

function object(o){
	function F(){}
	F.prototype = o;
	return new F();
}

6.3.5 寄生式繼承

寄生式(parasitic)繼承是與原型式繼承緊密相關的一種思路,並且同樣也是由克羅克福德推而廣之的。寄生式繼承的思路與寄生建構函式和工廠模式類似,即建立一個僅用於封裝繼承過程的函式,該函式在內部以某種方式來增強物件,最後再像真地是它做了所有工作一樣返回物件。

function createAnother(original){
	var clone = object(original); //通過呼叫函式建立一個新物件
	clone.sayHi = function(){ //以某種方式來增強這個物件
		alert("hi");
	};
	return clone; //返回這個物件
}

使用寄生式繼承來為物件新增函式,會由於不能做到函式複用而降低效率;這一 點與建構函式模式類似。

6.3.6 寄生組合式繼承

組合繼承是 JavaScript 最常用的繼承模式;不過,它也有自己的不足。組合繼承最大的 問題就是無論什麼情況下,都會呼叫兩次超型別建構函式:一次是在建立子型別原型的時候,另一次是在子型別建構函式內部。沒錯,子型別最終會包含超型別物件的全部例項屬性,但我們不得不在呼叫子型別建構函式時重寫這些屬性。 所謂寄生組合式繼承,即通過借用建構函式來繼承屬性,通過原型鏈的混成形式來繼承方法。其背後的基本思路是:不必為了指定子型別的原型而呼叫超型別的建構函式,我們所需要的無非就是超型別原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超型別的原型,然後再將結果指定給子型別的原型。

function inheritPrototype(subType, superType){
	var prototype = object(superType.prototype); //建立物件
	prototype.constructor = subType; //增強物件
	subType.prototype = prototype; //指定物件
}
``