javascript 面向對象(實現繼承的幾種方式)

一、原型鏈繼承

核心: 將父類的實例做爲子類的原型app

缺點: 父類新增原型方法/原型屬性,子類都能訪問到,父類一變其它的都變了函數

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

        Person.prototype.getName = function () {    //對原型進行擴展
            return this.name;
        };

        function Parent (age) {
            this.age = age;
        };

        Parent.prototype = new Person('老明');   //這一句是關鍵 //經過構造器函數建立出一個新對象,把老對象的東西都拿過來。

        Parent.prototype.getAge = function () {
            return this.age;
        };

//        Parent.prototype.getName = function () {   //能夠重寫從父類繼承來的方法,會優先調用本身的。
//            console.log(222);
//        };

        var result = new Parent(22);
        console.log(result.getName());  //老明  //調用了從Person原型中繼承來的方法(繼承到了當前對象的原型中)  
        console.log(result.getAge());   //22   //調用了從Parent原型中擴展來的方法

二、構造繼承

基本思想
借用構造函數的基本思想就是利用call或者apply把父類中經過this指定的屬性和方法複製(借用)到子類建立的實例中。
由於this對象是在運行時基於函數的執行環境綁定的。也就是說,在全局中,this等於window,而當函數被做爲某個對象的方法調用時,this等於那個對象。
callapply 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。性能

因此,這個借用構造函數就是,new對象的時候(new建立的時候,this指向建立的這個實例),建立了一個新的實例對象,
而且執行Parent裏面的代碼,而Parent裏面用call調用了Person,也就是說把this指向改爲了指向新的實例,
因此就會把Person裏面的this相關屬性和方法賦值到新的實例上,而不是賦值到Person上面,
因此全部實例中就擁有了父類定義的這些this的屬性和方法。this

由於屬性是綁定到this上面的,因此調用的時候才賦到相應的實例中,各個實例的值就不會互相影響了。prototype

核心:使用父類的構造函數來加強子類實例,等因而複製父類的實例屬性給子類(沒用到原型)code

缺點: 方法都在構造函數中定義, 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法,沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能對象

function Person (name) {
            this.name = name;
            this.friends = ['小李','小紅'];
            this.getName = function () {
                return this.name;
            }
        };

//        Person.prototype.geSex = function () {    //對原型進行擴展的方法就沒法複用了
//            console.log("男");
//        };

        function Parent = (age) {
            Person.call(this,'老明');  //這一句是核心關鍵
            //這樣就會在新parent對象上執行Person構造函數中定義的全部對象初始化代碼,
            // 結果parent的每一個實例都會具備本身的friends屬性的副本
            this.age = age;
        };

        var result = new Parent(23);
        console.log(result.name);    //老明
        console.log(result.friends);  //["小李", "小紅"]
     console.log(result.getName());  //老明
     console.log(result.age);    //23
     console.log(result.getSex());  //這個會報錯,調用不到父原型上面擴展的方法

三、組合繼承

組合繼承(全部的實例都能擁有本身的屬性,而且可使用相同的方法,組合繼承避免了原型鏈和借用構造函數的缺陷,結合了兩個的優勢,是最經常使用的繼承方式)繼承

核心:經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後再經過將父類實例做爲子類原型,實現函數複用原型鏈

缺點:調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)get

function Person  (name) {
             this.name = name;
             this.friends = ['小李','小紅'];
         };

         Person.prototype.getName = function () {
             return this.name;
         };

        function Parent (age) {
            Person.call(this,'老明');  //這一步很關鍵
            this.age = age;
        };

        Parent.prototype = new Person('老明');  //這一步也很關鍵
        var result = new Parent(24);
        console.log(result.name);    //老明
        result.friends.push("小智");  //
        console.log(result.friends);  //['小李','小紅','小智']
        console.log(result.getName());  //老明
        console.log(result.age);    //24

        var result1 = new Parent(25);   //經過借用構造函數都有本身的屬性,經過原型享用公共的方法
        console.log(result1.name);  //老明
        console.log(result1.friends);  //['小李','小紅']

四、寄生組合繼承

核心:經過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點

缺點:堪稱完美,但實現較爲複雜

function Person(name) {
            this.name = name;
            this.friends = ['小李','小紅'];
        }

        Person.prototype.getName = function () {
            return this.name;
        };

        function Parent(age) {
            Person.call(this,"老明");
            this.age = age;
        }

        (function () {
            var Super = function () {};     // 建立一個沒有實例方法的類
            Super.prototype = Person.prototype;
            Parent.prototype = new Super();     //將實例做爲子類的原型
        })();

        var result = new Parent(23);
        console.log(result.name);
        console.log(result.friends);
        console.log(result.getName());
        console.log(result.age);
相關文章
相關標籤/搜索