js是如何实现继承的?

1. 原型链继承

原型链继承的原理很简单,直接让子类的原型对象指向父类的实例,当子类的实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承

function Person() {
    this.name = 'Back_kk';
}
Person.prototype.getName = function() {
    return this.name;
}

function Student() {}
// 让子类的原型对象指向父类的实例
Student.prototype = new Person();
// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 原型的实例等于自身
Student.prototype.constructor = Student;

const student = new Student();
console.log(student.name); // Back_kk
console.log(student.getName()); // Back_kk

缺点:
1. 由于所有Student实例原型都指向同一个Person实例, 因此对某个Student实例的来自父类的引用类型变量修改会影响所有的Student实例
2. 在创建子类实例时无法向父类构造传参, 即没有实现super()的功能

function Person() {
    this.obj = {
        name: 'Back_kk',
        age: 18
    };
}
function Student() {}
Student.prototype = new Person();
// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 原型的实例等于自身
Student.prototype.constructor = Student;

const student1 = new Student();
student1.obj.name = '佩奇';
const student2 = new Student();
console.log(student2.obj.name); // 佩奇

2. 借用构造函数继承

构造函数继承,即在子类的构造函数中调用父类的构造函数,使用apply() 或 call() 方法将父对象的构造函数绑定在子对象上,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参。

function Person(name) {
    this.name = name
}
Person.prototype.getName = function() {
    return this.name;
}
function Student() {
    Person.apply(this, arguments);
}

const student = new Student('Back_kk');
console.log(student.name); // Back_kk
console.log(student.getName()); // 报错 student.getName is not a function

优点:

  • 解决了原型链实现继承的不能传参的问题,和父类的原型共享的问题。

缺点:

  • 父类的方法如果定在在原型上,则对子类而言是不可见的,即子类继承不到父类原型上的属性和方法
  • 为了子类能继承父类的方法,则需要把方法定义在构造函数中,这样会无法实现函数的复用,占用内存

3. 组合式继承

组合继承结合了原型集成和构造函数继承的特点。

使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承

这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性

function Person(name) {
    this.name = name;
}
Person.prototype.getName = function() {
    return this.name;
}
function Student() {
    // 构造函数继承
    Person.apply(this, arguments)
}
// 原型式继承
Student.prototype = new Person();

// 原型的实例等于自身
Student.prototype.constructor = Student;

const student = new Student('Back_kk');
console.log(student.name); // Back_kk
console.log(student.getName()); // Back_kk

缺点:

每次创建子类实例都执行了两次构造函数 (Person.apply和new Person()),虽然这并不影响对父类的继承,但子类创建实例时,原型链上会存在两份相同的属性和方法,这并不优雅。
在这里插入图片描述

“原型链上会存在两份相同的属性和方法”,其实是Person.prototype.proto === Person,导致原型链多出了一层,所以会存在两份属性和方法。
我们从上图中可以看出,Person.prototype的原型对象的构造函数指向Person自身。

4. ES6的class类继承

ES6 实现了Class,可以通过关键字extends实现继承

其实质是先创造出父类的this对象,然后用子类的构造函数修改this

注意:子类的构造方法中必须调用super方法,且只有在调用了super()之后才能使用this,因为子类的this对象是继承父类的this对象,然后对其进行加工,而super方法表示的是父类的构造函数,用来新建父类的this对象

  // 父类
  class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }
    getName() {
      console.log(this.name);
    }
  }
  // 子类extends继承父类,必须调用super方法
  class Student extends Person {
    constructor(name, age, address) {
      super(name, age);
      this.address = address;
    }
    say() {
      console.log(`hello,我叫${this.name},我的住址在${this.address}`);
    }
  }

  const stu1 = new Student('张三', 20, '西安市');
  // 调用父类的方法
  stu1.getName();  // 张三
  // 调用自己的方法
  stu1.say(); // hello,我叫张三,我的住址在西安市

缺点:

ES6提出的,有的浏览器不支持该写法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值