您的位置:首页 > Web前端 > JavaScript

JavaScript面向对象编程

2010-12-21 14:51 197 查看
JavaScript对象笔记中,记录的都是内置对象的使用,而本文的目的是学习如何创建自己的对象,如何在JS中进行面向对象编程,并且以这些为指导编写实用的工具。

创建自己的对象

要创建自己的对象,可以使用两种方式:

var obj = new  Object();//第一种方式。
var obj =  {};//第二种方法。

给对象添加属性

首先,我们来创建一个cainiao对象:

var cainiao = new  Object();

要给对象的属性赋值,需要使用“对象.属性名”的方法,例如:

cainiao.gender =  'male';
cainiao.yearOfBirth  = 1986;
cainiao.name = 'Chen  Zhe';

像普通的变量一样,JS对象的属性可以是是字符串,数组,数字,甚至可以是对象或者是函数。

给对象添加方法

其实对象的属性如果是函数,那么这个属性就可以叫做对象的方法。我们给cainiao对象设置一个info函数,显示出它的全部属性。代码如下:

cainiao.info =  function(){
 var str = '姓名:'  + this.name + ',性别:'  + this.gender  + ',出生年:' 
 + this.yearOfBirth;
 alert(str);
}
cainiao.info();

效果如下所示:



对象的属性也可以叫做对象的成员。

对象字面量

我们可以使用下面的简略语法定义上面的对象:

var cainiao = {

 gender : 'male',

 yearOfBirth :  1986,

 name : 'Chen Zhe',

 info : function(){

  var str = '姓名:'  + this.name + ',性别:'  + this.gender
  + ',出生年:'  + this.yearOfBirth;

 alert(str);

 }
};

如果调用cainiao.info(),将会得到和上面一样的结果。需要注意的是,属性之间使用逗号隔开,最后一个属性之后没有逗号。

构造函数

我们可能不只有一个cainiao对象,还有gaoshou,甚至还有一个wudi。我们可以像上面一样定义出这三个对象。但是他们之间有一些相似的特点,也就意味着我们会写出三段有很大重复的代码。构造函数可以帮助我们来缩减代码量。

首先,构造函数也是一个函数。雏形如下:

function Person(){}

和定义普通的函数没有什么区别。下面就来向Person构造函数中添加内容:

function  Person(name,gender,yearOfBirth,site){
 this.name = name;
 this.gender =  gender;
 this.yearOfBirth =  yearOfBirth;
 this.site = site;
 this.info =  function(){
  var str = '姓名:'  + this.name + ',性别:'  + this.gender
  + ',出生年:'  + this.yearOfBirth + '网站:'  + this.site;
  alert(str);  
 }
}

这样,构造函数就完成了。我们现在就可以使用如下语句来定义多个对象了:

var cainiao = new  Person('Chen Zhe','male',1986,'www.cainiao8.com');
var gaoshou = new  Person('Li Hai','male',1990,'www.u148.net');
var wudi = new  Person('不详','不知道','保密','bbs.blueidea.net');

调用他们的方法看看是否成功:

cainiao.info();
gaoshou.info();
wudi.info();

效果如下:







不过有一点需要注意,在上面的构造函数中,参数的名字和对象属性的名字是相同的,例如:

this.name = name;

虽然这样是合法的,但是如果要定义私有属性的话就不行了(后面会提),而且看起来比较混乱。所以最好将构造函数修改如下:

function  Person(nameArg,genderArg,yearOfBirthArg,siteArg){

 this.name =  nameArg;

 this.gender =  genderArg;

 this.yearOfBirth =  yearOfBirthArg;

 this.site =  siteArg;

 this.info =  function(){

  var str = '姓名:'  + this.name + ',性别:'  + this.gender 
  + ',出生年:'  + this.yearOfBirth + '网站:'  + this.site;

  alert(str); 
 }
}

给对象添加静态方法

什么是静态方法呢?就是直接添加在某个对象上的属性或者方法,它仅对一个对象有效,例如:

cainiao.skill =  '会点XHTML,CSS,现在还会说JavaScript了';

添加完这个语句之后,cainiao就拥有了skill属性。但是出自同一个构造函数的gaoshou和wudi却不受任何影响。当然了,我们可以给它们也加上这个属性:

