65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 中的原型继承

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (21投票s)

2015年7月8日

CPOL

5分钟阅读

viewsIcon

29369

JavaScript 中的原型继承

引言

我用 JavaScript 已经有一段时间了,大多数情况下都觉得它很直接。最近,我一直在使用它以及一些框架,包括 React.jsNode.js。作为一名 Web 开发者,JavaScript 是你工具箱的一部分,并且熟练使用它绝对是必需的。

我的背景是 C++ 和 C# 等经典的面向对象语言,我使用 JavaScript 进行 Web 开发。因此,JavaScript 中有几个地方让我感到困惑,因为它与大多数其他语言的运作方式不同。我希望能在未来的文章中涵盖那些其他方面。

本文的目的是涵盖 JavaScript 对面向对象的实现,特别是它是如何实现继承的。

背景

本文假定读者熟悉 JavaScript。虽然这里描述的概念同样适用于所有原型语言,但为了本文的目的,我将专门使用 JavaScript 来进行所有解释和示例。

大多数读者会熟悉 C++、Java、C# 等语言的经典继承。这可能会在理解 JavaScript 中的继承时造成一定程度的困惑,因为 JavaScript 使用的是所谓的原型继承。JavaScript 并非唯一采用这种技术实现继承的语言。其他语言包括 Self、LuaNewtonScript 等。

在经典的面向对象语言中,你基于抽象创建类(类型)。然后,我们将属性和行为分配给这些类。这些类在我们的代码中被实例化以执行各种操作和任务。例如,你可能定义一个 Order 类,并为其添加返回订单总金额的行为。在你的应用程序中,你会在需要时创建这些 Order 类的实例,并调用它们的行为,例如调用特定订单返回订单总额的行为。

JavaScript 中的对象

对象是 JavaScript 中的一个重要原始类型。该语言就是建立在对象之上的。而在建立在类之上的经典面向对象语言中,JavaScript 却是建立在对象之上的。因此,在 JavaScript 中,你可以如下创建一个新的对象实例。

myObject = new Object();

你也可以使用此语法来创建相同的对象。

var myObject = {};

此语法使用了对象字面量语法。在 JavaScript 中,对象本质上是键值对的集合,如下例所示。

myObject = {"Firstname": "Fred",  "LastName": "Smith" };

从这个对象字面量表示法可以看出,JSON 格式的来源就在于此。JSON 本质上是 JavaScript 对象字面量,不包含任何行为(方法)。

由于 JavaScript 是一种函数式语言(函数是第一公民),因此你也可以使用函数来创建对象,如下例所示。

function myObject(){}
var myObject = new myObject();

这就是在 JavaScript 中创建对象的方法。现在让我们继续讨论继承。

JavaScript 中的继承

到目前为止一切都相当直接。然而,JavaScript 的工作方式并非如此。它是一种无类、面向对象的语言。在 JavaScript(或任何其他原型语言)中没有类的概念。尽管某些文本可能表明 JavaScript 能够实现经典继承,但事实并非如此。这只是语法糖,给经典语言的开发者一种错觉,让他们认为可以在 JavaScript 中实现经典继承。归根结底,在 JavaScript 中,所有继承最终都是通过原型继承实现的。这是因为。

  • JavaScript 是无类的(所有经典的面向对象语言都依赖于的基本概念)
  • 所有继承最终都是通过原型链实现的(无论是直接还是间接使用语法糖)

经典继承在 JavaScript 中是模拟的。

什么是原型继承?

JavaScript 中的对象包含一个名为 prototype 的内部对象属性。

Object.prototype

prototype 对象是当前对象所基于的对象,即它是当前对象的原型。用经典面向对象的语言来说,prototype 就是当前对象所创建的基类或祖先。当前对象上找到的所有方法和属性也可以在其 prototype 上找到。这个 prototype 对象反过来也包含一个 prototype 对象,其中包含它所创建的对象。这条链(称为原型链)一直向上延伸,直到我们最终达到根 Object。此时我们就不能再往上走了,因为我们已经到达了原型链的顶部。

让我们从一个名为 o 的对象开始。

var o;

我们的对象 o 包含一个名为 prototype 的属性。这个对象就是 o 所创建的对象(或者用经典术语来说,是 o 所派生的对象)。

var o = new Object()
console.log("typeof Object: " + typeof o); //outputs object
var p = o.prototype;
console.log("typeof o.prototype: " + typeof p); //outputs undefined 

我们可以一直这样做,直到最终达到原型链的顶部。

JavaScript 中的继承是通过两个关键概念实现的。

  • 遍历原型链 - 如果 JavaScript 找不到指定的属性/方法,它会查找对象的 prototype。如果在那里找不到,它会查找该对象的 prototype。它会不断查找每个对象的 prototype 以查找指定的属性/方法,直到找到它或到达原型链的顶部。
  • prototype 对象可以被不同的对象共享。不同的对象可以共享同一个 prototype 对象,因此可以共享相同的方法和属性。

如何实现原型继承?

现在我们已经理解了 JavaScript 中实现继承的底层概念,让我们来看一个简单的例子。在这里,我们定义了一个名为 Vehicle 的对象。然后,我们向 Vehicle 对象添加一个名为 make 的属性,并添加一个返回该属性的函数。

//Defining and instantiating an object called Vehicle using a function
function Vehicle() {}
var vehicle = new Vehicle();

//Adding properties to the Vehicle object. We can either add these as
//instance methods / properties, or to the Vehicle prototype so all 
//Vehicles can inherit them.

//adding an instance property 
function Vehicle(make) {
    // Instance properties can be set on each instance of the class
    this.make = make;
}

//Adding the property to the prototype ensures that all instances 
//share the property
Vehicle.prototype.Make = function() {
    console.log("My make is " + this.make);
};

var vehicle = new Vehicle('Vehicle');
vehicle.Make(); // My make is Vehicle

到目前为止一切都很好。让我们通过创建一个 Car 类来扩展 Vehicle 对象。

function Car(make){
	Vehicle.call(this, make);
}

Car.prototype = new Vehicle();
var car = new Car("Car");
car.Make();

我们正在将 Vehicle 对象赋值给 Carprototype。这使得 Car 对象可以继承 Vehicle 的所有方法和属性。我们使用 JavaScript 的 call() 方法来实现这一点。它的作用是允许我们调用一个函数并指定其执行的上下文 - 因此我们传入了 this 对象。我们知道 Car 已经继承了 Vehicle 的属性,因为我们调用了仅在我们的 Vehicle 对象上定义的 Make() 方法。

摘要

对于来自经典面向对象背景的开发者来说,这种继承方法可能更难理解。我知道我花了一段时间才完全掌握其中的运作原理。如果你使用 JavaScript,那么理解 JavaScript 如何实现继承非常重要,希望本文能提供一个很好的概述。如果你希望我进一步阐述本文中的任何内容,请随时留下评论。

© . All rights reserved.