Javascript面向对象详解(第一部分,后面的高级特性陆续跟进中,敬请关注)




一直想写一篇关于Javascript面向对象的文章,最近终于动工了,本来以为不会写的很长,可是后来发现有很多东西要写,大家先看着这前面的一部分吧,后面有更多的高级特性陆续跟进中,放心,绝对不是太监贴啊,对Javascript对象不太了解或者没有了解的人可以仔细看看哦,有错误之处大家多多指正哦,本人水平有限 


(1)为什么要面向对象

在十年前或者也许更晚的时候,javascript都是一种被人当作玩具来使用的语言,大多时候,没有人乐于深入研究它的特性,而只是用它来实现各种花里胡哨的特效来炫耀自己的技术。一时之间,各种网站中都充斥了噪音般的所谓的特效,直至今日,仍然有许多人没有清醒过来。但是Ajax的出现可能促使了大量的设计师开始关注Javascript,从而扭转了互联网的表现。的确,互联网是一个方便用户的地方,而不是一个炫耀个人技巧的地方,简单易用才是我们应该坚持的原则。

互联网趋向于简单易用,那Javascript还有什么意义呢?很多人说毕竟它只是一个玩具啊!和flash,silverlight比起来它的表现形式实在是太单调了,可是Javascript远没有想象的那么幼稚,它是一门发展了将近二十年的各方面都很完善的语言,它的表现丰富,虽然在图形方面它有着一定的缺点,但是在与html的交互中它却有着极其强大的功能,它虽然在很多特性方面都很简单,但是它的确是一门非常灵活的语言。

对于Javascript,很多初学者喜欢将其作为一种类似c的函数式语言来使用,这种写法的特点就是除了全局变量就是函数,这样写代码也未尝不可,但是它有着先天的弊端,首先它会创建大量的全局变量,全局变量过多会造成内存泄漏(如果没有在使用完之后手动回收的话),而且全局变量在脚本中的任何位置都是可见的,很多时候会不小心在某个函数里定义了一个和全局变量名字相同的变量,这样会造成混淆,这种混淆是致命的。第二个缺点也是为什么现在所有的语言都以面向对象作为自己的卖点的原因,因为当程序逻辑变得越来越复杂的时候,代码关系越来越复杂,想象有1000行代码,其中有50个函数,这50个函数之间的关系又相互依赖,当你写到第1001行的时候突然想到要向某个关系中再插入一个函数,你记得这个关系链在哪里吗?其中的关系又如何呢?如果你能用这种方式来写代码,而又可以记住无数复杂的关系,即使睡一觉也不会忘记的话,你就是天才。可惜天才是没有这样写代码的,高手写代码会考虑很多东西,例如:松耦合,重用性,内存回收,闭包特性,封装,下面会涉及到一些相关的东西。

(2)对象与函数

Javascript的对象是什么呢?让我们先来想想在其他声称面向对象的语言中它是什么吧,其实面向对象在我理解来就是一种组织代码的方式,它可以封装一些属性和方法到一个类,这个类大多时候是现实世界的抽象表达,然后它可以继承,可以多态,可以实例化。

在Javascript中,我认为它的面向对象和其他语言的面向对象有着本质的区别,因为在Javascript中没有类的概念,一切都是对象,对,一切都是对象,但是一切又可以写成完全没有对象的影子的形式,在Javascript中的对象和面向对象的对象有着概念上的不同,Javascript中的对象就是一个基本的实体,例如:html元素中的一个按钮,它就是一个按钮对象,没有实例化自任何类,但是你可以灵活地操作它,可以凭空生成它,也可以动态删除它,看起来的话,我们感觉这更符合我们理解世界的方式,我们不需要任何抽象,一个我们能看到的东西就是一个对象,我们无需想象它原来的抽象的样子,只需要知道它就是一个实实在在的对象就行。而在其他语言中,你要创建一个对象就必须先创建一个抽象的表示(类),然后实例化,这更像是一种组织代码的方式,而不是一种操作现实世界的体验,所以相比于其他的语言,我感觉Javascript的对象更充满了让人兴奋的元素。

