JavaScript面向对象之Prototypes和继承

一、前言

本文翻译自微软的牛人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的关系就像下图所表示的:

作为开发者,我们将用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对象。

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

图 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。

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

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

图 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所表示的样子。

图 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中的继承!

6.总结
希望能通过这篇文章让你能拨开prototype的迷雾。当然这只是功能强大又灵活的prototype的入门。更多的关于prototype的知识还是希望读者能够自己去探索和发现。

(0)

相关推荐

  • Javascript基于对象三大特性(封装性、继承性、多态性)

    Javascript基于对象的三大特征和C++,Java面向对象的三大特征一样,都是封装(encapsulation).继承(inheritance )和多态(polymorphism ).只不过实现的方式不同,其基本概念是差不多的.其实除三大特征之外,还有一个常见的特征叫做抽象(abstract),这也就是我们在一些书上有时候会看到面向对象四大特征的原因了. 一.封装性     封装就是把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),

  • 前端开发必须知道的JS之原型和继承

    一. 原型与构造函数 Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型.这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型.譬如普通函数: 复制代码 代码如下: function F(){ alert(F.prototype instanceof Object) //true; } 构造函数,也即构造对象.首先了解下通过构造函数实例化对象的过程. 复制代码 代码如下: function A(x){ this.x

  • Javascript面向对象编程(三) 非构造函数的继承

    今天是最后一个部分,介绍不使用构造函数实现"继承". 一.什么是"非构造函数"的继承? 比如,现在有一个对象,叫做"中国人". 复制代码 代码如下: var Chinese = { nation:'中国' }; 还有一个对象,叫做"医生". 复制代码 代码如下: var Doctor ={ career:'医生' } 请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个&

  • JS继承--原型链继承和类式继承

    什么是继承啊?答:别人白给你的过程就叫继承. 为什么要用继承呢?答:捡现成的呗. 好吧,既然大家都想捡现成的,那就要学会怎么继承! 在了解之前,你需要先了解构造函数.对象.原型链等概念...... JS里常用的两种继承方式: 原型链继承(对象间的继承)类式继承(构造函数间的继承) 原型链继承: 复制代码 代码如下: //要继承的对象var parent={name : "baba" say : function(){ alert("I am baba");}} //

  • javascript 面向对象编程基础:继承

    我们看到这里继承的概念是多么的直白,"拷贝一个类的prototype 到另外一个类",好,Code is cheap,看代码: function class1() { } function class2() { } class2.prototype = class1.prototype; class2.moreProperty1 = " class 2 additional string " ; class2.moreMethod1 = function () {

  • JavaScript使用原型和原型链实现对象继承的方法详解

    本文实例讲述了JavaScript使用原型和原型链实现对象继承的方法.分享给大家供大家参考,具体如下: 实际上JavaScript并不是一门面向对象的语言,不过JavaScript基于原型链的继承方式.函数式语法,使得编程相当灵活,所以可以利用原型链来实现面向对象的编程. 之前对JavaScript一直都是一知半解,这两天看了一下原型链这一块知识,综合练习了一下JavaScript的对象继承方式. 以下就是原型链和原型的关系,引用网上的一张图 在Javascript中,每个函数都有一个原型属性p

  • Javascript面向对象编程(二) 构造函数的继承

    今天要介绍的是,如何生成一个"继承"多个对象的实例. 比如,现在有一个"动物"对象的构造函数, 复制代码 代码如下: function Animal(){ this.species = "动物"; } 还有一个"猫"对象的构造函数, 复制代码 代码如下: function Cat(name,color){ this.name = name; this.color = color; } 怎样才能使"猫"继承&

  • JavaScript的原型继承详解

    JavaScript是一门面向对象的语言.在JavaScript中有一句很经典的话,万物皆对象.既然是面向对象的,那就有面向对象的三大特征:封装.继承.多态.这里讲的是JavaScript的继承,其他两个容后再讲. JavaScript的继承和C++的继承不大一样,C++的继承是基于类的,而JavaScript的继承是基于原型的. 现在问题来了. 原型是什么?原型我们可以参照C++里的类,同样的保存了对象的属性和方法.例如我们写一个简单的对象 复制代码 代码如下: function Animal

  • javascript 面向对象全新理练之继承与多态

    1 又是几个基本概念 为什么要说又呢? 在讨论继承时,我们已经列出了一些基本概念了,那些概念是跟封装密切相关的概念,今天我们要讨论的基本概念,主要是跟继承与多态相关的,但是它们跟封装也有一些联系. 1.1 定义和赋值 变量定义是指用 var a; 这种形式来声明变量. 函数定义是指用 function a(...) {...} 这种形式来声明函数. var a = 1; 是两个过程.第一个过程是定义变量 a,第二个过程是给变量 a 赋值. 同样 var a = function(...) {};

  • javascript的函数、创建对象、封装、属性和方法、继承

    一,function 从一开始接触到js就感觉好灵活,每个人的写法都不一样,比如一个function就有N种写法 如:function showMsg(){},var showMsg=function(){},showMsg=function(){} 似乎没有什么区别,都是一样的嘛,真的是一样的吗,大家看看下面的例子 复制代码 代码如下: ///----------------------------------------------------------------------------

  • javascript 面向对象,实现namespace,class,继承,重载

    由于组里项目大多的javascript,css等客户端工作是另一同事在负责,该同事又特忙无法重构,老大也就只是提建议并未立即实施重构.但是我前些日子也改过些许客户端的小bug,确实那代码看得让人有些云里雾里,不知身在哪山,轻易不敢动代码,于是就自己动手鼓捣起我曾又爱又恨的javascript来,自己写一个简单的js实现namespace,继承,重载等面向对象的特性.欢迎拍砖灌水 .定义namespace Namesapce.js 复制代码 代码如下: Namespace = new Object

  • js对象的构造和继承实现代码

    复制代码 代码如下: <script> //定义js的user对象 function User(name,age){ this.name=name, this.age=age, this.getName=function(){ return this.name; }, this.getAge=function(){ return this.age; } } //实例化一个对象 var use=new User("aa",21); alert(use.name); alert

  • javascript 面向对象全新理练之原型继承

    首先创建一个父类的实例化对象,然后将该对象赋给子类的 prototype 属性. 这样,父类中的所有公有实例成员都会被子类继承.并且用 instanceof 运算符判断时,子类的实例化对象既属于子类,也属于父类. 然后将子类本身赋值给它的 prototype 的 constructor 属性.(注意:这里赋值的时候是没有 () 的!) 这一步是为了保证在查看子类的实例化对象的 constructor 属性时,看到的是子类的定义,而不是其父类的定义. 接下来,通过对 o.method1() 调用的

随机推荐