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

JavaScript的面向对象程序设计

2008-03-04 22:53 387 查看
JavaScript的面向对象程序设计
引言:JavaScript是基于对象的,为什么这么说呢。作为脚本语言,操控DHTML等网页内部元素不应该太复杂,为了简化程序设计,JavaScript中创建了document等重要对象,这些对象是JavaScript的固有对象,用起来十分方便。document对象不妨简单的理解为用户界面对象,使用JavaScript编程时常常围绕着它,而不需要烦锁的定义、创建等过程。所以JavaScript更多考虑对现有对象的控制,而对对象的创建扩展的能力较差。即便如此,JavaScript仍可创建对象,只是没有C++那么完整。
一、本文名词解释:变量、串对象、对象变量(对象)、原型、原型对象、实例、函数对象(构造函数、构造器)。
为了形象的说明问题,引入名词“构造器”,构造器指函数对象,在JS中构造器也是构造函数。
对象变量常称为对象。当变量是个对象时,实际上它是一个指针,该指针指向对象的实际内存位置。对象指针可以复制,内存对象不可复制。要实现真正的复制是一件很麻烦的事情,可以想象,一个对象指针指向document时,要想复制它就很困难,因为document内部有众多元素并且存在循环引用的问题。
JS中字串不是对象,它可复制,但String对象则不同,它是对象。串变量、数值变量等都不是对象,在赋值时是复制,对象的赋值是指针的复制,而不是实际对象的复制,JS里指针的复制可理解为引用。
关于构造器、构造器的原型(类的原型)、实例、引用原型:构造器是一个函数,函数中有个特殊成员,名为prototype,它是原型对象。函数的prototype对象的成员就是类的属性、方法的定义部分,但portotype的成员不是构造器属性、方法的定义,实例是用“new 函数名()”等方法创建的某一个实际的对象。创建对象时须由构造器创建,实例继承类的构造器portotype中的属性及方法,为了实现继承,实例以隐藏方式引用原型对象,这样当调用继承的属性、方法时不需要指明prototype,除此以外,构造器还初始化类,可动态创建类的其它属性及方法。函数的原型对象就好像可供某工程施工者或本工程项目的用户使用的公用资料、设备的仓库,构造器就象图纸、施工方案等。类的实例就像是根据图纸建起来的房子(工程案例)。一个实例创建后,它就使用了一定的资源,资源的使用量与构造器的设计有关。一个实例的创建依赖构造器,创建后的实例称之为某某类(某某构造器)的实例。

二、类的原型与类的实例的创建
在C++中,使用class来定义类。例:
//-----------
class A{
public:
int p;
A(){ p=3; }
m(){ p++; }
};
A b; //创建实例b
//-----------
1、在JS中,函数不仅仅是函数,是一个function对象实例。例:
//-----------
function a(){…}//创建了一个function实例a
a.p=3;//为a添加属性p
c=a;//c变量是对function实例a的引用
c();//与a();调用同一函数
//-----------
2、在JS中function既用于定义函数又可用于创建类,用于创建类时,它取代class定义类,这时它就充当了类的构造器的作用,类的名称就是函数名本身。例:
//-----------
function a(){//a类的构造器
this.p=3; //创建public属性
this.m=function(x){this.p+=x;} //创建public方法
}
b=new a();//创建a类的实例
b.m(2);//调用方法
alert(b.p); //结果是5
//-----------
b=new a();语句创建a类的实例。用new创建了一个空对象,new后的构造函数a()对其初始化。
上例中this.p=3;是给当前对象动态创建属性p,p不是a的属性,却是b的属性,a的属性并不会复制给b。构造函数执行时通过this关键字实现对b动态创建属性p,初值为3,用b.p取得该属性。
3、this是一个特殊的对象,表示当前函数的父对象。就是说,谁的成员函数被调用,该函数中的this就是谁。这样,通过this就可将父对象移到函数体内部来使用,生存期限为函数执行结束。JS的全局变量、函数直接隶属于window。它们的父对象是window。调用一个函数时,不指明父对象,函数中的this指window。例:
//-----------
var t=3;
alert(this.t);//显示3
alert(window.t);//显示3
//-----------
//-----------
function a(){ this.p=3; }
a();//或window.a(); a()中的this指window,结果是undefined
b=new Array();
b.c=a;
b.c(); //a中的this指b,结果是3
//-----------
//-----------
function a(){ alert(this.b);}
c=new Array("cc");
c.b=3;
c.m=function(){ a(); }
c.m(); //显示undefined,程序中a();语句没用指明父对象,所以a()中的this指当前脚本的祖宗对象window。
//-----------
一个比较特殊的情况:new a()创建了对象,此时该对象是a()的父对象,该对象只有this可引用得到,a()执行后自动将this返回。但试图调用b.a()是错误的,因为构造函数只能执行一次,执行后就不在是b的成员了。
例:
4、用new创建对象的细节:
使用new a()创建实例时,首先创建空对象,并隐藏引用构造器的的原型对象,使得本实例继承原型对象中的所有成员。我们不能直接访问这个隐藏引用,对象建后,内部引用也建立,这时如果重建构造器中的原型对象,该构造器中的原型对象引用仍是原来的,关于prototype的问题下文将详细说明。其次是执行构造函数,对该对象初始化。其三,将新对象返回。