与很多编程语言类似,Javascript中的顶级对象是Object(),它是所有对象的父类对象,所以在Javascript中可以用 var myobject=new Object()来定义一个对象,但是实际上这种写法没有什么意义,因为Javascript中的变量是弱类型的,在初始化的时候即使你定义var myobject=0,在稍后你仍然可以将一个对象赋给myobject变量,这种特性是所有的脚本语言都基本具有的特性,它使你不必太关心操纵的变量的类型.

Javascript是一门很奇特而灵活的语言,从Function()顶层对象就可以看出来,Function对象是顶层对象,这意味着什么呢?上例子:

在Javascript中更多时候我们是这样来创建一个基本的对象的:

var myobject=function(param1,param2){
  this.name=param1;
  this.age=param2;
  this.showmsg=function(){
  alert("name:"+this.name+"<br />"+"age:"+this.age);
  }
}


这个基本的对象拥有两个属性和一个方法。如果你以前没有接触过面向对象的Javascript的话,你一定会说我定义了一个函数,但是你也许会对于函数内的this指针感到迷惑,事实上,我们的确定义了一个函数,但是在Javascript中函数是顶级对象(而不是其他语言中的只充当封装一小部分功能的那种函数),上面的代码相当于定义了一个对象,而this则指向了你定义的myobject对象。

关于this指针所指的对象事实上不在本教程的讨论范围之内,但是在一个对象内部它指向的是自己所属的对象,而在某个事件处理函数里,它指向的是接受事件的某个DOM元素,具体可以去google一下。

至于Javascript内部是如何实现对象机制的,或许不必关注太多,而只需要了解有哪些定义对象的方法和如何操作对象即可,事实上,Javascript内部的对象就是一个关联数组,由以名称作为键的字段和方法组成,这从如何遍历一个Javascript对象就可以看出来,就拿上面定义的myobject来说吧,遍历方法如下:

  var mynew=new myobject('a','b');//首先初始化一个对象,像这种带有参数的对象需要先初始化
  for(obj in myobject){
  alert(myobject[obj]);//弹出对象包含的所有元素
  }


结果将弹出三次窗口,分别是 a,b,function(){……}

定义对象(函数)的方法有很多种,下面列举其中的几种:


1.function myobject(){}
2.var myobject=function(){}
3.var myobject=new Object();
  myobject.name="";
  myobject.age="";


4.JSON方式,这是一种比较特殊但是又常用的方式,并且JSON可以用于与服务器交互信息,它的格式显而易见,结构清晰,具有比xml先天的优势,并且它是纯字符串,可以方便地在服务器和客户端之间传送,具体可以去google搜索一下,因为涉及到了一个较大的领域,而我们本文的目的是介绍一些Javascript的高级特性,故暂不提及JSON

(3)从一个对象创建实例?

从对象创建一个实例说起来貌似是很简单的东西,是啊,基本在所有的语言中,都是用new关键字来创建实例的,Javascript当然也不例外,可是关于对象的引用问题,你考虑过么?在php4中曾经采取了拷贝对象的方法来产生更多的对象,也就是说每个对象都是一个独立体,而不是通常的一系列对象共享类的方法,也就是为每个对象都会复制一份函数,这种工作方式造成在php4时代很多初学者对这个概念混淆不清,造成许多编码漏洞,而在Javascript同样存在同样的问题,而在客户端的这种对象的复制方式,极其容易造成内存泄漏,因为每次产生新的实例都会复制所有的属性和方法,占用大量的内存.

然而在Javascript中事实上是有解决方案的,在普通方式下,我们是这样产生一个实例的:


var myobject=function(param1,param2){
  this.name=param1;
  this.age=param2;
  this.showmsg=function(){
    alert("name:"+this.name+"<br />"+"age:"+this.age);
  }
}
var objectone=new myobject('a','b');
var objecttwo=new myobject('a','b');
var objectthree=new myobject('a','b');


看起来不错,用起来也不错,对象之间互相没有任何干扰,也能正常完成操作,一切看起来理所当然,但是你每次产生一个新对象的时候,脚本引擎都会给对象复制一份属性和方法,有没有觉得这样很浪费内存呢?在大型Javascript应用中首先考虑的就是内存问题,本文后面也会涉及这方面问题,所以这种方法应该杜绝使用.

正确的使用方法是 用prototype关键字来定义一个类的方法或者属性

例如:

 1 
 2   var myobject=function(param1,param2){
 3     this.name=param1;
 4     this.age=param2;
 5   }
 6   myobject.prototype.showmsg=function(){
 7     alert("name:"+this.name+"<br />"+"age:"+this.age);
 8   }
 9   var objectone=new myobject('a','b');
10   var objecttwo=new myobject('a','b');
11   var objectthree=new myobject('a','b');


这样的话你创建的对象之间可以共用方法,也就是msg()函数只定义了一次,其他的对象公用这一个方法,而不是复制出自己的方法.

(4)扩展内建的类

和其他面向对象的语言一样,Javascript也有自己的内建类,包括Array,Date,String之类的,这种特性允许你可以编写自己常用的方法来附加在这些内建类上,然后将这些方法包装到一个js文件中,以后调用即可免去多次编写的麻烦.Javascript内建的方法是有限的,很多其他编程语言的特性它都没有包含,这时候就需要我们自己去编写这些方法,下面具体说明:

例如,很多时候我们需要Javascript中的数组拥有打乱顺序的功能,这在游戏或者其他应用中经常用到,那你就可以用这种方法来实现:

//生成随机数字函数



function rand(x){
  return Math.ceil(Math.random()*x);
}
//生成随机数组的一个扩展方法
Array.prototype.random=function(){
  for(var i=(this.length-1);i>1;i--){
    var j=rand(this.length-1);
    var cache=this[i];
    this[i]=this[j];
    this[j]=cache;
    }
}


在上面的例子中,我们给数组元素添加了一个方法random(),它的作用就是随机打乱数组,在你的脚本中添加了这段语句之后,你就可以给任何的数组对象使用这个语句了,使用方法很简单,例如某个数组变量名叫做:myarr.你执行myarr.random()后,myarr数组的元素就被打乱顺序了,这样的扩展是不是很有用呢.

(5)对象的反射

反射是一种对象的机制,它允许你在完全不了解对象的情况下了解它的属性和方法,通常情况下,程序员对于自己所操纵的对象是如何组成的是非常了解的,但是在某些特殊情况下使用某个其他人写的复杂的对象的时候,我们需要快速了解这个对象的属性和方法,就需要用到反射的机制,当然反射的应用并不是局限于此,这里并不介绍它的应用领域,而只是介绍反射在Javascript中的使用方法.

首先我们可能想知道在某个对象中是否存在一个特定的属性或者方法,这时候我们可以简单地测试它:


if(myobject.someproperty){
……
}


这样可以简单测试,但是存在弊端,因为如果这个属性的返回值正好是 false,0,null,那么这样的测试将出现问题,更严谨的方式是这样的:


if(typeof(myobject.someproperty)!="undefined"){
……
}


在Javascript中如果没有定义一个对象或者变量的话,它总是返回 undefined类型.

也可以用其他的内建类来缩小测试范围:

if(myobject instanceof Object){
}


instanceof 是用来测试内建类或者自定义类的操作符,内建类指Array,Funtion,Date之类的内建类.这里有两个问题,首先用来测试JSON方式创建的对象的时候,因为它总是返回一个Object或者Array,所以不能确定它具体归属的自定义类.第二个问题是有关各个内建类的继承关系,例如:Function和Array都是继承自Object类的,所以如果你在代码中如果测试某个Array对象的话,如果你先测试它是否是Object,将返回true,如果测试它是否是Array的话,它也会返回true,这取决于你的测试顺序.

一个更简单而有用的方法,是遍历一个对象的所有属性和方法来快速了解一个对象的内部状态:

function myobject(){
  this.name="name";
  this.age="age";
  this.sex="sex";
  this.func=function(){
  }
}
var myobj=new myobject();
for(var i in myobj){
  alert(myobj[i]);
}


