JavaScript 原型继承

2025年2月16日 | 阅读 20 分钟

引言

JavaScript 是编程语言的宇宙中最流行的语言之一,它被认为是完成开发者必须执行的所有任务的核心组成部分。JavaScript 已发展成为一种语言,从简单的页面脚本演变为复杂的 Web 应用程序、API 和服务器端开发。

JavaScript 可以轻松应用,因为它当今最常用和最灵活的语言之一。然而,对于许多开发人员来说,JavaScript 的一个基本概念——基于原型的继承——仍然笼罩着一层神秘的面纱。请按照以下简单步骤创作你的大局观论文。

  1. 头脑风暴:花一些时间反思你的生活以及塑造了你的重大里程碑或事件。你遇到过哪些经历,它们如何影响了今天的你?
  2. 反思:选择一个对你来说有意义的重要记忆或经历。探索你

就 JavaScript 而言,原型可以与创建对象的蓝图相比较。需要注意的是,每个 JavaScript 对象都有一个原型,它决定了它继承的属性和方法。每当你在 JavaScript 中尝试访问对象的某个属性或调用某个方法时,首要且最重要的优先级是检查该属性或方法是否在对象本身中存在。在查找属性或方法时,它首先会查看原型链。如果找不到,它会向上查找原型链,直到达到顶层原型,通常称为所有对象的原型(Object.prototype)。

原型链这一概念构成了 JavaScript 继承模型的支柱。对象继承属性和方法不是从父类(如经典继承),而是从它们的原型继承。这种动态的继承性质使得 JavaScript 中的对象创建和操作更加灵活。

什么是原型继承?

原型继承是 JavaScript 中面向对象范型(OOP)的基础,因为它是面向对象编程的基础。学习原型继承是正确开发 JavaScript 编程的一个决定性特征,从而指定了语言中对象的构建、排列和协作方式。

原型的概念

首先,JS 是一种基于原型的语言,它通过原型(对象是任何具有特定特征、能力或质量的实体)将对象链接到其他对象。与 Java 和 C++ 等基于类的编程语言不同,JavaScript 没有传统的类来进行继承。然而,与变量不同,JavaScript 中的对象不会直接从它们的前辈对象继承属性或行为。它们通过原型获取它们。

对象原型是每个 JavaScript 对象都具有的特性,通过这种方式,对象之间建立了连接。这个原型就像一个存储空间,当访问父对象或原型对象时,它所包含的所有函数都可以被访问。当你累积对象上的一个属性子集或方法时,JavaScript 会立即在对象本身上搜索它。这个过程是它所属的原型链,然后,如果它不知道属性或方法,它会继续沿着原型链,遍历链接的原型,直到找到所需的属性或到达链的末端。

使用原型创建对象

有多种方法可以创建 JavaScript 对象,每种方法都利用原型继承。

1. 构造函数

构造函数是创建……最常见的模式。构造函数是创建具有原型的对象的常见方式。当您使用构造函数和 new 关键字创建新对象时,JavaScript 会自动为您设置原型链。构造函数的 prototype 属性充当构造的对象的原型。

Person 的作用是充当构造函数。构造函数用于创建具有某些初始能力的 contructor。

它只拉取一个参数 name,用于声明所创建对象的 name 属性。

  1. 在这里,构造函数内的 'this' 指的是新创建的对象,而 this.name = name; 将 'name' 属性分配给该对象。
  2. 此代码创建了一个方法,sayHello(),它将被添加到 Person 构造函数的 prototype 属性中。
  3. 将方法添加到原型可以使所有基于 Person 创建的实例共享相同的方法,从而节省内存。
  4. 在方法内部,this 指的是调用该方法的对象,而 this.name 是访问该对象 name 属性的方式。
  5. 此语句相当于创建一个 Person 类对象的 new 实例。
  6. personAlice = new Person("Alice") . Person 工厂方法的结果是现在有一个名为 person 的新对象,其 name 属性设置为“Alice”。
  7. 这将导致创建一个对象,然后将其分配给变量 person1。
  8. 这意味着对 person1 对象调用 sayHello 方法。
  9. 它显示“Hello, my name is ”,后跟 person1(“Alice”)的 name 属性。

在这里,我们不仅遵循 JavaScript 的面向对象编程原则,而是通过构造函数创建对象,将方法添加到原型对象并被所有实例共享。sayHello 方法还可以让每个 Person 对象通过在命令行上显示其姓名来正式称呼自己。

