zoukankan      html  css  js  c++  java
  • JS继承的几种方式

    JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。

    既然要实现继承,那么我们先定义一个父类:

    // 定义一个动物类
    function Animal (name) {
      // 属性
      this.name = name || 'Animal';
      // 实例方法
      this.sleep = function(){
        alert(this.name + '正在睡觉!');
      }
    }
    // 原型方法
    Animal.prototype.eat = function(food) {
      alert(this.name + '正在吃:' + food);
    };

    1、原型链继承

    核心: 将父类的实例作为子类的原型

    function Cat(){ 
    }
    Cat.prototype = new Animal();
    Cat.prototype.name = 'cat';
    
    // Test Code
    var cat = new Cat();
    alert(cat.name);
    alert(cat.eat('fish'));
    alert(cat.sleep());
    alert(cat instanceof Animal); //true 
    alert(cat instanceof Cat); //true

    特点:

    1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
    2. 父类新增原型方法/原型属性,子类都能访问到

    缺点:

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

    2、借用构造函数继承

    核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    
    // Test Code
    var cat = new Cat();
    alert(cat.name);
    alert(cat.sleep());
    alert(cat instanceof Animal); // false
    alert(cat instanceof Cat); // true

    特点:

    1. 解决了1中,子类实例共享父类引用属性的问题
    2. 创建子类实例时,可以向父类传递参数
    3. 可以实现多继承(call多个父类对象)(不完美,没有父类方法)

    缺点:

    1. 实例并不是父类的实例,只是子类的实例
    2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
    3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

    3、组合继承

    核心:通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,继承父类属性和方法,实现函数复用

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    Cat.prototype = new Animal();
    
    // Test Code
    var cat = new Cat();
    alert(cat.name);
    alert(cat.sleep());
    alert(cat instanceof Animal); // true
    alert(cat instanceof Cat); // true

    特点:

    1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
    2. 既是子类的实例,也是父类的实例
    3. 不存在引用属性共享问题
    4. 可传参
    5. 函数可复用
    6. 可以实现多继承(同上)

    缺点:

    1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)(仅仅多消耗了一点内存)

    4原型式继承

    核心:继承已有的对象而不是函数

    //对传入的对象执行一次浅复制
    function object(o){
          function F(){}
          F.prototype = o;
          return new F();
    }
    //例子
    var Animal = {
          name:"cat",
          friend:["aaron","cassic"]
    }
    var cat=object(Animal);
    cat.name="sss";
    cat.friend.push("aseaff");
    alert(cat.name);
    alert(cat.friend);

    特点:

    1. 没有必要创建构造函数,只是想让一个对象与另一个对象保持类似

    缺点:

    1. 与原型模式一样,包含引用类型的属性值会共享

    5寄生式继承

    核心 与原型式继承紧密相关,只是多了方法

    //对传入的对象执行一次浅复制
    function object(o){
          function F(){}
          F.prototype = o;
          return new F();
    }
    //增强对象
    function createobject(o){
         var clone=object(o);
         clone.sayname=function(){
            alert("hi");
    };
        return clone;
    }
    //例子
    var Animal = {
          name:"cat",
          friend:["aaron","cassic"]
    }
    var cat=createobject(Animal);
    cat.sayname();

    特点:

    1. 没有必要创建构造函数,只是想让一个对象与另一个对象保持类似
    2. 与原型式继承相比,有了自己的方法

    缺点:

    1. 与原型式继承一样,包含引用类型的属性值会共享

    6、寄生组合继承

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

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    function aaron(o,y){
    var Super = function(){};
    Super.prototype=y.prototype;
    o.prototype=new Super();
    o.prototype.constructor=o;
    } aaron(Cat,Animal); // Test Code var cat = new Cat(); alert(cat.name); cat.sleep(); alert(cat instanceof Animal); // true alert(cat instanceof Cat); //true

    特点:

    1. 堪称完美

    缺点:

    1. 实现较为复杂
    2. 不能实现多继承(不完美,同上)

    下面介绍多继承

    7 拷贝继承

    function Cat(name){
      var animal = new Animal();
      for(var p in animal){
        Cat.prototype[p] = animal[p];
      }
      Cat.prototype.name = name || 'Tom';
    }
    
    // Test Code
    var cat = new Cat();
    alert(cat.name);
    cat.sleep();
    alert(cat instanceof Animal); // false
    alert(cat instanceof Cat); // true
    

    特点:

    支持多继承

    缺点:

    效率较低,内存占用高(因为要拷贝父类的属性)(使用 for in 能遍历原型方法)

    8 组合使用构造函数和拷贝实现多继承

    function Parent1(name,age){
        this.name = name;
        this.age = age;
        this.height=180;
    }
    Parent1.prototype.say = function(){
        alert('hi');
    }
    function Parent2(name,age,weight){
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.height = 170;
        this.skin='yellow';
    }
    Parent2.prototype.walk = function(){
        alert('walk');
    }
     
    function Child(name,age,weight){
        Parent1.call(this,name,age);
        Parent2.call(this,name,age,weight);
    }
     
    for(var i in Parent1.prototype){Child.prototype[i] = Parent1.prototype[i]}
    for(var i in Parent2.prototype){Child.prototype[i] = Parent2.prototype[i]}
     
    var c1 = new Child('xiaoming',10,8);
    console.log(c1); //Child { name="xiaoming", age=10, height=170, 更多...}
    console.log(c1.constructor);//Child(name,age,weight)
    

    特点:javascript的多继承其实就是前面介绍的js循环拷贝继承的多次使用

    总结:javascript是可以利用call方法和prototype属性来实现多继承的。继承方法与单继承相似,只是将需要继承的多个父类依次实现,另外对于属性或共有方法重命的时候,以最后继承的属性和方法为主。因为会覆盖前面的继承。

    最好的两个继承是寄生组合继承和循环拷贝继承,但是寄生组合继承不能完美实现多继承,会漏掉父类原型方法,只有循环拷贝继承可以完美实现多继承。

  • 相关阅读:
    java中的上转型对象
    java工程项目里,在一个包里面,不能出现同名的类名,这问题是刚接触java才会遇到的,特别是新手一般都没有建立包,而是使用默认的,易出现同名的类名,导致eclipse提示错误
    接口作为参数,不同的接口调用不同的方法,例如:输出“I love Game”或输出“我喜欢游戏”
    JavaScript--原型链
    JavaScript--clientX,clientY、pageX,pageY、offsetLeft,offsetTop/offsetWidth,offsetHeight、scrollLeft,scrollTop/scrollWidth,scrollHeight、clientHeight,clientWidth区别
    JavaScript--结合CSS变形、缩放能拖拽的登录框
    JavaScript--放大镜
    JavaScript--返回顶部方法:锚链接、行内式js写法、外链式、内嵌式
    JavaScript--封装好的运动函数+旋转木马例子
    JavaScript--漏写var却还能使用标签
  • 原文地址:https://www.cnblogs.com/aaronchu/p/6168378.html
Copyright © 2011-2022 走看看