JavaScript面向对象之Prototypes和继承
本文深入解析JavaScript中Prototype与继承的核心机制。通过剖析对象与原型链的关系,阐述如何利用prototype实现方法共享与属性覆盖,并结合构造函数与new操作符,详解自定义对象继承的实现原理,帮助开发者掌握JS面向对象编程的关键技术。
前言
本文翻译自微软技术专家Scott Allen撰写的《Prototypes and Inheritance in JavaScript》。文章详细剖析了Prototype的本质及其在JavaScript继承机制中的作用,是深入理解JS面向对象编程的重要参考资料。译文如有不足,欢迎读者指正补充。
二、正文
JavaScript面向对象概述
JavaScript的面向对象模型与其他传统语言存在显著差异。在学习之前,建议暂时摒弃对其他语言面向对象概念的固有认知。JavaScript的OO机制更为强大、灵活,也更具探讨价值。
类和对象
1.类和对象
从传统视角来看,JavaScript属于面向对象语言。它将属性与行为封装在对象中。例如,JavaScript中的数组(array)就是一个由属性和方法(如push、reverse、pop等)组合而成的对象。
代码如下:
var myArray = [1, 2];
myArray.push(3);
myArray.reverse();
myArray.pop();
var length = myArray.length; 这里的关键问题是:上述方法(如push)究竟源自何处?在Java等静态语言中,通常通过class来定义对象结构。然而,JavaScript是一种“无类”(classless)语言,并不存在名为“Array”的类来定义并让所有数组继承这些方法。由于JavaScript的动态特性,我们可以在运行时随意向对象添加方法。例如,以下代码定义了一个表示二维空间坐标的对象,其中包含一个add方法。
代码如下:
var point = {
x : 10,
y : 5,
add: function(otherPoint)
{
this.x = otherPoint.x;
this.y = otherPoint.y;
}
}; 我们希望每个point对象都拥有add方法,同时希望所有point对象共享同一个add方法实现,而无需在每个point对象中重复添加。这就需要引入prototype机制。
关于Prototypes
2.关于Prototypes
在JavaScript中,每个对象都隐含着一个状态(state)属性——即对另一个对象的引用,称为该对象的prototype。我们之前创建的array和point对象同样包含各自的prototype引用。虽然prototype引用是隐含的,但它是ECMAScript标准的一部分,允许我们通过对象的_proto_属性(在Chrome浏览器中可见)来获取它。从概念上讲,对象与prototype的关系可如图2所示:

作为开发者,我们推荐使用Object.getPrototypeOf函数来替代直接访问_proto_属性,以查看对象的prototype引用。在撰写本文时,Object.getPrototypeOf函数已在Chrome、Firefox以及IE9中得到支持。未来更多浏览器也将支持此特性,这已是ECMAScript标准之一。我们可以使用以下代码来验证myArray和我们之前创建的point对象确实引用了两个不同的prototype对象。
代码如下:
Object.getPrototypeOf(point) != Object.getPrototypeOf(myArray); 在文章后续部分,我仍会使用_proto_,主要因为其在图示和表述中更为直观。但请记住,这并非规范做法,Object.getPrototypeOf才是获取对象prototype的推荐方法。
2.1是什么使Prototypes如此特别?
我们已经知道array的push方法源自myArray的prototype对象。图2展示了在Chrome中调用Object.getPrototypeOf方法获取myArray的prototype对象的结果。

图 2
注意myArray的prototype对象中包含了许多方法,如push、pop和reverse,这些正是我们在开头代码中使用的方法。prototype对象是push方法的唯一拥有者,但该方法是如何通过myArray调用到的呢?
代码如下:
myArray.push(3); 要理解其实现机制,首先要明确Prototype并不特殊。Prototype本质上就是对象。我们可以像操作其他JS对象一样,向这些对象添加方法和属性。但Prototype同时也是一个特殊的对象。
Prototype的特殊性源于以下规则:当我们通知JS解释器(runtime)在一个对象上调用push方法或读取某个属性时,解释器首先在该对象本身中查找该方法或属性。如果未找到,解释器会沿着_proto_引用查找该对象prototype中的成员。当我们在myArray中调用push方法时,JS未在myArray对象中找到push,但在myArray的prototype对象中找到了push,从而调用该方法(见图3)。

图 3
上述行为本质上就是对象本身继承了其prototype中的所有方法和属性。在JavaScript中,我们无需使用class来实现这种继承关系。即,一个JS对象从它的prototype中继承特性。
图3还告诉我们,每个array对象都能维护自己的状态(state)和成员。如果需要myArray的length属性,JS将从myArray中直接找到length的值,而不会去prototype中查找。我们可以利用这一特性来“覆盖”(override)一个方法,即将需覆盖的方法(如push)放入myArray自己的对象中。这样做即可有效地隐藏prototype中的push方法。
共享Prototype
3.共享Prototype
Prototype在JavaScript中真正神奇之处在于多个对象能引用同一个prototype对象。例如,我们创建两个数组:
代码如下:
var myArray = [1, 2];var yourArray = [4, 5, 6]; 这两个数组将共享一个相同的prototype对象,以下代码将返回true
代码如下:
Object.getPrototypeOf(myArray) === Object.getPrototypeOf(yourArray); 如果我们在两个数组中调用push方法,JS将调用它们共同的prototype中的push。

Prototype对象在JavaScript中赋予我们继承特性,同时也允许我们共享方法的实现。Prototype也是链式的。换句话说,prototype是一个对象,那么prototype对象也可以拥有一个指向其他prototype对象的引用。从图2中可以看到,prototype的_proto_属性是一个不为null的值,也指向另外一个prototype。当JS开始寻找成员变量(如push方法)时,它将沿着这些prototype的引用检查每一个对象,直到找到该对象或到达链的尾部。这种链式结构进一步增加了JavaScript中继承和共享的灵活性。
接下来你可能会问:我该如何设置自定义对象的prototype引用?例如,对于之前创建的point对象,我们如何将add方法添加到prototype对象中,使所有point对象都能继承它?在回答这个问题之前,我们先了解一下JavaScript中的函数。
关于Funciton
4.关于Funciton
函数在JavaScript中同样也是对象。函数在JavaScript中具有许多重要特性,本文无法一一列举。但像把一个函数赋值给一个变量或是将一个函数当做另外一个函数的参数在当今的JavaScript编程中是很基础的方式。
我们需要关注的是:因为函数是对象,所以它拥有方法、属性和一个prototype对象的引用。我们一起讨论一下下面代码的含义:
代码如下:
// this will return true:
typeof (Array) === "function"
// and so will this:
Object.getPrototypeOf(Array) === Object.getPrototypeOf(function () { })
// and this, too:
Array.prototype != null 第一行代码证明Array在JavaScript中是一个函数。待会儿我们将看到怎样调用Array函数来创建一个新的array对象。
第二行代码证明Array对象和function对象引用相同的prototype,就像我们之前看到的所有的array对象共享一个prototype。
最后一行证明Array函数有一个prototype属性。千万不要将这个prototype属性和_proto_属性混淆了。它们的使用目的和指向的对象都不相同。
代码如下:
// true
Array.prototype == Object.getPrototypeOf(myArray)
// also true
Array.prototype == Object.getPrototypeOf(yourArray); 我们用新学的知识重画之前的图片:

图 5
现在我们要创建一个array对象。其中一种方法就是:
代码如下:
// create a new, empty object
var o = {};
// inherit from the same prototype as an array object
o.__proto__ = Array.prototype;
// now we can invoke any of the array methods ...
o.push(3); 尽管上面的代码看起来不错,但问题是不是每一个JavaScript环境都支持对象的_proto_属性。幸运的是,JavaScript内置一个标准的机制用来创建新对象同时设置对象的_proto_属性,这就是“new”操作符。
代码如下:
var o = new Array();
o.push(3); “new”操作符在JavaScript中有三个重要的任务:首先,它创建一个新的空对象。接着,它设置这个新对象的_proto_属性指向调用函数的prototype属性。最后,执行调用函数同时把“this”指针指向新的对象。如果我们把上面的两行代码展开,将得到以下的代码:
代码如下:
var o = {};
o.__proto__ = Array.prototype;
Array.call(o);
o.push(3); 函数的“call”方法允许你调用一个函数同时指定这个函数里面的"this"指向传入的新对象。当然,我们也想通过上面的方法来创建我们自己的对象来实现对象的继承,这种函数就是我们所熟知的——构造函数。
构造函数
5.构造函数
构造函数是一个有两个独特标识的普通JavaScript函数对象:
1.首字母大写(容易识别)。
2.用new操作符连接来构造新对象。
Array就是一个构造函数——Array函数用new连接、首字母大写。JavaScript中的Array函数是内置的,但任何人都可以创建自己的构造函数。事实上,我们终于到了该为point对象来创建一个构造函数的时候了。
代码如下:
var Point = function (x, y) {
this.x = x;
this.y = y;
this.add = function (otherPoint) {
this.x = otherPoint.x;
this.y = otherPoint.y;
}
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2); 上面的代码中我们使用new操作符和Point函数来来构造一个point对象。在内存中你可以把最终的结果想成图6所表示的样子。

图 6
现在的问题是,add方法存在于每一个point对象中。鉴于我们对prototype的了解,把add方法加到Point.prototype中是一个更好的选择(不必把add方法的代码拷贝到每个对象中)。为了实现这个目的,我们需要在Point.prototype对象上做些修改。
代码如下:
var Point = function (x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function (otherPoint) {
this.x = otherPoint.x;
this.y = otherPoint.y;
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2); 好了!我们已经用prototype实现了JavaScript中的继承!

总结
6.总结
希望能通过这篇文章让你能拨开prototype的迷雾。当然这只是功能强大又灵活的prototype的入门。更多的关于prototype的知识还是希望读者能够自己去探索和发现。
怎么用 Promise 封装 JavaScript sleep 函数实现异步延时
2026-11-05 17:49:14
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
相关标签:
