更新:2007 年 11 月
ASP.NET 中的 AJAX 功能有助于创建客户端脚本并将其集成到 ASP.NET 应用程序中。这包括 ECMAScript (JavaScript) 的类型系统以及为现有 ECMAScript (JavaScript) 对象提供丰富的 .NET Framework 类的扩展。ASP.NET 还包括 ScriptManager 控件,此控件可用于管理这些脚本库以及应用程序中的任何自定义脚本。
本主题包含以下部分:


Microsoft AJAX Library 增加了一个类型系统以及一系列对 JavaScript 对象的扩展,可提供与 .NET Framework 功能类似的面向对象的常用功能。利用这些功能,可按一种结构化方式编写支持 AJAX 的 ASP.NET 应用程序,这不仅能提高可维护性,还简化了添加功能以及对功能分层的操作。Microsoft AJAX Library 扩展为 JavaScript 添加了下列功能:
-
类
-
命名空间
-
继承
-
接口
-
枚举
-
反射
该库还提供了针对字符串和数组的 Helper 函数。
类、成员和命名空间
Microsoft AJAX Library 包括基类及其派生的对象和组件。通过所有这些类,您可以使用面向对象的编程模型来编写客户端脚本。
Type 类为 JavaScript 编程添加了命名空间、类和继承等面向对象的功能。任何使用 Type 类注册的 JavaScript 对象都会自动获得访问此功能的权限。下面的示例演示如何使用 Type 类在 JavaScript 文件中创建并注册一个命名空间和类:
Type.registerNamespace("Demo"); Demo.Person = function(firstName, lastName, emailAddress) { this._firstName = firstName; this._lastName = lastName; this._emailAddress = emailAddress; } Demo.Person.prototype = { getFirstName: function() { return this._firstName; }, getLastName: function() { return this._lastName; }, getName: function() { return this._firstName + ' ' + this._lastName; }, dispose: function() { alert('bye ' + this.getName()); } } Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable); // Notify ScriptManager that this is the end of the script. if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
类有四种成员:字段、属性、方法和事件。字段和属性是名称/值对,用于描述类实例的特征。字段由基元类型组成,可直接进行访问,如下面的示例所示:
myClassInstance.name="Fred"
属性可以表示任何基元类型或引用类型。属性值需通过 get 和 set 访问器方法进行访问。在 Microsoft AJAX Library 中,get 和 set 访问器都是函数。按照约定,这些函数的名称中应使用前缀“get_”或“set_”。例如,若要获取或设置属性 cancel 的值,需要调用 get_cancel 或 set_cancel 方法。
对于在 AJAX 客户端应用程序生命周期中发生的操作,Microsoft AJAX Library 将引发相应的事件进行响应。Microsoft AJAX Library 还提供一种为 AJAX 客户端组件创建自定义事件的标准方式。有关更多信息,请参见创建自定义客户端事件和 AJAX 客户端生命周期事件。
Microsoft AJAX Library 提供一种有助于对常用功能进行分组的命名空间注册方式。下面的示例演示如何使用 Type.registerNamespace 和 .registerClass 方法向 Demo 命名空间中添加 Person 类。
若要对 ASP.NET 网页启用 AJAX 功能,必须向该页面添加 ScriptManager 控件。呈现该页面时,将自动生成对 AJAX 客户端脚本库的相应脚本引用。下面的示例演示一个包含 ScriptManager 控件的页面。
<asp:ScriptManager runat="server" ID="scriptManager" />
下面的示例演示如何完成以下过程:注册命名空间,创建类,然后重新注册该类。
Type.registerNamespace("Demo"); Demo.Person = function(firstName, lastName, emailAddress) { this._firstName = firstName; this._lastName = lastName; this._emailAddress = emailAddress; } Demo.Person.prototype = { getFirstName: function() { return this._firstName; }, getLastName: function() { return this._lastName; }, getName: function() { return this._firstName + ' ' + this._lastName; }, dispose: function() { alert('bye ' + this.getName()); } } Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable); // Notify ScriptManager that this is the end of the script. if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <title>Namespace</title> </head> <body> <form id="Main" runat="server"> <asp:ScriptManager runat="server" ID="scriptManager" /> </form> <div> <p>This example creates an instance of the Person class and puts it in the "Demo" namespace.</p> <input id="Button1" value="Create Demo.Person" type="button" onclick="return OnButton1Click()" /> </div> <script type="text/javascript" src="Namespace.js"></script> <script type="text/javascript" language="JavaScript"> function OnButton1Click() { var testPerson = new Demo.Person( 'John', 'Smith', 'john.smith@example.com'); alert(testPerson.getFirstName() + " " + testPerson.getLastName() ); return false; } </script> </body> </html>
访问修饰符
大多数面向对象的编程语言都包括“访问修饰符”这一概念。通过访问修饰符,可以指定类或成员可用的上下文,例如是对外部程序可用,还是对同一命名空间中的内部类可用,抑或是仅在特定的代码块中可用。JavaScript 中没有访问修饰符。但是,Microsoft AJAX Library 遵循以下约定:名称以下划线字符(“_”)开头的成员视为私有成员,不能从成员所属类的外部访问它们。
继承
继承是指一个类从另一个类派生的能力。派生类可自动继承基类的所有字段、属性、方法和事件。派生类可以添加新成员或者重写基类的现有成员,以更改这些成员的行为。
下面的示例包含两个在脚本中定义的类:Person 和 Employee,其中 Employee 派生自 Person。这两个类演示私有字段的用法,并且它们都具有公共属性和方法。此外,Employee 还重写 Person 类的 toString 实现并使用基类的功能。
Type.registerNamespace("Demo"); Demo.Person = function(firstName, lastName, emailAddress) { this._firstName = firstName; this._lastName = lastName; this._emailAddress = emailAddress; } Demo.Person.prototype = { getFirstName: function() { return this._firstName; }, getLastName: function() { return this._lastName; }, getEmailAddress: function() { return this._emailAddress; }, setEmailAddress: function(emailAddress) { this._emailAddress = emailAddress; }, getName: function() { return this._firstName + ' ' + this._lastName; }, dispose: function() { alert('bye ' + this.getName()); }, sendMail: function() { var emailAddress = this.getEmailAddress(); if (emailAddress.indexOf('@') < 0) { emailAddress = emailAddress + '@example.com'; } alert('Sending mail to ' + emailAddress + ' ...'); }, toString: function() { return this.getName() + ' (' + this.getEmailAddress() + ')'; } } Demo.Person.registerClass('Demo.Person', null, Sys.IDisposable); Demo.Employee = function(firstName, lastName, emailAddress, team, title) { Demo.Employee.initializeBase(this, [firstName, lastName, emailAddress]); this._team = team; this._title = title; } Demo.Employee.prototype = { getTeam: function() { return this._team; }, setTeam: function(team) { this._team = team; }, getTitle: function() { return this._title; }, setTitle: function(title) { this._title = title; }, toString: function() { return Demo.Employee.callBaseMethod(this, 'toString') + '\r\n' + this.getTitle() + '\r\n' + this.getTeam(); } } Demo.Employee.registerClass('Demo.Employee', Demo.Person);
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <title>Inheritance</title> </head> <body> <form id="Main" runat="server"> <asp:ScriptManager runat="server" ID="scriptManager" /> <script type="text/javascript" src="Inheritance.js"></script> </form> <h2>Inheritance</h2> <p /> <div> This file contains two classes defined in script: Person and Employee, where Employee derives from Person. <p /> Each class has private fields, and public properties and methods. In addition, Employee overrides the toString implementation, and in doing so, it uses the base class functionality. <p /> This example puts the Person class in the "Demo" namespace. <p /> </div> <div> <ul> <li><a href="#" onclick="return OnTestNewClick()">Object Creation</a></li> <li><a href="#" onclick="return OnTestDisposeClick()">Object Dispose</a></li> <li><a href="#" onclick="return OnTestPrivatePropertyClick()">Public vs. Private Properties</a></li> <li><a href="#" onclick="return OnTestInstanceMethodClick()">Instance Methods</a></li> <li><a href="#" onclick="return OnTestOverrideMethodClick()">Overriden Methods</a></li> <li><a href="#" onclick="return OnTestInstanceOfClick()">Instance Of Check</a></li> </ul> </div> <script type="text/javascript" language="JavaScript"> function GetTestPerson() { return new Demo.Person('Jane', 'Doe', 'jane.doe@example.com'); } function GetTestEmployee() { return new Demo.Employee('John', 'Doe', 'john.doe@example.com', 'Platform', 'Programmer'); } function OnTestNewClick() { var aPerson = GetTestPerson(); alert(aPerson.getFirstName()); alert(aPerson); alert(Object.getType(aPerson).getName()); var testPerson = GetTestPerson(); alert(testPerson.getFirstName()); alert(testPerson); return false; } function OnTestDisposeClick() { var aPerson = GetTestEmployee(); alert(aPerson.getFirstName()); aPerson.dispose(); } function OnTestPrivatePropertyClick() { var aPerson = GetTestEmployee(); alert('aPerson._firstName = ' + aPerson._firstName); alert('aPersona.getFirstName() = ' + aPerson.getFirstName()); return false; } function OnTestInstanceMethodClick() { var aPerson = GetTestEmployee(); aPerson.sendMail('Hello', 'This is a test mail.'); return false; } function OnTestOverrideMethodClick() { var testPerson = GetTestEmployee(); alert(testPerson); return false; } function OnTestInstanceOfClick() { var aPerson = GetTestEmployee(); if (Demo.Employee.isInstanceOfType(aPerson)) { alert(aPerson.getName() + ' is an Employee instance.\r\nTitle property: ' + aPerson.getTitle()); } return false; } </script> </body> </html>
接口
接口用于定义实现它的类的输入和输出要求。这样,函数可以和实现同一接口的类进行交互,而不用考虑该类还实现哪些其他功能。
下面的示例定义一个 Tree 基类和一个 IFruitTree 接口。两个派生类 Apple 和 Banana 可实现 IFruitTree 接口,但 Pine 类不实现该接口。实现 IFruitTree 接口的任何类都可确保 bearFruit 方法是该类的成员。
Type.registerNamespace("Demo.Trees"); Demo.Trees.IFruitTree = function() {} Demo.Trees.IFruitTree.Prototype = { bearFruit: function(){} } Demo.Trees.IFruitTree.registerInterface('Demo.Trees.IFruitTree'); Demo.Trees.Tree = function(name) { this._name = name; } Demo.Trees.Tree.prototype = { returnName: function() { return this._name; }, toStringCustom: function() { return this.returnName(); }, makeLeaves: function() {} } Demo.Trees.Tree.registerClass('Demo.Trees.Tree'); Demo.Trees.FruitTree = function(name, description) { Demo.Trees.FruitTree.initializeBase(this, [name]); this._description = description; } Demo.Trees.FruitTree.prototype.bearFruit = function() { return this._description; } Demo.Trees.FruitTree.registerClass('Demo.Trees.FruitTree', Demo.Trees.Tree, Demo.Trees.IFruitTree); Demo.Trees.Apple = function() { Demo.Trees.Apple.initializeBase(this, ['Apple', 'red and crunchy']); } Demo.Trees.Apple.prototype = { makeLeaves: function() { alert('Medium-sized and desiduous'); }, toStringCustom: function() { return 'FruitTree ' + Demo.Trees.Apple.callBaseMethod(this, 'toStringCustom'); } } Demo.Trees.Apple.registerClass('Demo.Trees.Apple', Demo.Trees.FruitTree); Demo.Trees.GreenApple = function() { Demo.Trees.GreenApple.initializeBase(this); // You must set the _description feild after initializeBase // or you will get the base value. this._description = 'green and sour'; } Demo.Trees.GreenApple.prototype.toStringCustom = function() { return Demo.Trees.GreenApple.callBaseMethod(this, 'toStringCustom') + ' ... its GreenApple!'; } Demo.Trees.GreenApple.registerClass('Demo.Trees.GreenApple', Demo.Trees.Apple); Demo.Trees.Banana = function(description) { Demo.Trees.Banana.initializeBase(this, ['Banana', 'yellow and squishy']); } Demo.Trees.Banana.prototype.makeLeaves = function() { alert('Big and green'); } Demo.Trees.Banana.registerClass('Demo.Trees.Banana', Demo.Trees.FruitTree); Demo.Trees.Pine = function() { Demo.Trees.Pine.initializeBase(this, ['Pine']); } Demo.Trees.Pine.prototype.makeLeaves = function() { alert('Needles in clusters'); } Demo.Trees.Pine.registerClass('Demo.Trees.Pine', Demo.Trees.Tree);
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <title>Interface</title> </head> <body> <form id="Main" runat="server"> <asp:ScriptManager runat="server" ID="scriptManager" /> </form> <h2>Interface</h2> <p /> <div> This file contains a Tree base class, and an IFruitTree interface. Apple and Banana, two derived classes implement that interface, whereas, Pine does not implement that interface. <p /> </div> <script type="text/javascript" src="Interface.js"></script> <div> <ul> <li><a href="#" onclick="return OnTestNewClick()">Object Creation</a></li> <li><a href="#" onclick="return OnTestImplementsClick()">Implements Check</a></li> <li><a href="#" onclick="return OnTestInterfaceMethodClick()">Call interface method</a></li> </ul> </div> <script type="text/javascript" language="JavaScript"> function OnTestNewClick() { var apple = new Demo.Trees.Apple('Apple'); alert(apple.returnName()); apple.makeLeaves(); return false; } function OnTestImplementsClick() { var apple = new Demo.Trees.Apple(); if (Demo.Trees.IFruitTree.isImplementedBy(apple)) { alert('Apple implements IFruitTree'); } else { alert('Apple does not implement IFruitTree'); } var pine = new Demo.Trees.Pine(); if (Demo.Trees.IFruitTree.isImplementedBy(pine)) { alert('Pine implements IFruitTree'); } else { alert('Pine does not implement IFruitTree'); } return false; } function OnTestInterfaceMethodClick() { var apple = new Demo.Trees.Apple(); ProcessTree(apple); var pine = new Demo.Trees.Pine(); ProcessTree(pine); var banana = new Demo.Trees.Banana(); ProcessTree(banana); var g = new Demo.Trees.GreenApple(); ProcessTree(g); return false; } function ProcessTree(tree) { alert('Current Tree ' + tree.returnName()); alert(tree.toStringCustom()); if (Demo.Trees.IFruitTree.isImplementedBy(tree)) { alert(tree.returnName() + ' implements IFruitTree; Fruit is ' + tree.bearFruit()); } } </script> </body> </html>
枚举
枚举是指包含一组命名整数常量的类。您可以像访问属性那样访问这些值,如下面的示例所示:
myObject.color = myColorEnum.red
枚举提供不同于整数的另一种易读的表示形式。有关 Microsoft AJAX Library 中的枚举的更多信息,请参见 Type.registerEnum 方法 (ASP.NET AJAX)。
下面的示例定义一个命名颜色的枚举,这些命名颜色用于表示十六进制的值。
Type.registerNamespace("Demo"); // Define an enumeration type and register it. Demo.Color = function(){}; Demo.Color.prototype = { Red: 0xFF0000, Blue: 0x0000FF, Green: 0x00FF00, White: 0xFFFFFF } Demo.Color.registerEnum("Demo.Color");
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <title>Enumeration</title> </head> <body> <form id="Main" runat="server"> <asp:ScriptManager runat="server" ID="scriptManager" /> </form> <div> <p>This example creates an Enumeration of colors and applies them to page background.</p> <select id="ColorPicker" onchange="ChangeColor(options[selectedIndex].value)"> <option value="Red" label="Red" /> <option value="Blue" label="Blue" /> <option value="Green" label="Green" /> <option value="White" label="White" /> </select> </div> <script type="text/javascript" src="Enumeration.js"></script> <script type="text/javascript" language="JavaScript"> function ChangeColor(value) { document.body.bgColor = eval("Demo.Color." + value + ";"); } </script> </body> </html>
反射
反射是指在运行时检查程序的结构和组件的能力。实现反射的 API 是对 Type 类的扩展。通过这些方法,可以收集有关对象的信息,例如该对象继承自谁,它是否实现特定的接口,以及它是否是特定类的实例等。
下面的示例使用反射 API 对前面接口示例中的 GreenApple 类进行测试。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html > <head> <title>Reflection</title> </head> <body> <form id="Main" runat="server"> <asp:ScriptManager runat="server" ID="scriptManager" /> </form> <div> <p>This example tests the Demo.Trees.GreenApple class against various reflection APIs.</p> <input id="Button1" value="Check Type" type="button" onclick="return OnButton1Click()" /> <input id="Button2" value="Check Inheritance" type="button" onclick="return OnButton2Click()" /> <input id="Button3" value="Check Interface" type="button" onclick="return OnButton3Click()" /> </div> <script type="text/javascript" src="Interface.js"></script> <script type="text/javascript" language="JavaScript"> var g = new Demo.Trees.GreenApple(); var gt = Demo.Trees.GreenApple; var a = new Array( Demo.Trees.Apple, Demo.Trees.Tree, Demo.Trees.Pine, Demo.Trees.IFruitTree, Sys.IContainer); function OnButton1Click() { for (var i = 0; i < a.length; i ++) { if (a[i].isInstanceOfType(g)) { alert(gt.getName() + " is a " + a[i].getName() + "."); } else alert(gt.getName() + " is not a " + a[i].getName() + "."); } } function OnButton2Click() { for (var i = 0; i < a.length; i ++) { if (gt.inheritsFrom(a[i])) { alert(gt.getName() + " inherits from " + a[i].getName() + "."); } else alert(gt.getName() + " does not inherit from " + a[i].getName() + "."); } } function OnButton3Click() { for (var i = 0; i < a.length; i ++) { if (Type.isInterface(a[i])) { if (gt.implementsInterface(a[i])) { alert(gt.getName() + " implements the " + a[i].getName() + " interface."); } else alert(gt.getName() + " does not implement the " + a[i].getName() + " interface."); } else alert(a[i].getName() + " is not an interface."); } } </script> </body> </html>

JavaScript 基类型的扩展可为这些类型提供附加功能。有关这些扩展的更多信息,请参见下列主题:
Sys.Debug 类可提供丰富的调试功能。有关更多信息,请参见 调试和跟踪 AJAX 应用程序概述和 Sys.Debug 类概述。
如果基于 Microsoft AJAX Library 创建组件,则可以创建由 ScriptManager 控件自动管理的脚本文件的调试版本和发行版本。通过在脚本文件名中添加“.debug”部分,可以标识脚本文件的调试版本。例如,下面的脚本文件名标识同一文件的零售版本和调试版本:
-
MyScript.js(零售版)
-
MyScript.debug.js(调试版)

任何 ASP.NET 网页均可以通过在 <script> 块中引用脚本文件来对其进行访问,如下面的示例所示:
<script type="text/javascript" src="MyScript.js"></script>
但是,以此方式调用的脚本将不能参与部分页呈现,或无法访问 Microsoft AJAX Library 的某些组件。若要使脚本文件可在支持 AJAX 的 ASP.NET Web 应用程序中用于部分页呈现,必须在该页面的 ScriptManager 控件中注册该脚本。若要注册脚本文件,请创建一个指向相关文件的 ScriptReference 对象,并使之将该文件添加到 Scripts 集合中。下面的示例演示如何在标记中执行此操作:
<asp:ScriptManager ID="SMgr" runat="server"> <Scripts> <asp:ScriptReference path="MyScript.js" /> </Scripts> </asp:ScriptManager>
若要使脚本文件得到 ScriptManager 控件的正确处理,每个文件都必须在末尾包含对 Sys.Application.notifyScriptLoaded 方法的调用。此调用可通知应用程序,已完成文件加载。下面的示例演示用于实现此目的的代码:
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
此外,您还可以将 .js 文件作为资源嵌入在托管代码程序集中(如果创建的 ASP.NET 服务器控件在客户端脚本中实现其 AJAX 功能,则可能需要这样做)。将脚本嵌入在程序集中时,脚本中便无需包括通知语句。此外,您也不必在脚本引用中指定 path 属性。但是,必须提供不带文件扩展名的程序集名称,如下面的示例所示:
<asp:ScriptManager ID="SMgr" runat="server"> <Scripts> <asp:ScriptReference Name="MyScript.js" Assembly="MyScriptAssembly"/> </Scripts> </asp:ScriptManager>
![]() |
---|
对页面开发人员而言,此情况并不常见,因为嵌有脚本库的大多数控件都会从内部引用其脚本。有关更多信息,请参见 演练:将 JavaScript 文件作为资源嵌入到程序集中。 |
此外,通过在代码中创建脚本引用并将它们添加到 Scripts 集合中,还可以用编程方式注册脚本。有关更多信息,请参见 动态分配脚本引用。
使用 ScriptManager 控件的注册方法,可以注册部分页更新所需的脚本。您可以按下列方式使用这些方法:
-
若要在代码中生成客户端脚本,请以字符串形式生成一个脚本块,然后将其传递给 RegisterClientScriptBlock 方法。
-
若要添加没有 Microsoft AJAX Library 依赖项的独立脚本文件,请使用 RegisterClientScriptInclude 方法。
-
若要添加嵌入在程序集中的脚本文件,请使用 RegisterClientScriptInclude 方法。
说明:
使用这些方法注册的脚本不具有本地化支持。
有关脚本注册方法的完整列表及其用法,请参见 ScriptManager 控件概述。
任何要注册的脚本块或内联脚本都必须位于页面的 <form> 元素中。否则,该脚本将不能在 ScriptManager 控件中注册,从而无法访问 ASP.NET AJAX 功能。有关更多信息,请参见 Sys.Application.initialize 方法。
