面试题: 对象继承的方式有哪些

在 JavaScript 中,对象继承可以通过多种方式实现。每种方法都有其特点和适用场景。以下是几种常见的对象继承方式:

1. 原型链继承(Prototype Chain Inheritance)

这是最基础的对象继承方式,利用了 JavaScript 的原型机制。每个对象都有一个内部属性 [[Prototype]],它指向另一个对象,即该对象的原型。当尝试访问一个对象的属性时,如果对象本身没有这个属性,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(通常是 Object.prototype)。

javascript 复制代码
function Parent() {
  this.name = 'parent';
}

Parent.prototype.sayHello = function() {
  console.log('Hello from ' + this.name);
};

function Child() {}

// 设置 Child 的 prototype 为 Parent 的实例
Child.prototype = new Parent();
Child.prototype.constructor = Child; // 修复构造函数指针

const child = new Child();
child.sayHello(); // 输出: Hello from parent

缺点:所有子类实例共享同一个父类实例的属性,可能会导致意外的状态共享问题。

2. 构造函数继承(Constructor Function Inheritance)

通过使用 callapply 方法,可以在子类构造函数中调用父类构造函数,从而将父类的属性复制到子类实例上。这种方式可以避免原型链继承中的状态共享问题。

javascript 复制代码
function Parent(name) {
  this.name = name;
}

Parent.prototype.sayHello = function() {
  console.log('Hello from ' + this.name);
};

function Child(name, age) {
  Parent.call(this, name); // 调用父类构造函数
  this.age = age;
}

const child = new Child('child', 10);
console.log(child.name); // 输出: child
console.log(child.age);  // 输出: 10

缺点:子类不能继承父类的原型方法,需要额外的方法来继承这些方法。

3. 组合继承(Combination Inheritance)

组合继承结合了原型链继承和构造函数继承的优点,既可以通过构造函数继承父类的属性,又可以通过原型链继承父类的方法。

javascript 复制代码
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayHello = function() {
  console.log('Hello from ' + this.name);
};

function Child(name, age) {
  Parent.call(this, name); // 第一次调用 Parent
  this.age = age;
}

Child.prototype = new Parent(); // 第二次调用 Parent
Child.prototype.constructor = Child;

const child1 = new Child('child1', 10);
const child2 = new Child('child2', 20);

child1.colors.push('green');
console.log(child1.colors); // 输出: ['red', 'blue', 'green']
console.log(child2.colors); // 输出: ['red', 'blue']

缺点:两次调用父类构造函数,第一次是在创建子类实例时,第二次是在设置子类原型时,这可能会造成不必要的开销。

4. 原型式继承(Prototypal Inheritance)

这种继承方式不依赖于构造函数,而是直接基于对象创建新的对象。通常使用 Object.create() 方法来实现。

javascript 复制代码
const parent = {
  name: 'parent',
  sayHello: function() {
    console.log('Hello from ' + this.name);
  }
};

const child = Object.create(parent);
child.name = 'child';

child.sayHello(); // 输出: Hello from child

优点:简单明了,适合不需要复杂构造函数的情况。

5. 寄生式继承(Parasitic Inheritance)

寄生式继承是原型式继承的增强版,它在创建新对象的基础上,进一步增强对象的功能,然后再返回这个对象。

javascript 复制代码
function createChild(parent) {
  const child = Object.create(parent); // 使用原型式继承
  child.sayHi = function() {
    console.log('Hi from ' + this.name);
  };
  return child;
}

const parent = {
  name: 'parent',
  sayHello: function() {
    console.log('Hello from ' + this.name);
  }
};

const child = createChild(parent);
child.name = 'child';
child.sayHi(); // 输出: Hi from child

优点:可以自由地添加或修改对象的功能。

6. 寄生组合式继承(Parasitic Combination Inheritance)

寄生组合式继承是组合继承的优化版本,它只调用一次父类构造函数,避免了组合继承中的效率问题。这是目前最常用的继承模式之一。

javascript 复制代码
function inheritPrototype(child, parent) {
  const prototype = Object.create(parent.prototype); // 创建父类原型的副本
  prototype.constructor = child; // 修正构造函数指针
  child.prototype = prototype;   // 将副本赋值给子类的原型
}

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayHello = function() {
  console.log('Hello from ' + this.name);
};

function Child(name, age) {
  Parent.call(this, name); // 只调用一次 Parent
  this.age = age;
}

inheritPrototype(Child, Parent);

const child1 = new Child('child1', 10);
const child2 = new Child('child2', 20);

child1.colors.push('green');
console.log(child1.colors); // 输出: ['red', 'blue', 'green']
console.log(child2.colors); // 输出: ['red', 'blue']

优点:只调用一次父类构造函数,避免了重复调用的问题,同时保持了组合继承的优点。

7. 类(Class)继承(ES6+)

ES6 引入了 class 语法糖,使得继承更加简洁和直观。实际上,class 仍然是基于原型的,但它提供了一种更接近传统面向对象语言的语法。

javascript 复制代码
class Parent {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log('Hello from ' + this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类构造函数
    this.age = age;
  }

  sayAge() {
    console.log('I am ' + this.age + ' years old.');
  }
}

const child = new Child('child', 10);
child.sayHello(); // 输出: Hello from child
child.sayAge();   // 输出: I am 10 years old.

优点:语法简洁,易于理解和使用,特别适合面向对象编程。

总结

  • 原型链继承 是最基础的继承方式,但存在状态共享的问题。
  • 构造函数继承 解决了状态共享问题,但无法继承原型方法。
  • 组合继承 结合了两者的优势,但有重复调用父类构造函数的缺点。
  • 原型式继承寄生式继承 更加灵活,适用于不需要复杂构造函数的场景。
  • 寄生组合式继承 是组合继承的优化版本,解决了重复调用的问题,是最推荐的继承方式之一。
  • 类继承 提供了更简洁的语法,适合现代 JavaScript 开发。

选择哪种继承方式取决于具体的需求和项目背景。对于新项目,特别是使用 ES6+ 的项目,推荐使用 class 语法糖来实现继承,因为它不仅语法简洁,而且社区支持广泛。

相关推荐
翻滚吧键盘17 分钟前
vue绑定一个返回对象的计算属性
前端·javascript·vue.js
苦夏木禾21 分钟前
js请求避免缓存的三种方式
开发语言·javascript·缓存
超级土豆粉29 分钟前
Turndown.js: 优雅地将 HTML 转换为 Markdown
开发语言·javascript·html
乆夨(jiuze)1 小时前
记录H5内嵌到flutter App的一个问题,引发后面使用fastClick,引发后面input输入框单击无效问题。。。
前端·javascript·vue.js
小彭努力中1 小时前
141.在 Vue 3 中使用 OpenLayers Link 交互:把地图中心点 / 缩放级别 / 旋转角度实时写进 URL,并同步解析显示
前端·javascript·vue.js·交互
wei_shuo2 小时前
飞算 JavaAI 开发助手:深度学习驱动下的 Java 全链路智能开发新范式
java·开发语言·飞算javaai
熊猫钓鱼>_>2 小时前
用Python解锁图像处理之力:从基础到智能应用的深度探索
开发语言·图像处理·python
小飞悟2 小时前
前端高手才知道的秘密:Blob 居然这么强大!
前端·javascript·html
code_YuJun2 小时前
Promise 基础使用
前端·javascript·promise
GO兔2 小时前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go