面向?qū)ο?3-繼承

01 繼承的概念

繼承:通過某種方式使其中一個類型A獲取另一個類型B的屬性和方法,其中A類型成為子類型,B為父類型

Javascript中的繼承:

Object是所有對象的父級|父類型|超類型:js中所有的對象都直接或間接的繼承自O(shè)bject。

繼承有兩種方式:接口繼承和實現(xiàn)繼承,在js中只支持實現(xiàn)繼承,實現(xiàn)繼承主要依賴原型鏈來完成。

說明:其他語言中繼承通常通過類來實現(xiàn),js中沒有類的概念,js中的繼承是某個對象繼承另外一個對象,是基于對象的。


繼承方式

01 屬性拷貝實現(xiàn)繼承

問題:屬性拷貝如果拷貝的是引用類型,那么會有數(shù)據(jù)共享的問題

直接賦值與屬性拷貝有什么區(qū)別:

 直接賦值:所有的數(shù)據(jù)都是共享的

 屬性拷貝:僅僅引用類型是共享的

重新設(shè)置引用類型的值:

直接賦值:重新設(shè)置某個對象的引用類型值,那么另外一個對象會受到影響
屬性拷貝:重新設(shè)置某個對象的引用類型值,那么另外一個對象不會受到影響 會切斷他們之間的關(guān)系

代碼示例01

    var obj1 = {
        name:"張三",
        arr:[1,2,3],
        showName:function () {
            console.log(this.name);
        }
    };

    var obj2 = {};
    //要求obj2獲得obj1的屬性和方法
    for (var i in obj1)
    {
        obj2[i] = obj1[i];
    }

    console.log(obj2);
    obj1.arr.push(4);
    console.log(obj2.arr);  //arr[4]

    obj1.name = "李四";
    console.log(obj2.name);  //張三

    obj1.arr = ["demo1","demo2"];
    console.log(obj2.arr);  //arr[4]

代碼示例02

    var o1 = {
        name:"張三",
        arr:[1,2,3],
        showName:function () {
            console.log(this.name);
        }
    };

    var o2 = o1;
    console.log(o2);
    o1.name = "李四";
    console.log(o2.name); //李四

    o1.arr = ["demo1","demo2"];
    console.log(o2.arr);  //["demo1","demo2"];

02 原型式繼承

  • 利用動態(tài)特性

  • 直接替換原型對象

  • 設(shè)置子對象的原型對象等于父對象的原型對象

    01 原型對象成員共享
    
    02 子對象的構(gòu)造器屬性不正確(容易產(chǎn)生誤解)
    
    03 子對象獲取不到父構(gòu)造函數(shù)的實例成員
    

代碼示例

//利用動態(tài)特性

    function Person() {
        this.name = "默認(rèn)"
    }

    Person.prototype.showName = function () {
        console.log(this.name);
    };
    var p1 = new Person();
    p1.showName();
    
//直接替換原型對象

    function Person() {
        this.name = "默認(rèn)"
    }

    Person.prototype = {
        constructor:Person,
        showName:function () {
            console.log(this.name);
        }
    };
    var p1 = new Person();
    p1.showName();

//設(shè)置子對象的原型對象等于父對象的原型對象

    function Person() {
        this.name = "默認(rèn)"
    }
    Person.prototype.showName = function() {
        console.log(this.name);
    }

    function Student(){
        this.number = "201701"
    }

    //設(shè)置原型繼承
    Student.prototype =  Person.prototype;
    var stu = new Student();
    console.log(stu);
    console.log(stu.showName);
    console.log(stu.name);

03 安全擴展內(nèi)置對象

需求3:在所有的數(shù)組上面添加name屬性(name) 添加方法showName,在數(shù)組的原型對象上添加屬性和方法

缺點:

01 原型對象上面的屬性和方法可能會被覆蓋(不安全)

02 原型對象上面的屬性和方法會越來越多,不方便管理和維護,性能會降低(屬性的訪問)

03 可能會出現(xiàn)一些不容易察覺的錯誤(建議:在遍歷數(shù)組的時候使用普通for循環(huán))。for..in 循環(huán)會把原型對象上面的屬性和方法列舉出來。

代碼示例

    Array.prototype.name = "name";
    
Array.prototype.showName = function () {
        console.log(this.name);
};

var arr1 = [1,2,3];
var arr2 = ["demo01","demo02"];
console.log(arr1.name);
//arr2.showName();
console.log(arr2.name);  

for(var i in arr2)
{
    console.log(i, arr2[i]);  //會將新添加到原型對象一起遍歷
}

解決方法設(shè)置自定義構(gòu)造函數(shù)的原型對象為父構(gòu)造函數(shù)的一個實例,既能擁有父構(gòu)造函數(shù)的實例與原型成員,又不會在設(shè)置屬性與方法的時候改變其成員

代碼示例

    //01 提供自定義的構(gòu)造函數(shù)
    function MyArray() {
    
    }
    //02 設(shè)置自定義構(gòu)造函數(shù)的原型對象
    MyArray.prototype = new Array();   //擁有數(shù)組的所有屬性和方法
    
    //03 在自定義構(gòu)造函數(shù)上面添加屬性和方法
    MyArray.prototype.name = "name";
    MyArray.prototype.showName = function () {
        console.log(this.name);
    }
    //04 使用自定義構(gòu)造函數(shù)來創(chuàng)建對象
    var arr1 = new MyArray();
    arr1.push(1,2,3);
    console.log(arr1.length);
    console.log(arr1);

04 原型鏈繼承
子構(gòu)造函數(shù).prototype=new 父構(gòu)造函數(shù)();
(子構(gòu)造函數(shù)的實例可以繼承父構(gòu)造函數(shù)的實例屬性與原型方法)

代碼示例

    function Animal() {
        this.color = "紅色"
    }
    Animal.prototype.run = function () {
        console.log("run");
    }

    function Person() {
        this.name = "人"
    }
    Person.prototype = new Animal();
    Person.prototype.constructor = Person;  //注意,動態(tài)添加屬性與方法一定要在原型鏈繼承之后,如果在之前,添加的屬性與方法會被覆蓋
    Person.prototype.eat = function () {
        console.log("吃飯");
    }

    function Student() {
        this.className = "超神班";
    }

    Student.prototype = new Person();
//    Student.prototype.constructor = Student;
//    Student.prototype.read = function () {
//        console.log("閱讀");
//    }
    //錯誤的演示!?。?    //Student.prototype = {
        constructor : Student,
        read:function () {
            "讀書"
     //  }     使用字面量方式添加屬性和方法,會覆蓋原型鏈繼承
    }

    function Boy() {
        this.girlF = "林志玲";
    }

    Boy.prototype = new Student();
    Boy.prototype.constructor = Boy;
    Boy.prototype.play = function () {
        console.log("打游戲");
    }

    var boy = new Boy();
    console.log(boy);
    boy.eat();

注意點

  • 注意設(shè)置原型鏈的位置,先完成原型鏈繼承,再給原型添加成員
  • 進行原型鏈繼承時注意構(gòu)造器屬性
  • 繼承完成后,不能使用字面量方式給原型添加屬性與方法,會覆蓋。

問題

01 父對象的實例屬性會成為子對象的原型屬性,如果其實例屬性為引用類型,則存在數(shù)據(jù)共享的問題
02 在創(chuàng)建子類型的實例的時候,不能給父類型的構(gòu)造函數(shù)傳遞參數(shù)

代碼示例

    function Person(name) {
        this.name = name;
        this.friends = ["巴拉巴拉","嘩啦嘩啦","滴答滴答"];
    }
    Person.prototype.showName = function () {
        console.log(this.name);
    }

    function Boy() {
    }
    Boy.prototype = new Person("張三");//設(shè)置實現(xiàn)原型鏈繼承

    var boy1 = new Boy();
    console.log(boy1.friends);   //Array(4)

    var boy2 = new Boy();
    console.log(boy2.friends);    //Array(4)

    boy1.friends.push("烏拉烏拉");
    console.log(boy1.friends);   //Array(4)
    console.log(boy2.friends);   //Array(4)

    console.log(boy1.name);
    console.log(boy2.name);

05 原型鏈結(jié)構(gòu)

01 每個對象都是由構(gòu)造函數(shù)創(chuàng)建出來的
02 每個構(gòu)造函數(shù)都有一個與之相關(guān)連的原型對象(prototype)
03 構(gòu)造函數(shù)的原型對象本身也是對象,因此構(gòu)造函數(shù)的原型對象也有自己的構(gòu)造函數(shù)
04 構(gòu)造函數(shù)的原型對象的構(gòu)造函數(shù)也有相關(guān)聯(lián)的原型對象,而這個原型對象也是一個對象,因此也有構(gòu)造函數(shù)
05 構(gòu)造函數(shù)的原型對象的構(gòu)造函數(shù)的原型對象也有構(gòu)造函數(shù)..也有原型對象..也是對象...構(gòu)造函數(shù)....
以上 會形成一種鏈?zhǔn)降脑L問結(jié)構(gòu),這種結(jié)構(gòu)稱為原型鏈

原型鏈的終點是Object.prototype ,Object.prototype的原型對象是(Object.prototype.__ proto__ )

所有對象原型鏈的終點都是Object.prototype

代碼示例

function Person() {
    }

var p1 = new Person();

    //p1 是一個對象,因此有構(gòu)造函數(shù)(Person)
    //構(gòu)造函數(shù)都有原型對象因此Person.prototype存在(Object)
    //Person.prototype本身是一個對象(Object),因此也有構(gòu)造函數(shù)(Object)
    //Object的構(gòu)造函數(shù)也有原型對象,Object.prototype
    //Object.prototype本身也是一個對象,這個對象是Object類型
    //Object.prototype也有構(gòu)造函數(shù),構(gòu)造函數(shù)是Object

