【译】JavaScript 中的多态

1,257 阅读6分钟

原文:zellwk.com/blog/polymo…

很长时间以来,我认为“多态(Polymorphing)”是指将某种东西转变为🐑(因为魔兽)。羊的形象一直困扰我,使我难以确切地理解多态性。 (译者注:魔兽世界中,Polymorphing 是指将敌方单位变成一只羊的变形术。参考百度百科。中文读者应该不会有这个困扰)

今天,我想探究多态性实际上是什么。(滑稽的是:大多数有关 JavaScript 中的多态性的文章所涵盖的内容不到多态性实际内容的1/3)

什么是多态性?

Polymorphism 来自单词 Polymorph。

  • Poly:很多
  • Morph:从一种形式变成另一种

因此,多态性是采取多种形式的能力。

编程中有三种多态性:

  1. 特设多态性
  2. 参数多态性
  3. 子类型多态性

大多数有关面向对象编程和多态的文章仅解释了第三种类型。他们没有解释前两种。

特设(Adhoc)多态性

Adhoc 用于描述没有事先计划的事物的创建。换句话说,Adhoc 多态意味着在现场将某种东西从一种形式更改为另一种形式

Adhoc 多态性有多种形式:

  1. 运算符重载
  2. 函数重载
  3. 强制转换多态性

运算符重载

重载意味着能够做多件事。

例: JavaScript 中的+运算符可做很多事情。你可以使用它来对数字进行相加,也可以使用它来拼接字符串。

// Adding numbers
1 + 1 // Results in 2

// Adding Strings
'Hello' + ' ' + 'World' // Results in 'Hello World'

// Adding Numbers to Strings
1 + 'up' // Results in '1up'

结果的 type 会根据相加的内容而变化。

  • Number + Number => Number
  • Number + String => String

这个例子中的 + 操作符可以将值从一种类型(如 Number)转变成另一种类型(如 String)。

函数重载

在某些编程语言中,函数重载意味着创建两个(或多个)同名函数。每个函数根据给定的参数执行不同的操作。

Wikipedia 中有关在 C++ 中计算体积的示例

// Volume of a Cube.
int Volume(int s) {
  return s * s * s;
}

// Volume of a Cuboid.
long Volume(long l, int b, int h) {
  return l * b * h;
}

JavaScript 中的函数重载稍有不同,因为我们无法产生两个具有相同名称的不同函数。

我们使用一个函数,但是根据接收的参数改变结果

上面的示例可以用 JavaScript 重写如下:

function volumeCuboid (length, breadth, height) {
  return length * breadth * height
}

function volumeCube (length) {
  return volumeCuboid(length, length, length)
}


// Overloading happens here
function calculateVolume (...args) {
  if (args.length === 3) return volumeCuboid(...args)
  return volumeCube(args[0])
}

我们不需要依赖参数的数量。我们还可以根据每个参数的值改变结果。

例子:

我们可以有一个 createShape 函数,这个函数根据 shape 的值返回不同的对象。(工厂模式使用这种类型的多态性)。

function createShape (size, shape) {
  if (shape === 'triangle') return new Triangle(/* ... */)
  if (shape === 'rectangle') return new Rectangle(/* ... */)
  if (shape === 'square') return new Square(/* ... */)
}

(有趣的是:我从 Martin Fowler 的《重构:改进现有代码的设计》中了解了此版本的多态性。这使我对多态性更加好奇,最终使您阅读了这篇文章!)

如果我们进一步简化该理论,则所有 ifswitch 语句都会导致函数重载。

function createEmoji (emotion) {
  if (emotion === 'happy') return '😃'
  if (emotion === 'sad') return '😞'
  return 😑
}

强制转换多态性

JavaScript 有类型强制转换。它会在衡量时把值从一个类型转换为另一个类型。

