ECMAScript 2018 中的原型链改变技巧及利弊分析

阅读时长 10 分钟读完

前言

JavaScript 程序员一定都知道函数是作为一种对象来使用的,也就是说,函数可以像其他对象一样被拓展和修改。其中最重要的一个特性就是原型链(prototype chain)。

在 ECMAScript 2018 中,原型链相关的概念被重新整理和解释,它们将会影响我们如何编写代码、如何设计类库,以及其他一些重要的方面。本文将会探讨 ECMAScript 2018 中的原型链改变技巧及利弊分析。

原型链

首先,我们需要对原型链进行一个基本的介绍。

在 JavaScript 中,每个对象都拥有一个原型对象,也称为 "proto"。原型对象本身也是一个对象,并且它也拥有一个原型对象。这些原型对象组成了一条链,我们称其为原型链。

我们可以通过 Object.getPrototypeOf() 方法来访问一个对象的原型,例如:

在这个例子中,const obj = { foo: 'bar' } 创建了一个对象,它的原型为 {}(一个空对象)。

如果我们打印其中的 foo 属性,则会返回 bar

这是因为 JavaScript 引擎在读取 obj.foo 的时候,会在 obj 中查找 foo 属性。但如果在 obj 中没有找到,就会去 obj 的原型 {} 中查找,这样一直查找下去,直到 Object.prototype 这个顶级对象(也就是原型链的根)为止。如果还是没有找到,就会返回 undefined

利弊分析

但是,原型链也带来了一些问题。其中最常见的问题是,如果我们修改了一个对象的原型,所有基于该原型的对象都会受到影响。

例如:

-- -------------------- ---- -------
----- ------ - - ------ - ------------------------- - --
----- --- - - ------ - ------------------------- - --
-------------------------- --------

----- --- - - ------ - ------------------------- - --
-------------------------- --------

----------- -- -- ------------
----------- -- -- ------------

在这个例子中,我们创建了一个 animal 对象,它有一个 walk 方法,用于输出 "Walking..."。我们还定义了一个 dog 对象,它有一个 bark 方法,用于输出 "Barking..."。dog 的原型为 animal。我们还定义了一个 cat 对象,它有一个 meow 方法,也把 animal 作为原型。

如果我们在 animal 中添加了一个新方法,我们会发现 dogcat 也会受到影响:

这是因为它们的原型链中都有 animal

这种特性常常导致一些难以调试的错误。对于普通开发者来说,建议不要动态修改原型,以避免产生类似的问题。

Object.setPrototypeOf()

在 ECMAScript 5 中,我们可以使用 Object.create() 方法来创建一个具有指定原型的新对象。在 ECMAScript 6 中,我们还可以使用 class 关键字来创建类,它也支持基于原型的继承。那么在 ECMAScript 2018 中,我们有什么新的方法可以用来修改原型链吗?

在 ECMAScript 2018 中,我们可以使用 Object.setPrototypeOf() 方法来修改一个对象的原型。

例如:

-- -------------------- ---- -------
----- ------ - - ------ - ------------------------- - --
----- --- - - ------ - ------------------------- - --
----- --- - - ------ - ------------------------- - --

-------------------------- --------
-------------------------- --------

----------- -- -- ------------
----------- -- -- ------------

----------------------------- - ----- - ------------------------- - ---

---------- -- -- ------------
---------- -- -- ------------

在这个例子中,我们先创建了一个 animal 对象,表示动物类。它有一个 walk 方法。

然后,我们创建了 dogcat 两个对象,dog 有一个 bark 方法,cat 有一个 meow 方法。

通过 Object.setPrototypeOf(dog, animal)Object.setPrototypeOf(cat, animal)animal 设置为它们的原型。

最后,我们使用 Object.setPrototypeOf(animal, { run() { console.log('Running...') } })animal 的原型修改为一个新对象,表示动物可以奔跑。

通过这个操作,我们发现,dogcat 上的方法都已经发生了变化,它们新增了一个 run 方法。

利弊分析

虽然 Object.setPrototypeOf() 提供了一个很方便的方法来改变对象的原型,但同样也存在一些风险。因为原型链是全局的,这个方法有可能会影响到整个应用程序。因此,在使用这个方法的时候一定要非常小心。

另外,由于 Object.setPrototypeOf() 会影响到整个原型链,当你大量使用它时,你的代码可能变得难以维护。

Object.assign()

虽然 Object.setPrototypeOf() 使得修改原型变得更加容易,但它同样带来了风险。而且,它需要实时改变原型,这可能会影响性能。

还有一种更加安全和高效的方法来实现类似的功能,那就是 Object.assign()

例如:

-- -------------------- ---- -------
----- ------ - - ------ - ------------------------- - --
----- --- - - ------ - ------------------------- - --
----- --- - - ------ - ------------------------- - --

------------------ --------
------------------ --------

----------- -- -- ------------
----------- -- -- ------------

--------------------- - ----- - ------------------------- - ---

---------- -- -- ------------
---------- -- -- ------------

在这个例子中,我们使用 Object.assign()animal 合并到 dogcat 中。

Object.setPrototypeOf() 不同,Object.assign() 并没有直接改变对象原型。它仅仅将 animal 中的方法和属性复制到了 dogcat 中。后续修改原型的操作也不会影响到 dogcat

利弊分析

使用 Object.assign() 版本的代码更加安全和高效。但它有一个限制,就是它只能用来合并方法和属性,不能用来修改原型链。如果你需要改变原型链,还是需要使用 Object.setPrototypeOf()

应用

让我们来看一个更具实际应用的例子,它演示了如何使用 Object.assign() 来实现基于原型的类继承。

我们有一个 Mammal 类,它是所有哺乳动物的基类。我们还有一个 Dog 类,它从 Mammal 继承,并添加了一些狗特有的方法。

-- -------------------- ---- -------
----- ------ -
  ----------------- -
    --------- - -----
  -

  ------ -
    ------------------------- -- -------------
  -
-

----- --- ------- ------ -
  ----------------- ------ -
    ------------
    ---------- - ------
  -

  ------ -
    -------------------------- ------------ -- -------------
  -
-

现在,我们想要创建一个 Cat 类,也从 Mammal 继承。我们可以重用 Dog 类的定义,并将 Dog 中的特定属性和方法替换为 Cat 中的。

首先,让我们看看如何使用 Object.assign() 来复制 Dog.prototypeCat.prototype 中:

然后,我们再从 Cat 的原型中删除 bark 方法,以使它只继承 Mammal 中的 walk 方法:

完整的代码如下:

-- -------------------- ---- -------
----- ------ -
  ----------------- -
    --------- - -----
  -

  ------ -
    ------------------------- -- -------------
  -
-

----- --- ------- ------ -
  ----------------- ------ -
    ------------
    ---------- - ------
  -

  ------ -
    -------------------------- ------------ -- -------------
  -
-

----- --- ------- ------ -
  ----------------- ------ -
    ------------
    ---------- - ------
  -
-

---------------------------- ---------------
------ -------------------

----- --- - --- ------------ --------- -------
----------- -- -- ------ -- -----------

在这个例子中,我们通过复制 Dog.prototypeCat.prototype 中,并从 Cat 的原型中删除了 bark 方法,来实现了基于原型的继承。

结论

在 ECMAScript 2018 中,我们可以使用 Object.setPrototypeOf()Object.assign() 来改变对象的原型。虽然这些方法可以让我们更加便捷地修改原型链,但我们一定要非常小心,并避免滥用。修改原型链可能会对全局应用程序产生影响。

当我们需要实现类继承时,可以使用基于原型的继承来实现,例如通过将 Dog.prototype 复制到 Cat.prototype 中,并从中删除特定的方法和属性来实现。这样会更加安全和高效,并且不会影响到全局应用程序。

希望通过本文,能够帮助读者更加了解 ECMAScript 2018 中的原型链改变技巧及利弊分析,并提供一些指导意义。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/675157e88bd460d3ad88b249

纠错
反馈