【設計模式+原型理解】第三章:javascript五種繼承父類方式

【前言】html

  咱們都知道,面向對象(類)的三大特徵:封裝、繼承、多態dom

  繼承:子類繼承父類的私有屬性和公有方法函數

  封裝:把相同的代碼寫在一個函數中this

  多態:spa

    ->重載:JS嚴格意義上是沒有重載,但能夠經過傳遞不一樣參數實現不一樣功能prototype

    ->重寫:子類重寫父類的方法(這裏只要把父類的原型一改,父類的其餘實例會受到影響,又由於子類的原型鏈繼承父類的實例,這就會致使一樣會影響到子類的實例,本質是由於在JS原型繼承中,因爲它的核心原理,繼承並非從父類中拿過一份如出一轍的東西拷貝過來,而是讓子類和父類之間增長了一個原型鏈這樣一個橋樑)3d

 

【一、原型繼承】htm

  什麼是原型繼承,下面是一個很是常見的例子:對象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="#div1"></div>
</body>
<script>
    console.dir(document.getElementById('div1'))
</script>
</html>

  當你展開id="div1"這個dom節點的時候,你會看到,它的__proto__嵌套了一層又一層,以下,這是一條很是長的原型鏈blog

// #div1.__proto__ -> HTMLDivElement.prototype -> HTMLElement.prototype -> Element.prototype
// -> Node.prototype -> EventTarget.prototype -> Object.prototype

  上面的原型鏈很是長,可是是如何將它們一級一級關聯起來的呢?實現原理以下:

// 第四層 Object

// 第三層 myObject
function myObject() {

}
myObject.prototype = {
    constructor: myObject,
    hasOwnProperty: function () {}
};

// 第二層 myEventTraget
function myEventTraget() {
    
}
myEventTraget.prototype = new myObject(); // 子類的原型等於父類的實例
myEventTraget.prototype.constructor = myEventTraget;
myEventTraget.prototype.addEventListener = function () {};

// 第一層 myNode
function myNode() {

}
myNode.prototype = new myEventTraget(); // 子類的原型等於父類的實例
myNode.prototype.constructor = myNode;
myNode.prototype.createElment = function () {};

// 實例化
var n = new myNode; // 打印出來,看看n的結果,已經有四層__proto__了。

  實例n 打印出來的樣子以下:

  

  上面的多層繼承,是否是看起來特別想dom的原型繼承,一層套一層,下面來個簡化版的。

// 原型繼承簡化
function A() { this.x = 100; } A.prototype.getX = function () { console.log(this.x) }; function B() { this.y = 200; } // 如今,B想繼承A的私有+公有的屬性和方法 B.prototype = new A; B.prototype.constructor = B; var b = new B;

  上面簡化版的原型繼承原理,能夠參考下圖:

  原型繼承總結

    「原型繼承」是JS最多見的一種繼承方式

    子類B想要繼承父類A中的全部屬性和方法(私有+公有),只須要B.prototype = new A便可

    特色:它把父類中私有+公有的都繼承到子類原型上(即子類公有的)

    核心:原型繼承並非把父類中的屬性和方法克隆一份如出一轍的給B,而是讓B和A之間增長原型鏈的鏈接,之後實例b想要A中的getX方法,須要一級級向上查找來使用。

 

【二、call繼承】

  -> 把父類私有屬性和方法,克隆一份如出一轍的,做爲子類私有的屬性和方法

function A() { // 一個函數,它有三種角色:一、普通函數(私有做用域);二、類(new);三、普通對象(__proto__)
    this.x = 100;
    this.a = function () {
        console.log(this.x);
    }
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    this.y = 100;
    // this->b
    A.call(this);// ->A.call(b) 把A執行,讓A中的this變爲了n!!!
    //此時的A.prototype對B類來講是沒用的,由於B沒有繼承A
}
var b = new B;
console.log(b.x); // ->100
b.a(); // ->100
b.getX();// ->b.getX is not a function

  值得注意的是,b.getX() // -> b.getX is not a function。爲何會這樣呢,由於b實際上並無繼承A類,因此A.prototype對B是沒有任何做用的,此時的A,實際上只是做爲函數,而不是做爲一個類!!!

  總結call繼承

    ->call繼承,把父類的私有屬性和私有方法所有都拿過來了,可是卻拿不了父類的公有方法,這是它的缺點,也是優勢。

 

【三、冒充對象繼承】

  ->把父類私有的+公有的屬性和方法克隆一份如出一轍的,給子類私有的

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    // -> this->b
    var temp = new A; // 將A的實例當作普通對象來作遍歷,這就是冒充對象
    for (var key in temp) {// 把父類A私有的和公有的,都複製過來給子類B私有的
        this[key] = temp[key];
    }
    temp = null;
}
var b = new B;
b.getX(); // ->x、getX

  注意:for in的寫法,能夠把對象公用和私有的屬性和方法,所有都打印出來;

     obj.propertyIsEnumerable(key),此方法能夠判斷對象的私有屬性

     obj .hasOwnProperty(key),此方法一樣也能夠判斷對象的私有屬性

  總結「冒充對象繼承」:

    ->這個繼承方法比call跟完善了一步,call繼承只是把父類的私有拿過來變成本身私有的,可是「冒充對象繼承」則是把父類的私有+公有的屬性和方法拿過來變成本身私有的。

 

【四、混合模式繼承】

  ->原型繼承 + call繼承

function A() {
    tihs.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x)
};
function B() {
    A.call(this); // ->這一步,即等於: x=100
}
B.prototype = new A; // ->這一步,即等於:B.prototype: x=100 getX=....
B.prototype.constructor = B;
var b = new B;
b.getX();

  總結:

    ->這種方法,缺點是將A這個類,執行了兩次

    ->首先父類私有的複製了兩遍,第一遍是用call把父類私有的,複製給了子類私有的;第二遍就是使用原型繼承,把父類私有+公有的屬性,給了子類公有的

    ->也就是說重複了父類私有的存在於子類私有上, 也存在於子類公有上。也就是說,重複了一次父類私有的複製。

 

【五、寄生組合式繼承】(強烈推薦

  ->目的:子類繼承父類,父類私有的子類就繼承私有的,父類公有的子類就繼承公有的(注意,是在__proto__又套了一層原型)

// 寄生組合式繼承
function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    // ->this->b
    A.call(this);
}
// B.prototype = Object.create(A.prototype); // 意思是把父類的原型,給了子類的原型
// Object.create建立了一個對象,而且把這個新對象的原型指向了a的原型,而後B的原型指向了這個新對象
B.prototype = objectCreate(A.prototype);
B.prototype.constructor = B;
var b = new B;
console.log(b);


// Object.create的原理以下
function objectCreate(o) {
    function Fn() {}
    Fn.prototype=o;
    return new Fn;
}

    注意,這種方式跟原型繼承是有區別的,

    ->原型繼承:是把父類私有+公有的屬性給了子類的公有上

    ->寄生組合式繼承

      繼承私有:A.call(this);  

      繼承公有:先把父類私有的清空(這裏的清空能夠新建一個新對象,而後新對象的原型指向父類的原型便可),而後子類的原型指向該新對象

    ->比較繞,能夠看看下圖,即寄生組合式繼承原理圖

 

  總結「寄生式繼承」

    ->實際上是比較完美地實現了繼承,子類繼承父類,父類私有的屬性就放在子類私有上,父類公有的屬性就放在子類公有上。

 

--END--

相關文章
相關標籤/搜索