Object Oriented Programming in JavaScript
在Javascript中使用面向对象的编程
这是一篇,我个人认为最好的,Javascript面向对象编程的文章。翻译不好的地方,还望大家指正,谢谢。
如果您需要,可以访问下面的地址取得原文:http://mckoss.com/jscript/object.htm
在我的blog里,将会陆续推出这个理论的实践、源码。
介绍
大部分的Javascript的编写者,都只是把它做为简单的脚本引擎,来创建动态的Web页面。同时Web设计人员开始使用在IE浏览器中定义的对象模型,来处理Web页面的内容。但是大多数的开发者并没有认识到Javascript在其自身就具有强大的面向对象的功能。当不使用强类型的时候(变量不必先声明后使用),这种解析性的语言,可以巧妙的达成面向对象(object-oriented)的功能,包括:
· 封装 (Encapsulation)
· 多台 (Polymorphism )
· 继承 (Inheritance)
虽然,通过一系列的范例(对于好奇的读者,这些范例片断代码是很生动的),我将会阐述对象在Javascript中,对象是如何被使用,并且如何实现面向对象的。
简单对象(Simple Objects)
在Javascript中,最简单的可构建的对象,就是机制内建的Object对象。在Javascript中,对象是指定名称的属性(property)的集合。做为解析性语言,Javascript允许给一个对象创建任意个属性,在任何时间(不像C++,它的属性是可以在任何时间添加给对象。它们并不需要事先在对象的声明(definition)或者构造(constructor)中,进行定义)。
所以,举例来说,我们可以创建一个对象,然后添加一系列的属性给它,就像这样:
obj = new Object;
obj.x = 1;
obj.y = 2;
这里,Javascript对象,可以用图形表示成这样的结构:
另外需要注意的是,我们创建的x和y属性, 我们的对象默认有一个属性constructor t他指向一个Javascript内部对象函数(funciton)。 (译者注:prototype,原型在后文会有进一步的说明)
对象的构造函数(Object Constructors)
对于要定义的对象类型,Javascript允许我们自己给对象类型定义构造函数:
function Foo()
{
this.x = 1;
this.y = 2;
}
obj1 = new Foo;
这里要说明的是,我们可以创建任意多个Foo类型的对象实例,它们也都将分别初始化自己的x和y属性为1和2。
简单的方法的的实现(A Simple Method Implementation)
为了封装对象的行为功能,向调用者隐藏执行过程,我们需要给对象创建方法(method)。Javascript允许你将任意一个函数(function)分配给对象的一个属性。当我们使用 obj.Function 的语法调用函数的时候,将把函数原来定义this 的指向,当前这个对象(就像它在构造函数中的那样)。function Foo()
{
this.x = 1;
this.y = 2;
this.Bar = MyMethod;
}
function MyMethod(z)
{
this.x += z;
}
obj2 = new Foo;
obj2 | |
x | 1 |
y | 2 |
Bar | function MyMethod |
prototype properties | |
constructor | function Foo |
现在,我们简单的调用一下,做为对象的方法的Bar函数:
obj2.Bar(3);
obj2 | |
x | 4 |
y | 2 |
Bar | function MyMethod |
prototype properties | |
constructor | function Foo |
所以,你可以方便的给对象定义构造函数和方法,使其对调用者而言,隐藏它的实现过程。同样的,因为,Javascript不是强类型的,所以,我们可以通过定义有相同名字的方法的对象,来简单的实现多台性(polymorphism)。
function Foo()
{
this.x = 1;
this.DoIt = FooMethod;
}
function FooMethod()
{
this.x++;
}
function Bar()
{
this.z = 'Hello';
this.DoIt = BarMethod;
}
function BarMethod()
{
this.z += this.z;
}
obj1 = new Foo;
obj2 = new Bar;
obj1 | |
x | 1 |
DoIt | function FooMethod |
prototype properties | |
constructor | function Foo |
obj2 | |
z | Hello |
DoIt | function BarMethod |
prototype properties | |
constructor | function Bar |
function Poly(obj)
{
obj.DoIt();
}
Poly(obj1);
Poly(obj2);
obj1 | |
x | 2 |
DoIt | function FooMethod |
prototype properties | |
constructor | function Foo |
obj2 | |
z | HelloHello |
DoIt | function BarMethod |
prototype properties | |
constructor | function Bar |
使用原型实现方法(Using Prototypes to Implement Methods)
试想一下,这使很笨的办法,每次我们都要创建名称没有使用意义的方法函数,然后在构造函数里,把它们分配给每个方法属性。其实,我发现使用Javascript的原型(prototype)机制,是更为直接的方法。
每个对象,可以参照一个原型对象,原型对象包含有自己的属性。它就好比是一个对象定义的备份。当代码,引用一个属性的时候,它并不存在于对象本身里,那么Javascript将会自动的在原型的定义中查找这个属性。而且,事实上,一个对象的原型对象又可以参照另外一个原型对象,就这样以链式最终关联到基类对象的构造函数。(译者注:对于DOM对象等系统的对象,原型对象可以修改,但是不可以赋值改变的,只有自定义对象可以。)这是template模型(译者注:模板方法,《设计模式》中行为模式的一种),它可以简化我们对方法的定义,同时也可以产生强大的继承机制。
在Javascript中,原型对象是被分配给构造函数的。所以,为了修改对象的原型,必须首先修改构造函数的原型对象的成员。然后,当对象从构造函数被构造的时候,对象将会引用到构造函数的原型。
function Foo()
{
this.x = 1;
}
Foo.prototype.y = 2;
obj = new Foo;
document.write('obj.y = ' + obj.y);
obj.y = 2
obj | |||||
x | 1 | ||||
prototype properties | |||||
constructor | function Foo
| ||||
y | 2 |
即使我们并没有直接的把y属性分配给obj,obj对象仍然有一个y属性。当我们引用obj.y的时候,Javascript实际返回obj.constructor.prototype.y的引用。我们可以肯定的是,原型的值的改变,也将会反映到对象中。
Foo.prototype.y = 3;
document.write('obj.y = ' + obj.y);
obj.y = 3
obj | |||||
x | 1 | ||||
prototype properties | |||||
constructor | function Foo
| ||||
y | 3 |
我们也可以发现,一旦我们初始化一个属性的“私有”(private )的值,存放在原型中的值并不会收到影响:
obj.y = 4;
Foo.prototype.y = 3;
obj | |||||
x | 1 | ||||
y | 4 | ||||
prototype properties | |||||
constructor | function Foo
|
原型方法的命名(Prototype Method Naming)
我发现了可以直接定义类的原型的方法的语句,而不需要单独的函数的名称:
function Foo()
{
this.x = 1;
}
function Foo.prototype.DoIt()
{
this.x++;
}
obj = new Foo;
obj.DoIt();
obj | |||||
x | 2 | ||||
prototype properties | |||||
constructor | function Foo
| ||||
DoIt | function Foo.prototype.DoIt |
基于原型的子类继承(Prototype-based Subclassing )
一旦可以建立原型对象链,我们就可以使用它做为对象的子类的类型。这个方法要注意的是,我们创建了一个基类对象的实例,并把它做为我们的类的构造函数的原型对象。这么做,我们所创建的所有的对象,将继承基类对象的所有成员和(方法)。但是要注意,基类的构造函数只会被调用一次(译者注:从基类到子类的构造函数都是唯一的,即基类的构造函数)。这不像C++,基类的构造函数,对于每个继承的子类,都可以分别的调用。在后面,我将展示,当独立的构造函数被需要的时候,另外一种可选的方式来创建继承类。
function TextObject(st)
{
this.st = st;
this.fVisible = true;
}
function TextObject.prototype.Write()
{
document.write('
' + this.st);
}
function ItalicTextObject(st)
{ this.st = st;
}
ItalicTextObject.prototype = new TextObject('x');
ItalicTextObject.prototype.Write = ITOWrite;
function ITOWrite()
{
document.write('
' + this.st + '');
}
obj1 = new TextObject('Hello, mom');
obj2 = new ItalicTextObject('Hello, world');
obj1.Write();
obj2.Write();
Hello, mom
Hello, world
obj1 | |||||
st | Hello, mom | ||||
fVisible | true | ||||
prototype properties | |||||
constructor | function TextObject
| ||||
Write | function TextObject.prototype.Write |
obj2 | |||||
st | Hello, world | ||||
prototype properties | |||||
constructor | function TextObject
| ||||
fVisible | true | ||||
Write | function ITOWrite |
这个结构存在两个问题。一个是,当每次构造继承的类的时候,基类的构造函数都不会被调用。假如,构造函数不做太多的事情,只是初始化一些成员变量为静态的值,这个问题就不是太明显了。第二个,注意,我将不能使用"function Obj.prototype.Method"的方式,来定义继承类的成员。这是因为,对于构造函数来说,我要把这些方法的定义,放入新创建的原型对象,而不是添加到,默认的原型对象。
另一种子类继承方式(An Alternate Subclassing Paradigm)
我们可以提出一种方法,更类似于反映C++类的概念和子类的定义,以及从子类反向存取基类的纯原型链的风格。它需要添加新的方法DeriveFrom给基类。
function Function.prototype.DeriveFrom(fnBase)
{
var prop;
if (this == fnBase)
{
alert("Error - cannot derive from self");
return;
}
for (prop in fnBase.prototype)
{
if (typeof(fnBase.prototype[prop]) == "function" && !this.prototype[prop])
{
this.prototype[prop] = fnBase.prototype[prop];
}
}
this.prototype[fnBase.StName()] = fnBase;
}
function Function.prototype.StName()
{
var st;
st = this.toString();
st = st.substring(st.indexOf(" ")+1, st.indexOf("("))
return st;
}
function TextObject(st)
{
this.st = st;
this.fVisible = true;
}
function TextObject.prototype.Write()
{
document.write('
' + this.st);
}
function TextObject.prototype.IsVisible()
{
return this.fVisible;
}
function ItalicTextObject(st)
{
this.TextObject(st);
}
ItalicTextObject.DeriveFrom(TextObject);
function ItalicTextObject.prototype.Write()
{
document.write('
' + this.st + '');
}
obj1 = new TextObject('Hello, mom');
obj2 = new ItalicTextObject('Hello, world');
obj1.Write();
obj2.Write();
Hello, mom
Hello, world
obj1 | |||||||
st | Hello, mom | ||||||
fVisible | true | ||||||
prototype properties | |||||||
constructor | function TextObject
| ||||||
IsVisible | function TextObject.prototype.IsVisible | ||||||
Write | function TextObject.prototype.Write |
obj2 | |||||||||||||||
st | Hello, world | ||||||||||||||
fVisible | true | ||||||||||||||
prototype properties | |||||||||||||||
constructor | function ItalicTextObject
| ||||||||||||||
IsVisible | function TextObject.prototype.IsVisible | ||||||||||||||
TextObject | function TextObject
| ||||||||||||||
Write | function ItalicTextObject.prototype.Write |
我们还得到了一个额外的好处,那就是,我们可以从多个基类进行继承(多重的继承)。