JavaScript的繼承主要依靠原型鏈來實現的。咱們知道,構造函數,原型,和實例之間的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個原型對象的指針。javascript
實現原型鏈的方式以下java
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function SubType(){ this.subpropertype=false; } //讓原型對象稱爲另外一個構造函數的實例 SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function(){ return this.subpropertype; }; var instance=new SubType(); alert(instance.getSuperValue());//true //這個實例繼承了SuperType.prototype的constructor屬性? alert(instance.constructor==SuperType);//true
上述代碼繼承是經過SubType.prototype=new SuperType();
來實現,建立SuperType
的實例,並將該實例賦給SubType.prototype
。函數
繼承實現的本質是重寫原型對象,代之以一個新類型的實例。this
下圖爲構造函數,實例以及原型之間的關係圖:prototype
圖片描述指針
原型鏈頂端:全部引用類型都默認繼承Object,因此,全部函數的默認原型都是Object的實例,默認原型都會包含一個內部指針[[prototype]],指向Object.prototype。code
實例屬性變爲原型屬性對象
function SuperType(){ this.color=["red","green","blue"]; } function SubType(){ } SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.color.push("black"); alert(instance1.color);//"red,green,blue,black" var instance2 = new SubType(); alert(instance2.color);//"red,green,blue,black"
這個問題似曾相識,正是原型模式建立對象時因爲共享引用類型屬性,致使牽一髮動全身的問題。繼承
在建立子類型時,不能向超類型的構造函數傳遞參數。圖片
因此,單獨使用原型鏈狀況較少。
針對原型鏈的第一個問題,咱們可採用借用構造函數的技術來解決。基本思想就是在子類型構造函數的內部調用超類型構造函數。看例子:
function SuperType(){ this.color=["red","green","blue"]; } function SubType(){ //繼承自SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.color.push("black"); alert(instance1.color);//"red,green,blue,black" var instance2 = new SubType(); alert(instance2.color);//"red,green,blue"
在新建立的SubType子類型的實例中調用SuperType超類型構造函數,就能夠在新的實例對象上執行SuperType()函數中定義的全部對象初始化代碼。問題不就解決了嗎!
可是,這種模式的缺點是在超類型中定義的方法,對子類型是不可見的,沒法實現共享方法。
因此,這種方法也不經常使用。
組合上述兩種方法就是組合繼承。用原型鏈實現對原型屬性和方法的繼承,用借用構造函數技術來實現實例屬性的繼承。無疑,集二者之大成,這纔是最經常使用的繼承模式。看:
function SuperType(){ this.name=name; this.color=["red","green","blue"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ //繼承了SuperType SuperType.call(this,name); //本身又添加了一個 this.age = age; } //構建原型鏈 SubType.prototype = new SuperType(); //重寫SubType.prototype的constructor屬性,指向本身的構造函數SubType SubType.prototype.constructor=SubType; //原型方法,被實例們共享 SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("Nichola",29); instance1.color.push("black"); alert(instance1.color);//"red,green,blue,black" instance1.sayName();//"Nichola" instance1.sayAge();//29 var instance2 = new SubType("Grey",24); alert(instance2.color);//"red,green,blue" instance2.sayAge();//24 instance2.sayName();//"Grey"
這個方案已經看似perfect了。可是,後面再說。
藉助原型能夠基於已有的對象建立新的對象,沒必要所以建立自定義類型。
function object(o){ //返回一個對象以傳入對象爲原型 function F(){} F.prototype = o; return new F(); } var person ={ name:"Nichola", friends:["Shelly","Court","Van"] }; var person1 = object(person); person1.name = "Grey"; person1.friends.push("Rob"); var person2 = object(person); person2.name = "Linda"; person2.friends.push("Barble"); alert(person.friends);//"Shelly,Court,Van,Grey,Barble"
使用場合:需求簡單,只須要讓新對象與已有對象保持類似。優勢,沒必要建立構造函數,缺點,包含引用類型值的屬性始終共享相應的值。
Object.create()正是爲實現這種模式誕生。
與原型式繼承類似,也是基於某個對象或某些信息建立對象,而後加強對象,最後返回對象。實現方法:建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後返回這個對象。看!
function createAnother(original){ var clone = object(original);//經過調用函數建立對象 clone.sayHi= function (){ //加強對象 alert("Hi"); }; return clone;//返回對象 } //能夠返回新對象的函數 function object(o){ function F(){} F.prototype = o; return new F(); } var person ={ name:"Nichola", friends:["Shelly","Court","Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi();//"Hi"
這種繼承模式適用的場合:任何返回新對象的函數均可以。缺點是不能作到函數複用。
上面說到組合繼承也有缺點,就是不管在何種狀況下,都會調用兩次超類型構造函數,一次是在建立子類型原型時,還有一次是在子類型構造函數內部。
這種模式集中了寄生式和組合式繼承的優勢。
function SuperType(){ this.name=name; this.color=["red","green","blue"]; } function SubType(){ //第二次調用SuperType() SuperType.call(this,name); this.age = age; } //第一次調用SuperType() SubType.prototype = new SuperType(); SubType.prototype.constructor=SubType; SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("Nichola",29);
第一次調用SuperType():給SubType.prototype寫入兩個屬性name,color
第二次調用SuperType():給instance1寫入兩個屬性name,color
實例對象instance1上的兩個屬性就屏蔽了其原型對象SubType.prototype的兩個同名屬性。因此,組合模式的缺點就是在SubType.prototype上建立沒必要要的重複的屬性。
寄生組合式繼承基本模式:
function inheritPrototype(SubType,SuperType){ var prototype = object(superType.prototype);//建立對象 prototype.constructor = SubType;//加強對象 SubType.prototype = prototype;//制定對象 }
首先,建立超類型的一個副本;
其次,爲副本添加constructor屬性,使其指向子類型構造函數;
最後,將副本賦值給子類型原型。
function SuperType(){ this.name=name; this.color=["red","green","blue"]; } function SubType(){ SuperType.call(this.name); this.age = age; } function inheritPrototype(SubType,SuperType){ var prototype = object(superType.prototype);//建立對象 prototype.constructor = SubType;//加強對象 SubType.prototype = prototype;//制定對象 } SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("Nichola",29);
借用構造函數來繼承實例屬性,使用寄生式繼承來繼承超類型的原型,而後再將結果賦給子類型原型。這樣既能夠繼承超類型的實例屬性,也可繼承超類型原型中的原型屬性。這是最優解。