這裏先說基於原型鏈實現的繼承。那首先就得明白什麼是原型鏈了;函數
每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。this
那麼,假如咱們讓原型對象等於另外一個類型的實例,此時的原型對象將包含指向另外一個原型對象的指針,相應地,另外一個原型對象也包含着指向另外一個構造函數的指針。spa
假如另外一個原型又是另外一個類型的實例,那麼上述關係依然成立。層層遞進,就成一條鏈了。prototype
實現原型鏈的基本模式:指針
function SuperType() {對象
this.property = true;繼承
}原型鏈
SuperType.prototype.getSuperValue = function() {get
return this.property;原型
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); //實現繼承
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue); // true
上述SubType經過重寫原型對象的方式繼承了SuperType,因此原來位於SuperType裏的方法,如今也位於SubType裏了,咱們沒有使用SubType默認提供的原型,而是給它換了一個新原型。這個新原型就是SuperType的實例。
因而,新原型不只有做爲SuperType實例全部的屬性和方法,內部還有一個指針,指向了SuperType的原型。
從而,instance指向SubType的原型,SubType的原型又指向SuperType的原型;getSuperValue()的方法仍是在SuperType.prototype中,但property則位於SubType.prototype中,由於property是一個實例屬性,而getSuperValue()則是一個原型方法。既然SubType.prototype如今是SuperType的實例,那麼property固然位於該實例中了
此外,instance.constructor如今指向的是SuperType了,由於原來SubType.prototype指向了SuperType的原型,而這個原型對象的constructor屬性指向的是SuperType
默認的原型
咱們知道,全部的引用類型默認都繼承了Object,而這個繼承也是原型鏈實現的。全部的函數的默認原型都是Object的實例,所以默認原型裏面都會包含一個指針,指向Object.prototype
這也是全部自定義類型都會繼承toString(),valueof()等默認方法的根本緣由
原型和實例的關係
alert(isntance instanceof Object) ; // true
alert(isntance instanceof SuperType); // true
alert(isntance instanceof SubType) ; // true
alert(Object.prototype.isPrototypeOf(instance)); // true
alert(SuperType.prototype.isPrototypeOf(instance)); // true
alert(SubType.prototype.isPrototypeOf(instance)); // true
經過原型鏈實現繼承,不能使用字面量對象建立原型方法,那樣會重寫原型鏈,切斷原來原型與實例之間的聯繫
原型鏈存在的問題:
(1)最主要的問題是來自包含引用類型值的緣由
舉例:
function SuperType() {
this.colors = ['red', 'yellow', 'blue'];
}
function SubType() {
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push('black');
var instance2 = new SubType();
alert(instance1.colors); // 'red,yellow,blue,black'
alert(instance2.colors); // 'red,yellow,blue,black'
SuperType的每一個實例都會包含各自的colors屬性,當SubType經過原型鏈繼承以後,SubType.prototype也包含了一個colors屬性,爲全部實例所共享,全部修改instance1,會反映到instance2
(2)另外一個問題:不能向超類型的構造函數中傳遞參數
解決辦法:
1.借用構造函數(經典繼承)
function SuperType () {
this.colors = ['red', 'blue', 'green'];
}
function SubType() {
SuperType.call(this)
}
var instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors); // "red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors) // "red,blue,green"
代碼紅色部分借調了超類型的構造函數,經過使用call()方法,咱們其實是在(將來將要)新建立的SubType實例的環境下調用了SuperType構造函數。就會在新SubType對象上執行SuperType函數中定義的全部對象初始化代碼
優點: 傳遞參數,能夠在子類型構造函數中向超類型構造函數傳遞參數
function SuperType (name) {
this.name = name;
}
function SubType () {
SuperType.call (this, 'bob'); //繼承SuperType,同時傳遞參數
this.age = 29; //實例屬性
}
var instance = new SubType();
alert(instance.name); // 'bob'
alert(instance.age); //29
問題:方法都在構造函數中定義,沒法複用。並且,在超類型原型中定義的方法,對子類型不可見
2.組合繼承
思路:使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType (name, age) {
SuperType.call(this, name); //繼承屬性
this.age = age;
}
// 繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
};
var instance1 = new SubType('bob', 20);
instance1.colors.push('black');
alert(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // 'bob'
instance1.sayAge(); // 20
var instance2 = new SubType('alice', 21);
alert(instance2.colors); // "red,blue,green"
instance2.sayName(); // 'alice'
instance2.sayAge(); // 21
3.原型式繼承(object.create())
實現原理
function object(o) {
function F(){}
F.prototype = o;
return new F() //返回的是構造函數的實例,因此只有_proto_屬性,指向F.prototype
}
舉例:
var person = {
name: 'bob',
friends: ['a', 'b', 'c']
}
var anotherPerson = Object.create(person);
anotherPerson.name = 'alice';
anotherPerson.friends.push('d');
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('e');
alert(person.friends); // 'a,b,c,d,e'
在沒有必要興師動衆地建立構造函數,而只想讓一個對象與一個對象保持相似的狀況下,能夠用原型式繼承,不過,
包含引用類型的值的屬性始終都要共享相應的值,就像使用原型模型同樣
4.寄生式繼承
function createAnother (original) {
var clone = object.create(original); // 經過調用函數建立一個新對象
clone.sayHi = function() { // 以某種方式加強對象
alert('Hi');
}
return clone; //返回這個對象
}
var person = {
name: 'bob',
friends: ['a', 'b', 'c']
}
var anotherPerson = createAnother(person);
anotherPerson對象具備了person全部屬性和方法, 因爲採用object.create()建立,因此相似於原型式繼承
5.寄生組合式繼承
組合式繼承最大的不足是不管什麼狀況下,會調用兩次超類型構造函數;一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。
看下面例子:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
alert(this.name)
}
function SubType() {
SuperType.call(this,name); //第二次調用SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次調用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
alert(this.age);
}
解釋: 第一次調用SuperType()時,將SuperType()的實例屬性賦給了SubType.prototype;至關於在SubType.prototype上建立了兩個屬性name,colors
第二次調用SubType()構造函數時,又會調用SuperType()一次,此次又在新對象上建立了實例屬性name和colors,因而,這兩個屬性就屏蔽了原型中的兩個同名屬性。這樣就在SubType.prototype上建立了多餘的沒必要要的屬性
寄生組合式繼承:沒必要爲了指定子類型的原型而調用超類型構造函數,咱們所須要的無非就是超類型原型的一個副本而已。
本質上,就是利用寄生式繼承來繼承超類型的原型,再將結果指定給子類型原型
function inheritPrototype(subType, superType) {
var prototype = object.create(superType.prototype); //建立對象
prototype.constructor = subType; // 加強對象
subType.prototype = prototype; //指定對象
}
因此寄生式組合繼承:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
alert(this.name)
}
function SubType() {
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(subType, superType);
SubType.prototype.sayAge = function() {
alert(this.age);
}