2. Object.create()

Object.create() 方法使您能够创建具有您选择的原型或蓝图的对象。将最令人满意的原型作为参数提供给 Object.create() 函数,它将从此原型构造一个新对象。

在这里,personPrototype 被构造为一个简单的 JavaScript 对象,通过 providerByPerson 原型函数进行操作。它包含一个函数 sayHello,在该函数中,它通过调用 console.log 函数将消息写入控制台,它的作用是将消息记录到日志中。消息应说明孤独可能很可怕,除非你找到了生活的激情。

  • Object.create() 方法用于构造(person2),其原型等于 personPrototype(对象模板)。换句话说,person2 继承了 personPrototype 的属性和实现。
  • 第二步,person2.name = "Bob" 这一行创建 person2 并将其 name 属性设置为“Bob”。这个属性有一个方法 set and get,属于 person2,而不是来自 personPrototype。
  • 然后为 person2 调用 sayHello 方法。在这种情况下,继承的一个直接优势是 person2 在继承 sayHello 方法的同时,还可以访问在 person2 中定义的 name 属性。因此,当调用 sayHello 时,控制台现在显示“Hello, my name is Bob”。

此算法描述了 JavaScript 中的原型继承,作为拥有共享行为对象的示例。创建了一个 West 原型对象(personPrototype),其中包含通用方法,并创建了继承自该原型的新对象(person2)。通过使用此方法进行代码组织,实现了 JavaScript 应用程序中代码的可重用性。

3. 修改原型

原型可以动态地显示其行为,然后由使用它的所有其他对象反映出来。响应式数据功能是常用的功能之一,旨在强大的自定义和扩展对象行为。但是,在进行此设计过程时,我们应牢记不要导致不良后果。

此代码通过提供一个名为 sum() 的自定义方法来扩展 JavaScript 数组的功能,该方法存储在 Array.prototype 中,使数组能够通过代码对值求和。以下是代码的细分:

  • 它创建一个名为 sum() 的函数,该函数将应用于整个 Array.prototype。Array 构造函数将被修改以包含一个方法,该方法将可用于 JavaScript 中的每个数组。
  • 在 sum() 方法定义内部,this 指的是正在操作该方法的数组。
  • 此方法将对数组的所有元素进行迭代并将它们的总和相加。
  • 累加器 (acc) 从零个元素开始,对于数组中的每个迭代 (curr),它将 curr 添加到 acc。
  • 循环的最后一步是将所有元素相加作为最终总和,并将其作为返回值返回。
  • 最后,我们将 sum() 方法添加到 Array.prototype,以执行我们数组中数字的总和:[1, 2, 3, 4, 5]。
  • 随后,我们直接在 numbers 数组的元素上调用 sum() 方法,显式地(numbers.sum())。
  • sum() 方法将对 numbers 数组的所有元素求和,使用之前提供的自定义逻辑,该逻辑仅将元素相加并返回 15 的结果。
  • 最后但同样重要的是,使用 console.log 工具将 sum 行 15 记录到控制台。

开发人员可以继承原型方法来向 Array.prototype 添加自定义方法。因此,他们可以扩展 JavaScript 中数组的功能以满足他们的特定需求。也就是说,您可以使用 sum() 方法完成此操作,而无需重复求和逻辑,这会非常耗时。一方面,受益于第三方原型很好,但您应该记住不要无故修改它们,因为它们可能与其他 JavaScript 功能冲突,有时会导致崩溃。

4. 内置原型

JavaScript 为 Objects、Array 和 Functions 等基本类型的内置值提供了内置原型。事实上,这些原型展示了一般方法以及被所有新构造的对象类型所采纳的必要性。这些标准库已经提供了广泛的低级程序,使得基本操作的编程更快、耗时更少。

  • 让我们考虑一个包含 1、2 和 3 的数组 arr。
  • 将为 arr 调用 toString() 方法。
  • toString() 方法将数组中的每个元素转换为字符串,并在每个元素之间用逗号分隔。
  • 因此,arr.toString() 的输出将是“1,2,3”,这是数组元素的字符串表示。
  • 在 hasOwnProperty() 方法中,我们检查一个对象(或数组)是否具有具有指示键(或索引)的属性。
  • 这里,检查 arr.hasOwnProperty(0) 以查看 arr 在索引 0 处是否具有任何属性。
  • 由于该函数接受我们已经用元素 [1, 2, 3] 初始化过的数组 arr,这些元素映射到索引 0、1 和 2,因此它将为 true。
  • 可以验证数组的元素是否位于索引 0 处,因为变量 index 的值为 0。