gaoshou.skill =  '精通HTML,CSS,JS,FLEX,PHP,.NET,Linux编程,Perl,Ruby,XXX...';
wudi.skill =  '能用啤酒瓶盖与沙子手工打磨出CPU等PC基本零部件。';

同样,我们也可以给构造函数添加静态方法,但是这同样不会影响使用该构造函数定义的对象。例如:

Person.showName =  function(){

 alert(this.name);
};
Person.showName();
cainiao.showName();

我们给Person定义了一个showName函数,但是它只对构造函数Person本身有用(但是没有什么实际意义),如果在cainiao对象上调用这个方法将会出错。

给对象定义私有成员

公开的对象?在前面定义的所有对象,其所有的属性都是暴露在外的,可以随便访问,甚至修改也没问题。例如我们可以通过cainiao.name直接访问到对象的名字等等属性。如果我们希望有一个属性对外保密,就可以给对象定义私有属性:

function  Person(nameArg,genderArg,yearOfBirthArg,siteArg,privacyArg){
 ……
 var privacy =  privacyArg;
 ……
}
var cainiao = new  Person('Chen Zhe','male',1986,'www.cainiao8.com','有10跟脚趾头');
alert(cainiao.privacy);

对构造函数作出一定修改之后,我们给cainiao对象添加了一个privacy属性,如果试图alert它的值的话将会显示undefined。

下面再来看看私有方法,如果我们对Person构造函数作出如下修改:

function  Person(nameArg,genderArg,yearOfBirthArg,siteArg,privacyArg){
 function  insideOnly(){

  alert('我只在对象内部可以调用!');

 }
}

那么insideOnly将不能被外部所访问,如果试图执行这个函数将会出错。需要注意的是,私有方法并没有绑定到对象上,它的this关键字也并不指向对象。如果需要在私有方法内引用对象,那么可以使用如下方法:

function  Person(nameArg,genderArg,yearOfBirthArg,siteArg,privacyArg){
 ……
 var myOwner = this;

 function  insideOnly(){

  alert(myOwner.name);

 }
}

首先在构造函数中定义一个myOwner变量,将this关键字赋给它,之后就可以在私有函数里使用这个变量来访问对象本身了。

特权方法

私有成员不能在对象的外部访问,不能访问的对象有什么用?

我们可以使用特权方法来访问这些私有属性。所谓特权方法就是使用this关键字定义的函数,例如:

function  Person(nameArg,genderArg,yearOfBirthArg,siteArg,privacyArg){
 ……
 this.showPrivacy =  function(){

 var str = '秘密:'  + this.name + privacy + '!';

 alert(str);

 };
}
var cainiao = new  Person('Chen Zhe','male',1986,'www.cainiao8.com','有10跟脚趾头');
cainiao.showPrivacy();

我们首先给Person构造函数添加了一个showPrivacy特权方法,方法中使用了私有变量privacy的值。最后我们在cainiao对象上调用该方法,结果如下:



我们成功地利用特权函数访问了私有成员。

prototype浅析

什么是prototype呢?它的中文意思是原型、蓝本,也可以理解为模子。还是来看看实例吧:

首先,我们还是使用Person构造函数定义一个cainiao对象:

var cainiao = new  Person('Chen Zhe','male',1986,'www.cainiao8.com');

然后我们再给Person的prototype添加一些属性,来看看它对cainiao对象的影响。

Person.prototype.jump  = function(){

 var str =  this.name + '已经离地5厘米了!';

 alert(str);
}
cainiao.jump();

我们给Person.prototype定义了一个jump方法,之后再在cainiao对象上调用这个方法。效果如下:



可见,对Person.prototype的修改同样会影响到使用Person定义的对象。即使对象在prototype改变以前已经定义了。其实我们从例子里就可以看出来,我们先使用Person定义了cainiao对象,之后才修改了Person的prototype。但是修改对cainiao同样产生了影响,这就是JavaScript的动态继承机制。

失灵的prototype

首先,我们要否定上面这句话“对Person.prototype的修改同样会影响到使用Person定义的对象。”请看如下代码:

