JavaScript 中的串联继承与类继承

Fad*_*adi 3 javascript oop inheritance factory composition

当我一开始看到连接继承时,它对我来说就像一个组合,但人们一直将其命名为继承。然而,类使用原型来创建将对象连接在一起的原型链。现在的问题是,如果串联继承和类继承都做同样的事情,该使用哪一个?
这是两种场景串联继承的示例

function Person(name, address) {
 const _name = name
 const _address = address
 const toString = () => `name: ${this.name}, address: ${this.address}`
 return {
   _name,
   _address,
   toString
 }
}


function Employee(name, address, salary) {
 const getAnnualSalary = () => 12 * salary
 return Object.assign({ getAnnualSalary }, Person(name, address))
}



Run Code Online (Sandbox Code Playgroud)

类继承


class Person {
  constructor(name, address) {
    this.name = name
    this.address = address
  }
  toString() { return `name: ${this.name}, address: ${this.address}` }
}


class Employee extends Person {
  constructor(name, address, salary) {
    super(name, address)
    this.salary = salary
  }

  getAnnualSalary() { return 12 * this.salary }
}
Run Code Online (Sandbox Code Playgroud)

Ben*_*Ben 5

串联继承和原型继承(在 JS 中,有时使用关键字实现class)是两种不同的委托方法。委托是一种机制,对象可以通过该机制从其他对象而不是从类(在 Java 意义上)定义获取其部分或全部状态和行为。

\n

在 JS 中,“继承”这个词与通过原型继承委托给祖先对象有很强的关联,但(令人惊讶的是)根据一些人的说法,情况并非总是如此

\n
\n

串联继承是将一个或多个源对象的属性组合成新的目标对象的过程。不管你相信与否,它是 JavaScript 中最常用的继承形式。

\n
\n

还:

\n
\n

在 JS 中,连接继承的本质往往被通用名称 \xe2\x80\x9cmixins\xe2\x80\x9d 所掩盖。令人困惑的是, \xe2\x80\x9cmixins\xe2\x80\x9d 在其他语言中甚至在某些 JavaScript 库中具有其他含义。它还具有调用 \xe2\x80\x9cmixins\xe2\x80\x9d 会令人困惑的用途。由于这些原因,我更喜欢更精确的术语\xe2\x80\x9c串联继承\xe2\x80\x9d。

\n
\n

请注意,尽管 JavaScript 包含“类语法”(例如class foo extends bar {}),但它没有通常意义上的“经典”或基于类的继承。在 JavaScript 中,使用类语法的继承总是通过原型继承来实现。因此,在 JavaScript 中,基于类的继承几乎完全是对原始原型继承模型的语法糖,自从 Brendan Eich 推出该语言的第一个十天版本以来,该模型一直存在于 JavaScript 中。

\n

“组合”是一个重载的术语。对象组合通常涉及将一个对象委托给其中包含的另一个对象的功能或状态。从技术上讲,它具体意味着子对象的生命周期与复合的生命周期相关,但是,根据我的经验,“组合”通常用于表示“聚合”,即组合,但子对象的生命周期是不受复合体控制,它们通常通过构造函数注入。另一方面,函数组合是一种组合编程元素的模式,其中函数调用嵌套在形式中f(g(x))

\n

您可以通过遵循对象组合模式来实现串联继承,将对象提供给函数以串联到复合对象上。

\n

在以下示例中, 的实例p将包括作为 提供的对象上存在的功能ancestor

\n
function createP(ancestor) {\n  const p = { \n    ...ancestor, \n    bar() {} \n  }\n  return p\n}\nconst o = { foo() {} }\nconst p = createP(o) // `p` now has both methods `foo` and `bar`\n
Run Code Online (Sandbox Code Playgroud)\n

对于 JS 中的原型继承,有一个固定的、语言支持的机制,用于通过原型链动态查找功能。

\n

在原型继承中,继承的功能位于原型链上某个单独的对象上。这种间接性赋予了这种继承风格不同的非功能特征。

\n

例如:

\n
    \n
  • 与通过连接继承相比,通过原型继承包含哪些功能可以说不太清楚,因为功能可以存在于(可能很长)原型链上的任何位置。此外,在对象创建后,可以在原型链中添加和删除功能。
  • \n
  • 对于 API,原型继承对于保留子对象的功能非常有用,使它们看起来简单,同时使它们能够以面向对象的方式方便地公开位于原型链上的功能。例如。您可以Array#splice直接调用您创建的每个数组:[].splice(...),即使该splice函数位于其他位置(位于Array.prototype)。
  • \n
  • 在原型继承中,祖先对象上的方法需要以这样的方式编写:它们的目标(即this)可以是另一个对象。this可以说,在串联继承中,不再强调使用。
  • \n
  • 在原型继承中,继承者和祖先之间存在持续的隐式耦合。这可能是错误的来源,并使代码更难以推理。
  • \n
  • 更一般地,在串联继承中,内置的 JS 原型继承机制(包括newthisextendssuper、原型链等)不再被强调。Douglas Crockford 推广了一种观点,即 JavaScript 语言的这一部分变得复杂,而不是简化,因此应该尽可能避免。串联继承提供了一种替代的继承机制。
  • \n
  • 串联继承绕过了原型继承的一些本体论约束。原型链接暗示着“is-a”关系,由此在串联继承中不暗示本体论关系。您可以将任意数量的对象混合在一起,以获得所需的功能(仅此而已)。
  • \n
\n

在两种方法之间进行选择取决于主观偏好和风格。两种方法都有其用处。

\n