MST

星途 面试题库

面试题:JavaScript 原型链属性查找机制与 ES6 类继承的深入探讨

ES6 引入了类继承语法,它在底层依然基于原型链实现属性查找。请深入分析 ES6 类继承在原型链属性查找方面与传统构造函数继承方式的异同点,包括但不限于语法糖下实际的原型链构建、属性查找优先级以及在多重继承场景下的表现,并举例说明在复杂业务场景中如何利用这些特性进行更优雅的代码设计。
19.4万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

相同点

  1. 基于原型链实现:ES6 类继承和传统构造函数继承本质上都是基于原型链来实现属性查找。无论是 class 语法还是构造函数方式,当访问一个对象的属性时,如果对象自身没有该属性,都会沿着原型链向上查找,直到找到该属性或到达原型链顶端(null)。
  2. 属性查找优先级:都遵循先在实例自身查找属性,若找不到则在原型对象上查找的原则。例如:
// 传统构造函数方式
function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(this.name +'makes a sound.');
};
const dog = new Animal('Buddy');
dog.speak(); // 先在 dog 实例找 speak,找不到去 Animal.prototype 找

// ES6 类方式
class Animal {
    constructor(name) {
        this.name = name;
    }
    speak() {
        console.log(this.name +'makes a sound.');
    }
}
const dog = new Animal('Buddy');
dog.speak(); // 同样先在 dog 实例找 speak,找不到去 Animal.prototype 找

不同点

  1. 语法糖下实际的原型链构建
    • 传统构造函数继承:通过 callapply 方法在子构造函数中调用父构造函数来实现属性继承,然后通过修改子构造函数的 prototype 为父构造函数的实例来实现原型链继承。例如:
function Parent(name) {
    this.name = name;
}
Parent.prototype.sayName = function() {
    console.log('Parent name:'+ this.name);
};
function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
- **ES6 类继承**:使用 `extends` 关键字简洁地实现继承。内部自动处理了原型链的构建,包括设置子构造函数的 `prototype` 为父构造函数 `prototype` 的一个副本,并修正 `constructor` 指向子构造函数。例如:
class Parent {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log('Parent name:'+ this.name);
    }
}
class Child extends Parent {
    constructor(name, age) {
        super(name);
        this.age = age;
    }
}
  1. 多重继承场景下的表现
    • 传统构造函数继承:实现多重继承比较复杂,通常需要借助一些库(如 mixin 模式),手动将多个对象的方法和属性混合到一个构造函数的原型上。例如:
function mixin(target, source) {
    Object.keys(source.prototype).forEach(key => {
        if (!target.prototype[key]) {
            target.prototype[key] = source.prototype[key];
        }
    });
    return target;
}
function A() {}
A.prototype.aMethod = function() {
    console.log('a method');
};
function B() {}
B.prototype.bMethod = function() {
    console.log('b method');
};
function C() {}
mixin(C, A);
mixin(C, B);
const c = new C();
c.aMethod();
c.bMethod();
- **ES6 类继承**:原生不支持多重继承,只能继承自一个父类。如果需要类似多重继承的效果,同样可以使用 `mixin` 模式,但相比传统构造函数方式,由于 `class` 语法的限制,实现起来可能需要更多的技巧。例如:
function mixin(...mixins) {
    return function(Base) {
        class Mixin extends Base {
            constructor(...args) {
                super(...args);
                mixins.forEach(mixin => {
                    Object.assign(this, new mixin());
                });
            }
        }
        return Mixin;
    };
}
class A {
    aMethod() {
        console.log('a method');
    }
}
class B {
    bMethod() {
        console.log('b method');
    }
}
const C = mixin(A, B)(class {});
const c = new C();
c.aMethod();
c.bMethod();

在复杂业务场景中利用这些特性进行优雅代码设计

  1. 代码复用:在一个电商系统中,有 Product 类作为基础类,包含产品的基本属性和方法,如 namepricegetDetails 方法。BookElectronics 类继承自 Product 类,复用 Product 类的属性和方法,同时添加各自特有的属性和方法。
class Product {
    constructor(name, price) {
        this.name = name;
        this.price = price;
    }
    getDetails() {
        return `Name: ${this.name}, Price: ${this.price}`;
    }
}
class Book extends Product {
    constructor(name, price, author) {
        super(name, price);
        this.author = author;
    }
    getDetails() {
        return `${super.getDetails()}, Author: ${this.author}`;
    }
}
class Electronics extends Product {
    constructor(name, price, brand) {
        super(name, price);
        this.brand = brand;
    }
    getDetails() {
        return `${super.getDetails()}, Brand: ${this.brand}`;
    }
}
  1. 多态性:在图形绘制系统中,有 Shape 类作为基类,包含 draw 方法。CircleRectangle 类继承自 Shape 类,各自重写 draw 方法以实现不同的绘制逻辑。
class Shape {
    draw() {
        console.log('Drawing a shape');
    }
}
class Circle extends Shape {
    draw() {
        console.log('Drawing a circle');
    }
}
class Rectangle extends Shape {
    draw() {
        console.log('Drawing a rectangle');
    }
}
const shapes = [new Circle(), new Rectangle()];
shapes.forEach(shape => shape.draw());
  1. 利用原型链属性查找优化内存:如果有大量相似对象,它们共享原型上的方法,通过原型链查找属性,可以减少内存占用。例如在游戏开发中,大量的游戏角色对象继承自一个基类,共享一些通用的行为方法(如移动、攻击等),这些方法定义在原型上,每个角色实例只保存自身特有的属性(如角色名称、等级等)。