关键字:javascript,脚本,面向对象
作者:城市刀客 [ chouchy ]
Javascript本身是一种解释型的、基于对象的脚本语言。随着AJAX等概念的提出,Javascript在B/S开发中的重要性日见凸显。为了更好地组织我们的代码,实现脚本的封装、继承和多态,在此,我试图研究一下Javascript实现面向对象编程的方法。
一、Javascript的基于对象 VS 面向对象
Javascript为我们提供了一些内置的对象,例如Array、Boolean、Date、Function、Math、Number、Object、RegExp、Error 以及 String 对象等,用户可以直接从这些类实例化出对象,并使用其中的属性和方法。这体现了Javascript是基于对象的编程语言。一种面向对象的语言,应当具有封装,继承,多态等基本特性。而Javascript并没有直接提供实现这些特性的方法。在下面的文字中,我将描述如何间接实现Javascript面向对象编程的特征。
二、Javascript面向对象所提供的基础支持
1、在Javascript中,我们可以通过创建构造函数(对象构造器),赋予对象属性,并在合适的时候赋予方法。然后通过使用 new 关键字来调用构造函数,从而创建一个新对象实例。
下面给出了使用构造函数的示例。
var myObj = new Object(); //创建没有属性的通用对象。
var myDate = new Date(1949, 9, 1); //创建一个 Date 对象。
function Student(id,name,age){
this.id = id;
this.name = name;
this.age = age;
}
var myStu = new Student(1,"chouchy",18); //创建一个用户定义的对象Student。
2、在编写构造函数时,可以使用原型对象(它本身是所有构造函数的一个属性)的属性来创建属性和方法。原型属性和方法将按引用复制给类中的每个对象,因此它们都具有相同的值。可以在一个对象中更改原型属性的值,新的值将覆盖默认值,但仅在该实例中有效。属于这个类的其他对象不受此更改的影响。
下面给出了使用自定义构造函数Student的示例,注意 this 关键字的使用。
Student.prototype.school = "SWNU";//为Student对象增加了一个属性:school
Student.prototype.study = studyMethod; //为Student对象增加了一个方法:studyMethod
function studyMethod(){
//实现方法:studyMethod
alert("Good Good Study,Day Day Up!");
}
测试:
alert(myStu.id+":"+myStu.name+":"+myStu.age);//显示myStu对象的属性:id,name,age
myStu.study;//调用myStu的方法:studyMethod
按照这个原则,可以给预定义的构造函数(都具有原型对象)定义附加属性和方法。
三、如何实现Javascript面向对象编程?
我们可以使用Javascript的自定义构造函数和构造函数的属性--原型对象来进行。下面我分别给出实现的模型。需要注意的是:javascript中并没有给我们提供诸如class,interface,extends,implement等关键字,也没有public,private,protected这些访问控制定义。我们只有用function,prototype去实现。
1、封装。
所谓封装,就是我们通过将类的成员变量声明为私有的,然后提供一个或多个共有的方法实现对该成员变量的访问或修改。实现封装可以实现隐藏类的实现细节,限制对属性的不合理修改,便于我们维护代码等优点。我们先定义一个简单的类:man,该类包含一个name属性和一个set/get方法:
<script language="JavaScript">
<!--
function Man(){
var $name="unknow";//注意变量$name的定义方式,后面解释。
this.getName = getName;
this.setName = setName;
function getName(){
alert($name);
}
function setName(name){
$name=name;
}
}
var man=new Man();
//alert(man.myName);这句将显示'undefined'
man.getName();//显示unknow
man.setName("chouchy");//显示chouchy
man.getName();
//-->
</script>
通过这个简单的示例,我们实现了创建一个自定义类,而且定义了一个属性$name,注意到$name的声明,它并没有绑定到这个类,因此只具有局部作用域,生命期仅在本类中,我们只能够通过我们给定的set/get方法访问(调用的时候不加this关键字)。同理,没有绑定的方法也只能在类的内部调用。因此,我们可以通过这样的形式实现类的封装。
如果对 alert(man.myName);这句将显示'undefined' 不明白,请看我的另一篇文章《javascript:数据类型Null与Undefined 》
2、继承。
继承是面向对象语言中扩展已有类型的一种有效途径,JavaScript没有提供用于实现继承的extends关键字或者“:”操作符,但是,由于它是一种动态语言,可以在需要的时候添加属性和方法。以下示例中继承机制的实现均基于这种原理。
<script language="JavaScript">
<!--
function supClass(str)
{
this.str=str;
this.changeStr=changeStr;
function changeStr()
{
//str="Changed by function changeStr()";
alert("supClass.str was changed by supClass.changeStr()!");
}
}
function subClass(str)
{
this.parent=new supClass("parent");//继承了supClass的所有属性和方法;
this.str = str;
this.changeStr2=changeStr2;
function changeStr2()
{
//str="Changed by function changeStr2()";
alert("subClass.str was changed by subClass.changeStr2()!");
}
}
var subclass=new subClass("child");
alert(subclass.parent.str);//访问父类的属性;
alert(subclass.str);//访问子类属性;
subclass.parent.changeStr();//访问父类的方法;
subclass.changeStr2();//访问子类方法;
//-->
</script>
我们也可以写成这样:
-----------------------------------------------------------------------------------------------------------
<script language="JavaScript">
<!--
function supClass(str)
{
this.str=str;
this.changeStr=changeStr;
function changeStr()
{
//this.str="Changed by function supClass.changeStr()";
alert("supClass.str was changed by supClass.changeStr()!");
}
}
function subClass(str2)
{
//this.parent=new supClass("parent");//继承了supClass的所有属性和方法;
var parent=new supClass("parent");
this.str=parent.str;
this.str2 = str2;
this.changeStr=parent.changeStr;
this.changeStr2=changeStr2;
function changeStr2()
{
//this.str2="Changed by function subClass.changeStr2()";
alert("subClass.str2 was changed by subClass.changeStr2()!");
}
}
var subclass=new subClass("child");
alert(subclass.str);//访问父类的属性;
alert(subclass.str2);//访问子类属性;
subclass.changeStr();//访问父类的方法;
subclass.changeStr2();//访问子类方法;
//-->
</script>
还可以写成这样:
-----------------------------------------------------------------------------------------------------------
<script language="JavaScript">
<!--
function supClass(str)
{
this.str=str;
this.changeStr=changeStr;
function changeStr()
{
//this.str="Changed by function supClass.changeStr()";
alert("supClass.str was changed by supClass.changeStr()!");
}
}
function subClass(str2)
{
supClass.prototype.str2=str2;//新增属性str2;
supClass.prototype.changeStr2=changeStr2;//新增方法changeStr2()
function changeStr2()
{
alert("supClass.str2 was changed by supClass.changeStr2()!");
}
return new supClass("parent");
}
var subclass=new subClass("child");
alert(subclass.str);//访问父类的属性;
alert(subclass.str2);//访问子类属性;
subclass.changeStr();//访问父类的方法;
subclass.changeStr2();//访问子类方法;
//-->
</script>
注意比较上面三种写法的区别与优劣。
3、多态。
多态能够让对象在运行时决定实际调用的方法体。由于JavaScript是一种动态语言,支持运行时绑定。
例:
<script language="JavaScript">
<!--
function supClass(str)
{
this.str=str;
this.changeStr=changeStr;
function changeStr()
{
alert("supClass.str was changed by supClass.changeStr()!");
}
}
function subClass(str)
{
var parent=new supClass("parent");
parent.str=str;
parent.changeStr=changeStr;
function changeStr()
{
alert("subClass.str was changed by subClass.changeStr()!");
}
return parent;
}
var supclass=new supClass("parent");
var subclass=new subClass("child");
supclass.changeStr();
subclass.changeStr();
//想想下面两段的结果是怎样
supclass=subclass;
supclass.changeStr();
//subclass=supclass
//subclass.changeStr();
//-->
</script>
上面的例子创建了一个父类跟一个子类,拥有一个同名方法,分别将父类和子类实例化并调用这个方法,显示的结果将随着方法所属类的不同而不同。这是因为例子使用的这种“继承”机制本身就是利用JavaScript语言的动态特性实现的,在一个类实例化的时候,它已经知道自己拥有哪些方法,而且,它也不关心这些方法从哪个类继承,只是将它们作为普通的成员方法来调用,所以,每一个类调用的同名方法都是属于自己的那个。再者,JavaScript是弱类型语言,它的变量声明的时候并不知道自己的类型,也并不知道将要被初始化成什么类型。赋予它什么类型的值,它就是什么类型的变量。本例子后面的一段代码很有意思:JavaScript在运行时既能够将子类变量赋给父类变量,也可以将父类变量赋给子类变量。
后记:在我写这篇文章的时候,网络上已经有一些朋友在对这方面做了一些研究。我参考了他们的一些观点,并提出了自己的想法。
可供参考的资料如下:
1、面向对象的Jscript
2、Classical Inheritance in JavaScript
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=1002403