JavaScript繼承詳細解釋

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);

看上面的這段代碼,我在裏面定義了兩個構造函數,分別是PersonStudent ,並且在 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);

如下:
在這裏插入圖片描述
驗證結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章