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)。
    
  • 相关阅读:
    I NEED A OFFER!
    水题 Codeforces Round #303 (Div. 2) A. Toy Cars
    模拟 HDOJ 5099 Comparison of Android versions
    模拟 HDOJ 5095 Linearization of the kernel functions in SVM
    贪心 HDOJ 5090 Game with Pearls
    Kruskal HDOJ 1863 畅通工程
    Kruskal HDOJ 1233 还是畅通工程
    并查集 HDOJ 1232 畅通工程
    DFS/并查集 Codeforces Round #286 (Div. 2) B
    水题 Codeforces Round #286 (Div. 2) A Mr. Kitayuta's Gift
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/12218033.html
Copyright © 2011-2022 走看看