总之,提供的代码演示了 JavaScript 中数组的两种常用方法的用法。

通过 toString() 方法,数组表示为字符串格式,逗号作为元素之间的分隔符。

hasOwnProperty() 方法用于验证给定数组中的特定索引是否具有具有该指定索引的属性。这包括验证在名为 arr 的给定数组的 0 位是否找到了一个元素。

5. 继承和原型链

类对象类型继承的一个优势在于它能够维护类链式继承结构。原始的“prototype”对象本身可以有自己的原型,从而创建继承链。这通常被称为“原型链”。对象可以不考虑链的级别,继承其上方的原型的特性和方法。

提供的代码显示了 JavaScript 中发生的原型继承,其中一个对象获得了第二个对象的属性和方法。让我们一步一步地分解:

  • 名为 this 的函数,无论它是否是 Animal 对象的构造函数。它就像一个基本的构造函数,不需要任何参数。
  • eat() fOne 将被添加到 Animal 原型本身。因此,Animal(以及所有将 Animal 作为基类的其他类)现在拥有 methods_sleep() 和 run()。
  • 像 Animal 类这样的对象,Dog 也是由函数构造的。然而,作用域不包含任何属性或方法。
  • 此代码通过使用 Object.create() 创建的新对象,向前分配了 Dog 原型。结果,这个新对象的原型被设置为 Animal.prototype。
  • 通过建立此链接的过程,puppy 被归类为 Animal 原型链。这表明狗和其他动物将拥有 Animal 的属性和方法。
  • 通过 Jan Dog new Dog() Kirkt。这些调用构造函数“Dog()”并返回一个新对象,其 __proto__ 设置为(Dog.prototype)。
  • 然后从 dog 对象调用 eat() 方法。由于 breed dog 内嵌在 Animal 类中,因此它也自动继承了 eat 方法。
  • 因此,在调用 dog.eat() 时,控制台打印“Eating...”

它展示了 JavaScript 中原型继承的功能。Dog 类中的两个构造函数通过其原型从 Animal 的构造函数中构造属性和方法值。因此,Dog 类现在可以调用 Animal 中定义的 eat() 方法,因为它们现在继承了 Animal 的方法。正是这种代码的性质鼓励了可重用性,并且还对对象的行为如何根据层次结构进行组织进行了结构化。

原型继承是基于 JavaScript 的原型方法的关键特征。使用原型是 JavaScript 帮助设计和开发稳定强大程序的途径。通过了解这种基于原型的继承如何工作,开发人员将能够利用该语言软件提供的所有功能来编写优雅的代码。尽管如此,编辑内置原型和设置存在固有风险,以免引起任何意外后果。原型继承为概念清晰的开发人员提供了明显的机会。这些开发人员可以扩展他们的可能性范围,并构建高效且可持续的 JavaScript 应用程序。

原型继承实践

原型继承实践是在现实世界领域开发 JavaScript 应用程序时,对原型动态继承模型的实现和使用。原型继承的基础对开发人员至关重要,因为它们使他们能够处理结构、扩展功能和重用代码以最大限度地减少重复的需求。在本探索中,我们将发现原型继承的多个实用实例和教学,这些实例将阐明核心概念并演示它如何帮助开发健壮且可管理的编码。

1. 扩展内置对象

原型继承使 JavaScript 开发人员能够向内置 JavaScript 对象的原型添加额外的函数或属性以进行自定义开发。例如,假设我们想向 Array 原型添加一个计算数组中所有元素之和的方法。

此函数实际上是一个 JavaScript 数组方法,由 sum()(一个附加到全局 Array 对象原型的自定义方法)扩展。以下是代码的细分:

  • 此任务是设计和编写一个名为 sum 的新方法作为 Array.prototype 的属性。
  • sum() 方法是一个实际应用,它使用 reduce() 方法在数组上工作。reduce() 是数组的内置高阶函数之一,用于将数组大小减小到单个值,例如数组中所有元素的总和。
  • 在 reduce() 方法内部:
  • acc 简写,是累加器,所以 acc 开始设置为 0。
  • curr 是我们数组中当前正在处理的元素。
  • 在每个元素 curr 的表示上,它将其添加到累加器 acc 中。
  • 最终产品存储在最终累积的总和中。
  • 创建了一个数字数组,其中包含数组的数字元素:[1, 2, 3, 4, 5]。
  • 遵循点符号,在 numbers 数组上调用 sum_methd() 以返回数组中每个数字的总和。
  • sum() 方法(除了 sum() 之外)计算 numbers 数组中所有元素的总和,并使用之前陈述的自定义标准。
  • 最后一个语句是 n 的平方,然后使用 console.log() 将其记录到控制台,因此 n 2 = 15。

在这里,为了逻辑上延长数组原型,我们添加了一个 sum() 方法,该方法查找数组所有元素的总和。通过这种方式,我们可以展示原型继承在向内置对象添加函数方面的优势,并且在应用程序内,我们可以提供可重用的实用方法。

2. 创建带原型的自定义对象

原型继承允许定义一个对象,该对象根据其特定行为和属性进行自定义。主要是,构造函数用于此执行,其中在此过程中生成的对象从构造函数的原型继承属性和方法。例如,让我们创建一个 Person 构造函数。

此语法在 JavaScript 中创建了一个 Person 类构造函数,它用作创建具有相似属性和方法的对象的模式。让我们一步一步地分解代码:

  • Person 实体构成了用于创建 Person 对象实例的构造函数。
  • 如果它使用 new 关键字创建新对象,并在给出输入 name 参数时为其 name 属性赋值。
  • 一个名为 sayHello 的函数被传递给 Person 构造函数的原型。
  • 这意味着稍后使用 Person 构造函数创建的所有对象都将拥有此方法。
  • this.sayingHello() 将一个问候消息打印到控制台,其中包括该人的姓名。
  • 在这种情况下,使用“new”关键字创建了一个 Person 对象。数据结构“people”被初始化,并且使用“addPerson”方法存储了第一个人的详细信息,姓名为“Alice”。
  • 在 Person 构造函数中,通过指定 this.name = "Alice",我们正在设置对象的属性。
  • sayHello 方法被传递给 person1 对象。
  • 由于 person1 是 Person 构造函数,它从 Person.prototype 继承 sayHello 方法,因此会触发方法 sayHello 的实现。
  • 该方法运行,其输出是“Hello, my name is Alice”到控制台。

3. 从父对象继承

原型继承的主要优势之一是它支持对象层次结构,这使得能够描述对象之间的类间关系。对象将连接原型行,并在形成原型链时能够从父对象“继承”属性和方法。考虑以下示例:

此行使用代码示例显示了 animal 和 dog 构造函数之间的 JS 原型继承。让我们一步一步地分解:

  • 此函数充当制造 Animal 类对象的工厂。它是一个空的构造函数,没有任何属性或方法分配给它。
  • 同样,eat() 是 Animal.prototype 定义的新方法。这意味着 Animal 的所有实例(以及所有基于它构造的对象)都将被允许调用此方法。
  • 像 animal Dog 一样,Dog 也通过构造函数传递。但是,它本身没有任何部分。
  • 在这里,使用语法,我们将 Canine 的原型更改为使用 Object.create() 动态创建的对象,并且此新对象的原型设置为 Animal 原型。
  • Dog 到 Animal 的链接创建一个原型链。通过这种方式,Dog 将被绑定到导航 Animal 类并带来它们各自的属性和方法。
  • D 是 Dog 的一种,并且是使用 new object 创建的。它产生一个新对象的实例,其原型设置为 Dog.prototype。
  • dog 对象然后通过 eat() 方法传递。通过这种方式,由于 dog 是 animal 的子类,因此它也成为 eat() 功能的子类。
  • 因此,当调用 dog.eat() 时,它会在控制台记录“Eating...”

在这种情况下,Dog 类将从 Animal 类继承原型方法 eat()。对于 Dog.prototype 的初始化,Object.create(Animal.prototype) 用作基础,而 Dog 的实例随后继承自 Animal 原型。这消除了原型继承如何用于捕获基础层次结构,基于该层次结构共享相关对象的行为。

4. 实现 Mixins

Mixins 是一种面向对象的 JavaScript 编程技术,它允许对象合并其他对象的特征或函数,而无需形成父子关系。原型继承提供了实现 mixins 的机会,主要是因为一个对象可以拥有多个原型。例如:

  • canSwimMixin 是一个具有 swim() 函数的游戏对象。
  • 在这里,执行时将打印“swimming...”日志消息到控制台。
  • 它实现了一个 Duck 工厂模式,有助于构造 Duck 对象并设置其所需的标准。它是一个基本的构造函数,没有任何属性或方法。
  • Object.assign() 方法用于在创建 duck 对象时将属性和方法从 canSwimMixin 传输到 duck 对象。
  • 下一步是实现 mixin can swim。由于它继承自 Duck 原型,Duck 实例也将能够访问 swim() 方法。
  • 给定一个使用 new 运算符的 Duck 示例。因此,上面的句子创建了一个新对象,其原型等于 Duck.prototype。
  • duck 对象是 duck 实例,调用 swim() 方法。Mixins,Duck 从 Duck.prototype 继承其原型(这就是为什么 Duck.prototype = Object 混合)。由于 Duck.prototype 具有 swim() 方法,因此 duck 可以访问并调用 swim() 方法。
  • 因此,如果调用 duck.swim(),则会在控制台记录“Swimming...”

Proto 继承在 JavaScript 库中发挥着重要作用,开发人员可以构建灵活、可重用且易于维护的代码。通过对基于原型的继承的轻松支持,开发人员可以扩展内置对象提供的功能,可以构建自己的蓝图来创建独特对象、建模层次关系、应用 mixins 以及建立复杂的设计模式。

原型的层次结构继承在其实际应用中,全面学习和掌握其概念对于提高您的 JavaScript 开发水平至关重要,也有助于构建强大且可扩展的应用程序。开发人员能够掌握原型组合,这是每个高效程序员武器库中最强大的武器之一。

最佳实践和注意事项

最佳实践和注意事项与 JavaScript 开发过程的元素有关,这些元素在提出原型继承问题时尤其重要。原型继承是一种强大而灵活的方法,但它本身也有其缺点。本次讨论将分为对与此特定主题相关的良好和坏实践以及在 JavaScript 开发中应用原型继承的研究。

1. 避免修改内置原型

JavaScript 程序员代码的一项主要原则是警惕修改内置原型。然而,程序员可能会修改内置原型以添加更多功能。这可能会在使用相同代码在其他环境中时导致一些不幸的意外行为。但是,值得考虑通过组合属性而不是修改原型来构建实用函数或对象扩展的可能性。

2. 封装原型修改

命名空间污染和与具有与您相同方法的代码的其他部分发生冲突是常见的。因此,在扩展原型或添加自定义方法时,请将此过程命名到适当的命名空间或模块中。它还通过指定和披露设计更改的原因和目标来确保原型设计被修改以实现正确的修改和可维护性。

3. 偏好组合对象而不是继承

原型继承确实是一种强大的代码重用机制,但同时,我们应该检查继承是否是特定情况下的正确选择。例如,让特定类组合许多对象而不是使用继承可以提供更整洁的构造,并最终提供更好的可维护性。因此,您可以实现更准确的对象组合,避免形成紧密联系,并最大限度地减少可能的意外副作用。

4. 文档原型扩展

详细说明您所做的扩展和自定义方法,以便其他处理代码库的开发人员能够了解上下文和指导。文档的清晰和简洁有助于消除人与人之间的混淆和协作机会,从而加强他们。

5. 性能注意事项

注意原型可移植性的性能影响,尤其是在对性能有要求的应用程序中。深原型链可能会通过属性查找和方法调用的成本来损害运行时性能。通过最小化继承链的长度并避免过多的原型遍历来创新原型链。

6. 广泛测试

原型继承使得原型重构复杂,这增加了错误的几率,因此在各种环境和场景中仔细测试所有潜在更改变得必要。设计单元测试以检查各种扩展的原型行为,并检查与现有代码的向后兼容性。

开发人员可以学习以最适合他们的方式使用 JS 中的原型继承,这让他们能够应对可能的挑战和风险,从而能够利用这项技术的巨大能力。不直接修改内置原型,使用封装来保护原型更改,偏好从其他对象创建对象(以前的模型),进行频繁文档记录,牢记性能问题,并反复测试,这些在最佳地编写 JavaScript 时都很重要。这些实践将确保开发人员以一种产生强大、干净且可扩展的应用程序的方式进行响应,并最终能够充分利用原型继承的特性。