1、類式繼承
第1步、聲明父類
function SuperClass() {
this.superValue = true;
}
第2步、為父類添加共有方法
SuperClass.prototype.getSuperValue = function() {
return this.superValue;
}
第3步、聲明子類
function SubClass() {
this.subValue = false;
}
第4步、繼承父類,核心就是這一步,子類的原型指向第一個(gè)類的實(shí)例。
實(shí)例化父類的時(shí)候,實(shí)例對(duì)象復(fù)制父類構(gòu)造函數(shù)內(nèi)的屬性和方法,并且將__proto__指向了父類的原型對(duì)象,這樣就擁有了父類原型對(duì)象上的屬性和方法。
將實(shí)例對(duì)象賦值給子類的原型,子類就可以訪問(wèn)父類的屬性和方法。
SubClass.prototype = new SuperClass();
第5步、為子類添加共有方法
SubClass.prototype.getSubValue = function() {
return this.subValue;
}
使用子類:
var instance = new SubClass();
console.log(instance.getSuperValue()); // true
console.log(instance.getSubValue()); // false
判斷類與實(shí)例的關(guān)系:
console.log(instance instanceof SuperClass); // true
console.log(instance instanceof SubClass); // true
console.log(SubClass instanceof SuperClass); // false 繼承關(guān)系,而不是類與實(shí)例的關(guān)系
console.log(SubClass.prototype instanceof SuperClass); // true
console.log(instance instanceof Object); // true 說(shuō)明所有對(duì)象也是Object的實(shí)例
缺陷一:子類的2個(gè)實(shí)例之間會(huì)互相影響
先科普一下,值類型有:字符串、數(shù)值、布爾值、null、undefined,特征是不可變。引用類型有:對(duì)象、數(shù)組、函數(shù),特征是可操作,可改變。
當(dāng)父類的共有屬性是引用類型,然后一個(gè)子類實(shí)例修改了父類的屬性值,那么任何指向引用類型的變量都會(huì)變,所以另一個(gè)子類實(shí)例的屬性也就變了。例如:
function SuperClass() {
this.superValue = [true];
}
function SubClass() {}
SubClass.prototype = new SuperClass();
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance1.superValue); // [true]
instance1.superValue.push(8);
console.log(instance1.superValue); // [true, 8]
console.log(instance2.superValue); // [true, 8]
缺陷二:子類實(shí)例無(wú)法向父類傳遞參數(shù)
子類實(shí)例只能向子類傳遞參數(shù),沒(méi)有辦法向父類傳遞參數(shù)。不舉例了。
2、構(gòu)造函數(shù)繼承
構(gòu)造函數(shù)繼承,用的是傳說(shuō)中的.call或.apply方法。
// 第一步、聲明父類
function SuperClass(id) {
// 引用類型共有屬性,這次再看看是否解決的上面的缺陷一
this.books = ['JS', 'HTML5', 'CSS'];
// 值類型共有屬性
this.id = id;
}
// 第二步、父類型聲明原型方法
SuperClass.prototype.showBooks = function() {
console.log(this.books);
};
// 第三步、聲明子類
function SubClass(id) {
SuperClass.call(this, id); // 精華所在
}
// 第四步,創(chuàng)建兩個(gè)子類實(shí)例
var instance1 = new SubClass(1);
var instance2 = new SubClass(2);
// 使用和測(cè)試
instance1.books.push('PHP');
console.log(instance1.books); // ["JS", "HTML5", "CSS", "PHP"]
console.log(instance1.id); //1
console.log(instance2.books); // ["JS", "HTML5", "CSS"]
console.log(instance2.id); // 2
instance1.showBooks(); // TypeError
注意上面代碼中的“精華所在”,也就是call的用法。
可以看到子類內(nèi)部就一行代碼,就是SuperClass.call(this, id);,當(dāng)子類實(shí)例創(chuàng)建的時(shí)候,SuperClass.call(this, id)就被執(zhí)行了一遍。那么發(fā)生了什么呢?
第一步,將SuperClass函數(shù)的this綁定到this指向的對(duì)象上,也就是子類實(shí)例
第二步,給SuperClass函數(shù)傳參,傳遞的參數(shù)先是SuperClass函數(shù)自帶的參數(shù),然后依次是arg1, arg2, arg3...,這里是id,也就是子類的參數(shù)id傳給了父類,父類呢,又賦值給了this指向的對(duì)象(也就是子類實(shí)例)的id方法
第三步,執(zhí)行SuperClass函數(shù)。這樣一來(lái),父類就傳遞給子類實(shí)例方法和屬性。
缺陷:看到上面的TypeError吧,父類的原型方法,子類根本繼承不到,非要繼承則必須放到父類構(gòu)造函數(shù)里,但構(gòu)造函數(shù)的方法寫到構(gòu)造函數(shù)的里面不是最佳實(shí)踐,因?yàn)楦鱾€(gè)子類實(shí)例不能共用方法,浪費(fèi)內(nèi)存,所以,請(qǐng)看下面的組合繼承:
3、組合繼承
類式繼承,是通過(guò)實(shí)例化一個(gè)父類,然后賦值給子類的prototype來(lái)實(shí)現(xiàn)。
構(gòu)造函數(shù)式繼承,是通過(guò)在子類的構(gòu)造函數(shù)作用環(huán)境里,執(zhí)行了一次父類的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的。
綜合上述兩點(diǎn),就構(gòu)成了組合繼承。
// 第一步、聲明父類,跟構(gòu)造函數(shù)式繼承一樣
function SuperClass(name) {
// 引用類型共有屬性,這次再看看是否解決的上面的缺陷一
this.books = ['JS', 'HTML5', 'CSS'];
// 值類型共有屬性
this.name = name;
}
// 第二步、父類聲明共有方法,也跟構(gòu)造函數(shù)式繼承一樣
SuperClass.prototype.getName = function() {
console.log(this.name);
};
// 第三步、聲明子類
function SubClass(name, time) {
SuperClass.call(this, name); // 這依然跟構(gòu)造函數(shù)式繼承一樣
this.time = time; // 這跟類式繼承一樣
}
// 第四步,子類原型繼承父類,跟類式繼承一樣
SubClass.prototype = new SuperClass();
// 第五步,子類原型方法,跟類式繼承一樣
SubClass.prototype.getTime = function() {
console.log(this.time);
}
// 使用和測(cè)試
var instance1 = new SubClass(1, 2015);
var instance2 = new SubClass(2, 2016);
instance1.books.push('PHP');
console.log(instance1.books); // ["JS", "HTML5", "CSS", "PHP"]
console.log(instance1.name); //1
console.log(instance1.time); //2015
console.log(instance2.books); // ["JS", "HTML5", "CSS"]
console.log(instance2.time); // 2016
缺陷:父類被調(diào)用了兩次,浪費(fèi)內(nèi)存。但是這是目前用得最多的繼承實(shí)現(xiàn)。