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 相关文章推荐
JScript中的undefined和"undefined"的区别
Mar 08 Javascript
Ajax搜索结果页面下方的分页按钮的生成
Apr 05 Javascript
json的定义、标准格式及json字符串检验
May 11 Javascript
javascript 获取HTML DOM父、子、临近节点
Jun 16 Javascript
js判断浏览器版本以及浏览器内核的方法
Jan 20 Javascript
jquery UI Datepicker时间控件的使用方法(加强版)
Nov 07 Javascript
零基础轻松学JavaScript闭包
Dec 30 Javascript
Node.Js中实现端口重用原理详解
May 03 Javascript
Vue.js更改调试地址端口号的实例
Sep 19 Javascript
VeeValidate 的使用场景以及配置详解
Jan 11 Javascript
vue-cli和v-charts实现可视化图表过程解析
Oct 08 Javascript
Vue中 axios delete请求参数操作
Aug 25 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
WML,Apache,和 PHP 的介绍
2006/10/09 PHP
php学习笔记 面向对象的构造与析构方法
2011/06/13 PHP
php设计模式之单例模式使用示例
2014/01/20 PHP
php检测图片主要颜色的方法
2015/07/01 PHP
thinkphp制作404跳转页的简单实现方法
2016/09/22 PHP
JavaScript 学习小结(适合新手参考)
2009/07/30 Javascript
jQuery实现智能判断固定导航条或侧边栏的方法
2016/09/04 Javascript
AngularJs ng-route路由详解及实例代码
2016/09/14 Javascript
Vue 固定头 固定列 点击表头可排序的表格组件
2016/11/25 Javascript
react-router browserHistory刷新页面404问题解决方法
2017/12/29 Javascript
JavaScript数组去重算法实例小结
2018/05/07 Javascript
详解JavaScript 中 if / if...else...替换方式
2018/07/15 Javascript
vue子路由跳转实现tab选项卡
2019/07/24 Javascript
layui固定下拉框的显示条数(有滚动条)的方法
2019/09/10 Javascript
Vue设置长时间未操作登录自动到期返回登录页
2020/01/22 Javascript
JS实现简易计算器
2020/02/14 Javascript
Vue中通过vue-router实现命名视图的问题
2020/04/23 Javascript
mapboxgl区划标签避让不遮盖实现的代码详解
2020/07/01 Javascript
[41:52]2018DOTA2亚洲邀请赛3月29日小组赛B组Effect VS Secret
2018/03/30 DOTA
Windows下PyMongo下载及安装教程
2015/04/27 Python
Python制作刷网页流量工具
2017/04/23 Python
python创建文件备份的脚本
2018/09/11 Python
Django 多环境配置详解
2019/05/14 Python
深入浅析Python科学计算库Scipy及安装步骤
2019/10/12 Python
opencv python如何实现图像二值化
2020/02/03 Python
pip安装提示Twisted错误问题(Python3.6.4安装Twisted错误)
2020/05/09 Python
使用Python实现音频双通道分离
2020/12/25 Python
什么是smarty? Smarty的优点是什么?
2013/08/11 面试题
2014年幼儿园元旦活动方案
2014/02/13 职场文书
三好学生个人先进事迹材料
2014/05/17 职场文书
工伤事故赔偿协议书范文
2014/09/24 职场文书
学校后勤工作总结2015
2015/05/15 职场文书
养成教育工作总结
2015/08/13 职场文书
聊聊golang中多个defer的执行顺序
2021/05/08 Golang
Python进行区间取值案例讲解
2021/08/02 Python
numpy array找出符合条件的数并赋值的示例代码
2022/06/01 Python