zoukankan      html  css  js  c++  java
  • 【设计模式+原型理解】第三章:javascript五种继承父类方式

    【前言】

      我们都知道,面向对象(类)的三大特征:封装、继承、多态

      继承:子类继承父类的私有属性和公有方法

      封装:把相同的代码写在一个函数中

      多态:

        ->重载:JS严格意义上是没有重载,但可以通过传递不同参数实现不同功能

        ->重写:子类重写父类的方法(这里只要把父类的原型一改,父类的其他实例会受到影响,又因为子类的原型链继承父类的实例,这就会导致同样会影响到子类的实例,本质是因为在JS原型继承中,由于它的核心原理,继承并不是从父类中拿过一份一模一样的东西拷贝过来,而是让子类和父类之间增加了一个原型链这样一个桥梁)

    【1、原型继承】

      什么是原型继承,下面是一个非常常见的例子:

    <!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__嵌套了一层又一层,如下,这是一条非常长的原型链

    // #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方法,需要一级级向上查找来使用。

     

    【2、call继承】

      -> 把父类私有属性和方法,克隆一份一模一样的,作为子类私有的属性和方法

    function A() { // 一个函数,它有三种角色:1、普通函数(私有作用域);2、类(new);3、普通对象(__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继承,把父类的私有属性和私有方法全部都拿过来了,但是却拿不了父类的公有方法,这是它的缺点,也是优点。

     

    【3、冒充对象继承】

      ->把父类私有的+公有的属性和方法克隆一份一模一样的,给子类私有的

    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继承只是把父类的私有拿过来变成自己私有的,但是“冒充对象继承”则是把父类的私有+公有的属性和方法拿过来变成自己私有的。

    【4、混合模式继承】

      ->原型继承 + 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把父类私有的,复制给了子类私有的;第二遍就是使用原型继承,把父类私有+公有的属性,给了子类公有的

        ->也就是说重复了父类私有的存在于子类私有上, 也存在于子类公有上。也就是说,重复了一次父类私有的复制。

     

    【5、寄生组合式继承】(强烈推荐

      ->目的:子类继承父类,父类私有的子类就继承私有的,父类公有的子类就继承公有的(注意,是在__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--

  • 相关阅读:
    signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);
    关于pthread_cond_wait使用while循环判断的理解
    linux的sleep()和usleep()的使用和区别
    C中结构体的存储分配
    扯扯python的多线程的同步锁 Lock RLock Semaphore Event Condition
    线程属性的初始化以及销毁
    Mysql数据库一个表字段中存了id,并以逗号分隔,id对应的详细信息在另一个表中
    sqlyog 注册码
    Oracle 12C卸载图文教程
    Oracle12c Release1安装图解(详解)
  • 原文地址:https://www.cnblogs.com/pengshengguang/p/10547624.html
Copyright © 2011-2022 走看看