这样将顺序弹出对象的所有属性和方法

(6)Javascript接口的实现

接口是一种面向对象的普遍机制,它指定一些行为但是不提供具体的实现,这在实际应用,特别是大型项目中是很有用的一种机制,它提供了一个实现来在不同的对象之间进行协调通信,它通常提供一种类似契约的机制,通过这个契约,程序员不必去考虑某个接口的内部实现,也不必考虑它在其他库代码中的实现.正如"接口"这个词的普遍概念,听到这个词,或许很多人会想到在硬件系统中某个接口的概念,在硬件系统中,接口也是提供一种向外的实现,外部设备可以通过接口与这个设备通信而不必知道其内部实现,同时它又可以与很多外部接口通信,因为只要在外部设备实现了与接口的统一就可以与该接口协调应用.

接口在Java或者c#中可以实现多重继承,它将多个类所需继承的一些公共方法抽象出来,接口封装的就是这些公共方法的行为规范,接口规定在其子类中必须实现它所有的方法,所以接口看起来更像一种强制的规定,这种规定使我们能够严格遵守约定,从而实现多个继承类之间的统一.

在Java中,假设有一个形状接口,它规定了作为一个形状所必须拥有的方法,而继承自它的类(例如:圆形类,方形类)必须实现所有的方法,之后假设在某个普通类中我们定义一个方法来计算面积:


public double addAreas(Shape s1,Shape s2)
{
   return s1.getArea()+s2.getArea();
}


我们用形状接口定义了参数,这使我们可以自由更改参数的具体类型,例如如果我们实现了一个circle类,它继承自shape接口,那么我们可以如此调用这个方法:......addArea([circle] s1,[circle] s2);

这并不是一个完整而正确的程序,而只是要表达一个意思,就是参数可以用任何实现了接口的类的实例来代替,这就是接口的有用之处,你无需知道形状接口下面继承了什么类,但是可以肯定的是它们都实现了接口的方法,我们在程序中只需要使用接口定义参数类型即可,以后可以用接口的任何子类来代替参数.

在强类型的编程语言中,接口还算一个比较清晰而且较容易实现的功能,而在弱类型的Javascript中实现接口有着前所未有的困难,因为在Javascript中无法预定义变量的类型,像上面的方法只能定义成这样:

 
  function  addAreas(s1,s2)
  {
     return s1.getArea()+s2.getArea();
  }


我们可以向参数传入任何类型的对象,而不是接口所提供的严格的约定那样.其实在Javascript中实现真正的接口是不可能的,更何况在Javascript中实现继承也是一件不容易的事情,在Javascript中的接口实际上就是在方法内测试传过来的参数,在此我们给其附加一些约定,这样,我们就实现了一种约定,如果传过来的参数不符合这个约定,方法就不能正常执行,这只是一种类似接口的约定方式.

我们可以定义很多约定,常用的包括检查参数对象中是否包含某个方法.一种复杂一点但是完美无缺的方案是给系统内建的Object顶级对象附加一个方法来判断其内是否包含某个方法:


Object.prototype.implement=function(funcname){
   return this&&this[funcname]&&this[funcname] instanceof Funtion;
}


这里应用了前面介绍的反射机制来进行判断对象自身的内容.

这样在方法addAreas(s1,s2)中我们就可以约定s1,s2必须包含getArea()的方法了

写法如下:if(s1.implement("getArea")) ......

还可以限制必须是数字类型:


function isNum(arg){
   return parseFloat(arg)!=NaN;
}


parseFloat()将返回参数中的数字部分,NaN是一个非数字的类型,但是因为值为"64any"的字符串也会返回时数字,所以我们将用parseFloat()先去掉所有的非数字部分,然后判断,是否是数字类型.

其他的限制可以自己探索一下,基本原理基本相同,当我们给方法加的限制越多的时候就限制了一个越严谨的接口.



未完,待续 

转载于:https://www.cnblogs.com/mier/archive/2009/05/05/1449290.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值