JavaScript類的繼承全面示例講解
1. ES5 中的繼承
首先假設我們有一個父類 Person,并且在類的內部和原型鏈上各定義了一個方法:
function Person(name, age) {
this.name = name;
this.age = age;
this.greed = function() {
console.log('Hello, I am ', this.name);
}
}
Person.prototype.getInfo = function() {
return this.name + ',' + this.age;
}1.1 修改原型鏈
這是最普遍的繼承做法,通過將子類的 prototype 指向父類的實例來實現:
function Student() {
}
Student.prototype = new Person();
Student.prototype.name = '夏安';
Student.prototype.age = 18;
const stud = new Student();
stud.getInfo();在這種繼承方式中,stud 對象既是子類的實例,也是父類的實例。然而也有缺點,在子類的構造函數中無法通過傳遞參數對父類繼承的屬性值進行修改,只能通過修改 prototype 的方式進行修改。
1.2 調用父類的構造函數
function Student(name, age, sex) {
Person.call(this);
this.name = name;
this.age = age;
this.sex = sex;
}
const stud = new Student('夏安', 18, 'male');
stud.greed(); // Hello, I am 夏安
stud.getInfo(); // Error這種方式避免了原型鏈繼承的缺點,直接在子類中調用父類的構造函數,在這種情況下,stud 對象只是子類的實例,不是父類的實例,而且只能調用父類實例中定義的方法,不能調用父類原型上定義的方法。
1.3 組合繼承
這種繼承方式是前面兩種繼承方式的結合體。
function Student(name, age, sex) {
Person.call(this);
this.name = name;
this.age = age;
this.sex = sex;
}
Student.prototype = new Person();
const stud = new Student('夏安', 18, 'male');
stud.greed();
stud.getInfo();這種方式結合上面兩種繼承方式的優(yōu)點,也是 Node 源碼中標準的繼承方式。唯一的問題是調用了父類的構造函數兩次,分別是在設置子類的 prototype 和實例化子類新對象時調用的,這造成了一定的內存浪費。
1.4 原型繼承
利用一個空對象作為中介,將某個對象直接賦值給空對象構造函數的原型。
function createObject(o) {
// 創(chuàng)建臨時類
function f() {
}
// 修改類的原型為o, 于是f的實例都將繼承o上的方法
f.prototype = o
return new f()
}這不就是Object.create嗎? createObject對傳入其中的對象執(zhí)行了一次淺復制,將構造函數f的原型直接指向傳入的對象。同樣也沒有解決修改原型鏈的缺點。
1.5 寄生式繼承
在原型式繼承的基礎上,增強對象,返回構造函數,或者說使用原型繼承對一個目標對象進行淺復制,增強這個淺復制的能力。
function Student() {
const clone = Object.create(Person);
clone.name = '夏安';
return clone;
}同樣也可以和之前的方法進行組合,這里就不再贅述。
2. ES6 中的繼承
在 ES6 中可以直接使用 extends 關鍵字來實現繼承,形式上更加簡潔。我們前面也提到了,ES6 對 Class 的改進就是為了避免開發(fā)者過多地在語法細節(jié)中糾纏。
我們設計一個 student 類來繼承之前定義的 person 類。
class Student extends Person {
constructor(name, age, sex) {
super(name, age);
this.sex = sex;
}
getInfo() {
return super.getInfo() + ',' + this.sex;
}
print() {
const info = this.getInfo();
console.log(info);
}
}
const student = new Student('夏安', 18, 'male');
student.print(); // 夏安,18,male在代碼中我們定義了 Student 類,在它的構造方法中調用了 super 方法,該方法調用了父類的構造函數,并將父類中的屬性綁定到子類上。
super 方法可以帶參數,表示哪些父類的屬性會被繼承,在代碼中,子類使用 super 繼承了 Person 類的 name 以及 age 屬性,同時又聲明了一個 sex 屬性。
在子類中,super 方法是必須要調用的,原因在于子類本身沒有自身的 this 對象,必須通過 super 方法拿到父類的 this 對象,可以在 super 函數調用前嘗試打印子類的 this,代碼會出現未定義的錯誤。
如果子類沒有定義 constructor 方法,那么在默認的構造方法內部自動調用 super 方法,并繼承父類的全部屬性。
同時,在子類的構造方法中,必須先調用 super 方法,然后才能調用 this 關鍵字聲明其他的屬性(如果存在的話),這同樣是因為在 super 沒有調用之前,子類還沒有 this 這一緣故。
class Student extends Person {
constructor(name, age, sex) {
console.log(this); // Error
super(name, age);
this.sex = sex;
}
}
除了用在子類的構造函數中,super 還可以用在類方法中來引用父類的方法。
class Student extends Person {
constructor(name, age, sex) {
super(name, age);
this.sex = sex;
}
print() {
const info = super.getInfo(); // 調用父類方法
console.log(info);
}
}值得注意的是,super 只能調用父類方法,而不能調用父類的屬性,因為方法是定義在原型鏈上的,屬性則是定義在類的內部(就像組合繼承那樣,屬性定義在類的內部)。
class Student extends Person {
constructor(name, age, sex) {
super(name, age);
this.sex = sex;
}
getInfo() {
return super.name; // undefinded
}
}此外,當子類的函數被調用時,使用的均為子類的 this(修改父類的 this 得來),即使使用 super 來調用父類的方法,使用的仍然是子類的 this。
class Person {
constructor() {
this.name = '夏安';
this.sex = 'male';
}
getInfo() {
return this.name + ',' + this.sex;
}
}
class Student extends Person {
constructor() {
super();
this.name = '安夏';
this.sex = 'Female';
}
print() {
return super.getInfo();
}
}
const student = new Student();
console.log(student.print()); // 安夏,Female
console.log(student.getInfo()); // 安夏,Female在上面的例子中,super 調用了父類的方法,輸出的內容卻是子類的屬性,說明 super 綁定了子類的 this。
到此這篇關于JavaScript類的繼承全面示例講解的文章就介紹到這了,更多相關JS 類的繼承內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
addEventListener()與removeEventListener()解析
這篇文章主要為大家詳細介紹了addEventListener()與removeEventListener(),用于處理指定和刪除事件處理程序操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04

