JavaScript 繼承全解析

ES5的繼承方式

ES6以前,JavaScript並無繼承這一現有的機制。bash

類式繼承

//聲明父類
function Father(){
    this.fatherVal = 'father';
}
//爲父類添加共有方法
Father.prototype.getFatherValue = function(){
    return this.fatherVal;
}
//聲明子類 
function Child(){
    this.childVal = 'child';
}
//繼承父類
Child.prototype = new Father();
//爲子類添加共有方法
Child.prototype.getChildValue = function(){
    return this.childVal;
}
複製代碼

子類的prototype被賦予父類的實例,新建立的對象複製了父類的構造函數內的屬性和方法而且將原型_proto_指向了父類的原型對象,這樣就擁有了父類的原型對象上的屬性和方法與父類構造函數中複製的屬性和方法。函數

var instance = new Child();
console.log(instance.getFatherValue());  //father
console.log(instance.getChildValue());   //child
console.log(instance instanceof Child);  //true
console.log(instance instanceof Father); //true
console.log(instance instanceof Object); //true
console.log(Child instanceof Father);    //false
console.log(Child.prototype instanceof Father);    //true
複製代碼

缺點:ui

  1. 子類實例共用父類的公有引用屬性。
  2. 沒法對父類構造函數內的屬性進行傳參初始化。
function Father(){
    this.companies =['bigo','yy','uc']
}
funtion Child(){}
Child.prototype = new Father();
var instanceA = new Child();
var instanceB = new Child();
console.log(instanceB.companies); //['bigo','yy','uc']
instanceA.companies.push('nemo');
console.log(instanceB.companies); //['bigo','yy','uc','nemo']
複製代碼

構造函數繼承

//聲明父類
function Father(val){
   this.companies =['bigo','yy','uc']
   this.val = val;
}
//聲明父類原型方法
Father.prototype.getCom = function(){
    console.log(this.companies);
}
//聲明子類
function Child(val){
    //繼承
    Father.call(this,val);
}
var instanceA = new Child('childA');
var instanceB = new Child('childB');

instanceA.companies.push('nemo');
console.log(instanceA.companies); //['bigo','yy','uc','nemo']
console.log(instanceA.val); //childA
console.log(instanceB.companies); //['bigo','yy','uc']
console.log(instanceB.val); //childB
複製代碼

對Child調用call,將子類中的變量在父類中執行一遍,而後父類給this綁定,因此子類繼承了父類的公有屬性。this

缺點:spa

因爲這種類型的繼承沒有涉及原型prototype,因此父類的原型方法不會被子類繼承,而若是想被子類繼承就必須放在構造函數中,這樣建立出來的每一個實例都會單獨擁有一份而不能共用。prototype

組合繼承

//聲明父類
function Father(val){
   this.companies =['bigo','yy','uc']
   this.val = val;
}
//聲明父類原型方法
Father.prototype.getValue = function(){
    console.log(this.val);
}
//聲明子類
function Child(val,newVal){
    //構造函數式繼承
    Father.call(this,val);
    this.newVal = newVal;
}
//類式繼承
Child.prototype = new Father();
//聲明子類原型方法
Child.prototype.getNewValue = function(){
    console.log(this.newVal);
}

var instanceA = new Child("fatherA","childA");
instanceA.companies.push('nemo');
console.log(instanceA.companies); //['bigo','yy','uc','nemo']
instanceA.getValue(); //fatherA
instanceA.getNewValue(); //childA

var instanceB = new Child("fatherB","childB");
console.log(instanceA.companies); //['bigo','yy','uc']
instanceB.getValue(); //fatherB
instanceB.getNewValue(); //childB
複製代碼

缺點:code

在使用構造函數繼承使用執行了一遍父類的構造函數,在實現子類原型的類式繼承再調用了一遍父類的構造函數,父類構造函數被調用了兩次。對象

原型式繼承

function inheritObject(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}

