返回

JS的强大继承关系,从JS继承与原型链剖析

前端

最近在排查巩固面试知识点的时候,发现继承,原型链这一块真是一生之敌,为了不让自己将来面临懵逼的困境,做个笔记总结一下。

本文讲解JavaScript各种继承方式和优缺点,欢迎各位提出不同意见探讨。

在类语言中,对象基于模板来创建,然后由类来实例化对象。在没有引入类概念的时候,我们通过构造函数来创建对象,然后通过函数来复用对象的属性和方法。这种方式虽然可以实现继承,但是它有许多缺点,例如:

  • 容易产生重复代码
  • 不利于代码维护
  • 不利于代码的复用

为了解决这些问题,JavaScript引入了原型链的概念。原型链允许对象继承其他对象的方法和属性。这样,就可以实现代码的复用,而且可以很方便地对代码进行维护。

JavaScript中的继承主要有以下几种方式:

  • 原型继承
  • 构造函数继承
  • 组合继承
  • 类继承

下面我们分别来介绍一下这几种继承方式。

原型继承

原型继承是一种简单而常用的继承方式。它通过对象的原型链来实现继承。每个对象都有一个原型对象,原型对象是对象的父对象。对象的属性和方法都存储在原型对象中。当对象访问一个属性或方法时,如果对象本身没有这个属性或方法,那么它就会去原型对象中查找。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

const person1 = new Person('John', 20);
person1.sayHello(); // Hello, my name is John.

在上面的例子中,Person函数的原型对象是Person.prototypeperson1对象是Person函数的实例,因此它的原型对象是Person.prototype。当person1对象调用sayHello方法时,它会先在自身查找这个方法,如果找不到,就会去原型对象Person.prototype中查找。

原型继承的优点是简单易用,而且可以实现代码的复用。它的缺点是,它不能继承对象的私有属性和方法。

构造函数继承

构造函数继承是另一种常用的继承方式。它通过构造函数来实现继承。子类的构造函数会调用父类的构造函数,这样子类就可以继承父类的属性和方法。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

function Student(name, age, major) {
  Person.call(this, name, age);
  this.major = major;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

const student1 = new Student('John', 20, 'Computer Science');
student1.sayHello(); // Hello, my name is John.

在上面的例子中,Student函数的构造函数会调用Person函数的构造函数,这样Student类就可以继承Person类的属性和方法。Student类的原型对象是Student.prototype,它是Person类的原型对象的子对象。

构造函数继承的优点是,它可以继承对象的私有属性和方法。它的缺点是,它需要显式地调用父类的构造函数,这可能会增加代码的复杂性。

组合继承

组合继承是原型继承和构造函数继承的结合。它通过组合使用原型继承和构造函数继承来实现继承。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

function Student(name, age, major) {
  Person.call(this, name, age);
  this.major = major;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.study = function() {
  console.log(`I am studying ${this.major}.`);
};

const student1 = new Student('John', 20, 'Computer Science');
student1.sayHello(); // Hello, my name is John.
student1.study(); // I am studying Computer Science.

在上面的例子中,Student函数的构造函数会调用Person函数的构造函数,这样Student类就可以继承Person类的属性和方法。Student类的原型对象是Student.prototype,它是Person类的原型对象的子对象。Student类还定义了一个新的方法study()

组合继承的优点是,它可以继承对象的私有属性和方法,而且它可以实现代码的复用。它的缺点是,它需要显式地调用父类的构造函数,这可能会增加代码的复杂性。

类继承

类继承是ES6中引入的一种新的继承方式。它使用class来定义类,然后使用extends关键字来继承类。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

class Student extends Person {
  constructor(name, age, major) {
    super(name, age);
    this.major = major;
  }

  study() {
    console.log(`I am studying ${this.major}.`);
  }
}

const student1 = new Student('John', 20, 'Computer Science');
student1.sayHello(); // Hello, my name is John.
student1.study(); // I am studying Computer Science.

在上面的例子中,Student类继承了Person类,因此它可以使用Person类的方法和属性。Student类还定义了一个新的方法study()

类继承的优点是,它简单易用,而且可以实现代码的复用。它的缺点是,它不支持多重继承。

继承方法的优缺点和使用场景

继承方法 优点 缺点 使用场景
原型继承 简单易用 不能继承对象的私有属性和方法 需要继承对象的所有属性和方法时
构造函数继承 可以继承对象的私有属性和方法 需要显式地调用父类的构造函数 需要继承对象的私有属性和方法时
组合继承 可以继承对象的私有属性和方法 需要显式地调用父类的构造函数 需要继承对象的私有属性和方法时
类继承 简单易用 不支持多重继承 需要继承对象的所有属性和方法时