最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript实现继承的几种方式

    正文概述 掘金(healerhe)   2021-03-14   416

    JavaScript实现继承的几种方式

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

    首先定义一个父类

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

    原型链继承

    function Dog(name) {
      // 属性
      this.name = name || 'Dog';
    }
    Dog.prototype = new Person();
    var dog = new Dog();
    console.log(dog.name); // Dog
    console.log(dog.eat('bone'));//Dog正在吃:bone
    console.log(dog.sleep());//Dog正在睡觉
    console.log(dog instanceof Animal); //true 
    console.log(dog instanceof Dog); //true
    

    优点:

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

    缺点:

    1. 创建子类实例无法向父类构造函数传参
    2. 继承单一, 无法实现多继承
    3. 所有子类的实例都会共享父类实例的属性。(原型对象的所有属性被所有实例共享,当其中一个实例修改原型属性时, 其他实例的原型属性也会被改变)。

    构造函数继承

    function Cat(name, age){
      Animal.call(this, name); //复制父类实例属性给子类
      this.age = age || 5;
    }
    
    var cat = new Cat("Tom", 18);
    console.log(cat.name); // Tom
    console.log(cat.age); // 18
    console.log(cat.sleep()); // Tom正在睡觉
    console.log(cat.eat("fish")); // Uncaught TypeError: cat.eat is not a function
    console.log(cat instanceof Animal); // false
    console.log(cat instanceof Cat); // true
    

    优点:

    1. 只继承了父类构造函数的属性,方法。没有继承父类原型上的属性和方法。
    2. 解决了原型链继承的缺点
    3. 可以实现多继承(也就是可以继承多个构造函数属性,在子类构造函数中call多个构造函数)
    4. 在子实例对象中可以向父实例对象传参

    缺点:

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

    组合继承(原型链继承+构造函数继承) (常用)

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

    function Rabbit(name, age) {
      Animal.call(this, name); // 构造函数继承, 第二次调用
      this.age = age || 5;
    }
    Rabbit.prototype = new Animal(); //原型继承 第一次调用
    Cat.prototype.constructor = Cat; //重点 组合继承也是需要修复构造函数指向的。
    
    
    var rabbit = new Rabbit("jine");
    console.log(rabbit.name); // jine
    console.log(rabbit.age); // 5
    console.log(rabbit.sleep()); // jine正在睡觉
    console.log(rabbit.eat("carrot")); // jine正在吃:carrot
    console.log(rabbit instanceof Animal); // true
    console.log(rabbit instanceof Rabbit); // false
    

    优点:

    1. 弥补了构造函数的缺陷。可以继承实例属性/方法,也可以继承原型属性/方法
    2. 可传参
    3. 函数可复用
    4. 每个子类实例引入的构造函数属性是私有的。不存在引用属性共享问题
    5. 既是子类的实例,也是父类的实例

    缺点:

    1. 调用了两次父类构造函数,生成了两份实例(损耗内存), 子类的构造函数会代替原型上的父类构造函数。

    原型式继承

    function CreateObject(obj) {
        var Fun = function() {};
        Fun.prototype = obj; // obj.__proto__===Object.prototype true; Fun.prototype.constructor === Object true
        return new Fun();
    }
    
    var person = {
        name: "xiaoming",
        friend: ["lily", "xiaohong"]
    }
    var person1 = CreateObject(person);
    var person2 = CreateObject(person);
    //person1.name改变后,person2.name并未改变, 是因为person.name这个属性是基础类型,不是引用类型。
    person1.name = "xiaohe"; 
    console.log(person2.name); //"xiaoming"
    console.log(person.name); //"xiaoming"
    //由于person.friend是一个引用类型,person1.friend修改的时候,会同时改变当前引用类型地址存放的值,导致了person和person2的值改变。
    person1.friend.push("xiaozhang"); 
    console.log(person2.friend); // ["lily", "xiaohong", "xiaozhang"]
    console.log(person.friend);// ["lily", "xiaohong", "xiaozhang"]
    person1.friend = ["xiaowang"]; //将person1的friend属性指向另一个引用地址。
    console.log(person2.friend); // ["lily", "xiaohong", "xiaozhang"]
    console.log(person.friend);// ["lily", "xiaohong", "xiaozhang"]
    //person1.friend引用指向的内存地址和person,person2的不同,所以person2和person的friend属性不会改变。
    person1.friend.push("xiaoli");
    console.log(person1.friend); // ["xiaowang", "xiaoli"]
    console.log(person2.friend); // ["lily", "xiaohong", "xiaozhang"]
    console.log(person.friend);// ["lily", "xiaohong", "xiaozhang"]
    

    优点: 类型与一个对象的复制,用函数包装了一层而已

    缺点:

    1. 包含引用类型值的属性始终都会共享相应的值
    2. 无法实现复用。(新实例属性都是后面添加的)

    寄生继承

    //这个构造函数与Object.create()的效果是一样的
    function CreateObject(obj) {
        var Fun = function() {};
        Fun.prototype = obj; 
        return new Fun();
    }
    
    var person = {
        name: "xiaoming",
        friend: ["lily", "xiaohong"]
    }
    
    function CreatChild(obj) {
        //创建对象,或者用var newObj = Object.create(obj);
        var newObj = CreateObject(obj); 
        newObj.sayName = function() { //增强对象
            console.log(this.name);
        }
        return newObj;
    }
    
    var child1 = CreatChild(obj);
    child1.sayName(); // xiaoming
    child1.name="xiaohua";
    child1.sayName(); // xiaohua
    
    

    优点:没有创建自定义类型, 因为只是套了个壳子返回对象。可以增强对象。 缺点: 没有用到原型, 无法复用属性及方法。

    寄生组合继承

    // 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
    function Person(name){
         this.name = name;
         this.colors = ["red","green","blue"];
    }
    Person.prototype.sayName = function(){
          console.log(this.name);
    };
    function Child(name,age){
         Person.call(this,name); //构造函数继承
         this.age = age;
    }
    (function(){
        // 创建超类型原型的一个副本
        var anotherPrototype = Object.create(Person.prototype);
        // 重设因重写原型而失去的默认的 constructor 属性
        anotherPrototype.constructor = Child;
        // 将新创建的对象赋值给子类型的原型
        Child.prototype = anotherPrototype;
    })();
    
    Child.prototype.sayAge = function(){
         console.log(this.age);
    };
    var child1 = new Child("luochen",22);
    child1.colors.push("purple");
    console.log(child1.colors);      // "red,green,blue,purple"
    child1.sayName();
    child1.sayAge();
    
    var child2 = new SubType("tom",34);
    console.log(child2.colors);      // "red,green,blue"
    child2.sayName();
    child2.sayAge();
    

    重点: 修复了组合继承的问题。

    实例继承

    function Cat(name){
      var instance = new Animal();
      instance.name = name || 'Tom';
      return instance;
    }
    
    var cat = new Cat();
    console.log(cat.name);  // Tom
    console.log(cat.sleep()); // Tom正在睡觉
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // false
    

    缺点:

    1. 实例是父类的实例,不是子类的实例
    2. 不支持多继承

    拷贝继承

    function Cat(name){
      var animal = new Animal();
      for(var p in animal){
        Cat.prototype[p] = animal[p];
      }
      this.name = name || 'Tom';
    }
    
    var cat = new Cat();
    console.log(cat.name); // "Tom"
    console.log(cat.sleep()); // Tom正在睡觉
    console.log(cat instanceof Animal); // false
    console.log(cat instanceof Cat); // true
    

    优点:支持多继承

    缺点:

    1. 效率较低,内存占用高(因为要拷贝父类的属性)
    2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

    ES6 Class语法糖继承

    class Person {
        constructor(name) {
            this.name = name||"default name";
        }
        reName(name) {
            this.name = name;
        }
    }
    class Child extends Person {
        constructor(name) {
            super(name);
        }
    }
    var child1 = new Child();
    console.log(child1.name); // default name
    child1.reName("Tom");
    console.log(child1.name); // Tom
    

    起源地下载网 » JavaScript实现继承的几种方式

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元