JavaScript面向对象之Prototypes和继承


Posted in Javascript onJuly 12, 2012

一、前言

本文翻译自微软的牛人Scott Allen Prototypes and Inheritance in JavaScript ,本文对到底什么是Prototype和为什么通过Prototype能实现继承做了详细的分析和阐述,是理解JS OO 的佳作之一。翻译不好的地方望大家修改补充。

二、正文
JavaScript中的面向对象不同于其他语言,在学习前最好忘掉你所熟知的面向对象的概念。JS中的OO更强大、更值得讨论(arguably)、更灵活。

1.类和对象
JS从传统观点来说是面向对象的语言。属性、行为组合成一个对象。比如说,JS中的array就是由属性和方法(如push、reverse、pop 等)组合成的对象。

var myArray = [1, 2]; 
myArray.push(3); 
myArray.reverse(); 
myArray.pop(); 
var length = myArray.length;

问题是:这些方法(如push)从哪里来的?一些静态语言(比如JAVA)用class来定义一个对象的结构。但是JS是没有”class"(classless)的语言,没有一个叫做“Array"的类定义了这些方法给每个array去继承。因为JS是动态的,我们可以在需要的时候随意的往对象中添加方法。例如,下面的代码定义了一个对象,该对象表示二维空间中的坐标,里面有一个add方法。
var point = { 
x : 10, 
y : 5, 
add: function(otherPoint) 
{ 
this.x = otherPoint.x; 
this.y = otherPoint.y; 
} 
};

我们想让每一个point对象都有一个add方法。我们也希望所有的poin对象共享一个add方法,而不必把add方法加到所有的point对象当中。这就需要让prototype登场了。

2.关于Prototypes
JS中每一个对象都有一个隐含的属性(state)——对另一个对象的引用,称为对象的prototype.我们上面创建的array和point当然也都含有各自prototype的引用。prototype引用是隐含的,但是它是ECMAScript已实现的,允许我们使用对象的_proto_(在Chrome中)属性来获取它。从概念上理解我们可以认为对象和prototype的关系就像下图所表示的:

JavaScript面向对象之Prototypes和继承

作为开发者,我们将用Object.getPrototypeOf 函数来替代_proto_属性来查看对象的prototype引用。在写这篇文章的时候,Object.getPrototypeOf这个函数已经在Chrome,firefox,还有IE9中提供了支持。在未来还会有更多的浏览器来支持这一特性,这已经是ECMAScript的标准之一。我们可以用以下代码来证明myArray和我们之前创建的point对象确实引用了两个不同的prototype对象。
Object.getPrototypeOf(point) != Object.getPrototypeOf(myArray);

在文章接下来的部分,我还将使用到_proto_,主要是因为_proto_在图示和句子里里比较直观。但要记住这不是规范的,Object.getPrototypeOf才是用来获取对象prototype的推荐方法。

2.1是什么使Prototypes如此特别?

我们已经知道了array的push方法来自myArray的prototype对象。图2是Chrome中的一个截图,我们调用Object.getPrototypeOf方法来取得myArray的prototype对象。

JavaScript面向对象之Prototypes和继承

图 2

注意到myArray的prototype对象里包含了很多方法,比如push、pop还有reverse这些我们在开头代码里使用过的。prototype对象才是push方法的唯一拥有者,但这个方法是如何通过myArray调用到的呢?

myArray.push(3);

要想明白它是怎样实现的,第一步是认清Protytype一点儿不特殊。Prototype就是一些对象。我们可以给这些对象添加方法、属性,像其他任何的JS对象一样。但同时Prototype也是一个特殊的对象。

Prototype的特殊是因为下列规则:当我们通知JS我们想要在一个对象上调用push方法或是读取某个属性的时候,解释器(runtime)首先去寻找这个对象本身的方法或属性。如果解释器没有找到该方法(或属性)就会沿着_proto_引用去寻找对象的prototype中的各个成员。当我们在myArray中调用push方法,JS没有在myArray对象中找到push,但是在myArray的prototype对象中找到了push,即调用了该push方法(图3)。

JavaScript面向对象之Prototypes和继承

图 3

我所描述的这种行为本质上就是对象本身继承了 它的prototype中的所有方法和属性。我们在JS中不需要用class来实现这种继承关系。即,一个JS对象从它的prototype中继承特性。

