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 相关文章推荐
代码触发js事件(click、change)示例应用
Dec 13 Javascript
使用impress.js制作幻灯片
Sep 09 Javascript
微信企业号开发之微信考勤百度地图定位
Sep 11 Javascript
详解JavaScript中基于原型prototype的继承特性
May 05 Javascript
基于JavaScript Array数组方法(新手必看篇)
Aug 20 Javascript
JavaScript获取键盘按键的键码(参照表)
Jan 10 Javascript
js实现简单的二级联动效果
Mar 09 Javascript
微信小程序 动画的简单实例
Oct 12 Javascript
npm 常用命令详解(小结)
Jan 17 Javascript
vue.js 2.*项目环境搭建、运行、打包发布的详细步骤
May 01 Javascript
使用axios发送post请求,将JSON数据改为form类型的示例
Oct 31 Javascript
详解JavaScript中的Object.is()与"==="运算符总结
Jun 17 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
PHP+FLASH实现上传文件进度条相关文件 下载
2007/07/21 PHP
php str_replace的替换漏洞
2008/03/15 PHP
PHP反射使用实例和PHP反射API的中文说明
2014/07/02 PHP
CodeIgniter配置之routes.php用法实例分析
2016/01/19 PHP
PHP5.3连接Oracle客户端及PDO_OCI模块的安装方法
2016/05/13 PHP
php生成毫秒时间戳的实例讲解
2017/09/22 PHP
jQuery 常见开发使用技巧总结
2009/12/26 Javascript
javascript原型模式用法实例详解
2015/06/04 Javascript
用JS生成UUID的方法实例
2016/03/30 Javascript
总结JavaScript设计模式编程中的享元模式使用
2016/05/21 Javascript
Javascript小技能总结(推荐)
2016/06/02 Javascript
JavaScript获取URL中参数querystring的方法详解
2016/10/11 Javascript
微信小程序wx.previewImage预览图片实例详解
2017/12/07 Javascript
swiper自定义分页器使用方法详解
2020/09/14 Javascript
JS解析后台返回的JSON格式数据实例
2018/08/06 Javascript
解决vue路由后界面没有变化,但是链接有的问题
2018/09/01 Javascript
nodejs中函数的调用实例详解
2018/10/31 NodeJs
用vuex写了一个购物车H5页面的示例代码
2018/12/04 Javascript
jquery无缝图片轮播组件封装
2020/11/25 jQuery
es6中reduce的基本使用方法
2019/09/10 Javascript
Vue 封装防刷新考试倒计时组件的实现
2020/06/05 Javascript
[45:34]完美世界DOTA2联赛PWL S3 Rebirth vs CPG 第一场 12.18
2020/12/19 DOTA
python设置windows桌面壁纸的实现代码
2013/01/28 Python
Python数据结构之图的应用示例
2018/05/11 Python
Python SQL查询并生成json文件操作示例
2018/08/17 Python
Django之Mode的外键自关联和引用未定义的Model方法
2018/12/15 Python
使用Django简单编写一个XSS平台的方法步骤
2019/03/25 Python
pandas计算最大连续间隔的方法
2019/07/04 Python
英国袜子店:Sock Shop
2017/01/11 全球购物
新加坡最受追捧的体验平台:Hapz
2018/01/01 全球购物
方正Java笔试题
2014/07/03 面试题
班组长安全职责
2014/01/05 职场文书
部队学习十八大感言
2014/01/11 职场文书
促销活动方案模板
2014/02/24 职场文书
创业计划书之牛肉汤快餐店
2019/10/08 职场文书
云服务器部署 Web 项目的实现步骤
2022/06/28 Servers