1.原型鏈繼承
爲了讓子類繼承父類的屬性(也包括方法),首先需要定義一個構造函數。然後,將父類的新實例賦值給構造函數的原型。代碼如下:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(`我的名字是${this.name}`);
}
function Student(name,stuno) {
this.name = name;
this.stuno = stuno;
}
let pp = new Person('彭昱暢')
let why = new Student('哈哈',10001);
console.log(pp);
console.log(why);
看上面的這段代碼,我在裏面定義了兩個構造函數,分別是Person
和 Student
,並且在 Person
構造函數的原型上添加了 sayName
方法.
此時此刻,Person
的實例化對象 pp
能夠調用到這個方法,但是Studnet
的實例化對象 why 並不能訪問到.
用下面的一張圖上面代碼中涉及的來展示原型鏈:
但是我們現在有一個需要,就是我們希望 Student
的實例化對象 why
也能夠執行 sayName
方法,該怎麼辦呢?
在原型鏈中我們有講到,原型鏈的查找,其實和我們的作用域查找非常的相似,會一直想上級作用域查找,直至查找到全局.從上面的一張圖中我們可以明顯的看到在實例化對象 why
的原型鏈上並不能查找到 sayName
方法,所以現在我們就要改變 why
的原型鏈,讓它的原型鏈上也有 sayName
方法.
why
是構造函數 Student
的實例化對象,只要 Student
的原型發生了變化,那麼它的實例化對象 why
的原型對象也會發生變化,所以我們要做的就是讓構造函數 Student
的原型指向構造函數 Person
的原型,如下圖:
這樣只需要在原先的代碼基礎上添加下面的一行代碼就可以實現修改原型鏈的需求:
Student.prototype = Person.prototype;
現在 Student
的實例化對象 why
也可以訪問到 sayName
方法.
到這裏其實我們就已經實現了原型鏈繼承.
但是我還有更進一步的需求,我們需要 Student
構造函數實例化出來的對象有一個 sayStuno
方法,按照我們之前的方法,我們需要給 Student
的原型上添加這個方法.如下圖:
但是現在又有問題出現了,如果我們這樣做的話,Person
的實例化對象也就有了 sayStuno
方法,這樣 兩個構造函數的實例就都能訪問 sayStuno
方法了,但是因爲實例化對象 pp
並沒有學號,所以打印的結果是 undefined
.
所以我們要將 sayStuno
方法 ‘私有化’ ,就是隻能夠通過 Student
的實例化對象訪問到,而其他的方法訪問不到.
現在我們要構造函數 Student
的實例化對象和 Person
的實例化對象都能訪問到 sayName
方法,但是 sayStuno
方法要求只能 Student
訪問.
此時我們就可以用構造函數 Person
來實例化一個對象,這個對象裏面存儲的就是我們需要的 sayStuno
方法.然後讓 Student
的原型指向這個函數,這樣做的目的就是爲了 Student
的實例化對象 why
在進行原型鏈查找的時候可以找到 sayStuno
方法,而且 pp
查找不到.
辦法如下面的一張圖所示:
實現的代碼很簡單,就只需要添加下面的代碼即可:
Student.prototype = new Person();
Student.prototype.sayStuno = function () {
console.log(`我的學號是${this.stuno}`);
}
此時我們分別用兩個實例化對象訪問 sayStuno
方法,結果如下:
二者依舊可以訪問 sayName
,但是 pp
不可以訪問 sayStuno
,why
可以訪問 sayStuno
原型鏈查找過程:
完整的代碼如下:
function Person(name) {
this.name = name;
}
let pp = new Person('彭昱暢')
Person.prototype.sayName = function () {
console.log(`我的名字是${this.name}`);
}
function Student(name,stuno) {
this.name = name;
this.stuno = stuno;
}
////核心語句,繼承的實現全靠這條語句了:
Student.prototype = new Person();
Student.prototype.sayStuno = function () {
console.log(`我的學號是${this.stuno}`);
}
let why = new Student('哈哈',10001);
console.log(pp);
console.log(why);
其實原型鏈繼承說白了就是與自己本身的原型鏈切斷聯繫,繼承其他構造函數的原型.
2.構造函數繼承
使用父類的構造函數來增強子類實例,等於是複製父類的實例屬性給子類(沒用到原型)
我們先來觀察下面的一段代碼:
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,stuno) {
this.name = name;
this.age = age;
this.sex = sex;
this.stuno = stuno;
}
let pp =new Person('彭昱暢',18,'男');
let why = new Student('哈哈',13,'女',10001)
console.log(pp)
console.log(why);
對於上面這段代碼,你品,你細品!!!
有沒有發現一個問題,Person
構造函數和 Student
構造函數也太像了叭! Student
就只比 Person
多了一個參數, 身爲懶癌晚期患者,如果可以肯定是不想敲這麼多重複的代碼的,所以就有了下面一個辦法.
既然二者之間那麼像,那我能不能直接在 Student
構造函數中調用 PErson
構造函數呢.當然是可以的啦.
function Stdeunt(name,age,sex,stuno) {
Person(name,age,sex);
this.stuno = stuno;
}
這個時候我們只需要解決一個問題,那就是 this
的指向問題,如果我們不做任何的更改,直接調用的話,那麼當函數的執行權進入 Person
函數的時候,this
就會指向 window
,而不是我們要實例化的對象,所以我們只需要下面這樣做就可以實現構造函數的繼承liao…
不瞭解call的用法的可以戳鏈接哦https://blog.csdn.net/lhrdlp/article/details/104320665
完整的代碼如下哦:
//父類
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//子類
function Student(name,age,sex,stuno) {
// 核心語句
Person.call(this,name,age,sex);
this.stuno = stuno;
}
let pp =new Person('彭昱暢',18,'男');
let why = new Student('哈哈',13,'女',10001)
console.log(pp)
console.log(why);
這樣做也有缺點哦:
方法都在構造函數中定義, 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法,無法實現函數複用,每個子類都有父類實例函數的副本,影響性能
3. 組合繼承
就是將原型鏈繼承和構造函數繼承組合在一起;繼承兩個優點
其背後的思路是 使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數複用,又保證每個實例都有它自己的屬性。
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(`我的名字叫${this.name}`);
}
function Student(name,age,stuno) {
Person.call(this,name,age);
this.stuno = stuno;
}
Student.prototype = new Person();
Student.prototype.sayStuno = function () {
console.log(`我的學號是${this.stuno}`);
}
let why = new Student('哈哈',14,10001);
let pp = new Person('彭昱暢',18);
這個時候我們進行如下的測試:
是不是已經實現了我們的需求,創建對象,構造函數 Student
的實例化對象和 Person
的實例化對象都能訪問到 sayName
方法,但是 sayStuno
方法要求只能 Student
訪問.
如果前面兩種繼承已經明白了,那麼組合繼承就沒有一點點難度了,畫了下面的圖輔助理解.
但是這樣做會帶來一個問題,就是我們的Person
構造函數會執行兩次,一次是創建子類型的時候,另一次是在子類型構造函數的內部.那麼下面要介紹的寄生組合繼承就解決了這個問題.
4. 寄生組合繼承
所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(`我的名字叫${this.name}`);
}
function Student(name,age,stuno) {
Person.call(this,name,age);
this.stuno = stuno;
}
//核心代碼
function fn(){}
fn.prototype = Person.prototype;
Student.prototype = new fn();
Student.prototype.sayStuno = function () {
console.log(`我的學號是${this.stuno}`);
}
let why = new Student('哈哈',14,10001);
let pp = new Person('彭昱暢',18);
在這裏我們創建了一個新的構造函數fn
,通過之前的學習我們已經瞭解到函數 fn
肯定會有他自己的原型,但是我們在這裏改變了他的原型.
讓 fn
的原型和 Person
的原型指向同一個對象,然後用構造函數 fn
實例化一個新的空對象,讓構造函數 Student
的原型指向 這個空對象.
如下圖:
做如下驗證:
5.聖盃模式
聖盃模式就是借用構造函數封裝
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(`我的名字叫${this.name}`);
}
function Student(name,age,stuno) {
Person.call(this,name,age);
this.stuno = stuno;
}
//聖盃模式核心代碼
function inhert(parent,child) {
function fn() {};
fn.prototype = parent.prototype;
child.prototype = new fn();
child.prototype.constructor = Student;
child.prototype.parent = Person;
}
inhert(Person,Student);
Student.prototype.sayStuno = function () {
console.log(`我的學號是${this.stuno}`);
}
let why = new Student('哈哈',14,10001);
let pp = new Person('彭昱暢',18);
如下:
驗證結果: