JS继承

作者: SEELE7 | 来源:发表于2017-08-21 09:47 被阅读0次

    JS中的继承

    许多OO语言都支持两种继承方式:接口继承和实现继承;

    因为JS中没有类和接口的概念 , 所以JS不支持接口继承 , 只支持实现继承;

    实现继承主要是依靠原型链来实现的;

    1.原型链

    基本思想:

    利用原型让一个对象继承另一个对象的属性和方法 ;

    回顾构造函数,原型和实例的关系 :

    每个构造函数都有一个原型对象 ; 原型对象都包含一个指向构造函数的指针 , 而实例包含一个指向原型 ;

       function Person(name,age) {
           this.name = name;
           this.age = age;
       }
       Person.prototype.speakName = function () {
           console.log(this.name)
       }
       
       var p1 = new Person("老王",25);
       var p2 = new Person("小王",15);
    

    那假如我们把一个实例对象,作为另外一个实例的原型对象会发生什么呢?

          //定义一个构造函数。
        function Father () {
            // 添加name属性.  默认直接赋值了。当然也可以通过构造函数传递过来
            this.name = "马云";
        }
          //给Father的原型添加giveMoney方法
        Father.prototype.giveMoney = function () {
            alert("我是Father原型中定义的方法");
        }
        //再定义一个构造函数。
        function Son () {
            //添加age属性
            this.age = 18;
        }
        //关键地方:把Son构造方法的原型替换成Father的对象。  因为原型是对象,任何对象都可以作为原型
        Son.prototype = new Father();
          //给Son的原型添加getMoney方法
        Son.prototype.getMoney = function () {
            alert("我是Son的原型中定义的方法");
        }
        //创建Son类型的对象
        var son1 = new Son();
    
        //发现不仅可以访问Son中定义属性和Son原型中定义的方法,也可以访问Father中定义的属性和Father原型中的方法。
        //这样就通过原型完成了类型之间的继承。 
        // Son继承了Father中的属性和方法,当然还有Father原型中的属性和方法。
        son1.giveMoney();
        son1.getMoney();
        alert("Father定义的属性:" + son1.name);
        alert("Son中定义的属性:" + son1.age);
    
    1. 定义Son构造函数后,我们没有再使用Son的默认原型,而是把他的默认原型更换成了Father类型对象。
    2. 这时,如果这样访问 son1.name, 则先在son1中查找name属性,没有然后去他的原型( Father对象)中找到了,所以是"马云"。
    3. 如果这样访问 son1.giveMoney(), 先在son1中找这个方法,找不到去他的原型中找,仍然找不到,则再去这个原型的原型中去找,然后在 Father的原型对象中 找到了。
    4. 从图中可以看出来,在访问属性和方法的时候,查找的顺序是这样的:对象->原型->原型的原型->...->原型链的顶端。 就像一个链条一样,这样 由原型连成的"链条",就是我们经常所说的原型链。
    5. 从上面的分析可以看出,通过原型链的形式就完成了JavaScript的继承。

    2.默认原型链

    原型链的顶端一定是Object这个构造函数的原型对象。

    3. 测试数据类型

    isPrototypeOf( 对象 ) : 这是个 原型对象 的方法,参数传入一个对象,判断参数对象是不是由这个原型派生出来的。 也就是判断这个原型是不是参数对象原型链中的一环。

    4.原型链的缺陷

    父类型的构造函数创建的对象,会成为子类型的原型

    缺陷:

    1. 父类型中定义的实例属性 , 会成为子类型的原型属性 ; 子类型原型中的属性被所有的子类型的实例所共用;(属性共用)
    2. 原型链继承中,只有一个地方用到了父类型的构造函数 , 也就意味着只有一次传递参数的机会 , 并且参数的值在所有的子类实例里都是一样的;(参数传递困难)

    5.借用构造函数弥补缺陷

    如何借用?

    ​ 使用call或apply完成==构造函数的借调== ,目的在于借用父类型(或其他类型)的构造函数 , 完成子类型的属性添加 ;

    原理

    ​ call和apply 可以更改 构造方法内部的 this 指向指定的对象上;

        function Father (name,age) {
            this.name = name;
            this.age = age;
        }
        //如果这样直接调用,那么father中的this只的是 window。 因为其实这样调用的: window.father("李四", 20)
        // name 和age 属性就添加到了window属性上
        Father("李四", 20);
        alert("name:" + window.name + "\nage:" + window.age);  //可以正确的输出
    
        //使用call方法调用,则可以改变this的指向
        function Son (name, age, sex) {
            this.sex = sex;
              //调用Father方法(看成普通方法),第一个参数传入一个对象this,则this(Son类型的对象)就成为了Father中的this
            Father.call(this, name, age);
        }
        var son = new Son("张三", 30, "男");
        alert("name:" + son.name + "\nage:" + son.age + "\nsex:" + son.sex);
          alert(son instanceof Father); //false
    

    注意

    ​ 在这里并不是真正的继承 , 借用的构造函数在这里也只是作为一个普通的方法;

    缺陷

    ​ 单独使用借用时 ; 父类型原型对象中的共享属性和方法 ; 通过借用无法获取 ;

    6. 组合继承

    组合函数利用了原型继承和构造函数借调继承的优点,组合在一起。成为了使用最广泛的一种继承方式。

          //定义父类型的构造函数
        function Father (name,age) {
            // 属性放在构造函数内部
            this.name = name;
            this.age = age;
            // 方法定义在原型中
            if((typeof Father.prototype.eat) != "function"){
                Father.prototype.eat = function () {
                    alert(this.name + " 在吃东西");
                }
            }  
        }
          // 定义子类类型的构造函数
        function Son(name, age, sex){
              //借调父类型的构造函数,相当于把父类型中的属性添加到了未来的子类型的对象中
            Father.call(this, name, age);
            this.sex = sex;
        }
          //修改子类型的原型为父类型的对象。这样就可以继承父类型中的方法了。
        Son.prototype = new Father(    );
        var son1 = new Son("志玲", 30, "女");
        alert(son1.name);
        alert(son1.sex);
        alert(son1.age);
        son1.eat();
    

    总结

    共同的方法和属性通过原型链继承 ;

    需要传入参数的属性(子类型实例中的不相同属性)通过 call或apply的方法 借调一个 构造函数来完成属性的添加 (子类属性中的同名属性会覆盖掉原型中的属性);

    相关文章

      网友评论

          本文标题:JS继承

          本文链接:https://www.haomeiwen.com/subject/nrhzrxtx.html