Object Oriented Programming in JavaScript
在Javascript中使用面向对象的编程
by Mike Koss
March 26th, 2003
Translated by Daniel.Seewood (http://seewood.isme.net)
这是一篇,我个人认为最好的,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对象,可以用图形表示成这样的结构:
obj |
x |
1 |
y |
2 |
prototype properties |
constructor |
function Object |
另外需要注意的是,我们创建的x和y属性, 我们的对象默认有一个属性constructor t他指向一个Javascript内部对象函数(funciton)。 (译者注:prototype,原型在后文会有进一步的说明)
对象的构造函数(Object Constructors)
对于要定义的对象类型,Javascript允许我们自己给对象类型定义构造函数:
function Foo()
{
this.x = 1;
this.y = 2;
}
obj1 = new Foo;
obj1 |
x |
1 |
y |
2 |
prototype properties |
constructor |
function 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
prototype |
DoIt |
function Foo.prototype.DoIt |
|
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
prototype |
Write |
function TextObject.prototype.Write |
|
Write |
function TextObject.prototype.Write |
obj2 |
st |
Hello, world |
prototype properties |
constructor |
function TextObject
prototype |
Write |
function TextObject.prototype.Write |
|
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
prototype |
Write |
function TextObject.prototype.Write |
IsVisible |
function TextObject.prototype.IsVisible |
|
IsVisible |
function TextObject.prototype.IsVisible |
Write |
function TextObject.prototype.Write |
obj2 |
st |
Hello, world |
fVisible |
true |
prototype properties |
constructor |
function ItalicTextObject
prototype |
Write |
function ItalicTextObject.prototype.Write |
IsVisible |
function TextObject.prototype.IsVisible |
TextObject |
function TextObject
prototype |
Write |
function TextObject.prototype.Write |
IsVisible |
function TextObject.prototype.IsVisible |
|
|
IsVisible |
function TextObject.prototype.IsVisible |
TextObject |
function TextObject
prototype |
Write |
function TextObject.prototype.Write |
IsVisible |
function TextObject.prototype.IsVisible |
|
Write |
function ItalicTextObject.prototype.Write |
我们还得到了一个额外的好处,那就是,我们可以从多个基类进行继承(多重的继承)。
参考文件(References)
surprise! JavaScript is object-oriented - October 1997 article on Builder.Com written by Dan Shafer. Explores and explains some of these issues (see also his follow up article in November 1997).
JavaScript reference - MSDN reference. See especially definitions of:
constructor
prototype
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
watch() method documentation - DevGuru. Allows a function to be called whenever an object property is changed, is not supported by Internet Explorer's implementation of JScript.
Document Object Model for Internet Explorer - MSDN web site.
Checking for a Prototype Chain - Person/Employee example of subclassing from this page by Yehuda Shirna (Doc JavaScript)
Prototype Property - Wrox JavaScript Programmers Reference
Object Hierarchy and Inheritance in JavaScript - Netscape Online Documentation
ECMA-262 (PDF) - ECMAScript Standard Documentation