例如,你可以在 if 语句中使用任何表达式。JavaScript 将表达式转换为 truefalse。如果表达式转换为 true,则表示该表达式为真。如果表达式转换为 false,则表示该表达式为假。

const string = 'hello'
if (string) {
  console.log(string)
}

另一个示例:你可以使用 == 对字符串和数字进行比较(尽管通常不建议这样做)。

22 == '22' // true

由于类型强制发生在现场临时发生的,因此它是 adhoc 多态性的一种形式。

变量重载?

我对这一点不太确定。

维基百科将多态定义为:

多态是为不同类型的实体提供单个接口,或者使用单个符号来表示不同类型

“用单个符号代表不同的类型”对我来说就像变量重载。(变量重载不是一个实际术语。这是我想出的东西)。

我们已经在 JavaScript 中重载了变量,因为每个变量都可以表示任何值。

// Variables in JavaScript can represent any value
const str = 'string'
const num = 123
const bool = true
const array = []
const obj = {}
const nah = null

参数多态

参数多态性是与参数相关的多态性……但这不是很有用,所以让我们描述一下它的含义。

参数多态性包括两个部分:

  1. 可以包含多种数据类型的数据
  2. 可以处理多种类型数据的函数

可以包含多种数据类型的数据

JavaScript 中的所有内容都是一个对象。因此对象是参数化的。可以将其转换为其他类型的数据。

对象还可以存储多种类型。不管存储什么值。

const object = {
  str: 'hello',
  num: 123,
  bool: true
}

数组也是参数化的。它使您可以存储多种类型的数据,而不必关心它们是什么。

const array = ['hello', 123, true]

可以处理多种类型数据的函数

可以处理多种类型数据的函数称为多态函数。它们不在乎会发生什么,它们会应用被告知要做的转换,然后吐出结果。

map 就是一个很好的例子。它接受一个数组,然后吐出另一个数组。中间发生了什么无关紧要。

const doubled = [1, 2, 3].map(num => num * 2)

你可以使用 map 将数字转换为字符串。

const toString = [1, 2, 3].map(num => `${num}`)

Object.assign 是另一个例子。它接收一个对象并吐出另一个对象,但是它并不关心每个对象内部的内容。

Object.assign({}, { property: 'value'})

子类型多态

子类型多态涉及 从父对象创建派生对象。可以称为包含多态(Inclusion Polymorphism),子类化(Subclassing)或继承(Inheritance)。(继承是一个很容易理解的词,我将在之后解释)。

然后,派生对象可以覆盖父对象的方法,并且该方法仍然有效。

示例:

假设您有一个带有 sayHi 方法的 Human 类:

class Human {
  constructor(name) {
    this.name = name
  }

  sayHi() {
    console.log(`Hi! My name is ${name}`)
  }
}

然后,从 Human 创建 DeveloperDesigner 子类。

class Developer extends Human {/* ... */}
class Designer extends Human {/* ... */}

我们的 DesignerDeveloper 会更多地谈论他们自己,因此我们可以覆盖 sayHi 方法。

class Developer extends Human () {
  sayHi() {
    console.log(`Hi! My name is ${name}. I am a developer.`)
  }
}

class Designer extends Human () {
  sayHi() {
    console.log(`Hi! My name is ${name}. I am a designer.`)
  }
}

现在您有三个不同的类。他们每个人都可以 sayHi。你可以正常使用 sayHi,它们都可以会生效,但是它们会产生不同的结果。

const zell = new Human('Zell')
const vincy = new Developer('Vincy')
const tim = new Designer('Tim')

zell.sayHi() // Hi! My name is Zell.
vincy.sayHi() // Hi! My name is Vincy. I am a developer.
tim.sayHi() // Hi! My name is Tim. I am a designer.

这就是了!

小结

有三种多态性:

  1. 特设多态性
  2. 参数多态性
  3. 子类型多态性

很有可能,您已经在不了解多态的情况下使用它😉。我希望这可以为你描述清楚多态!