06 原型鏈中屬性搜索規(guī)則

對象在訪問(讀取|寫)屬性的時候,先遍歷當(dāng)前的對象,查找有沒有指定的屬性

01 如果有該屬性,那么就直接使用
02 如果沒有該屬性,那么就遍歷當(dāng)前對象的原型對象,在原型對象身上查詢指定的屬性
    01 如果找到那么就直接使用
    02 如果沒有找到,那么就繼續(xù)向上查詢
03 重復(fù)這個過程,直到Object.prototype,如果找到那么就使用,如果沒有找到那么就返回undefined或者是報錯。

07 借用構(gòu)造函數(shù)繼承
作用:獲取父構(gòu)造函數(shù)的實例成員(解決傳參的問題)
注意:獲取不到父構(gòu)造函數(shù)的原型成員,可以采用原型式繼承獲取

代碼示例

    function Person(name,age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.showName = function () {
        console.log(this.name);
    };

    function Boy(name,age) {
        //構(gòu)造函數(shù)內(nèi)部會默認(rèn)創(chuàng)建空對象并賦值給this
        Person.call(this,name,age);  //this.name = name;  借用構(gòu)造函數(shù),也就是將構(gòu)造函數(shù)Person中的實例屬性給了this,this又指向由Boy構(gòu)造函數(shù)創(chuàng)建的實例
    }

    Boy.prototype = Person.prototype;    //原型式繼承C

    var boy1 = new Boy("張三",20);
    var boy2 = new Boy("李四",30);
    console.log(boy1);
    console.log(boy2);
    boy1.showName();

08 組合繼承
借用構(gòu)造函數(shù)獲取父構(gòu)造函數(shù)的實例成員;
利用原型式繼承,獲取父構(gòu)造函數(shù)的原型對象;

問題:原型對象數(shù)據(jù)的共享問題

09 深拷貝繼承

用法:使用for ...in循環(huán)來遍歷,如果是值類型,直接賦值;如果是引用類型,那么新創(chuàng)建一個對象,再次遍歷,持續(xù)整個過程,使用遞歸函數(shù)。注意:使用for ...in循環(huán)會把原型對象成員也拷貝,使用hasOwnProperty方法進行過濾。

代碼示例1

    var person = {
        name:"張三",
        car:{
            type:"飛船",
            color:"黑色",
            des:{
                number:666888
            }
        },
        arr:[1,2,3,4]
    };


    //參數(shù)1:目標(biāo)對象 ,參數(shù)2:要拷貝屬性的對象
    function deepCopy(obj1,obj2) {
        obj1 = obj1 || {};  //容錯性處理
        for(var i in obj2)
        {
            //只拷貝實例屬性
            if (obj2.hasOwnProperty(i))
            {
                if (typeof obj2[i] == "object")
                {
                    //引用類型,新創(chuàng)建對象,再次遍歷
                    //需要判斷是數(shù)組還是對象
                    obj1[i] = Array.isArray(obj2[i])?[]:{};  //Array.isArray方法判斷是否是數(shù)組,
                    deepCopy(obj1[i],obj2[i]);
                }else
                {
                    obj1[i] = obj2[i];
                }
            }

        }
    }

    var p = {};
    deepCopy(p, person);
    console.log(p);

    //驗證共享的問題是否解決
    person.car.type = "銀河戰(zhàn)艦";
    console.log(p.car.type);

    console.log(person);

代碼示例2
深拷貝實現(xiàn)繼承,數(shù)據(jù)共享問題解決

    function deepCopy(obj1,obj2) {
        obj1 = obj1 || {};  //容錯性處理
        for(var i in obj2)
        {
            //只拷貝實例屬性
            if (obj2.hasOwnProperty(i))
            {
                if (typeof obj2[i] == "object")
                {
                    //引用類型,新創(chuàng)建對象,再次遍歷
                    //需要判斷是數(shù)組還是對象
                    obj1[i] = Array.isArray(obj2[i])?[]:{};
                    deepCopy(obj1[i],obj2[i]);
                }else
                {
                    obj1[i] = obj2[i];
                }
            }

        }
    }

    function Person(name,age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.showName = function () {
        console.log(this.name);
    }

    Person.prototype.hi = "hi";

    function Boy(name,age) {
        //構(gòu)造函數(shù)內(nèi)部會默認(rèn)創(chuàng)建空對象并賦值給this
        Person.call(this,name,age);  //this.name = name;  借用構(gòu)造函數(shù)
    }


    deepCopy(Boy.prototype,Person.prototype);

    var boy1 = new Boy("張三",20);
    var boy2 = new Boy("李四",30);
    console.log(boy1);
    console.log(boy2);
    boy1.showName();
    console.log(boy1.hi); //hi
    Boy.prototype.hi = "hahahahah";
    console.log(boy1.hi); //hahahahah

    var p1 = new Person();
    console.log(p1.hi);   //hi

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容