zoukankan      html  css  js  c++  java
  • JavaScript(7)--- 继承

    JavaScript(7)--- 继承

    概念 首先继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承,继承也是为了数据共享。

    之间有讲过js中的原型和原型链:JavaScript(6)--- 原型链

    原型链就是继承的一种,子类 没有的东西到 父类 去寻找, 父类 如果还是没有就会到 爷爷 那去寻找,直到顶层,也就是到 Object 这一层如果还是没有就真没有了。

    当然除了原型链,还有其它继承方式,下面都会一一说明。

    1、原型链继承
    2、借用构造函数(经典继承)
    3、组合继承
    4、原型式继承
    5、寄生式继承
    6、寄生组合式继承
    

    一、原型链继承

    1、示例

          //人类构造函数
           function Person(name, age, sex) {
               this.name = name;
               this.sex = sex;
               this.age = age;
           }
           //人的原型方法
           Person.prototype.eat = function () {
               console.log("人可以吃东西");
           };
           //学生构造函数
           function Student(score) {
               this.score = score;
           }
           //改变学生的原型的指向即可===>学生和人已经发生关系
           Student.prototype = new Person("小明", 10, "男");
           //添加学生原型方法
           Student.prototype.study = function () {
               console.log("学生需要学习");
           };
           var stu = new Student(100);
           //父类 所拥有的特性 被继承来的
           console.log(stu.name);
           console.log(stu.age);
           console.log(stu.sex);
           stu.eat();
           //学生自有的特性
           console.log("---------------");
           console.log(stu.score);
           stu.study();
    

    运行结果

    关键点 让新实例的原型指向父类的实例,这样就可以继承父类的属性和方法,实现数据共享。

    缺点 1、因为改变原型指向的同时实现继承,直接初始化了属性,继承过来的属性的值都是一样的了
       2、引用类型的属性被所有实例共享

    二、借用构造函数继承

    1、示例

    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    Person.prototype.say = function () {
      console.log("父类方法");
    };
    function Student(name,age,sex,score) {
      //借用构造函数 -> 构造函数名字.call(当前对象,属性,属性,属性....);
      Person.call(this,name,age,sex);
      this.score = score;
    }
    var stu1 = new Student("张三",10,"男","100");
    console.log(stu1.name, stu1.age, stu1.sex,  stu1.score);
    
    var stu2 = new Student("李四",20,"女","120");
    console.log(stu2.name, stu2.age, stu2.sex,  stu2.score);
    
    var stu3 = new Student("王五",30,"妖","130");
    console.log(stu3.name, stu3.age, stu3.sex, stu3.score);
    

    运行结果

    重点.call() 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

    解决:解决了属性继承,并且值不重复的问题

    缺陷

    1、只继承了父类构造函数的属性,没有继承父类原型的属性。

    2、无法实现构造函数的复用。(每次用每次都要重新调用)

    3、每个新实例都有父类构造函数的副本,臃肿。


    三、组合继承

    概念:原型链继承+借用构造函数继承 = 组合继承

    1、示例

    //父类构造函数
    function Person(name,age,sex) {
      this.name=name;
      this.age=age;
      this.sex=sex;
    }
    //父类原型
    Person.prototype.say=function () {
      console.log("父类方法");
    };
    function Student(name,age,sex,score) {
      //借用构造函数:属性值重复的问题
      Person.call(this,name,age,sex);
      this.score=score;
    }
    //改变原型指向----继承
    Student.prototype=new Person();//不传值
    //子类原型
    Student.prototype.eat=function () {
      console.log("子类方法");
    };
    //创建子类对象
    var stu=new Student("张三",20,"男","100分");
    console.log(stu.name,stu.age,stu.sex,stu.score);
    stu.say();
    stu.eat();
    //创建子类对象
    var stu2=new Student("李四",200,"女","90分");
    console.log(stu2.name,stu2.age,stu2.sex,stu2.score);
    stu2.say();
    stu2.eat();
    

    运行结果

    解决 解决了 只继承了父类构造函数的属性,没有继承父类原型的属性 的缺陷。

    缺陷 调用了两次父类构造函数(耗内存),(一次是在子类型构造函数内部,另一次是在创建子类型原型的时候)


    四、原型式继承

    1、示例
        function CreateObj(o) { //传递一个字面量函数
            function F() {} //创建一个构造函数
            F.prototype = o; //把字面量函数赋值给构造函数的原型
            return new F(); //最终返回出实例化的构造函数
        }
        var person = { //字面量对象
            name: '小小',
            friend: ['香蕉', '小鳄鱼', '青蛙']
        };
        var person1 = CreateObj(person); //传递
        console.log(person1.name);
        person1.name = "中中"
        console.log(person1.name);
        var person2 = CreateObj(person); //传递
        console.log( person2.name);
    

    运行结果

    重点 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。

    缺点 包含引用类型的属性值始终都会共享相应的值, 这点跟原型链继承一样。

    注意 这里修改了person1.name的值,person2.name的值并未改变,是因为person1.name='person1'是给person1添加了name值,并非修改了原型上的name值。

    因为我们找对象上的属性时,总是先找实例上对象,没有找到的话再去原型对象上的属性。实例对象和原型对象上如果有同名属性,总是先取实例对象上的值


    五、寄生式继承

    概念 它的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某张方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

    function createAnother(original){
            var clone = Object.create(original); //这个就是上面的原型函数
            clone.sayHi = function(){ //只是通过这个函数 可以新增加属性和方法
                console.log('2020.1.18号');
            }
            return clone;
        }
    

    我理解了一下,就是又创建了一个函数,然后在函数内部定义一个对象来实现原型式继承方式,然后再给这个对象添加方法或属性值呀,最后再将这个对象返回,

    这就成为了寄生式继承方式。让我们来调用这个函数吧!

    var person = {
            name:"Nick",
            friends:["xiaowang","xiaochen"]
        };
    var person1 = createAnother(person); //新增加sayHi方法
    person1.sayHi();
    

    重点 就是给原型式继承外面套了个壳子。

    优点 没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。

    缺点 没用到原型,无法复用。


    六、寄生组合式继承(常用)

    上面的组合继承最大的缺点就是:组合继承(构造函数和原型的组合)会调用两次父类构造函数的代码。

    因此引入寄生组合式继承,寄生组合继承 是寄生继承跟组合继承的结合版。

    即通过借用构造函数来继承属性,通过原型链的方式来继承方法,而不需要为子类指定原型而调用父类的构造函数,我们需要拿到的仅仅是父类原型的一个副本。

    因此可以通过传入子类和父类的构造函数作为参数,首先创建父类原型的一个复本,并为其添加constrcutor,最后赋给子类的原型。这样避免了调用两次父类的

    构造函数,为其创建多余的属性。

    1、代码示例

       // 父类构造函数
        function Parent(name){
          this.name = name;
          this.colors = ['red', 'blue', 'green'];
        }
    
        //父类原型
        Parent.prototype.sayName = function(){
          console.log(this.name);
        }
    
        //子类构造函数
        function Child(name,age){
          //拷贝父类构造函数
          Parent.call(this,name);
          this.age = age;
        }
    
        //原型式继承
        function CreateObj(o){
          function F(){};
          F.prototype = o;
          return new F();
        }
    
        // Child.prototype = new Parent(); // 这里换成下面
        function prototype(child,parent){
          var prototype = CreateObj(parent.prototype); //创建对象
          prototype.constructor = child;               //增强对象
          child.prototype = prototype;                 //指定对象
        }
        prototype(Child,Parent);
    
        var child1 = new Child('小小', 18);
        console.log(child1);
    

    运行结果

    优点 这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了再Parent.prototype上面创建不必要的,多余的属性。普遍认为寄生组合式继承是引用类型

    最理想的继承方式


    七、总结

    这么多种继承方式,本质上其实就两种

    1.通过原型链,即子类的原型指向父类的实例从而实现原型共享。
    2.借用构造函数,即通过js的apply、call实现子类调用父类的属性、方法;
    

    原型链方式: 可以实现所有属性方法共享,但无法做到属性、方法独享(例如Sub1修改了父类的函数,其他所有的子类Sub2、Sub3...想调用旧的函数就无法实现了);

    借用构造函数: 除了能独享属性、方法外还能在子类构造函数中传递参数,但代码无法复用。总体而言就是可以实现所有属性方法独享,但无法做到属性、方法共享

    (例如,Sub1新增了一个函数,然后想让Sub2、Sub3...都可以用的话就无法实现了,只能Sub2、Sub3...各自在构造函数中新增)。

    组合继承: 就是把以上两种继承方式一起使用,把共享的属性、方法用原型链继承实现,独享的属性、方法用借用构造函数实现。


    参考

    1、如何理解javascript中寄生组合式继承?

    2、JS继承的多种方式

    3、JS继承的几种方式



    别人骂我胖,我会生气,因为我心里承认了我胖。别人说我矮,我就会觉得好笑,因为我心里知道我不可能矮。这就是我们为什么会对别人的攻击生气。
    攻我盾者,乃我内心之矛(4)。
    
  • 相关阅读:
    AVL树
    快速排序
    基数排序LSD_Radix_Sort
    归并排序
    JDBC连接池与工具类
    cookie的基础以及小案例
    javase基础4
    tomcat的request和response小案例
    javase基础3
    Servlet以及一个简单的登录案例
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/12218033.html
Copyright © 2011-2022 走看看