js的继承

200 阅读5分钟

前言

在许多语言例如c++、Java中或多或少都存在着一种子承父业的关系,就是子类可以访问到父类的方法或者属性,这种机制我们在编程语言中叫继承,在js中也不例外,接下来我们来看看js中几种不同的继承方式。

1. 原型链继承

在js中存在着一套机制,当我们在一个对象中访问某个方法或者属性时,如果查找不到的话,v8引擎会顺着原型链往上一直查找直到找到或者找到null为止,这种机制我们称之为原型链

tips:js中的类就是js中的构造函数,只不过类的功能比构造函数更强大

那么我们顺着原型链这种思路,想要实现继承,那么就要让子类可以访问到父类中的属性和方法,根据原型链的机制,那么我们只需要让子类的原型等于父类的实例对象即可,下面给大家展示一段js代码:

核心代码: Child.prototype = new Parent();

Parent.prototype.say = function () {
  console.log('hello');
};

function Parent() {
  this.name = 'parent';
  this.age = 50;
}

Child.prototype = new Parent(); // {name: 'parent', age: 50},原型链继承

function Child() {
  this.name = 'child';
}

const c = new Child();
console.log(c.name); // child
console.log(c.age); // 50
c.say() // hello

缺点:子类无法给父类传参

2. 借用构造函数继承

在js中存在一个call()方法,这个方法可以强行改变一个对象中this的指向并且可以传入参数,然后执行调用call的函数。

根据call这个特点,我们只需要在子类中让父类调用call从而让父类上面的this强行指向子类的this,那么子类就能访问到父类的方法和属性了,同时也能向父类传递参数,接下来我们来用代码展示一下:

核心代码: Parent.call(this, age);

Parent.prototype.say = function () {
  console.log('hello');
};

function Parent(age) {
  this.name = 'parent';
  this.age = age;
}

function Child(name, age) {
  Parent.call(this, age); // 借用call实现构造函数继承
  this.name = name;
}

const c = new Child('child', 50);
console.log(c.name); // child
console.log(c.age); // 50

虽然这个方法实现了子类访问父类,并且能够向父类传递参数,但是子类并不能访问到父类原型上面的方法,按照继承的定义,子类也能访问到父类的方法,下面我们来调用一下父类上面的say()看看是否可以访问。

Parent.prototype.say = function () {
  console.log('hello');
};

function Parent(age) {
  this.name = 'parent';
  this.age = age;
}

function Child(name, age) {
  Parent.call(this, age);
  this.name = name;
}

const c = new Child('child', 50);
console.log(c.name); 
console.log(c.age); 
c.say()
image.png

缺点:无法继承父类的原型

3. 组合继承

在看过了上面两种方法之后,可能就会有同学说上面这两个方法怎么阴阳互补啊,这两凑合凑合不就可以完整的继承了吗?说的没错,现在的这种组合继承就是将两者结合起来从而实现继承的效果,下面直接展示代码:

核心代码:

Child.prototype = new Parent();

Parent.call(this, age);

Parent.prototype.say = function () {
  console.log('hello');
};

function Parent(age) {
  this.name = 'parent';
  this.age = age;
}

Child.prototype = new Parent();

function Child(name, age) {
  Parent.call(this, age);
  this.name = name;
}

const c = new Child('child', 50);
console.log(c.name); // child
console.log(c.age); // 50
c.say() // hello
image.png

4. 原型式继承

在之前的文章中我们了解到并不是所有的对象都拥有原型,想要实现这个效果得使用Object.create()

Object.create()是 JavaScript 中创建对象的一种方法,它允许你创建一个新对象,并将这个新对象隐式原型(__proto__)设置为传入的对象

这个方法可以创建一个新对象,并且这个新对象会继承其传入对象的所有属性和方法。然而,它并不会复制这些属性,而是通过原型链访问它们,下面我们在浏览器打印测试一下:

image.png

根据这个方法的效果,我们可以用其来实现继承的效果,只需要将子赋值为Object.create(父),下面我们直接来看代码展示:

 const obj = {
   name: '张三',
   age: 40
 } 
 let newObj = Object.create(obj)
 console.log(newObj.name);

5. 寄生组合继承

看到这种继承方式的名字,想必大家对这种方法可能有种猜测,就是需要使用组合继承这种方式,但是也不完全是。

在这种方法中,我们为了让子参数能进行传递,需要用到借用构造函数继承这种方式来实现,也就是Parent.call(this, age);,接着我们就需要让子类继承父类原型上面的方法。

为了实现这个我们只需要让子类的显式原型的隐式原型指向父类的显式原型,这样子类就能通过原型链访问到父类显式原型上的方法。为了实现这个方法我们就需要用到上文所说的Object.create()这个方法,这个方法能让新创建的对象的隐式原型等于传入的对象,根据这一特点,我们只需要让Child.prototype = Object.create(Parent.prototype);即可,接下来为大家展示完整代码:

Parent.prototype.say = function () {
  console.log('hello');
};

function Parent(age) {
  this.name = 'parent';
  this.age = age;
}

Child.prototype = Object.create(Parent.prototype); // 让子类访问父类显式原型上的方法
function Child(name, age) {
  Parent.call(this, age);
  this.name = name;
}

const c = new Child('child', 50);
console.log(c.name); 
console.log(c.age); 
c.say() 

6. class继承

这个方法是用js中的类来实现的,想必学习过java的同学应该并不陌生,可以说基本一模一样,这种方法的基础语法就是 Class 子类 extends 父类 {},并且如果要向父类传递参数的话,只需要使用super()方法,将要传递的参数放入这个函数的参数中即可,下面直接展示代码:

class Parent {
  constructor(name, age) { // 构造函数
    this.name = name
    this.age = age
  }
  say() {
    console.log('hello');
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name, age)
    this.sex = 'boy'
  }
}

const c = new Child('张三', 18)
console.log(c);
c.say()

总结

  1. 原型链继承:子类无法给父类传参
  2. 借用构造函数继承:无法继承父类的原型
  3. 组合继承:父类构造函数执行了两次
  4. 原型式继承:
  5. 寄生组合继承:
  6. class继承:extends + super