var situation = {
    companies:['bigo','yy','uc'];
    area:'guangzhou';
}

var situationA = inheritObject(situation);
situationA.area = 'shenzhen';
situationA.companies.push('tencent');

var situationB = inheritObject(situation);
situationB.area = 'beijing';
situationB.companies.push('baidu');

console.log(situationA.area);   //shenzhen
console.log(situationA.companies); //['bigo','yy','uc','tencent','baidu']
console.log(situationB.area); //beijing
console.log(situationB.companies); //['bigo','yy','uc','tencent','baidu']
console.log(situation.area);  //guangzhou
console.log(situation.companies); //['bigo','yy','uc','tencent','baidu']
複製代碼

是類式繼承的一個封裝,其中的過渡對象就至關於類式繼承的子類,而後返回新的實例化對象。繼承

缺點:ip

跟類式繼承同樣,父類的公有引用屬性被共有。

寄生式繼承

function inheritObject(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}

var situation = {
    companies:['bigo','yy','uc'];
    area:'guangzhou';
}

function createSituation(obj){
    //經過原型繼承建立新對象
    var newObj = new inheritObject(obj);
    //定義新對象方法
    newObj.getArea = function(){
        console.log(newObj.area)
    }
    //返回對象
    return obj;
}
複製代碼

只是在原型式繼承的基礎上添加了新屬性和方法,仍是跟原型式繼承同樣的缺點。

寄生式組合繼承

function inheritObject(obj){
    function F(){};
    F.prototype = obj;
    return new F();
}

//傳遞參數  child,parent 子類父類
function inheritPrototype(child,parent){
    //複製一份父類的原型副本保存在變量中;
    var fatherProto = inheritObject(father.prototype);
    //修正由於重寫子類原型致使子類的constructor屬性被修改;
    fatherProto.constructor = child;
    //設置子類的原型
    child.prototype = fatherProto;
}


//聲明父類
function Father(val){
   this.companies =['bigo','yy','uc']
   this.val = val;
}
//聲明父類原型方法
Father.prototype.getValue = function(){
    console.log(this.val);
}
//聲明子類
function Child(val,newVal){
    //構造函數式繼承
    Father.call(this,val);
    this.newVal = newVal;
}
//類式繼承
Child.prototype = new Father();
inheritPrototype(Child,Father);
//聲明子類原型方法
Child.prototype.getNewValue = function(){
    console.log(this.newVal);
}
複製代碼
  1. 在構造函數繼承中咱們已經調用了父類的構造函數,還差一個原型的副本
  2. 經過原型繼承獲得副本,可是這時候fatherProto的constructor須要指向子類。
  3. 最後將副本fatherProto賦給子類的原型prototype。

總的來講,就是既要構造函數,又要原型繼承,可是又避免了組合繼承的兩次調用父類構造函數的問題,最大的改變式對子類原型賦予的式父類原型的一個引用。

var instanceA = new Child("fatherA","childA");
instanceA.companies.push('nemo');
console.log(instanceA.companies); //['bigo','yy','uc','nemo']
instanceA.getValue(); //fatherA
instanceA.getNewValue(); //childA

var instanceB = new Child("fatherB","childB");
console.log(instanceA.companies); //['bigo','yy','uc']
instanceB.getValue(); //fatherB
instanceB.getNewValue(); //childB

複製代碼

注意點:

此時子類若是須要添加原型方法,必須經過prototype點語法一個個添加,不然會覆蓋掉繼承父類的原型對象。

ES6的繼承方式

ES6 新增了Class語法,Class 能夠經過extends關鍵字實現繼承,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。

Class 繼承

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

複製代碼

class 實現繼承的核心在於使用 extends 代表繼承自哪一個父類,而且在子類構造函數中必須調用 super,由於這段代碼能夠當作 Parent.call(this, value)。

若是子類沒有定義constructor方法,這個方法會被默認添加。

相關文章
相關標籤/搜索