三、如何创建私有属性呢?
当函数内部的对象被注册为外部变量时,函数体内的其它变量成为副本保留。注意,父对象或用new创建的对象也会被注册到函数体内。
//-----------
function a(){
var p=3; //创建private属性,它是函数内部的变量
function cc(){}//创建private方法
this.m=function(x){p+=x; return p;} //创建public方法
}
b=new a();//创建实例
alert(b.m(2)); //结果是5
//-----------
上例中p为对象b的私有属性,对象的私有属性、方法只能被其成员方法调用。每次用new创建对象时,构造函数内部的变量及函数都会产生副本,供new创建的对象的成员函数使用。如果创建多个对象,就产生多个副本。每个副本当然会占用一定的内存空间,如何减少副本所占的空间呢?有两种方法可解决,其一是对函数做引用处理,其二使用继承的办法。这里先讲一下前者,后者涉及继承问题,比较麻烦,下文再叙。
当把成员函数移到构造函数外,构造函数在创建方法时使用函数作引用即可减少内存占用。
function mm(x){ this.p+=x;}
function a(){
this.p=3; //创建private属性
this.m=mm; //创建public方法,由于mm是个函数对象,这个赋值只是个引用。
}
b=new a();//创建实例
b.m(2);
alert(b.p); //结果是5
一般情况下,内存占用了就不会主动释放。
这种方式建立的成员函数无法访问private成员。

四、构造函数中能不能有返回值?
在C++中,构造函数是不能有返回值的。而在javascript中,用new a()已经创建实例,那么返回值又有何用?其实,当返回值为对象时,new创建的实例不被采用,而使用返回的对象。如:你返回document对象、数组对象、String对象(不是串) 、用new创建的对象等。
function a(){
var th=new Array();
th.p=3;
return th;
}
b=new a(); //等价于b=a();使用new时多创建了一个继承a.prototype的空对象。private空间不变。
使用上例原理创建对象有不少好处:创建public属性、方法是在th中完成的而不是在this中完成的。this用起来虽然方便但容易造成混乱,当程序比较长是,本人不大喜欢this。private属性、方法的创建则与前面讲的一样。本例中由于a()有返回值,b接收到的是th对象,b就是th对象的引用。与new a()生成的对象无关,a对象中的prototype也不会被继承的。有意思的是,当a()返回值是对象时,a()的私有空间没有释放,它做为b的private空间,因此这里的b=new a();与b=a();是一样的,都能访问其私有空间。再推广,只要函数内的对象被返回到函数体外部或直接赋值(引用)给外部变量,那么该函数每次执行的private空间就不会被释放,供这个外部对象变量使用,从语句的形式上看,当函数执行时,只要让外部变量引用内部对象,该函数就已充当构造器的作用了。例:
//------------------
var kk;
function a(){
var c=3;
var th=new Array();
th.m=function(){alert(c);}
kk=th;
}
a();
kk.m(); //显示3
//------------------
当调用函数创建实例,与此同时函数内部对象也被其它外部变量引用时,那么该实例与这个外部变量共用同一个私有空间。例:
var kk;
function a(){
var c=3;
th=new Array();
kk=th;
th.m=function(){c++; return c;}
this.m=function(){c++; return c;}
}
b=new a();
alert(b.m()); //显示4
alert(kk.m()); //显示5

