译前言:之前翻译了一篇Mozilla关于面对对象的JavaScript的简介,里面大体描述了JavaScript是如何实现OOP的。但是个人感觉比较偏理论,对prototype的描述不是提别清楚,因此就重新上网搜索的一些资源,找到了另外几篇介绍JavaScript OOP的教程。以下是其中的第一篇。
原文地址:http://www.javascriptkit.com/javatutors/oopjs.shtml
贡献:本教程由Tim Scarfe撰写,JavaScriptKit.com编辑。更多关于作者的信息请参见结尾。
对于编写面向对象的Web应用来说,JavaScript是一种美妙的语言。它支持OOP(Object-oriented Programming,面向对象编程),因为它支持基于原型的继承。许多程序员不认可JS是一种合适的面向对象语言,因为他们太过于习惯C#和Java的类模式了。很多人都没意识到JavaScript其实是支持继承的。当你编写面向对象的代码的时候,你立即被赋予了一种能力:你编写的代码能够重用并且是封装好的。
对象好在哪儿?
对象好就好在它能够像有生命的事物那样运作——对象拥有属性以及方法。因此,倘若我们说到台灯,属性可以是它的高度或者宽度,比如说12厘米;方法可以是发光(一种行为)。当它发光时,它的光亮度这个属性会比没有发光的时候更大。
JavaScript赋予你为你自己的应用编写自定义对象的能力。你可以把你自己的对象编写到事务处理中,这些事务可以在你想要它们执行的时候执行,并且你的代码是封装的。封装好的代码可以被初始化任意多的次数。
使用new Object()创建对象
在JavaScript中有多种创建对象的方法,它们都有施展自己作用的地方。最简单的方法是使用new关键字,具体是new Object():
<scriptlanguage="javascript" type="text/javascript">
<!--
person = new Object()
person.name ="Tim Scarfe"
person.height ="6Ft"
person.run =function() {
this.state = "running"
this.speed = "4ms^-1"
}
//-->
</script>
我们定义了一个自定义对象“person”,然后给它添加了属性和方法。在这个示例中,自定义的方法的作用只是初始化了其它两个属性。
使用标识符创建对象
另一种定义对象的内联方式是通过标识符。JavaScript 1.2以上的版本都支持这种标识符表示法,它在创建即时对象的方面是一种强大的模式:
<script language="javascript" type="text/javascript">
<!--
// 对象标识符
timObject = {
property1 : "Hello",
property2 : "MmmMMm",
property3 : ["mmm", 2, 3, 6, "kkk"],
method1 : function(){alert("Method had been called" + this.property1)}
};
timObject.method1();
alert(timObject.property3[2]) // 结果显示为“3”
var circle = { x : 0, y : 0, radius: 2 } // 另一个例子
// 嵌套
var rectangle = {
upperLeft : { x : 2, y : 2 },
lowerRight : { x : 4, y : 4}
}
alert(rectangle.upperLeft.x) // 结果显示为“2”
//-->
</script>
这种标识符表示法可以包含数组、任意JavaScript表达式或者值。
使用new关键字或者标识符来创建自定义对象都既简单又合乎逻辑,但是它们最大的不足是结果都“不能”重用——我们不能简单地把已经创建好的对象初始化成其它不同的版本。例如,在上面第一个示例中,如果person的名字(name)不是“Tim Scarfe”,我们需要重新定义整个对象来迁就这个改变。
构造器与原型
在OOP的世界中,前面的几种定义对象的方法在许多情况下都太局限了。我们需要的是一个创建对象“类型”的方法,这种“类型”可以被使用多次而不需要每次都重新定义对象来满足每个特殊实例的需求。实现它的标准方法是使用对象构造器功能。
对象构造器仅仅是一个常规的JavaScript函数,因此它和其它函数的功能一样(例如,定义参数,调用其它函数,等等)。它与其它普通函数的区别在于构造器通过new关键字来调用(后面你将会看到)。通过把对象的定义构建在函数的语法上,我们同时得到了函数的鲁棒性。
我们拿现实世界中的事物“猫(cat)”作为例子。猫的属性可以是它的颜色或者名字。方法可以是“喵喵”叫。重要的是每只猫都会有一个不同的名字,甚至连叫声都不一样。要创建一个能够灵活满足这种需求的对象类型,我们就会用到对象构造器:
<script language="javascript" type="text/javascript">
<!--
function cat(name) {
this.name = name;
this.talk = function() {
alert( this.name + " say meeow!" )
}
}
cat1 = new cat("felix")
cat1.talk() //alerts "felix says meeow!"
cat2 = new cat("ginger")
cat2.talk() //alerts "ginger says meeow!"
//-->
</script>
这里的函数cat()就是一个对象构造器,对象的属性和方法都在它内部通过使用关键字“this”作为前缀声明了。使用构造器定义的对象之后都可以用关键字new实例化。请注意我们是如何轻易地定义大量猫的实例并且每个实例都有它自己的名字的——那就是构造器带给自定义对象的灵活性。构造器为对象创造了蓝图,而不是仅仅创造了对象本身。
使用原型给对象添加方法
上面我们看到了如何仅仅通过在函数体中声明方法就把它添加到了构造函数中。对此的改进是通过原型,这也更加流行,因为它看上去更优雅。原型(prototype)是在JavaScript中的一种继承机制。当我们想要一个对象在它被定义之后再去继承一个方法时,常常会用到它。可以把原型法想成是在对象被定义之后再给它附加上一个方法,然后这个对象的所有实例都立刻共享了这个方法。
我们使用原型法在上述原始cat()对象的基础上扩展一个方法来修改猫的名字:
<script language="javascript" type="text/javascript">
<!--
cat.prototype.changeName = function(name) {
this.name = name;
}
firstCat = new cat("pursur")
firstCat.changeName("Bill")
firstCat.talk() //alerts "Bill says meeow!"
//-->
</script>
你可以看到我们仅仅使用关键字“prototype”紧跟在对象名后面来运用这个功能。自定义方法changeName()现在可以被所有cat的实例共享了。
在JavaScript内建对象中使用原型
原型不仅仅能作用于自定义对象,还能作用于某些内建对象,例如Data()或者String。对于后者而言,通常的规则是你可以在任何通过new关键字初始化的内建对象中使用原型。我将给你一个后者的示例,给JavaScript的内建对象Array添加功能。
IE5不支持Array的shift()和unshift()方法,而NS4+支持,因此我们用原型法给它添加上:
<script language="javascript" type="text/javascript">
<!--
// shift()和unshift()方法
if(!Array.prototype.shift) { // 如果该方法不存在
Array.prototype.shift = function(){
firstElement = this[0];
this.reverse();
this.length = Math.max(this.length-1,0);
this.reverse();
return firstElement;
}
}
if(!Array.prototype.unshift) { // 如果该方法不存在
Array.prototype.unshift = function(){
this.reverse();
for(var i=arguments.length-1;i>=0;i--){
this[this.length]=arguments[i]
}
this.reverse();
return this.length
}
}
//-->
</script>
可能性是无止境的。
子类与超类
在Java与C++中,类的层次都有一个显式的概念。例如,每个类都有一个超类并从超类中继承属性和方法。任何一个类都可以被扩展,或者说被继承,得到的子类可以继承它父类的行为。我们可以看到,JavaScript支持基于原型继承而不是基于类。不过,通过其它的方法实现继承也是可能的。
下面是通过函数实现继承的例子:
<script language="javascript" type="text/javascript">
<!--
// thanks to webreference
function superClass() {
this.supertest = superTest; //attach method superTest
}
function subClass() {
this.inheritFrom = superClass;
this.inheritFrom();
this.subtest = subTest; //attach method subTest
}
function superTest() {
return "superTest";
}
function subTest() {
return "subTest";
}
var newClass = new subClass();
alert(newClass.subtest()); // yields "subTest"
alert(newClass.supertest()); // yields "superTest"
//-->
</script>
在大多数情况下,基于原型的继承对JavaScript应用来说都是相对较好的选择。
作为关联数组的对象
众所周知,点号(.)操作符可以用来访问“[ ]”操作符操作的数组成员。
<script language="javascript" type="text/javascript">
<!--
// These are the same
object.property
object["property"]
//-->
</script>
重要的是,请注意对象语法中上例的property是一个标识符,而在数组语法中它是字符串。使用数组语法来访问对象的一个显著的好处是,因为是字符串类型,你可以轻易地连接字符串,操作它们来访问对象。基于这一点,为了能与标准的语法协同运行,eval()方法需要被用到。
如何循环输出对象中的属性
你需要用到for/in循环:
<script language="javascript" type="text/javascript">
<!--
testObj = {
prop1:"hello",
prop2:"hello2",
prop3:new Array("helloa",1,2)
}
for(x in testObj) alert( x + "-" + testObj[ x ] )
//-->
</script>
(后面有一小段内容是关于Jscript.NET的,译者觉得用处不大,就略过了。)
关于作者:TimScarfe是英国伦敦西区的一位Web开发工程师。他在W3C标准和可接入性领域非常活跃。个人主页:http://www.developer-x.com/