Microsoft AJAX库是用JavaScript编写的,但带有浓厚的面向对象色彩。JavaScript语言支持对象,也允许创建自定义的对象。但由于它本身没有对象继承的,因而它不支持完整的面向对象机制。ASP.NET AJAX添加了一些类型系统和命名空间与继承的概念,将JavaScript语言提高到了另一个层次。

  这些扩展保存在一些.js文件中。这些文件没有以独立文件的形式随ASP.NET的安装而部署到Web服务器上,它们以资源的形式嵌入在ASP.NET AJAX程序集(system.web.extensions)中。

JavaScript语言扩展

  JavaScript语言带有一些内建对象,其中包括Function、Object、Boolean、Array、Number和String。所有内部对象都有一个名为prototype的只读属性。我们可使用prototype属性来为相应类的新实例提供共有的功能。可以将新的功能添加到应用程序内部的类的原型中来扩展和提升给定类的功能。

  类型信息是改进后的JavaScript语言中的重要方面。除基本类型的实例外,JavaScript中的其他对象均为纯粹的object类型。有了JavaScript语言扩展,我们便获得了一套与.NET Framework类似的类型信息系统。

  Microsoft AJAX库中的顶层对象见下表:

  在Web页面中编写脚本代码时,常见的错误是不使用DOM的getElementById方法,而直接访问HTML元素。假设客户端页面中有一个名为TextBox1的文本框,应使用如下方式输出文本内容:

alert(document.getElementById("TextBox1").value);

  这种形式过于冗长,Microsoft AJAX库提供了一个全局函数$get来简化书写,也就是说$get函数是document.getElementById函数的快捷方式,如:

alert($get("TextBox1").value);

  $get函数有两个重载,对于另一个重载,我们可将一个容器作为第二个参数,如:

var parent = $get("Div1");
$get("TextBox1", parent);

  如果指定的容器支持getElementById方法,那么该函数会返回element.getElementById的输出。否则,$get函数会使用DOM接口来对该元素子树的内容进行遍历。

面向对象的扩展:命名空间

  在JavaScript中,Function对象是将代码与属性关联的主要工具,用于构建新的组件。在Microsoft AJAX库中,Function对象得到了扩展,整个进了类型信息和面向对象的概念。

  命名空间提供了一种对代码库中类型进行组织和分类的方式。命名空间本身不是类型,但这会对它所包含的每种类型赋予额外的信息,以便更好的限定某个对象。默认情况下,所有自定义的JavaScript函数都位于全局命名空间。出于管理方面的原因,在Microsoft AJAX库中,我们可将自定义的函数与某个命名空间关联。在Microsoft AJAX库中声明自定义类型时,我们可以这样做:

Type.registerNamespace("Core35");
Core35.Person
= function Core35$Person(firstName, lastName)
{
this._firstName = firstName;
this._lastName = lastName;
}

//Define the body of members
function Core35$Person$ToString()
{
return this._lastName + ", " + this._firstName;
}
...
//Define the prototype of the class
Core35.Person.prototype = {
ToString: Core35$Person$ToString;
get_FirstName: Core35$Person$get_FirstName;
set_FirstName: Core35$Person$set_FirstName;
get_lastName: Core35$Person$get_lastName;
set_lastName: Core35$Person$set_lastName;
}

//Register the class, extending out own IntroAjax Person class
IntroAjax.Person.registerClass("Core35.Person");

  Type.registerNamespace方法能够将指定的命名空间添加到运行时环境中。

  定义在命名空间声明下面的Core35.Person函数描述了Core35命名空间的Person类型。最后,新定义的函数必须以类的形式向Microsoft AJAX框架注册。为此,我们使用当前函数的registerClass方法。registerClass方法定义在Function对象的原型中,因而所有函数都会继承它。在该方法的内部,它会将当前函数的_typeName属性设置为该方法的第一个参数--实际的类名。

  registerClass方法能够接受许多参数,第一个参数是必选的,指示一个公共名称,以便以类的形式暴露JavaScript函数。其他可选参数是父类和该类实现的接口。

  在新类的定义中,我们可以使用匿名函数或命名函数,从语法上讲,这两种方案都是可接受的。但惯例是选择命名函数,并使用完全限定名称对每个函数进行命名,同时用美元符号($)代替句点(.)。

面向对象的扩展:继承

  让我们定义一个新类Citizen,通过添加两个属性对Person进行扩展,这两个属性分别代表公民的地址和身份证号码:

//Declare the class
Core35.Citizen = function Core35$Citizen(firstName, lastName, id)
{
...
}
//Define the prototype of the class
Core35.Citizen.prototype = {
...
}
//Register the class
Core35.Citizen.registerClass("Core35.Citizen", Core35.Person);

  注意,registerClass的第一个参数这字符串,但第二个参数必须是对象的引用。让我们逐步充实这段代码。

  在构造函数中,我们设置私有成员,并调用基类的构造函数,对基类成员进行初始化。initializeBase方法(定义在Function上)用于获取和调用基类的构造函数:

Core35.Citizen = function Core35$Citizen(firstName, lastName, id)
{
Core35.Citizen.initializeBase(
this, [firstName, lastName]);
this._id = id;
this._address = "";
}

  传入initializeBase中的包括当前对象的引用和一个数组,该数组包含调用基类构造函数所需的参数。我们可使用方括号([...])以内联方式来定义数组。

  面向对象中的虚函数在JavaScript中如何实现呢?基类的原型中添加的成员可以在派生类中直接重写。如果派生类要添加的成员在基类中已存在,那么它会被隐式地重写。下面是Citizen类的原型:

Core35.Citizen.prototype =
{
ToString: Core35$Citizen$ToString,
get_ID: Core35$Citizen$get_ID,
get_Address: Core35$Citizen$get_Address,
set_Address: Core35$Citizen$set_Address
}

  该类重写了定义在父类中的ToString方法:

function Core35$Citizen$ToString()
{
var temp = Core35.Citizen.callBaseMethod(this, 'ToString');
temp
+= " [" + this._id + "] ";
return temp;
}

  我们可使用callBaseMethod来调用基类中的同名方法。callBaseMethod方法定义在Function类上,接受3个参数:实例、方法名称及一个可选的基类方法参数数组。

面向对象的扩展:接口

  一般来讲,接口包括方法、属性和事件。在JavaScript中,它只包含方法。

  在JavaScript中,要想定义接口,我们在通过构造函数和原型创建一个常规的类,但只需使构造函数和每个原型方法抛出未实现异常即可。下面的代码演示了内建接口Sys.IDisposable的定义:

Type.registerNamespace("Sys");
Sys.IDisposable
= function Sys$IDisposable()
{
throw Error.notImplemented();
}
function Sys$IDisposable$dispose()
{
throw Error.notImplemented();
}
Sys.IDisposable.prototype
=
{
dispose: Sys$IDisposable$dispose
}
Sys.IDisposable.registerInterface(
'Sys.IDisposable');

  下面这条语句用于注册Citizen类,使它派生自Person,并实现IDisposable接口:

Core35.Citizen.registerClass("Core35.Citizen", Core35.Person, Sys.IDisposable);

  为实现给定的接口,JavsScript类只需定义接口中的所有方法,并在注册该类时列出该接口即可:

function Core35$Citizen$dispose
{
this._id = "";
this._address = "";
}
Core35.Citizen.prototype
=
{
dispose: Core35$Citizen$dispose
...
}

  注意,即使要实现给定接口的类没有重写其中的所有方法,也不会有任何运行时错误抛出。

  如果某个类要实现多个接口,只需在registerClass方法中以额外参数的形式逐一列出所有接口即可,如下所示:

Sys.Component.registerClass('Sys.Component', null,
Sys.IDisposable,
Sys.INotifyPropertyChange,
Sys.INotifyDisposing,
Sys.INotifyDisposing);

应用程序核心组件

  AJAX客户端由3个主要逻辑层次构成:JavaScript扩展、核心框架类及用户界面(UI)框架类。如下图所示:

  每个ASP.NET AJAX页面的执行由一个应用程序对象控制,该对象在这个代码库内部实例化。这个应用程序对象是一个私有类(Sys._Application类)的实例。当ASP.NET AJAX页面加载到浏览器后,Sys._Application类的实例会立即被创建,并赋给Sys.Application对象:

Sys.Application = new Sys._Application();

  此外,每个ASP.NET AJAX页面还会被注入以下脚本代码:

<script type="text/javascript">
<!--
Sys.Application.initialzie();
// -->
</script>

  这段代码会被插入到页面<form>的结束标签后,指示加载一些脚本文件以便为页面加载脚本管理器。因此,Sys.Application对象是ASP.NET AJAX页面的中枢。

  注意,JavaScript设有标记私有成员的符号,因此,根据约定,私有成员用下划线(_)作为其名称的前缀。

  Sys.Application对象主要用于提供对页面组件的访问,它的findComponent方法能够遍历当前页面的Microsoft AJAX组件层次结构,直到找到与指定的ID匹配的组件。该方法有两个原型:

Sys._Application.findComponent(id);
Sys._Application.findComponent(id, parent);

  第一个重载接受目标组件的ID,该方法会使用这个ID从整个层次结构的根节点开始查找组件。如果指定非空的parent参数,则仅取于在以该对象为根的子树中查找。id参数必须为字符串,parent参数必须为Microsoft AJAX库的对象。该方法返回与指定的ID匹配的对象,如未找到,则返回null。

  Microsoft AJAX库还支持获取运行时组件的快捷方式$find方法。$find方法是findComponent的别名:

var $find = Sys.Application.findComponent;

  我们可以使用该方法来查找由服务器控件、扩展程序(extender)和自定义JavaScript代码创建的所有组件。但不能使用$find来查找DOM组件。对于DOM组件,应使用$get。

  在使用Microsoft AJAX库的页面首次加载时,load事件会被触发。注意,该事件是页面生存期中的,而不属于应用程序的生存期。因此,只要发生常规的回发,我们就会收到新的load事件。但由XMLHttpRequest控制的AJAX回发不会触发该事件。

  load事件在页面加载且初始化完毕后触发。对于使用Microsoft AJAX的页面来说,该事件胜于浏览器用于初始化的onload事件。触发Microsoft AJAX库的load事件时,即说明页面已为与用户进行交互做好了准备。unload事件会在Microsoft AJAX运行库释放页面级中的所有资源前触发。出于对应用程序稳定性的考虑,应使用该事件替代浏览器的onunload事件来执行清理工作。

  定义load和unload处理程序最简单的方式是利用预定义的函数名:pageLoad和pageUnload。这些函数必须是全局的、无参数的:

<script type="text/JavaScript" language="JavaScript">
function pageLoad()
{
alert(
"Being loaded");
}
function pageUnload()
{
alert(
"Being unloaded");
}
</script>

网络堆栈

  任何AJAX库之所以受欢迎,是因为它们能够在客户端执行out-of-band Web请求。具体来讲,ASP.NET AJAX Extensions允许我们调用Web服务方法及定义在页面代码隐藏类的静态方法。该功能利用了Microsoft AJAX库内建的网络支持。

  在Microsoft AJAX库中,远程请求由Sys.Net.WebRequest类的实例表示。下表列出了该类的属性:

  WebRequest类定义了获取或设置目标URL的url属性和用于向请求添加标头字符串的headers属性。如果请求的方法为POST,我们可以通过body来设置请求的主体。invoke方法用于执行请求。在请求完成时,该类会通过completed事件发出通知。

  每个Web请求都是通过一个内部类(Web请求管理器)执行的,它会使用“执行器(executor)”来打开套接字并发送数据包。所有执行器派生自公共基类Sys.Net.WebRequestExecutor类。

  Microsoft AJAX库只定义了一个HTTP执行器,即Sys.NetXMLHttpExecutor类。该执行器使用广为人知的XMLHttpRequest对象为执行HTTP请求。

  所有AJAX库都与XMLHttpRequest浏览器对象有关。执行器除了是浏览器对象XMLHttpRequest的引用外,还可以是什么呢?一般来讲,HTTP执行器可以是任何能够用来执行Web请求的工具。其他执行器可以是基于HTTP框架(frame)的。其思想是,使用动态创建的内联框架来下载给定请求的响应,然后将结果解析到可用的对象中。

DOM事件

  IE、Firefox和Safari各自具有独立的事件模型。为此,Microsoft AJAX库的事件模型提供了一种新抽象的编程接口,结合了标准的W3C编程接口和IE不可扩展的模型。新的编程接口在很大程度上是为遵循标准的W3C而建立的。

  不同的浏览器使用不同的方法名称(Firefox使用add/removeEventListener,而IE使用attach/detachEvent)。并且,不同浏览器将数据传给处理程序的方式亦各不相同。IE中,数据查询程序通过全局的事件对象window.event来接收数据。而在firefox中,事件数据是以形参(argument)的方式传给处理程序。在Microsoft AJAX库中,事件处理程序统一接收到带有事件数据的实参(parameter)。

  另一个显著差异是鼠标和键盘事件的表示法。Microsoft AJAX库通过提供特殊的枚举类型(如Sys.UI.Key和Sys.UI.MouseButton)对不同浏览器的差异进行了抽象:

function button1_Click(e)
{
if(e.button == Sys.UI.MouseButton.leftButton)
{
...
}
}
function keyboard_EnterPressed(e)
{
if(e.keyCode == Sys.UI.Key.enter)
{
...
}
}

  Microsoft AJAX库还提供了添加和移除DOM事件处理程序的快捷方式。如,我们可使用别名$addHandler和$removeHandler来添加和移除处理程序:

$addHandler(element, "eventName", handler);
$removeHandler(element,
"eventName", handler);

  许多情况下,我们可能希望为某个组件同时注册多个DOM事件处理程序。Microsoft AJAX提供了一种精简的语法实现此功能:

initialize: function()
{
var elem = this.get_element();
$addHandlers(
elem,
{[
'mouseover' : this._mouseHoverHandler,
'mouseout' : this._mouseOutHandler,
'focus' : this._focusHandler,
'blur' : this._blurHandler
]},
this);
}

  别名$clearHandlers能一次性移除特定DOM元素的所有处理程序。

  注意,如果编写某个组件的同时,还要包装一些事件,那么在组件被释放前,一定要清除所有不需要的处理程序。

其他工具

  Microsoft AJAX库中包含许多功能各异的组件,为开发者提供额外的工具。

  Sys.StringBuilder类实现了高级文本操作功能。该类是模仿.NET类库中的StringBuilder类行为。

  另外值得一提的类是Sys._Debuf,这个内部类实例会赋给全局对象Sys.Debug。我们可使用它来检查条件、设置断点或输出跟踪文本。

posted on 2011-05-02 22:37  辛勤的代码工  阅读(504)  评论(0编辑  收藏  举报