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小类库获取浏览器的高度和宽度信息
Jan 15 Javascript
js调用AJAX时Get和post的乱码解决方法
Jun 04 Javascript
js history对象简单实现返回和前进
Oct 30 Javascript
使用JavaScript的ActiveXObject对象检测应用程序是否安装的方法
Apr 15 Javascript
Node.js 的异步 IO 性能探讨
Oct 08 Javascript
JS模式之简单的订阅者和发布者模式完整实例
Jun 30 Javascript
jQuery实现别踩白块儿网页版小游戏
Jan 18 Javascript
使用cropper.js裁剪头像的实例代码
Sep 29 Javascript
Jquery遍历筛选数组的几种方法和遍历解析json对象,Map()方法详解以及数组中查询某值是否存在
Jan 18 jQuery
JavaScript ECMA-262-3 深入解析(一):执行上下文实例分析
Apr 25 Javascript
使用Vant完成通知栏Notify的提示操作
Nov 11 Javascript
JavaScript代码实现简单计算器
Dec 27 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查看请求头信息获取远程图片大小的方法分享
2013/12/25 PHP
wordpress网站转移到本地运行测试的方法
2017/03/15 PHP
通过event对象的fromElement属性解决热区设置主实体的一个bug
2008/12/22 Javascript
input 输入框内的输入事件详细分析
2010/03/17 Javascript
Javascript核心读书有感之表达式和运算符
2015/02/11 Javascript
轻量级的原生js日历插件calendar.js使用指南
2015/04/28 Javascript
JavaScript模块规范之AMD规范和CMD规范
2015/10/27 Javascript
Bootstrap学习笔记之css样式设计(2)
2016/06/07 Javascript
AngularJS教程之MVC体系结构详解
2016/08/16 Javascript
JavaScript动态绑定详解
2017/09/14 Javascript
JavaScript解决浮点数计算不准确问题的方法分析
2018/07/09 Javascript
脚手架vue-cli工程webpack的作用和特点
2018/09/29 Javascript
新手快速上手webpack4打包工具的使用详解
2019/01/28 Javascript
JavaScript使用ul中li标签实现删除效果
2019/04/15 Javascript
javascript实现贪吃蛇游戏(娱乐版)
2020/08/17 Javascript
JavaScript浅层克隆与深度克隆示例详解
2020/09/01 Javascript
pyramid配置session的方法教程
2013/11/27 Python
python爬虫教程之爬取百度贴吧并下载的示例
2014/03/07 Python
利用numpy+matplotlib绘图的基本操作教程
2017/05/03 Python
Python中使用支持向量机(SVM)算法
2017/12/26 Python
Python+OpenCV实现车牌字符分割和识别
2018/03/31 Python
python中的decorator的作用详解
2018/07/26 Python
python,Django实现的淘宝客登录功能示例
2019/06/12 Python
Python实现K折交叉验证法的方法步骤
2019/07/11 Python
Python 字符串、列表、元组的截取与切片操作示例
2019/09/17 Python
python实现一个猜拳游戏
2020/04/05 Python
简单了解Python字典copy与赋值的区别
2020/09/16 Python
html5图片上传预览示例分享
2014/04/14 HTML / CSS
ktv总经理岗位职责
2014/02/17 职场文书
2014离婚协议书范文
2014/09/10 职场文书
居委会四风问题个人对照检查材料
2014/09/25 职场文书
农业项目投资意向书
2015/05/09 职场文书
2015年公务员试用期工作总结
2015/05/28 职场文书
Java spring单点登录系统
2021/09/04 Java/Android
MySQL 数据 data 基本操作
2022/05/04 MySQL
js面向对象编程OOP及函数式编程FP区别
2022/07/07 Javascript