Person.prototype = {

 mark:'Person.Proto'
}
var cainiao = new  Person('Chen Zhe','male',1986,'www.cainiao8.com');
Person.prototype = {

 mark:'Person.NewProto'
}
alert(cainiao.mark);

alert的输出会是什么呢?根据上面的经验,在alert之前,对Person.prototype的最后修改将它的mark属性赋值为'Person.NewProto'。但是实际的输出结果如下:



这不是bug,而且各个浏览器里都一样。原因分析如下:

Person.prototype = {

 mark:'Person.Proto'
}

一段首先创建一个对象字面量,并且赋值给Person.prototype,我们称这个对象字面量为Proto。

var cainiao = new  Person('Chen Zhe','male',1986,'www.cainiao8.com');

在创建cainiao对象并且按照构造函数初始化对象的同时,会悄悄给cainiao对象设置一个内部属性,我们暂且将其命名为xxx,并且将其赋值为Person.prototype的,也就是上面提到的对象字面量Proto,JavaScript的动态继承也就是由这个xxx实现的。(这个JavaScript的内部实现机制不是我拍脑门想出来的,是Netscape的工作人员的一篇文章里提到的,下面也会用程序来证明这个流程。)然后,

Person.prototype = {

 mark:'Person.NewProto'
}

这里我们将创建另外一个对象字面量NewProto,并且赋值给Person.prototype。到这里就很明显了,尽管我们修改了Person.prototype,但是并没有影响到cainiao对象。当我们访问cainiao.mark的时候,它首先在自己内部找,找不到就会去找xxx,自然就找到了Proto,而非NewProto。

那么xxx这个实现动态继承的关键究竟是什么呢?在FireFox中它是__proto__,有以下代码为证。至于在IE中xxx是什么我还没找到。

Person.prototype = {

 mark:'Person.Proto'
}
var cainiao = new Person('Chen Zhe','male',1986,'www.cainiao8.com');
cainiao.prototype = {

mark:'Person.NewProto'
}
alert(cainiao.mark);
alert(cainiao.__proto__.mark);

两次alert的结果都是一样的,都是Proto。

alert(cainiao.__proto__===Person.prototype);

结构为true。

以上代码经过测试适用于FireFox和chrome。NetScape应该也可以,IE不行。

个人观点:

Person.prototype = {}的方式最好只在定义构造函数的时候使用一次,而且要紧跟着构造函数写,以后修改prototype的时候都使用Person.prototype.protertyA = ‘valueA’的形式。

最后的构造函数

我们在上面看到了定义对象,以及设这和修改其属性的各种方法,我们可以在构造函数中定义属性,可以在对象外部定义(静态)属性,也可以在对象的prototype中定义属性。下面是我使用的大致格式:

function  Person(nameArg,genderArg,yearOfBirthArg,siteArg,privacyArg){

//公有属性

 this.name =  nameArg;

 this.gender =  genderArg;

 this.yearOfBirth =  yearOfBirthArg;

 this.site =  siteArg;

//私有属性

 var privacy =  privacyArg;

//特权方法

 this.showPrivacy =  function(){

 };
}
Person.prototype = {

//公有方法

 info : function(){ 
 },

 func:function(){
 }
}

对象的继承

继承是面向对象的关键概念,关于JavaScript的继承,我读到了几篇文章。首先是大名鼎鼎的Classical Inheritance in JavaScript中文版),不过我觉得最好的,最容易理解的还是?Netscape 在1997年的一篇文章——Object Hierarchy and Inheritance in JavaScript中文版),文中的代码都解释的比较清楚。
Classical Inheritance in JavaScript是一篇比较难懂的文章,菜鸟看起来比较费劲。不过高手就是不一样,淘宝UED的岁月如歌老师不但给出了代码的注释,而且通过调试指出了原文中的bug,强!
尽管如此,还是不推荐这篇文章,因为让JavaScript支持经典的继承是在利用一种语言去模仿其它语言的编程模式,记得某期《程序员》里有一篇文章说过,这是一个大忌。而且原文中实现继承的方法对JavaScript进行了改造,使用method方法向对象添加新属性,个人认为这不太合适。文中最后已经补充了作者的新想法:“I now see my early attempts to support the classical model in JavaScript as a mistake。”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: