JavaScript 中的继承

2018-05-15 17:26 更新
预备知识: 基本的计算机素养,对 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基础(参见 First stepsBuilding blocks)以及面向对象的JavaScript (OOJS) 基础(参见 Introduction to objects)。
目标: 理解在 JavaScript 中如何实现继承。

原型式的继承

到目前为止,我们已经看到了一些行为中的继承 - 我们已经看到了原型链是如何工作的,以及成员如何沿着链继承。 但大多数情况下,这涉及内置的浏览器功能。 我们如何在从另一个对象继承的JavaScript中创建一个对象?

如前所述,有些人认为JavaScript不是一个真正的面向对象的语言。 在"经典OO"语言中,您倾向于定义某种类的对象,然后可以简单地定义哪些类继承自其他类(参见 .htm"class ="external"> C ++继承一些简单的例子)。 JavaScript使用不同的系统 - "继承"对象没有功能复制到它们,而是它们继承的功能通过原型链(通常称为原型继承)链接。

让我们通过一个具体的例子探讨如何做到这一点。

开始

首先,将自己的本地副本复制到我们的 class ="external"> oojs-class-inheritance-start.html 文件(请参阅 class-inheritance-start.html"class ="external"> running live )。 在这里,你会发现相同的 Person()构造函数示例,我们一直使用模块,只有一点区别 - 我们只定义了构造函数中的属性:

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

方法是在构造函数原型上定义的所有,例如:

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

假设我们想创建一个 Teacher 类,就像我们在初始面向对象定义中描述的那样,它继承了来自 Person 的所有成员,但还包括:

  1. A new property, subject — this will contain the subject the teacher teaches.
  2. An updated greeting() method, which sounds a bit more formal than the standard greeting() method — more suitable for a teacher addressing some students at school.

定义 Teacher() 构造函数

我们需要做的第一件事是创建一个 Teacher()构造函数 - 在现有代码下面添加以下代码:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

这看起来类似于Person构造函数在很多方面,但有一些奇怪的地方,我们以前没有见过 - Web / JavaScript / Reference / Global_Objects / Function / call"> call() 函数。 这个函数基本上允许你调用定义在其他地方,但在当前上下文中的函数。 第一个参数指定要在运行函数时使用的 this 的值,其他参数指定函数在运行时应该传递给它的参数。

注意:在这种情况下,我们在创建新对象实例时指定继承的属性,但请注意,您需要在构造函数中将其指定为参数,即使实例不需要它们 指定为参数(例如,您可能已经有一个属性在创建对象时设置为随机值)。

因此,在这种情况下,我们有效地运行 Teacher()构造函数中的 Person()构造函数(见上文),导致相同的属性定义在 > Teacher(),但是使用传递给 Teacher()而不是 Person()的参数的值(我们使用 / code>作为 this 传递给 call()的值,这意味着 this 将是 Teacher >函数)。

构造函数中的最后一行简单地定义了教师将要拥有的新的主体属性,而普通人没有。

注意,我们可以这样做:

function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}

但是这只是重新定义属性,而不是从 Person()继承它们,因此它违背了我们要做的事情。 它还需要更多的代码行。

设置 Teacher() 的 prototype 和 constructor引用

一切都很好,到目前为止,但我们有一个问题。 我们定义了一个新的构造函数,并且默认情况下有一个空的原型属性。 我们需要获取 Teacher()以继承在 Person()的原型上定义的方法。 那么我们该怎么做呢?

  1. Add the following line below your previous addition:
    Teacher.prototype = Object.create(Person.prototype);
    Here our friend create() comes to the rescue again — in this case we are using it to create a new prototype property value (which is itself an object that contains properties and methods) with a prototype equal to Person.prototype, and set that to be the value of Teacher.prototype. This means that Teacher.prototype will now inherit all the methods available on Person.prototype.
  2. We need to do one more thing before we move on — the Teacher() prototype's constructor property is currently set as Person(), because of the way we inherited from it (this Stack Overflow post has more information on why) — try saving your code, loading the page in a browser, and entering this into the JavaScript console to verify:
    Teacher.prototype.constructor
  3. This can become a problem, so we need to set this right — you can do so by going back to your source code and adding the following line at the bottom:
    Teacher.prototype.constructor = Teacher;
  4. Now if you save and refresh, entering Teacher.prototype.constructor should return Teacher(), as desired.

向 Teacher() 增加新的函数 greeting()

要完成我们的代码,我们需要在 Teacher()构造函数中定义一个新的 greeting()函数。

最简单的方法是在 Teacher()的原型上定义它 - 在代码的底部添加以下内容:

Teacher.prototype.greeting = function() {
  var prefix;

  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.';
  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Mrs.';
  } else {
    prefix = 'Mx.';
  }

  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

这会警告教师的问候语,其也使用适当的名称前缀作为他们的性别,使用条件语句。

范例尝试

现在您已输入所有代码,请尝试通过在JavaScript的底部(或您选择的类似选项)放置以下代码,从 Teacher()创建一个对象实例:

var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');

现在保存并刷新,并尝试访问新的 teacher1 对象的属性和方法,例如:

teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();

这些都应该工作很好; 前面三个从通用 Person()构造函数(类)继承的访问成员,而最后两个访问成员只在更专业的 Teacher() 构造函数(类)。

我们在这里介绍的技术不是在JavaScript中创建继承类的唯一方法,但它是可行的,它给你一个好主意如何在JavaScript中实现继承。

您可能还想查看一些新的 脚本语言,基于JavaScript是基于Ecma国际负责标准化ECMAScript。"> ECMAScript 功能,允许我们在JavaScript中更干净地执行继承(参见 org / zh-CN / docs / Web / JavaScript / Reference / Classes">类)。 我们没有覆盖这里,因为他们还没有支持非常广泛的浏览器。 我们在这组文章中讨论的所有其他代码结构支持早在IE9或更早版本,并且有办法实现早期的支持。

一个常见的方法是使用JavaScript库 - 大多数受欢迎的选项都有一套易于使用的功能,可以更容易和快速地继承。 CoffeeScript 例如提供 class extends 等。

更多练习

在我们的 OOP理论部分中,我们还包括一个 Student 类作为概念,继承所有 Person 的特征,并且还具有与 Teacher 不同的 greeting()方法 >的问候。 看看学生的问候语在该部分,并尝试实现自己的 Student()构造函数,它继承了 Person()的所有功能,并实现 不同的 greeting()函数。

对象成员总结

总而言之,基本上有三种类型的属性/方法需要担心:

  1. Those defined inside a constructor function that are given to object instances. These are fairly easy to spot — in your own custom code, they are the members defined inside a constructor using the this.x = x type lines; in built in browser code, they are the members only available to object instances (usually created by calling a constructor using the new keyword, e.g. var myInstance = new myConstructor()).
  2. Those defined directly on the constructor themselves, that are available only on the constructor. These are commonly only available on built-in browser objects, and are recognized by being chained directly onto a constructor, not a instance. For example, Object.keys().
  3. Those defined on a constructor's prototype, which are inherited by all instances and inheriting object classes. These include any member defined on a Constructor's prototype property, e.g. myConstructor.prototype.x().

如果你不确定是哪个,不要担心它只是 - 你还在学习,熟悉将伴随着实践。

何时在 JavaScript 中使用继承?

特别是在这最后一篇文章之后,你可能会想"这是复杂的"。 嗯,你是对的,原型和继承代表JavaScript的一些最复杂的方面,但很多JavaScript的力量和灵活性来自它的对象结构和继承,这是值得了解它是如何工作的。

在某种程度上,您始终使用继承 - 每当使用WebAPI的各种功能,或者在您对字符串,数组等调用的内置浏览器对象上定义的方法/属性时,您将隐式使用继承。

在使用继承在你自己的代码,你可能不会经常使用它,特别是开始,在小项目中 - 浪费时间使用对象和继承只是为了它,当你 不需要它们。 但是随着你的代码库越来越大,你更有可能找到它的需要。 如果您发现自己正在开始创建一些具有类似功能的对象,那么创建一个通用对象类型以包含所有共享功能并在更专门的对象类型中继承这些功能可能是方便和有用的。

注意:由于JavaScript的工作方式,原型链等,对象之间的功能共享通常称为委托 - 专门的对象将该功能委托给 通用对象类型。 这可能比调用继承更准确,因为"继承"功能不会复制到正在执行"继承"的对象。 相反,它仍然保留在通用对象中。

当使用继承时,建议不要有太多的继承,并仔细跟踪您定义方法和属性的位置。 有可能开始编写代码来临时修改内置浏览器对象的原型,但是你不应该这样做,除非你有一个很好的理由。 太多的继承会导致无尽的混乱,当你尝试调试这样的代码,无尽的痛苦。

最终,对象只是另一种形式的代码重用,如函数或循环,具有自己的特定角色和优点。 如果你发现自己创建了一堆相关的变量和函数,并希望将它们全部一起跟踪并整齐打包,一个对象是个好主意。 当您想将数据集合从一个地方传递到另一个地方时,对象也非常有用。 这两个事情都可以在不使用构造函数或继承的情况下实现。 如果你只需要一个对象的单个实例,那么你可能最好使用一个对象字面量,你肯定不需要继承。

总结

本文涵盖了我们认为您现在应该知道的核心OOJS理论和语法的其余部分。 此时,您应该了解JavaScript对象和OOP基础,原型和原型继承,如何创建类(构造函数)和对象实例,向类添加功能,以及创建从其他类继承的子类。

在下一篇文章中,我们将了解如何使用JavaScript对象表示法(JSON),这是一种使用JavaScript对象编写的常见数据交换格式。

另见

  • ObjectPlayground.com — A really useful interactive learning site for learning about objects.
  • Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
  • You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号