编写面向对象的 JavaScript 第二部分






4.37/5 (21投票s)
2003年12月9日
8分钟阅读

116187

1078
使用 Cfx 开发 JavaScript 控件类库。
引言
第一部分 介绍了 JavaScript 的面向对象特性。JavaScript 的不寻常之处在于它支持原型继承,以及通过原型继承实现类继承的不优雅方式。
本期介绍了一个框架,该框架以一种简化 JavaScript 中编写类层次结构的方式应用了原型继承。使用这个框架,我们现在可以像传统的面向对象语言一样,轻松地通过类继承模式定义和派生新类。
策略
将基类原型分配给派生类
为了支持类继承,我们需要构建一个类似于虚函数表 (vtable) 的机制。框架通过 JavaScript 的原型来模拟 vtable。原型本质上是关联数组,它们描述了分配给实例的属性模板。因此,在 JavaScript 中,“类继承”是通过将基类的原型值分配给派生类的原型来模拟的。一旦分配,派生类可以覆盖其原型条目并实现自己的方法,或者默认使用从基类提供的实现。
JavaScript 类框架 (Cfx)
JavaScript 类框架 (Cfx
) 通过一个明确定义的编码模式简化了 JavaScript 中类层次结构的编写。该框架通过自动将基类原型和属性分配给其派生类来实现类继承。
Cfx
由一组 JavaScript 对象组成,提供定义新类、提供有用的 DOM 例程和 JavaScript 工具的服务。这些对象被 Cfx
对象封装,作为框架的整体命名空间。
![]() |
图 1. JavaScript 类框架 (Cfx) |
Cfx.Js
Cfx.Js
(JavaScript) 对象是一个通用的实用程序对象,它封装了用于识别对象类型的函数。它还具有提取对象或函数字符串名称以及将对象类型转换为函数类型的函数。
类型 |
IsArray( arg )
//Returns true if argument is an array; false otherwise.
IsDefined( arg )
//Returns true if argument is defined; false otherwise.
IsEmpty( obj )
//Returns true if an object posses no properties; false otherwise.
IsFunction( arg )
//Returns true if argument is a function; false otherwise.
IsNull( obj )
//Returns true if an object is a null; false otherwise.
IsObject( arg )
//Returns true if argument is an object; false otherwise.
IsString( arg )
//Returns true if argument is a string; false otherwise. |
名称 |
FunctionName( func )
//Returns the name of the function.
ObjectName( obj )
//Returns the name of the object.
|
转换 |
ToFunction( obj )
//Converts an object to a function.
|
图 2. Cfx.Js 方法 |
Cfx.Class
Cfx.Class 提供了 JavaScript 类继承基础设施。使用 Cfx.Class.New
方法定义新类。此方法将基类的原型条目分配给派生类的原型数组,并允许派生类原型数组从多个基类继承,其继承顺序与参数列表的指定顺序相反。因此,参数列表中紧跟在派生类后面的类名代表最重要的基类,这是一种解决继承冲突的简单快捷方式。
GetName( classArg )
//Returns the class name from the class argument.
IsDefined( thisClass )
//Returns true class is defined; false otherwise.
IsInitializing( thisClass )
//Returns true if class in initializing; false otherwise.s
//in initializing; false otherwise.
InstanceOf( thisClass, targClass )
//Determines if a class in an instance of target class
New( thisClass [,baseClassArg1, baseClassArg2, ..., baseClassArgN] )
//Defines a new class.
//Copies prototypes and properties from base class(es) to new class.
|
图 3. Cfx.Class 方法 |
Cfx.Dom
Cfx.Dom
封装了文档对象模型,提供了用于检索文档项的有用方法。
方法 |
元素 |
FindElementById( elementName )
//Returns DOM element matched by the element id attribute.
FindElementByName( elementName )
//Returns DOM element matched by the element name attribute.
FindElementSetById( elementName )
//Returns a collection of DOM elements matched by the element id attribute.
FindElementSetByName( elementName )
//Returns a collection of DOM elements matched by
//the element name attribute.
GetElementById( elementId )
//Returns the DOM element of the elementId.
GetElementByName( elementName )
//Returns DOM element of the elementName.
GetElementTerms( argElement )
GetElementTerms( argElement, splitChar )
//Returns collection of element terms from the element id attribute.
|
表单 |
GetForm()
GetForm( formIndex )
GetForm( formId )
//Returns document form.
|
图像 |
FindImage( imageName )
//Returns DOM image matched by the element id attribute.
FindImageSet( imageName )
//Returns a collection of DOM images matched by the
//element id attribute.attribute.
GetImage( imageName )
//Returns a DOM image for the p; Returns a DOM image for
//the imageName id attribute.
|
提交 |
PostBack()
PostBack( formIndex )
PostBack( formIndex, eventTarget )
PostBack( formIndex, eventTarget, eventArgument )
PostBack( eventTarget )
PostBack( eventTarget, eventArgument )
//Submits form to server.
|
属性 |
浏览器 |
isIE
//Returns true if browser is IE; false otherwise.
isNetscape
//Returns true if browser is Netscape; false otherwise.
isMozilla
//Returns true if browser is Mozilla; false otherwise.
|
图 4. Dom 对象属性和方法 |
JavaScript 内置对象扩展
在 JavaScript 中,所有对象都是可扩展的,Cfx
充分利用了这一特性。该框架扩展了 Function
对象,提供了向类添加方法的机制。Object
被扩展了 className
和 baseClass
属性。这些属性被 Cfx 用来确定所有以 Object
作为所有对象基类的已实例化对象的类层次结构。
Error
、Date
和 String
等内置对象被扩展了 className
和 baseClass
属性,默认将 Object
作为它们的基类。
![]() |
图 5. Cfx 中的 JavaScript 内置扩展 |
为简洁起见,Error
、Date
、RegExp
和 String
的默认属性和函数未显示,只显示了它们的扩展。此外,Cfx
还扩展了 String
类,增加了 trim
函数,用于删除字符串开头和结尾的空格。有关 JavaScript 内置对象扩展的更多信息将在下面提供。
对象 |
所有 JavaScript 对象的根。className
//All objects inherit the className property.
//The className property for Object class is "Object".
baseClass
//Base class property – a base class or array of base classes.
//The baseClass property for Object class is null.
InstanceOf( targetClass )
//Returns true if this is an instance of targetClass; false otherwise.
InitInstance()
//Initializes class instance.; Initializes class instance.ance.;
//Initializes class instance.
|
函数 |
函数的对象表示。className
//The className property for Function class is "Function".
baseClass
//The baseClass property for Function class is null.
instances
//The instances property informs class to keep track of class instances.
Method
Defines a new method on a function object.
|
Error(错误) |
异常对象。className
//The className property for Error class is "Error".
baseClass
//The baseClass property for Error class is Object.
|
日期 |
日期和时间对象。className
//The className property for Date class is "Date".
baseClass
//The baseClass property for Date class is Object.
|
RegExp |
正则表达式对象。className
//The className property for String class is "RegExp".
baseClass
//The baseClass property for String class is Object.
|
字符串 |
字符串对象。className
//The className property for String class is "String".
baseClass
//The baseClass property for String class is Object.
trim()
//Removes leading and trailing whitespaces from strings.
|
图 6. 对 JavaScript 内置对象的扩展 |
使用 Cfx 开发类层次结构
使用 Cfx
,我们可以定义一个类层次结构以及派生类来改进其基类的行为。例如,验证文本输入元素是非常常见的需求,这就是 ASP.NET 提供验证器的原因。然而,有时您可能需要编写自己的客户端验证代码,以满足特定的需求。本节将演示如何使用 Cfx
定义一个验证器类层次结构。
下面的类图显示了一个验证器类层次结构。Validator
类代表所有派生验证器的基类。TextValidator
继承自 Validator
类,而 WildcardValidator
类则继承自 TextValidator
类。
![]() |
图 7. 验证器类层次结构 |
编写类
第一个感兴趣的类是 Validator
类。它代表所有验证器类型的基类。我们可以将其视为一个抽象类,尽管 JavaScript 没有抽象类的概念。在 JavaScript 中,函数同时充当对象的构造函数和类定义。
function Validator()
{
使用以下代码模式,类将在接下来的三个小节中定义:
- 类定义部分
- 类实例部分
- 方法定义部分
类定义部分
类定义和初始化发生在构造函数函数块的这个区域。Cfx.Class.IsDefined
确定 Validator
类(实际上是一个 JavaScript 函数对象)是否已定义。如果类未定义,则调用 Cfx.Class.New
来创建 Validator
类。在类初始化期间,Cfx.Class.New
会回调构造函数函数(由 Cfx.Class.IsInitializing
检测到)以将方法和属性分配给新类。**不要省略** Cfx.Class.IsInitializing
代码块中的 return
语句。控制必须返回到 Cfx.Class.New
以完成类构造。
///////////////////////////////////////////////////////////////////////
// Class definition.
///////////////////////////////////////////////////////////////////////
if ( !Cfx.Class.IsDefined( Validator ) )
{
Cfx.Class.New( Validator );
if ( Cfx.Class.IsInitializing( Validator ) )
{
Validator.Method( FieldName );
Validator.Method( Element );
Validator.Method( Value );
Validator.Method( Enable );
Validator.Method( IsEnabled );
Validator.Method( SetFocus );
// Define default properties.
Validator.fieldName = null;
Validator.element = null;
return;
}
}
|
图 8. 验证器类定义部分 |
类实例部分
在 Validator
类构造函数的这个部分,由于该类不打算实例化,因此不需要初始化任何实例。这就是我们指定一个类为抽象类的方式。在这种情况下,类实例只是被返回。
//////////////////////////////////////////////////////////////////////////
// Setup class instance.
/////////////////////////////////////////////////////////////////////////
return this;
|
图 9. 验证器类实例部分 |
方法定义部分
方法定义部分定义了所有分配给类定义部分中类的引用的方法。类方法定义在构造函数函数内部,并且作用域限定在类内。这避免了将方法名暴露到全局命名空间,从而防止了命名冲突。
//////////////////////////////////////////////////////////////////////////
// Methods definitions.
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// FieldName : fieldName accessor.
//////////////////////////////////////////////////////////////////////////
function FieldName()
{
// setter.
if ( arguments.length )
this.fieldName = arguments[0];
// getter.
return this.fieldName;
}
//////////////////////////////////////////////////////////////////////////
// Element : element accessor.
//////////////////////////////////////////////////////////////////////////
function Element()
{
// setter.
if ( arguments.length )
this.element = arguments[0];
// getter.
return this.element;
}
//////////////////////////////////////////////////////////////////////////
// Value : Value accessor.
//////////////////////////////////////////////////////////////////////////
function Value()
{
// setter.
if ( arguments.length )
this.element.value = arguments[0];
// getter.
return this.element.value;
}
//////////////////////////////////////////////////////////////////////////
// IsEnabled : returns whether the Element is enabled or disabled.
//////////////////////////////////////////////////////////////////////////
function IsEnabled()
{
return !this.element.disabled;
}
//////////////////////////////////////////////////////////////////////////
// Enable : enables or disables an element.
//////////////////////////////////////////////////////////////////////////
function Enable( state )
{
this.element.disabled = !state;
}
//////////////////////////////////////////////////////////////////////////
// SetFocus : sets the text element focus.
//////////////////////////////////////////////////////////////////////////
function SetFocus()
{
if ( this.element != null )
{
if ( Cfx.Js.IsString( this.element.value ) )
{
this.element.focus();
if ( this.element.type == "text" )
this.element.select();
}
}
}
}
|
图 10. 验证器方法定义部分 |
派生类
TextValidator
类是使用与 Validator
类相同的编码模式(类定义、类实例和方法定义部分)派生出来的。在其类定义部分,Cfx.Class.New
创建 TextValidator
类,继承在 Validator
类上定义的类方法和属性。TextValidator
添加了一个访问器方法 AcceptChars
,用于获取和设置其 acceptChars
属性。
Cfx.Class.New( TextValidator, Validator );
与 Validator
类不同,请注意类实例部分有额外的代码。这是因为 TextValidator
类打算用于实例化。InitInstance
方法从其类对象初始化实例,提供默认值。因此,在下面的代码中,TextValidator
类的实例从 TextValidator
类对象获取 fieldName
、element
和 acceptChars
属性及其默认值。
this.InitInstance();
调用 InitInstance
后,会检查构造函数参数。如果存在参数,它们将用于覆盖新实例的默认值。最后一步是从构造函数返回实例。
if ( arguments.length )
{
this.fieldName = arguments[0];
this.element = Dom.FindElementById( this.fieldName );
}
return this;
TextValidator
类的 `Method definition section` 定义了 `AcceptChars` 访问器方法。TextValidator
类的所有其他方法和属性都从 Validator
类继承。下面是 TextValidator
类的完整定义。
function TextValidator()
{
/////////////////////////////////////////////////////////////////////////
/ Class definition.
/////////////////////////////////////////////////////////////////////////
if ( Cfx.Class.IsDefined( TextValidator ) == false )
{
Cfx.Class.New( TextValidator, Validator );
if ( Class.IsInitializing(.TextValidator )
{
// Define members.
TextValidator.Method( AcceptChars );
// Define default properties.
TextValidator.acceptChars = "\\w";
return;
}
}
/////////////////////////////////////////////////////////////////////////
// Setup instance.
/////////////////////////////////////////////////////////////////////////
this.InitInstance()
if ( arguments.length )
{
this.fieldName = arguments[0];
this.element = Cfx.Dom.FindElementById( this.fieldName );
}
return this;
//////////////////////////////////////////////////////////////////////////
// Methods definitions
//////////////////////////////////////////////////////////////////////////
function AcceptChars()
{
// setter.
if ( arguments.length )
this.acceptChars = arguments[0];
// getter.
return Cfx.Js.IsDefined( this.acceptChars ) ?
this.acceptChars : TextValidator.acceptChars;
}
}
|
图 11. TextValidator 类定义 |
同样,WildcardValidator
也从 TextValidator
派生,并增加了额外的方法和属性。
function WildcardValidator()
{
/////////////////////////////////////////////////////////////////////////
// Class definition.
/////////////////////////////////////////////////////////////////////////
if ( Cfx.Class.IsDefined( this ) == false )
{
Cfx.Class.NewClass( WildcardValidator, TextValidator );
if ( Cfx.Class.IsInitializing( this ) )
{
// Define members.
WildcardValidator.Method( WildChar );
// Initialize class variables.
WildcardValidator.wildChar = "\*";
WildcardValidator.wildLimit = 4;
return;
}
}
/////////////////////////////////////////////////////////////////////////
// Setup instance.
/////////////////////////////////////////////////////////////////////////
if ( arguments.length )
{
this.fieldName = arguments[0];
this.element = Cfx.Dom.FindElement( this.fieldName );
}
return this;
//////////////////////////////////////////////////////////////////////
// Methods definitions
/////////////////////////////////////////////////////////////////////
function WildChar()
{
// setter.
if ( arguments.length )
this.wildChar = arguments[0];
// getter.
return Cfx.Js.IsDefined( this.wildChar ) ?
this.wildChar : WildcardValidator.wildChar;
}
function WildLimit()
{
// setter.
if ( arguments.length )
this.wildLimit = arguments[0];
// getter.
return Cfx.Js.IsDefined( this.wildLimit ) ?
this.wildChar : WildcardValidator.wildLimit;
}
}
|
图 12. WildcardValidator 类定义 |
演示
Inheritance
演示程序是一个 ASP.NET 应用程序,它使用 Cfx
来构建 JavaScript 类层次结构。验证器被定义为样本网页上显示的文本输入元素的包装器。使用验证器对象,我们可以访问和修改文本输入元素,并演示验证器的类继承。
该网站的 URL 是 https:///JsOOP/Inheritance/WebForm1.aspx。将源代码复制到 https:// 主目录的 `JsOOP/Inheritance` 子目录中。您可以使用 Visual Studio 创建 ASP.NET 项目,或者使用“控制面板”中的“管理工具”中的 IIS 管理工具。此演示已在运行时版本 1.0 和 1.1 上进行了测试。
JavaScript 类框架脚本文件包含在 `WebForm1.aspx` 的网页开头附近,然后是验证类层次结构的脚本文件。
<script language="JavaScript"
src="scripts/Cfx.js"type="text/javascript"></script>
<script language="JavaScript"
src="scripts/Validator.js"type="text/javascript"></script>
<script language="JavaScript"
src="scripts/TextValidator.js"type="text/javascript"></script>
<script language="JavaScript"
src="scripts/WildcardValidator.js"type="text/javascript"></script>
在 `OnLoad` 函数中,会创建对应于文本元素的验证对象。一个 WildcardValidator
类对象包装了 `TextBox1` 字段,并且正是在这个初始对象创建过程中,框架创建了整个 Validator
类层次结构。从 `valText1` 对象调用 `InstanceOf` 方法演示了框架创建类层次结构的过程。
alert( "in OnLoad");
var valText1 = new WildcardValidator( "TextBox1");
alert( valText1.FieldName() );
alert( valText1.ClassName() + "instance of Validator = "+
valText1.InstanceOf( Validator ) );
TextValidator
类的对象包装了 `TextBox2` 和 `TextBox3` 文本元素。以下代码演示了如何使用类层次结构中定义的方法来操作文本元素。请注意,如何通过 `TextValidator` 对象的 `valText2` 和 `valText3` 将 `textbox3` 的值赋给 `textBox2`。
var valText2 = new TextValidator( "TextBox2" );
valText2.Enable( false );
alert( valText2.FieldName() );
valText2.SetFocus();
alert( valText2.FieldName() );
var valText3 = new TextValidator( "TextBox3" );
alert( valText3.FieldName() );
valText3.Value( valText2.Value().trim() );
结论
使用 Cfx
,客户端脚本现在可以使用熟悉的面向对象设计和风格进行编写。虽然这里介绍的对象仅仅是文本字段的包装器,但它们在封装和隐藏实现细节方面做得非常好。这对于隐藏各种浏览器呈现网页之间的细微差别非常有用,最重要的是能够实现代码重用。以这种方式构建脚本还使您能够设计更丰富的客户端网页,从而增强您网站的用户体验。另请注意,Cfx
中使用的命名约定是方法名的 Pascal 命名法和属性名的 camel 命名法。这有助于在两者之间进行视觉区分。
最后一篇文章将演示如何使用此框架为 ASP.NET 网页 `pages.controls` 开发 JavaScript 控件类层次结构,从而提供丰富的客户端功能。
历史
- 版本 1.0