前端面试|JS的继承方法

  1. 原型链继承
    原型链继承涉及构造函数、原型和实例。
function Parent() { this.name = 'parent1'; this.play = [1, 2, 3] } function Child() { this.type = 'child2'; } Child.prototype = new Parent() console.log(new Child())

创建子类实例时,不能向父构造函数传递参数,导致继承的父类属性没有值。如果包含引用类型,会被所有的实例对象共享,容易造成修改混乱
const s1 = new Child() const s2 = new Child() s1.play.push(4) console.log(s1.play, s2.play)// [1, 2, 3, 4]

  1. 借助构造函数
    使用call或apply继承。可以解决向父构造函数传参的问题,可以继承父类的实例属性和方法,但是不能获取父构造函数原型上的属性和方法
function Parent(){ this.name = "parent" }Parent.prototype.getName = function(){ return this.name }function Child(){ Parent.call(this) this.type = "Child" }let child = new Child()console.log(child) console.log(child.getName())

  1. 组合继承
    使用call或apply和原型继承。父类构造函数的属性和方法继承到了子类构造函数的实例中,并且继承了父类构造函数原型对象上的成员,但会给子类添加很多不必要的属性和方法
function Parent() { this.name = "parent" this.play = [1, 2, 3] }Parent.prototype.getName = function () { return this.name }function Child() { // 第一次调用父类 Parent.call(this) this.type = "child" }// 第二次调用父类 Child.prototype = new Parent() Child.prototype.constructor = Child const child1 = new Child() const child2 = new Child() child1.play.push(4)console.log(child1) console.log(child2) console.log(child1.getName())

组合继承解决了构造函数继承和原型链继承的缺点,但是却调用了两次父类,造成了额外的性能开销
  1. 寄生组合式继承
    寄生式组合继承的方式是使用父类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性
function clone (parent, child) { // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程 child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; }function Parent6() { this.name = 'parent6'; this.play = [1, 2, 3]; } Parent6.prototype.getName = function () { return this.name; } function Child6() { Parent6.call(this); this.friends = 'child5'; }clone(Parent6, Child6); Child6.prototype.getFriends = function () { return this.friends; }let person6 = new Child6(); console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6} console.log(person6.getName()); // parent6 console.log(person6.getFriends()); // child5

  1. es6 extends
    es6的extends原理就是基于寄生组合继承实现的。
  2. 【前端面试|JS的继承方法】原型继承
    原型继承就是基于已有的对象来创建新的对象。实现原理就是向函数中传入一个对象,然后返回一个以这个对象为原型的对象。缺点与原型链继承相同:只能继承父构造函数的原型对象上的成员,不能继承父构造函数的实例对象的成员
使用Object.create方法实现继承。
let parent4 = { name: "parent4", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } }; let person4 = Object.create(parent4); person4.name = "tom"; person4.friends.push("jerry"); let person5 = Object.create(parent4); person5.friends.push("lucy"); console.log(person4.name); // tom console.log(person4.name === person4.getName()); // true console.log(person5.name); // parent4 console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"] console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

因为Object.create实现的是浅拷贝,所以对于引用类型,存在修改数据混乱的问题。
  1. 寄生式继承
    在原型继承的基础上增加一些方法。无法实现函数的复用
let parent5 = { name: "parent5", friends: ["p1", "p2", "p3"], getName: function() { return this.name; } }; function clone(original) { let clone = Object.create(original); clone.getFriends = function() { return this.friends; }; return clone; }let person5 = clone(parent5); console.log(person5.getName()); // parent5 console.log(person5.getFriends()); // ["p1", "p2", "p3"]

ES5继承和ES6继承的区别
ES5的继承是通过prototype和构造函数机制来实现。ES5的继承是先创建子类的实例对象,然后再将父类的方法添加到this上
function parent(a, b) { this.a = a; this.b = b; }function child(c) { this.c = c }; parent.call(child, 1, 2) // 使用call绑定其实是实现了如下代码: // child.prototype = new Parent(1, 2) console.log(child);

ES6的继承机制是先创建父类的实例对象this,然后再调用子类的构造函数修改this
class Parent { constructor(a, b) { this.a = a this.b = b } }class child extends Parent { constructor(a, b, c) { // super(a, b) this.c = c } }const c = new child(1, 2, 3) console.log(c);

可以看到:ES5的继承原理是先创建子类元素child的实例对象,然后再把父类元素parent的原型对象中的属性赋值给子类元素child的实例对象里面,从而实现继承;ES6引入了class的概念,父类首先实例化出来,再修改子类构造函数中的this实现继承

    推荐阅读