MST

星途 面试题库

面试题:JavaScript 继承中的寄生组合式继承原理及优化

详细解释 JavaScript 寄生组合式继承的原理,并用代码实现一个完整的寄生组合式继承示例。同时思考并说明在现代 JavaScript 开发环境下,寄生组合式继承还有哪些可优化的方向,以及优化的思路和方法。
17.3万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

寄生组合式继承原理

  1. 原型链继承核心:通过将子类的原型指向父类的实例,这样子类就可以访问到父类原型上的属性和方法,从而实现继承。但传统的原型链继承存在问题,比如在实例化子类时,会把父类实例的属性和方法也复制一份到子类实例上,造成不必要的内存浪费。
  2. 借用构造函数:为了解决上述问题,在子类构造函数内部调用父类构造函数,通过 callapply 方法,将父类的属性和方法绑定到子类实例上。这样,每个子类实例都有自己独立的属性副本,不会相互干扰。但这种方式只能继承父类实例的属性和方法,无法继承父类原型上的属性和方法。
  3. 寄生组合式继承优化:结合了原型链继承和借用构造函数的优点,通过创建一个临时构造函数,将其原型指向父类的原型,然后让子类的原型等于这个临时构造函数的实例。这样既避免了在子类实例上重复创建父类实例的属性和方法,又能让子类访问到父类原型上的属性和方法。

代码实现

// 父类
function Parent(name) {
    this.name = name;
    this.sayName = function() {
        console.log('My name is', this.name);
    };
}

Parent.prototype.sayHello = function() {
    console.log('Hello');
};

// 子类
function Child(name, age) {
    // 借用构造函数继承父类实例属性
    Parent.call(this, name);
    this.age = age;
}

// 创建一个临时构造函数
function Temp() {}
// 将临时构造函数的原型指向父类的原型
Temp.prototype = Parent.prototype;
// 创建子类的原型
Child.prototype = new Temp();
// 修复子类原型的 constructor 指向
Child.prototype.constructor = Child;

// 测试
let child = new Child('John', 25);
child.sayName(); // 输出: My name is John
child.sayHello(); // 输出: Hello
console.log(child.age); // 输出: 25

可优化方向、思路及方法

  1. 使用 Object.create 简化步骤:现代 JavaScript 中,可以使用 Object.create 方法来简化寄生组合式继承的实现。Object.create 方法会创建一个新对象,其原型为传入的对象。
// 父类
function Parent(name) {
    this.name = name;
    this.sayName = function() {
        console.log('My name is', this.name);
    };
}

Parent.prototype.sayHello = function() {
    console.log('Hello');
};

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

// 使用 Object.create 优化
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// 测试
let child = new Child('John', 25);
child.sayName(); 
child.sayHello(); 
console.log(child.age); 
  1. 使用 class 语法糖:ES6 引入的 class 语法糖,本质上也是基于原型链的继承,但语法更简洁,代码可读性更高。
class Parent {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log('My name is', this.name);
    }
    sayHello() {
        console.log('Hello');
    }
}

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

// 测试
let child = new Child('John', 25);
child.sayName(); 
child.sayHello(); 
console.log(child.age); 

使用 class 语法糖不仅简化了继承的实现,还在语义上更加清晰,更符合面向对象编程的习惯。同时,class 语法在编译时还可以进行一些优化,提升性能。