• JS实现继承的几种方式


    JS继承的实现方式

    既然要实现继承,那么首先我们得有一个父类,代码如下:

    function Person(name,age){
      this.name=name;
      this.age=age;
      this.favor=["run","sing","write"];
      this.sleep = function(){
         console.log(this.name + '正在睡觉!');
      }
    }
    Person.prototype.eat=function(food){
       console.log(this.name+"喜欢吃:"+ food);
    }

    1、原型链继承

    核心: 让子类的原型等于父类的实例

    function Student(){ }
    Student.prototype=new Person();
    Student.prototype.name="lilei";

    测试代码

    var std1=new Student();
    std1.favor.push("painting");  
    console.log(std1.favor);       //["run", "sing", "write", "painting"]
    console.log(std1.name);        // lilei
    console.log(std1.age);         //undefined
    console.log(std1.sleep());           //lilei正在睡觉
    console.log(std1.eat("dumpling"));   //lilei喜欢吃:dumpling
    console.log(std1 instanceof Person); //true
    console.log(std1 instanceof Student);//true
    
    var std2=new Student();
    console.log(std2.favor);  //["run", "sing", "write", "painting"]
    console.log(std2.name);   //同实例1
    console.log(std2.age);    //同实例1
    console.log(std2.sleep());     //同实例1
    console.log(std2.eat("rice")); //lilei喜欢吃:rice

    特点:
    非常纯粹的继承关系,实例是子类的实例,也是父类的实例;
    父类新增原型方法/原型属性,子类都能访问到。

    缺点:
    要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中;
    无法实现多继承;
    来自原型对象的引用属性是所有实例共享的 (eg. favor);
    创建子类实例时,无法向父类构造函数传参。

    2、构造继承

    核心:在子类型构造函数的内部调用父类型的构造函数,等于是复制父类的实例属性给子类(没用到原型)

    提示:函数只不过是在特定环境中执行代码的对象,可以通过call()或apply()方法在新创建的对象上执行构造函数

    function Student(name,age,classid){ 
      Person.call(this,name,age);  //name和age是父类的形参
     
      this.classid=classid;
    }

    测试代码

    var std1=new Student("zhangsan",19,"#1");
    std1.favor.push("painting");   
    console.log(std1.favor);        //["run", "sing", "write", "painting"]
    console.log(std1.name);         //zs
    console.log(std1.age);          //19
    console.log(std1.sleep());      //zs正在睡觉!
    // console.log(std1.eat("dumpling"));   Uncaught TypeError: std1.eat is not a function
    console.log(std1 instanceof Person);   //false
    console.log(std1 instanceof Student);  //true
    
    
    var std2=new Student("lisi",20,"#1");  
    console.log(std2.favor);      //["run", "sing", "write"]
    console.log(std2.name);       //lisi
    console.log(std2.age);        //20
    console.log(std2.sleep());    //zs正在睡觉!
    //console.log(std2.eat("rice"));   Uncaught TypeError: std2.eat is not a function 

    特点:
    解决了原型链中,子类实例共享父类引用属性的问题; ( favor)
    创建子类实例时,可以向父类传递参数;
    可以实现多继承(call多个父类对象)。
    缺点:
    实例并不是父类的实例,只是子类的实例;
    只能继承父类的实例属性和方法,不能继承原型属性/方法;   如,eat()
    无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。

    5、组合继承

    核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后让子类的原型等于父类的实例,实现函数复用。

    function Student(name,age,classid){ 
      Person.call(this,name,age);  //实现对实例属性和方法的继承   第二次调用Person()
      this.classid=classid;
    }
    
    Student.prototype=new Person();  //实现对原型属性和方法的继承  第一次调用Person()
    
    Student.prototype.sayclass=function(){
      console.log(this.classid);
    }

    测试代码

    var std1=new Student("zhangsan",19,"#1001");  
    std1.favor.push("painting");
    console.log(std1.favor);        //["run", "sing", "write", "painting"]
    console.log(std1.name);         //zhangsan
    console.log(std1.age);          //19
    console.log(std1.sleep());      //zhangsan正在睡觉!
    console.log(std1.eat("dumpling"));   //zhangsan喜欢吃:dumpling
    console.log(std1.sayclass());        //#1001
    console.log(std1 instanceof Person);  //true
    console.log(std1 instanceof Student);  //true
    
    
    var std2=new Student("lisi",20,"#1002");  
    console.log(std2.favor);   // ["run", "sing", "write"]
    console.log(std2.name);    //lisi
    console.log(std2.age);     //20
    console.log(std2.sleep());  //lisi正在睡觉!
    console.log(std2.eat("rice"));  //lisi喜欢吃:rice

    特点:
    弥补了构造函数继承的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
    既是子类的实例,也是父类的实例
    不存在引用属性共享问题
    可传参
    函数可复用

    缺点:
    调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

    4、拷贝继承

    方法1核心思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

    var person={
      age:18,
      favor:["run","sing","write"]
    }
    function object(o){   //o表示父类型的对象
        function F(){};
        F.prototype=o;
        return new F();
    }  //直接返回临时类型(子类型)的对象
    
    var std1=object(person);
    std1.name="sssss";
    std1.age=20;
    var std2=object(person);

    测试代码:

    std1.favor.push("black");
    console.log(std1.favor);   //["run", "sing", "write", "black"]
    console.log(std1.name);    //sssss
    console.log(std1.age);      //20

    console.log(std2.favor); //["run", "sing", "write", "black"] console.log(std2.name); //undefined console.log(std1.age); //20

    特点:
    从本质上讲,object()对传入其中的对象进行了一次浅拷贝。
    在没有必要创建构造函数,而只是想让一个对象继承另一个对象时,用该方法即可。

    缺点:
    不支持多继承
    来自原型对象的引用属性是所有实例共享的 (eg. favor);

    方法2核心思想:把父对象的属性,全部拷贝给子对象

    function extendCopy(o) {
     var copy = {};
     for (var i in o) { 
       copy[i] = o[i];
     }
    
       copy.name="sssss"
       return copy;
    }
    
    var std1=extendCopy(person);
    std1.age=20;
    var std2=extendCopy(person);

    测试代码:

    std1.favor.push("black");
    console.log(std1.favor);   //["run", "sing", "write", "black"]
    console.log(std1.name);    //sssss
    console.log(std1.age);      //20
    
    console.log(std2.favor);    //["run", "sing", "write", "black"]
    console.log(std2.name);     //sssss
    console.log(std1.age);      //20

    特点:
    支持多继承
    缺点:
    效率较低,内存占用高(因为要拷贝父类的属性)
    无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

    如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝。

    5、寄生组合继承

    核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

    function Student(name,age,classid){ 
      Person.call(this,name,age);  //实现对实例属性和方法的继承
      this.classid=classid;
    }
    
    (function(){
      // 创建一个没有实例方法的类
      var Super = function(){};
      Super.prototype = Person.prototype;
      //将实例作为子类的原型
      Student.prototype = new Super();  
    })();
    
    Student.prototype.sayclass=function(){
      console.log(this.classid);
    }

    测试代码:

    var std1=new Student("zhangsan",19,"#1001");  
    std1.favor.push("painting");
    console.log(std1.favor);        //["run", "sing", "write", "painting"]
    console.log(std1.name);         //zhangsan
    console.log(std1.age);          //19
    console.log(std1.sleep());      //zhangsan正在睡觉!
    console.log(std1.eat("dumpling"));   //zhangsan喜欢吃:dumpling
    console.log(std1.sayclass());        //#1001
    console.log(std1 instanceof Person);  //true
    console.log(std1 instanceof Student);  //true
    
    
    var std2=new Student("lisi",20,"#1002");  
    console.log(std2.favor);   // ["run", "sing", "write"]
    console.log(std2.name);    //lisi
    console.log(std2.age);     //20
    console.log(std2.sleep());  //lisi正在睡觉!
    console.log(std2.eat("rice"));  //lisi喜欢吃:rice

    特点:
    堪称完美
    缺点:
    实现较为复杂

  • 相关阅读:
    中国大学MOOC-数据结构基础习题集、07-2、Insert or Merge
    NSDate相关
    切换中文键盘时覆盖输入框的完美解决方案
    自定义UITableView的右侧索引
    调用系统的短信和发送邮件功能,实现短信分享邮件分享
    程序出现警告,解决方式
    键盘隐藏问题
    java修饰符
    java覆盖和隐藏
    java嵌套类
  • 原文地址:https://www.cnblogs.com/jiayuexuan/p/7486472.html
Copyright © 2020-2023  润新知