图3还告诉我们每个array对象都能维护自己的状态(state)和成员。如果我们需要myArray的length属性,JS将从myArray中找到length的值而不会去prototype中去寻找。我们能运用这个特性来“override"一个方法,即,将需覆盖的方法(像push)放到myArray自己的对象中。这样做就可以有效的将prototype中的push方法隐藏掉。

3.共享Prototype
Prototype在JS中真正神奇的地方是多个对象能引用同一个prototype对象。比如,我们创建两个数组:

var myArray = [1, 2];var yourArray = [4, 5, 6];

这两个数组将共享一个相同的prototype对象,下面的代码将返回true
Object.getPrototypeOf(myArray) === Object.getPrototypeOf(yourArray);

如果我们在两个数组中调用push方法,JS将调用他们共同的prototype中的push。

JavaScript面向对象之Prototypes和继承

Prototype对象在JS中给我们这种继承的特性,它们也允许我们共享方法的实现。Prototype也是链式的。换句话说,prototype是一个对象,那么prototype对象也可以拥有一个指向别的prototype对象的引用。从图2中可以看到prototype的_proto_属性是一个不为null的值也指向另外一个prototype.当JS开始寻找成员变量的时候,比如push方法,它将沿着这些prototype的引用检查每一个对象直到找到这个对象或达到链的尾部为止。这种链的方式更增加了JS中继承和共享的灵活性。

接下来你也许会问:我怎样设置自定义对象的prototype引用?比如,我们之前创建过的对象point,我们怎样加入一个add方法到prototype对象中,让所有的point对象都能继承它?在我们回答这个问题之前,我们先了解一下JS中的函数.

4.关于Funciton
函数在JS中同样也是对象。函数在JS中有很多重要的特性,在此文章中我们不能一一列举。但像把一个函数赋值给一个变量或是将一个函数当做另外一个函数的参数在当今的JS编程中是很基础的方式。

我们需要关注的是:因为函数是对象,所以它拥有方法、属性和一个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在JS中是一个函数。待会儿我们将看到怎样调用Array函数来创建一个新的array对象。

第二行代码证明Array对象和function对象引用相同的prototype,就像我们之前看到的所有的array对象共享一个prototype。

最后一行证明Array函数有一个prototype属性。千万不要将这个prototype属性和_proto_属性混淆了。它们的使用目的和指向的对象都不相同。

// true 
Array.prototype == Object.getPrototypeOf(myArray) 
// also true 
Array.prototype == Object.getPrototypeOf(yourArray);

我们用新学的知识重画之前的图片:

JavaScript面向对象之Prototypes和继承

                                            图 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);

尽管上面的代码看起来不错,但问题是不是每一个JS的环境都支持对象的_proto_属性。幸运的是,JS内置一个标准的机制用来创建新对象同时设置对象的_proto_属性,这就是“new”操作符。
var o = new Array(); 
o.push(3);

“new”操作符在JS中有三个重要的任务:首先,它创建一个新的空对象。接着,它设置这个新对象的_proto_属性指向调用函数的prototype属性。最后,执行调用函数同时把“this”指针指向新的对象。如果我们把上面的两行代码展开,将得到以下的代码:
var o = {}; 
o.__proto__ = Array.prototype; 
Array.call(o); 
o.push(3);

函数的“call”方法允许你调用一个函数同时指定这个函数里面的"this"指向传入的新对象。当然,我们也想通过上面的方法来创建我们自己的对象来实现对象的继承,这种函数就是我们所熟知的——构造函数。

5.构造函数
构造函数是一个有两个独特标识的普通JS函数对象:

1.首字母大写(容易识别)。

2.用new操作符连接来构造新对象。

Array就是一个构造函数——Array函数用new连接、首字母大写。JS中的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所表示的样子。

JavaScript面向对象之Prototypes和继承

图 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实现了JS中的继承!

JavaScript面向对象之Prototypes和继承

