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 相关文章推荐
网上应用的一个不错common.js脚本
Aug 08 Javascript
JavaScript关于select的相关操作说明
Jan 13 Javascript
jquery 图片轮换效果
Jul 29 Javascript
innerHTML属性,outerHTML属性,textContent属性,innerText属性区别详解
Mar 13 Javascript
微信小程序使用第三方库Immutable.js实例详解
Sep 27 Javascript
Javascript中arguments对象的详解与使用方法
Oct 04 Javascript
JS回调函数基本定义与用法实例分析
May 24 Javascript
判断jQuery是否加载完成,没完成继续判断的解决方法
Dec 06 jQuery
微信小程序实现列表下拉刷新上拉加载
Jul 29 Javascript
vue与bootstrap实现简单用户信息添加删除功能
Feb 15 Javascript
vue自定义表单生成器form-create使用详解
Jul 19 Javascript
vue项目中监听手机物理返回键的实现
Jan 18 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&mysql(一)
2006/10/09 PHP
php visitFile()遍历指定文件夹函数
2010/08/21 PHP
详解EventDispatcher事件分发组件
2016/12/25 PHP
PHP实现求两个字符串最长公共子串的方法示例
2017/11/17 PHP
浅析PHP中的闭包和匿名函数
2017/12/25 PHP
PHP迭代器和迭代的实现与使用方法分析
2018/04/19 PHP
非常不错的功能强大代码简单的管理菜单美化版
2008/07/09 Javascript
JS.GetAllChild(element,deep,condition)使用介绍
2013/09/21 Javascript
使用javascript控制cookie显示和隐藏背景图
2014/02/12 Javascript
JS使用正则表达式除去字符串中重复字符的方法
2015/11/05 Javascript
设置点击文本框或图片弹出日历控件的实现代码
2016/05/12 Javascript
js接收并转化Java中的数组对象的方法
2016/08/11 Javascript
vue.js初学入门教程(1)
2016/11/03 Javascript
jQuery实现倒计时重新发送短信验证码功能示例
2017/01/12 Javascript
js实现自动图片轮播代码
2017/03/22 Javascript
Angular4的输入属性与输出属性实例详解
2017/11/29 Javascript
JS函数节流和函数防抖问题分析
2017/12/18 Javascript
JavaScript实现预览本地上传图片功能完整示例
2019/03/08 Javascript
在VUE中实现文件下载并判断状态的方法
2019/11/08 Javascript
请求时token过期自动刷新token操作
2020/09/11 Javascript
深入理解Python3中的http.client模块
2017/03/29 Python
Python爬取十篇新闻统计TF-IDF
2018/01/03 Python
OpenCV2.3.1+Python2.7.3+Numpy等的配置解析
2018/01/05 Python
简单实现Python爬取网络图片
2018/04/01 Python
python pandas 组内排序、单组排序、标号的实例
2018/04/12 Python
python远程调用rpc模块xmlrpclib的方法
2019/01/11 Python
HTML5新增的8类INPUT输入类型介绍
2015/07/06 HTML / CSS
Feelunique中文官网:欧洲最大化妆品零售电商
2020/07/10 全球购物
教师考核评语
2014/04/28 职场文书
化工操作工岗位职责
2014/04/29 职场文书
操行评语大全
2014/04/30 职场文书
自主招生推荐信范文
2014/05/10 职场文书
党员剖析材料范文
2014/12/18 职场文书
2015年学生会主席工作总结
2015/04/21 职场文书
Pytorch 如何加速Dataloader提升数据读取速度
2021/05/28 Python
python 字典和列表嵌套用法详解
2021/06/29 Python