物件導向--繼承

Saikikoko發表於2019-01-19

在ES6 class類宣告出來之前,ES5的繼承十分得蛋疼,這裡就寫一下ES5繼承的具體操作,最後和ES6做一下對比,你會發現ES6出來真是太好了!!!

在講知識點之前,首先明確幾個概念

  • ES5中建構函式宣告方式:function A(){}(大寫的函式名)
  • 例項化:new A();
  • 例項化生成的物件稱為例項:var a = new A()
  • 原型:A.prototype
  • 例項可以通過__proto__訪問原型,即:a.__proto__ === A.prototype
  • 原型上的constructor屬性指向建構函式本身(可以修改),即:A.prototype.constructor === A

ES5

首先給出一個建構函式

function Person(opt){
    this.name = opt.name;
    this.age = opt.age;
}

Person.prototype.sayName = function(){
    alert(this.name);
}
複製程式碼
  1. 私有屬性的繼承
function Student(opt){
    Person.call(this,opt);//當生成例項時,此步驟可以將Person的私有屬性掛載至例項上
    
    this.sex = opt.sex;//擴充套件私有屬性
}
複製程式碼
  1. 原型鏈的繼承

繼承中最麻煩的就是原型鏈的繼承,為了大家便於理解,這裡給出幾種方案,並比較他們的優劣。

方案一:

Student.prototype = Person.prototype
複製程式碼

此種方案雖然簡便,但是有個十分嚴重的缺點,因為原型本身是一個物件,通過直接賦值的形式,則你在Student的原型上做的所有擴充套件都會影響到Person.

思路: 既然不能直接將原型賦值給子類,那麼勢必要通過中間介質來訪問父類的原型,利用原型鏈,底層例項可以向上層原型鏈訪問的特點,我們可以利用Person生成一個例項,並把它賦值給Student.prototype,此時Student.prototype就可以通過__proto__訪問到Person的原型,也就可以使用它的方法

方案二:

Student.prototype = new Person({});
複製程式碼

可能看了上面的程式碼,你們可能有點不理解,不急下面給出解釋.

首先從程式碼本身來看,我們把Person例項出的一個物件賦值給了Student的原型,那麼我們就來看看,現在Student的原型是什麼樣的

let opt = {
    name: "erha",
    age: 18,
    sex: "男"
};

let student = new Student(opt);
console.log(new Person({}));
console.log(student);
console.log(student.__proto__ === Student.prototype);//true
複製程式碼

物件導向--繼承
現在Student的原型就如紅框中所示. 此時雖然Student的原型上沒有sayName的方法,但是student例項可以通過原型鏈找到Person.prototype原型上的sayName方法

物件導向--繼承
此時不僅可以給Student的原型新增方法不會對Person的原型造成影響,而且仍然可以呼叫Person原型上的方法

Student.prototype.sayAge = function(){
    alert(this.age);
}
student.sayName();//彈窗'erha'
student.sayAge();//彈窗18
let person = new Person(opt);
person.sayName();//彈窗'erha'
person.sayAge();//報錯
複製程式碼

此時原型鏈的情況

物件導向--繼承
可以看到對Student原型的操作並沒有對Person原型造成影響

但是我們看到這種方法,會在Student的原型上產生無用的公有屬性

物件導向--繼承

因此給出改進的方案三:

/*既然方案二會產生無用的公有屬性,那麼我們就定義一個沒有私有屬性的建構函式*/
function A(){};
A.prototype = Person.prototype;//然後和Person的原型關聯
Student.prototype = new A();//和方案二原理類似
Student.prototype.sayAge = function(){
    alert(this.age);
}
let studentA = new Student(opt);
console.log(studentA);
複製程式碼

物件導向--繼承

可以看到方案三不僅繼承了方案二的優點,而且完善了方案二原型上會有無效的公有屬性的缺點.

最後,因為通過new A({})生成的例項並沒有constructor屬性,所以我們還需要修正一下Student原型上的construtor.

Student.prototype.constructor = Student;
複製程式碼

最後給出完成的方案三程式碼

function Person(opt){
    this.name = opt.name;
    this.age = opt.age;
}

Person.prototype.sayName = function(){
    alert(this.name);
}

function Student(opt){
    Person.call(this,opt);//當生成例項時,此步驟可以將Person的私有屬性掛載至例項上

    this.sex = opt.sex;//擴充套件私有屬性
}

let opt = {
    name: "erha",
    age: 18,
    sex: "男"
};
/***************/
function A(){};
A.prototype = Person.prototype;//然後Person的原型關聯
Student.prototype = new A();
/***************/
Student.prototype.sayAge = function(){
    alert(this.age);
}
Student.prototype.constructor = Student;
let studentA = new Student(opt);
console.log(studentA);
複製程式碼

雖然方案三,完美地解決了繼承和擴充套件的問題,但是還是有它的缺點:步驟繁瑣

方案四:

Object.create()方法建立一個新物件,使用現有的物件來提供新建立的物件的__proto__

function Person(opt){
    this.name = opt.name;
    this.age = opt.age;
}

Person.prototype.sayName = function(){
    alert(this.name);
}

function Student(opt){
    Person.call(this,opt);//當生成例項時,此步驟可以將Person的私有屬性掛載至例項上

    this.sex = opt.sex;//擴充套件私有屬性
}

let opt = {
    name: "erha",
    age: 18,
    sex: "男"
};
/***************/
Student.prototype = Object.create(Person.prototype);//建立一個新物件,並將Person原型提供給新建物件的__proto__,最後賦值給Student的原型
/***************/
Student.prototype.sayAge = function(){
    alert(this.age);
}
let studentA = new Student(opt);

console.log(studentA);
複製程式碼

物件導向--繼承

最後結果和方案三一模一樣,但是使用原生JS自帶的方法省去很多步驟,但是它們的原理是一樣的.

最後來看看ES6是如何實現繼承的.

ES6

class Person{
    constructor(opt){
        this.name = opt.name;
        this.age = opt.age; 
    }
    
    sayName(){
        alert(this.name);
    }
}

class Student extends Person{
    constructor(opt){
        super(opt);//繼承私有屬性
        this.sex = opt.sex;
    }
    
    //擴充套件方法
    sayAge(){
        alert(this.age);
    }
}

let opt = {
    name: "erha",
    age: 18,
    sex: "男"
};

let studentA = new Student(opt);
console.log(studentA);

複製程式碼

物件導向--繼承

可以看到使用extends,直接解決了ES5中最麻煩的原型鏈繼承,且呼叫super()也直接繼承了父類的私有屬性,並且私有屬性的擴充套件和公有屬性的擴充套件都寫在了class內部,讓人看上去更加得直觀,並且class寫法更貼合其他真正物件導向語言的寫法

相關文章