JS常见的五种继承方式

一、原型链继承

特点: 实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)

将子类的原型指向父类构造函数的实例对象

修改简单数据类型不会造成数据共享,修改引用类型是会造成child实例之间的数据共享

js 复制代码
 function Parent() {
      this.name = 'abc';
      this.info = {
        weight: 180,
        height: 180
      }
    }

    function Child() {
      this.age = 12
    }

    const parent1 = new Parent()
    Child.prototype = parent1  //将子类的原型指向了父类构造函数的实例对象
     
    const child1 = new Child()
    const child2 = new Child()
    child1.name = 'xym'
    child2.info.height = 100
    console.log(child1)
    console.log('---')
    console.log(child2);

缺点: 1、新实例无法向父类构造函数传参。

2、所有新实例都会共享父类实例的引用类型属性。当原型上有一个引用类型的属性时,所有实例都共享的是该引用类型属性的 同一份引用

二、借用构造函数继承

js 复制代码
    function Parent() {
      this.name = 'abc';
      this.info = ['hehe','haha']
    }
    
    Parent.prototype.say = function (){
      console.log('111');  //缺点:无法继承父类原型上的方法
    }

    function Child() {
      this.age = 12
      Parent.call(this)   //在子类构造函数中调用父类构造函数,父类中的this绑定到了子类实例上
    }    
     
    const child1 = new Child()
    const child2 = new Child()
    child1.info = ['aaa']
    
    console.log(child1)
    console.log('---')
    console.log(child2);
    console.log(new Parent())

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

特点:

1、解决了原型链继承引用属性共享的问题。

2、可以继承多个构造函数属性(call 多个)。

js 复制代码
function Father() {
  this.fromFather = 'father';
}
function Mother() {
  this.fromMother = 'mother';
}

function Child() {
  Father.call(this);
  Mother.call(this);
}

3 、在子实例中可向父实例传参。

js 复制代码
    function Parent (age){
      this.age = age 
    }

    function Son (name, age){
      this.name = name
      Parent.call(this, age)    //可以在调用父构造函数时传参
    }

    const son1 = new Son('xym', 21)
    console.log(son1);

缺点: 1、只能继承父类构造函数的属性,不能继承原型上的方法

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

三、组合继承(组合原型链继承和借用构造函数继承)

js 复制代码
 function Parent (){
      this.name = ['aaa','bbb']
    }

    function Child (){
      this.age = 21
      Parent.call(this)    //第二次调用Parent
    }
    
    Child.prototype = new Parent()   // 第一次调用Parent

    var child1 = new Child() 
    var child2 = new Child() 
    child1.name = ['aaa'] //为什么解决了共享属性的问题?在第二次调用Parent
                          //时,this.name覆盖掉了第一次用原型链继承的this.name 
    console.log(child1, child2);

重点: 结合了两种模式的优点,传参和复用

缺点: 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

四、寄生组合继承

js 复制代码
 function Parent(){
      this.name = ['aaa','bbb']
    }

    function Child(){
      Parent.call(this)
    }

    Parent.prototype.sing = function (){ console.log('111');
    }
    Child.prototype.dance = function (){ console.log('222');
    }
    
    //Object.create()为ES5新语法,如果不兼容,就自己手写实现
    if(!Object.create){
      Object.create = function(proto){
        function F (){}
        F.prototype = proto     //F的prototype赋值为proto对象
        return new F()    // new 创建一个新实例对象,将这个对象的原型(__proto__)指向proto对象
      }
    }

    Child.prototype = Object.create(Parent.prototype)
    //  Object.create() 创建一个空对象
    // 将空对象的__proto__(原型)指向Parent.prototype
    // 将新对象赋值给 Child.prototype
    // 缺点:这里Child.prototype 重新赋值会把原本Child原型上的方法覆盖掉
    
    console.log(new Child());   这个缺点该怎么解决???

解决方案:

js 复制代码
 function Parent() {
        this.name = ['aaa', 'bbb'];
      }

      function Child() {
        Parent.call(this);
      }

      Parent.prototype.sing = function () { console.log('111'); };

      // 先建立继承关系
      Child.prototype = Object.create(Parent.prototype);

      // 再添加子类方法
        Child.prototype.dance = function () { console.log('222'); };

      // 修复constructor指向
      Child.prototype.constructor = Child;

      console.log(new Child());
      // 现在可以访问:
      new Child().dance() 

五、class 类实现继承

通过 extends 实现继承, super调用父类构造函数。子类实例可以直接调用父类的方法,子类class内部还可以直接调用父类方法

js 复制代码
// 父类
class ParentClass {
    constructor(name) {
        this.name = name;
    }

    // 父类的一个方法
    greet() {
        console.log(`Hello from ParentClass! My name is ${this.name}.`);
    }
}

class ChildClass extends ParentClass {
    constructor(name, age) {
        super(name); //继承属性
        this.age = age;
    }

    // 子类自己的方法
    introduce() {
        console.log(`I am ${this.name} and I am ${this.age} years old.`);
        this.greet()
    }
}

const child = new ChildClass("Alice", 5);

// 子类实例可以直接调用父类的方法
child.greet();      // 输出: Hello from ParentClass! My name is Alice.
child.introduce();  // 输出: I am Alice and I am 5 years old.
相关推荐
大佐不会说日语~9 分钟前
Redis高可用架构演进面试笔记
redis·面试·架构
在逃的吗喽25 分钟前
黑马头条项目详解
前端·javascript·ajax
JHCan3331 小时前
一个没有手动加分号引发的bug
前端·javascript·bug
天涯学馆2 小时前
为什么越来越多开发者偷偷用上了 Svelte?
前端·javascript·svelte
拾光拾趣录2 小时前
为什么浏览器那条“假进度”救不了我们?
前端·javascript·浏览器
香菜狗2 小时前
vue3响应式数据(ref,reactive)详解
前端·javascript·vue.js
油丶酸萝卜别吃3 小时前
SSE与Websocket有什么区别?
前端·javascript·网络·网络协议
27669582923 小时前
拼多多小程序 anti_content 分析
java·javascript·python·node·拼多多·anti-content·anti_content
The_era_achievs_hero3 小时前
uni-appDay02
javascript·vue.js·微信小程序·uni-app
rzl024 小时前
SpringBoot6-10(黑马)
linux·前端·javascript