6.总结
希望能通过这篇文章让你能拨开prototype的迷雾。当然这只是功能强大又灵活的prototype的入门。更多的关于prototype的知识还是希望读者能够自己去探索和发现。
Javascript 相关文章推荐
JavaScript 嵌套函数指向this对象错误的解决方法
Mar 15 Javascript
解析javascript 数组以及json元素的添加删除
Jun 26 Javascript
判断javascript的数据类型(示例代码)
Dec 11 Javascript
动态添加option及createElement使用示例
Jan 26 Javascript
table行随鼠标移动变色示例
May 07 Javascript
JavaScript实战之菜单特效
Aug 16 Javascript
express+mockjs实现模拟后台数据发送功能
Jan 07 Javascript
Vue数据监听方法watch的使用
Mar 28 Javascript
基于JS实现带动画效果的流程进度条
Jun 01 Javascript
Vue配合iView实现省市二级联动的示例代码
Jul 27 Javascript
layui从数据库中获取复选框的值并默认选中方法
Aug 15 Javascript
vue多页面项目中路由使用history模式的方法
Sep 23 Javascript
jQuery $.get 的妙用 访问本地文本文件
Jul 12 #Javascript
js原型链原理看图说明
Jul 07 #Javascript
jqTransform form表单美化插件使用方法
Jul 05 #Javascript
解决遍历时Array.indexOf产生的性能问题
Jul 03 #Javascript
JavaScript中变量提升 Hoisting
Jul 03 #Javascript
JavaScript Scoping and Hoisting 翻译
Jul 03 #Javascript
jquery 中多条件选择器,相对选择器,层次选择器的区别
Jul 03 #Javascript
You might like
一次编写,随处运行
2006/10/09 PHP
PHP用户指南-cookies部分
2006/10/09 PHP
PHP编程中字符串处理的5个技巧小结
2007/11/13 PHP
解析php中如何直接执行SHELL
2013/06/28 PHP
php根据操作系统转换文件名大小写的方法
2014/02/24 PHP
作为程序员必知的16个最佳PHP库
2015/12/09 PHP
PHP实现长文章分页实例代码(附源码)
2016/02/03 PHP
php简单实现批量上传图片的方法
2016/05/09 PHP
PHP实现可添加水印与生成缩略图的图片处理工具类
2018/01/16 PHP
JS延迟加载(setTimeout) JS最后加载
2010/07/15 Javascript
如何确保JavaScript的执行顺序 之jQuery.html并非万能钥匙
2011/03/03 Javascript
firefox下jquery iframe刷新页面提示会导致重复之前动作
2012/12/17 Javascript
常用的几段javascript代码分享
2014/03/25 Javascript
jQuery中size()方法用法实例
2014/12/27 Javascript
纯JS实现旋转图片3D展示效果
2015/04/12 Javascript
jQuery 1.9.1源码分析系列(十)事件系统之主动触发事件和模拟冒泡处理
2015/11/24 Javascript
javascript移动开发中touch触摸事件详解
2016/03/18 Javascript
你了解vue3.0响应式数据怎么实现吗
2019/06/07 Javascript
js校验开始时间和结束时间
2020/05/26 Javascript
[06:10]6.81新信使新套装!给你一个炫酷的DOTA2
2014/05/06 DOTA
[17:45]DOTA2 HEROES教学视频教你分分钟做大人-军团指挥官
2014/06/11 DOTA
关于django 数据库迁移(migrate)应该知道的一些事
2018/05/27 Python
Python数据持久化shelve模块用法分析
2018/06/29 Python
python 中文件输入输出及os模块对文件系统的操作方法
2018/08/27 Python
python 堆和优先队列的使用详解
2019/03/05 Python
Python多版本开发环境管理工具介绍
2019/07/03 Python
Python爬虫之urllib基础用法教程
2019/10/12 Python
Django使用list对单个或者多个字段求values值实例
2020/03/31 Python
在python中使用pyspark读写Hive数据操作
2020/06/06 Python
sklearn的predict_proba使用说明
2020/06/28 Python
会走动的图形html5时钟示例
2014/04/27 HTML / CSS
斯洛伐克最大的婴儿食品和用品网上商店:Feedo.sk
2020/12/21 全球购物
高三自我评价
2014/02/01 职场文书
个人社会实践自我鉴定
2014/03/24 职场文书
2014年人力资源部工作总结
2014/11/19 职场文书
《植物妈妈有办法》教学反思
2016/02/23 职场文书