五、使用prototype实现继承(静态创建成员)
//------------------
function a(){ a.prototype.p=3; }
b=new a();//创建实例
//------------------
portotype是function对象特有的属性,类的原型放在function的prototype中,我们称prototype为原型对象,实例继承原型对象中的所有成员,实例能过隐藏引用了构造器中的原型对象实际继承,也就是说原型对象中所有成员都可以被实例直接使用,上例中b.p值为3(因为实例的原型引用是隐藏的,无须写出prototype),而b.prototype.p则不存在。
用例子说明:prototype对象与c++中的public有一定的相似之处,在prototype中定义公有属性、事件或方法。a.prototype.p与a.p不是同一个变量,prototype中的属性及方法是类的原型,在new创建时,a.prototype.p并没有复制给b.prototype.p,因为prototype是function对象特有的,不是普通new生成的对象固有的,但new创建的对象内部隐藏引用了构造器中的原型对象,这样新建的对象就可以通过这个内部引用访问原型对象中的成员。b.p也不是a.prototype.p的副本,JS在读取属性时,先在自身对象中找属性,如果找不到则在它隐藏引用的原型对象中找。因此,当b.p未定义时,b.p就是a.prototype.p的引用而不是副本;当b.p定义后,b.p就不再是a.protype.p的引用。显然执行b.p=4是创建了b.p,并不会改变原型a.prototype.p的值。因此,原型中方法、属性具能“透明”特点,由该原型创建的实例都可“透明”的读取或调用当时new中的原型对象的成员而不能直接更改它,除非你引用构造器中的原型来修改。这里强调一点:构造器中有原型对象的引用,实例中也有原型对象的隐藏引用,这两个引用当然指向同一个原型对象,如果你在创建实例后修改了构造器中的原型对象引用,那么实例中的引用的原型对象与构造器中引用的原型对象将不是同一对象。prototype的这些的特性与继承没有太大的区别。
构造器中的portotype里有constructor成员,它引用构造器本身。
实例有个constructor属性,它也是继承来的,它是对构造器的引用。例:
function a(){
this.p=3;
}
a.p=4;
kk=new a();
alert(kk.constructor.p); //结果是4

六、提高prototype应用的效率
function a(){
a.prototype.p=3;
a.prototype.m=function(){ a.prototype.p=4;}
}
本例中定义了方法m()。由于a()也是构造函数,所以在每次用new创建时实例时都会被执行一次,在执行过程中又创建函数对象 function(){ a.prototype.p=4;},并赋值给m,虽然m只是引用该函数对象,但是这个函数对象是新建的,也就是说每执行一次a()就为m方法创建了一个新的函数对象,这样是比较耗资源的。如果把m方法的函数对象放在a()之外,m对它做引用就可节省内存。例:
function abc(){ a.prototype.p=4;}
function a(){
a.prototype.p=3;
a.prototype.m=abc;
}
或:
function a(){
a.prototype.p=3;
}
a.prototype.m=function(){a.prototype.p=4;}//这样更好
有得也有失:内存节约了,但没能以内联方式书写程序,程序看上去稍微乱了一点。
以下举个错误的例子:
function a(){
a.prototype.p=3;
}
a..m=function(){a.prototype.p=4;}
b=new a();
b.m();//错误的调用
a..m=function(){a.prototype.p=4;}语句给函数对象a添加了方法m(),这个方法不会被继承,仅对象a自身可使用,只有在prototype中的属性及方法才会被继承。使用new和function关键字均创建对象。一个创建函数对象,一个创建实例。

七、创建子类,即通过某基类创建一个新类:
//------------------
function a(){ this.p2=2; }
a.prototype.p=1;

function a2(){ this.p3=3;}
a2.prototype=new a(); //a2的原型对象由a生成,当然constructor也被继承
a2.prototype.constructor=a2;//修改constructor,让它指向自身才时正确的
a2.prototype.p4=4;

b=new a2();
//------------------
a2.prototype含有a的所有属性
b通过内部原型引用,查找a2.prototype中的成员
a2.prototype也是用new得来的对象,当某成员找不到时,也同样通过a2.prototype内部的原型引用查找a.prototype中的成员。这样b继承了a类与a2类所有的成员。如果a、a2中重名成员,则a2优每
构造器的标准引用就应是引用其自身,a2.prototype.constructor=a2;的作用是使构造器引用标准化,因为a2.prototype=new a();造成a2.prototype.constructor引用a。
六、大括号定义对象,数组定义类
略:
var _object_types = {
'function' : Function,
'boolean' : Boolean,
'regexp' : RegExp,
// 'math' : Math,
// 'debug' : Debug,
// 'image' : Image;
// 'undef' : undefined,
// 'dom' : undefined,
// 'activex' : undefined,
'vbarray' : VBArray,
'array' : Array,
'string' : String,
'date' : Date,
'error' : Error,
'enumerator': Enumerator,
'number' : Number,
'object' : Object
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: