App下載

javascript中如何實現(xiàn)繼承

猿友 2021-02-01 13:53:26 瀏覽數(shù) (2638)
反饋

類式繼承

//聲明父類
//聲明父類
function SuperClass() {
  this.superValue = true;
}
//為父類添加共有方法
SuperClass.prototype.getSuperValue = function () {
  return this.superValue;
};
?
//聲明子類
function SubClass() {
  this.subValue = false;
}
?
//繼承父類
SubClass.prototype = new SuperClass();
//為子類添加共有方法
SubClass.prototype.getSubValue = function () {
  return this.subValue;
};
?
var instance = new SubClass();
console.log(instance.getSuperValue()); //true
console.log(instance.getSubValue()); //false

類式繼承需要將父類的實例賦值給子類原型, subClass.prototype 繼承了 superClass。 這種類式繼承有兩個缺點。其一,由于子類通過原型 prototype 對父類實例化,繼承了父類,父類中的共有屬性要是引用類型,就會在子類中被所有實例共用,因此一個子類的實例更改子類原型從父類構(gòu)造函數(shù)中繼承來的共有屬性就會直接影響到其他子類,如下:

function SuperClass() {
    this.courses = ['語文', '數(shù)學', '英語']
}
function SubClass() {}
SubClass.prototype = new SuperClass();
?
var instance1 = new SubClass()
var instance2 = new SubClass()
?
console.log(instance2.courses) //['語文', '數(shù)學', '英語']
instance1.courses.push('化學')
console.log(instance2.courses) //['語文', '數(shù)學', '英語', '化學']

instance1 的修改直接影響了 instance2,這是一個災難的陷阱。其二,由于子類實現(xiàn)的繼承是靠其原型 prototype 對父類的實例化實現(xiàn)的,因此在創(chuàng)建父類的時候,是無法向父類傳遞參數(shù)的,因而在實例化父類的時候也無法對父類構(gòu)造函數(shù)內(nèi)的屬性進行初始化。如何解決這個問題?請繼續(xù)往下看。

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

function SuperClass(current) {
  this.courses = ["語文", "數(shù)學", "英語"];
  this.current = current;
}
?
//父類聲明原型方法
SuperClass.prototype.getCourses= function () {
  console.log(this.courses);
};
?
//聲明子類
function SubClass(current) {
  SuperClass.call(this, current);
}
?
var instance1 = new SubClass("語文");
var instance2 = new SubClass("數(shù)學");
?
instance1.courses.push('化學')
console.log(instance1.courses); //["語文", "數(shù)學", "英語", "化學"]
console.log(instance1.current); //語文
console.log(instance2.courses); //["語文", "數(shù)學", "英語"]
console.log(instance2.current); //數(shù)學
?
instance1.getCourses() //TypeError: instance1.getCourses is not a function

SuperClass.call(this, current) 這條語句是構(gòu)造函數(shù)繼承的精華。由于 call 這個方法可以更改函數(shù)的作用環(huán)境,因此在子類中,對 SuperClass 調(diào)用這個 call 就是將子類中變量在父類中執(zhí)行一遍,由于父類中是給 this 綁定屬性的,因此子類也就繼承了父類的共有屬性。 由于這種類型的繼承沒有涉及原型 prototype,所以父類的的原型方法不會被子類繼承,要想被子類繼承,只能將 showCourse 放在父類構(gòu)造函數(shù)中,但是這樣就違背了代碼復用的原則。為了綜合以上兩種繼承的優(yōu)點,于是有了組合繼承。

組合繼承

//組合繼承
?
function SuperClass(current) {
  //引用類型共有屬性
  this.courses = ["語文", "數(shù)學", "英語"];
  // 值類型共有屬性
  this.current = current;
}
?
SuperClass.prototype.getCourses = function () {
  console.log(this.courses);
};
?
SuperClass.prototype.getCurrent = function () {
  console.log(this.current);
};
?
// 聲明子類
function SubClass(current, time) {
  //構(gòu)造函數(shù)繼承父類屬性
  SuperClass.call(this, current);
  this.time = time;
}
//類式繼承 子類原型繼承父類
SubClass.prototype = new SuperClass();
//子類原型方法
SubClass.prototype.getTime = function () {
  console.log(this.time);
};

在子類構(gòu)造函數(shù)中執(zhí)行父類構(gòu)造函數(shù),在子類的原型上實例化父類就是組合模式,子類實例中更改父類繼承下來的引用類型屬性 courses 不會改變其他實例,測試如下

var instance1 = new SubClass("語文", "9:00");
instance1.getTime(); //9:00
instance1.courses.push('化學')
instance1.getCourses(); //["語文", "數(shù)學", "英語", "化學"]
instance1.getCurrent(); //語文
console.log(instance1.current)//語文
?
var instance2 = new SubClass("數(shù)學", "10:00");
instance2.getTime(); //10:00
instance2.getCourses(); //["語文", "數(shù)學", "英語"]
instance2.getCurrent(); //數(shù)學
console.log(instance2.current)//數(shù)學

但是該模式在執(zhí)行子類構(gòu)造函數(shù)時執(zhí)行了一遍父類函數(shù),在實現(xiàn)子類原型繼承時又執(zhí)行了一遍父類構(gòu)造函數(shù),調(diào)用了父類構(gòu)造函數(shù)兩遍,顯然是一個設計缺陷,那還有更好的方式嗎?針對該缺陷,出現(xiàn)了“寄生組合式繼承”

寄生組合式繼承

在介紹這種繼承方式之前,需要先了解 “原型式繼承”和“寄生式繼承”

基礎了解

原型式繼承

function inheritObject(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
?
var course = {
  name: "語文",
  alikeCourse: ["數(shù)學", "英語"],
};
?
var newCourse = inheritObject(course);
newCourse.name = "化學";
newCourse.alikeCourse.push("物理");
?
var otherCourse = inheritObject(course);
otherCourse.name = "政治";
otherCourse.alikeCourse.push("歷史");
?
console.log(newCourse.name); //化學
console.log(newCourse.alikeCourse); //["數(shù)學", "英語", "物理", "歷史"]
?
console.log(otherCourse.name); //政治
console.log(otherCourse.alikeCourse); //["數(shù)學", "英語", "物理", "歷史"]
?
console.log(course.name); //語文
console.log(course.alikeCourse); //["數(shù)學", "英語", "物理", "歷史"]

inheritObject 可以看做是對類式繼承的一種封裝,其中的過度類 F 相當于類式繼承中的子類。在類式繼承中存在的共用引用類型的問題依然存在,但是過度類 F 構(gòu)造函數(shù)中無內(nèi)容,所以開銷較小。

寄生式繼承

“寄生式繼承”是在“原型式繼承”的基礎上繼續(xù)增強。

function inheritObject(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
?
var course = {
  name: "語文",
  alikeCourse: ["數(shù)學", "英語"],
};
?
function createCourse(obj) {
  //通過原型繼承方式創(chuàng)建新對象
  var o = new inheritObject(obj);
  // 拓展新對象
  o.getName = function () {
    console.log(this.name);
  };
  return o;
}
?
const newCourse = createCourse(course)

這種方式在某個對象內(nèi)部持續(xù)增長屬性,像寄生式生長,所以稱之為寄生繼承。寄生繼承是對原型繼承的二次封裝,并且在二次封裝的過程中對繼承的對象做了拓展,這樣新創(chuàng)建的對象不僅僅有父類中的屬性和方法,而且還添加了新的屬性和方法。在這種思想的基礎上,結(jié)合組合式繼承,衍生了 “寄生組合式繼承”

實現(xiàn)

function inheritObject(o) {
    function F() {}
    F.prototype = o;
    return new F();
  }
function inheritPrototype(subClass, superClass) {
    //復制一份父類的原型副本保存在變量中
    var p = inheritObject(superClass.prototype)
    //修正因為重寫子類原型導致子類的constructor屬性被修改
    p.constructor = subClass
    //設置子類的原型
    subClass.prototype = p
}

以上父類原型保存一個副本,賦值給子類原型,從而實現(xiàn)繼承,且并未重新調(diào)用一次父類函數(shù),測試如下,與組合繼承模式相近

//test
?
function SuperClass(current) {
  //引用類型共有屬性
  this.courses = ["語文", "數(shù)學", "英語"];
  // 值類型共有屬性
  this.current = current;
}
?
SuperClass.prototype.getCourses = function () {
  console.log(this.courses);
};
?
SuperClass.prototype.getCurrent = function () {
  console.log(this.current);
};
?
// 聲明子類
function SubClass(current, time) {
  //構(gòu)造函數(shù)繼承父類屬性
  SuperClass.call(this, current);
  this.time = time;
}
?
//寄生式繼承 子類原型繼承父類
inheritPrototype(SubClass, SuperClass);
?
//類式繼承 子類原型繼承父類
// SubClass.prototype = new SuperClass();
?
//子類原型方法
SubClass.prototype.getTime = function () {
  console.log(this.time);
};
?
var instance1 = new SubClass("語文", "9:00");
var instance2 = new SubClass("數(shù)學", "10:00");
?
instance1.getTime(); //9:00
instance1.courses.push("化學");
instance1.getCourses(); //["語文", "數(shù)學", "英語", "化學"]
instance1.getCurrent(); //語文
console.log(instance1.current); //語文
?
instance2.getTime(); //10:00
instance2.getCourses(); //["語文", "數(shù)學", "英語"]
instance2.getCurrent(); //數(shù)學
console.log(instance2.current); //數(shù)學

區(qū)別僅在

//寄生式繼承 子類原型繼承父類
inheritPrototype(SubClass, SuperClass);
?
//類式繼承 子類原型繼承父類
// SubClass.prototype = new SuperClass();

從而實現(xiàn)多個子類多個實例不相互影響,父類構(gòu)造函數(shù)僅僅調(diào)用一次,堪當 JavaScript 繼承的終極模式。


0 人點贊