JS面试题:请你说说JS中的继承

185 阅读4分钟

在JavaScript中的继承指的是让一个子类可以访问父类的属性和方法。JS中的继承是通过原型链来实现的,实例对象的隐式原型( __ proto __ )会指向构造函数的显示原型(prototype),因此当要查找一个对象的属性或方法时,且此时对象本身并没有定义这个属性或方法,V8引擎就会沿着原型链向上查找直到找到对应的属性或方法,或者查找到null为止。接下来就说说JS中的继承方法。

继承有哪些方式

原型链继承
   function Car(color, money) {
    this.color = color
    this.money = money 
  }
  
  BMW.prototype = new Car('black',30)  //将子类的原型指向父类的实例
  function BMW() {
  }
  
  let BMW3 = new BMW()
  
  console.log(BMW3.color);//输出 black
  console.log(BMW3.money);//输出 30

值得注意的是,原型链继承无法给父类灵活传参且多个实例对象共用了同一个原型对象是会存在属性相互影响。

构造函数继承
function Car() {
  this.color = 'black'
  this.money = '50'
}
function BMW() {
  Car.call(this)
}

let BMW5 = new BMW()

console.log(BMW5.color);//输出 black
console.log(BMW5.money);//输出 50

值得注意的是,构造函数继承只能继承父类的实例属性和方法,无法继承父类的原型属性和方法。

组合继承(也叫经典继承)
function Car() {
  this.color = 'red'
  this.money = 55 
}

BMW.prototype = new Car() //将子类的原型指向父类的实例
function BMW() {
  Car.call(this)// 继承父类的实例属性
}

let BMWM2 = new BMW()


console.log(BMWM2.color);//输出red
console.log(BMWM2.money);//输出55

值得注意的是,这种方法存在多次父类函数的调用会造成多的性能开销。

原型式继承
let Car = {
  name: 'BMWi7',
  money: 150,
  abc:['a','b','c']
}
let car1 = Object.create(Car)//使用 Object.create()方法创建一个基于Car的新对象
let car2 = Object.create(Car)
car1.money = 100 // 修改car1对象的属性
car1.abc.push('d')//修改car1对象的属性(此时该属性为引用类型数组)

console.log(car1.money);//输出100
console.log(car2.money);//输出150
console.log(car1.abc);//输出['a','b','c','d']
console.log(car2.abc);//输出['a','b','c','d']

值得注意的是,原型式继承是浅拷贝,当修改子对象的属性时,不会影响到原型对象或其他子对象,但如果属性是引用类型(如数组),则会影响到其他子对象,即存在共享属性的问题。并且子类无法添加默认属性。

寄生式继承
  let Car ={
    color : 'black',
    money : '80'
  }
  function cloneBMW(origin) {
    let BMW = Object.create(origin)//通过Object.create()方法创建一个新对象BMW,并将origin对象作为BMW的原型。
    BMW.number = function() {  //在BMW对象上添加了一个新的方法number,该方法返回1000。
      return 1000
    }
    return BMW
  }
  
  let BMWX5 = cloneBMW(Car)//调用cloneBMW函数,传入Car对象作为参数生成了一个新对象BMWX5。

  console.log(BMWX5.money);//输出80
  console.log(BMWX5.number());//输出1000
  

值得注意的是,寄生式继承可以添加额外的方法或属性,但它也存在共享属性的问题。

寄生组合式继承
Car.prototype.number = 1000  //在Car的原型上加一个number属性,值为1000
function Car( money) {
  this.color = 'black'
  this.money = money
}
function BMW(money) {
  Car.call(this,money) //继承Car的color和money属性
}

BMW.prototype = Object.create(Car.prototype)//建立了BMW对Car的原型链继承关系。
BMW.prototype.constructor = BMW//修复了constructor的指向,确保正确指向BMW构造函数。
let BMW5 = new BMW(50)

console.log(BMW5.number);//输出 1000
console.log(BMW5.money);//输出 50

寄生组合式继承避免了组合继承中多次调用父类构造函数带来的性能问题,并且正确地设置了子类原型的constructor指向。

class继承
class Parent {
    constructor(name) {
      this.name = name;
    }
    getName() {
      return this.name;
    }
  }
  
  class Child extends Parent {
    constructor(type, name) {
      super(name)  
      this.type = type;
    }
  }
  
  let c = new Child('child', 'Tom')
  
  console.log(c.name);//输出Tom
  

在ES6中,使用class进行继承时,可以通过 extends关键字实现子类对父类的继承。class继承易于理解和维护,因此在实际开发中经常被使用。

总结

JS中的继承是通过原型链来实现的,所以要理解JS中的继承我们还需要明白原型链的作用和原理。

以上JS的继承方法适用不同的场景,在实际开发过程中需要根据具体的情况来进行选择,当然class继承其较高的可维护性在ES6中是推荐使用class继承的。

JS的继承属于面试中的常考题,学习和理解它可以帮助我们在面试中更加的游刃有余。