首页 >> 编程知识

Ajax基础之Javascript篇、Ajax.NET简介及缺陷

Ajax基础之Javascript篇



5.2  Ajax基础
在进行Ajax开发以前,我们必须先对一些基本的知识进行了解。虽然Ajax.NET已经将Ajax实现细节封装起来,并大大简化了我们的操作,但是了解基本的Ajax实现方式对于帮助我们在开发中能更好理解和实施,以及更有效地优化和排错是至关重要的。此外,虽然我们不必编写具体的Ajax调用代理,但是在调用代理前获取数据,以及返回数据后怎样使数据能够正确显示,这两部分工作都需要我们具有基本的JavaScript操作DOM模型的能力。接下来我们将来对这几方面的知识进行讨论。

5.2.1  XMLHttpRequest对象
对于Ajax技术的基础和核心,XMLHttpRequest对象应该是我们必须要了解的一个对象,Ajax实现的关键发送异步请求并接收响应执行回调都是通过它来完成的。XMLHttpRequest最早是在Microsoft Internet Explorer 5.0以ActiveX组件中被引入的,之后各大浏览器厂商都以JavaScript内置对象的方式实现了XMLHttpRequest对象。虽然大家对它的实现方式有所区别,但是绝大多数浏览器都提供了类似的属性和方法,在实际脚本编写方法上区别不大,并且实现得到的效果也基本相同,目前W3C正致力于将XMLHttpRequest对象制定一个统一的标准使各个浏览器厂商遵照执行,以利于Ajax技术的推广与发展。

XMLHttpRequest提供了一个相对精简易用的API,下面我们就将简单地介绍一下它所提供的属性和方法以及怎么利用这些属性和方法完成一次Ajax的请求和响应处理。

1.readyState属性

当一个XMLHttpRequest对象被创建后,此属性标识了此对象正处于什么状态,我们可以通过对此属性的访问,来判断此次请求的状态是什么然后做出相应的操作。具体此属性的值代表的意义见表5-1。

表5-1


 说    明
 
0
 未初始化状态;此时,已经创建一个XMLHttpRequest对象,但是还没有初始化此对象的属性
 
1
 准备发送状态;此时,已经调用了XMLHttpRequest对象的Open()方法,并且已经准备好将一个XMLHttpRequest请求发送到服务端
 
2
 已发送状态;此时,已经调用了XMLHttpRequest对象的Send()方法,但是并没有收到任何响应
 
3
 正在接收状态;此时,已经开始接收HttpResponse响应信息但是还没有完成接收
 
4
 完成响应状态;此时,已经完成了HttpResponse响应的接收
 

2.responseText属性

此属性描述的是一个HttpResponse中的全部文本内容,通过访问它,可以得到一次XMLHttpRequest得到响应回传的全部文本内容。只有当ReadyState的值为3或4时此属性才会有部分或者全部值,否则此属性只会是空字串。

3.responseXML属性

只有当ReadyState属性为4,并且响应头部的Content-Type的MIME类型被指定为XML(text/xml或者application/xml)时,此属性才会有值并且被解析为一个XML文档,否则此属性为Null。若是回传的XML文档结构不良或未完成响应回传,此属性也会为Null,由此可见,此属性用来描述被XMLHttpRequest解析后的XML文档的属性。

4.status属性

用于描述服务器Http请求的状态值,通过此属性值我们可以判断服务器的响应状态,如我们通常通过判断status==200来判断服务器是否正常返回。但是注意,必须是日readyState为3或4时才能对此属性进行访问。

5.status属性

用于描述服务器Http请求的状态文本,通过此属性我们可以得到服务器响应的状态的描述文本,与status属性同样,必须在readyState为3或4时才能对此属性进行访问。

6.onreadystatechange事件

每当readyState发生改变时触发此事件,我们一般都通过此事件来触发回传处理函数。

7.open()方法

XMLHttpRequest对象是通过open(method,uri,async,username,password)的方法来进行初始化工作的,通过调用此方法将得到一个可以用来进行发送(send()方法)的对象。其中method参数是用来指定发送请求的HttpRequest类型,其值类型为字串,值可以为get、post、put、delete等;uri参数是用来指定请求被发送到的服务器地址,该地址会被自动解析为绝对地址,所以在这里可以用相对地址来表示;async是一个类型为boolean类型的参数,默认情况下为true,此时表示为异步提交,如果希望发送一个同步请求可以将此值设为false;在服务器需要验证访问用户的情况,我们可以设置username以及password两个参数。

当open()方法被调用时,XMLHttpRequest对象将会把readyState属性设为1,且初始化其他属性,如果此时一个请求正在被发送或者响应正在被接收,则前一请求的数据和内容将会丢失,请求将会被取消。

8.send()方法

当调用open()方法后,我们就可以通过调用send()方法按照open()方法设定的参数将请求进行发送。当open()方法中async参数为true时,在send()方法调用后立即return,否则将会中断直到请求返回。需要注意的是,send()方法必须在readyState为1时,即调用open()方法以后调用。在调用send()方法以后到接收到响应头之前,readyState的值将被设为2,一旦开始接收到响应消息,readyState将会被设为3,直到响应接收完成,readyState的值才会被设为4。

9.abort()方法

该方法可以暂停一个HttpRequest的请求发送或是HttpResponse的接收,并且将XMLHttpRequest对象设置为初始化状态。

10.setRequestHeader()方法

该方法用于在调用open()方法后,设置HttpRequest头的信息,setRequestHeader(header,value)方法包含两个参数,前一个是header键名称,后一个是其值。

11.getResponseHeader()方法

此方法在readyState为3或4时,用于获取HttpResponse的头部信息,此外我们还可以通过getAllResponseHeaders()获取所有的HttpResponse的头部信息。

在搞清楚了XMLHttpRequest的这些基本属性方法以后,我们就可以开始编写我们的第一个Ajax程序了。我们准备通过点击一个按钮然后通过Ajax的方式到服务端取回一个Hello world!的字符串显示在界面的一个文本框里。

我们在一个配置好的站点工程里面新建一个名为AjaxTest.aspx页面。首先我们在cs文件中的page_load事件函数中写下如下代码:

AjaxTest.aspx.cs:

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Request.QueryString["s"] == "1")//使用查询字串来指示这个请求是通过Ajax发出的

        {

            Response.Write("hello world!");//向HttpResponse中输出hello world!

            Response.End();//将页面缓冲发送向客户端浏览器 并中止该页输出 

                           //如果去掉这句 会得到多余的HTML代码

        }

    }

相对来说,我们在前台页面中书写的代码将会多一些,慢慢地你会发现这也许是Ajax的一个惯例:

AjaxTest.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AjaxTest.aspx.cs" Inherits= "AjaxTest" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>测试</title>

    <script language="javascript" type="text/javascript">

    <!--

    function GetInfo(){//我们就是通过这个函数来异步获取信息的

        var xmlHttpReq = null;//声明一个空对象用来装入XMLHttpRequest

        if (window.XMLHttpRequest){//除IE5 IE6 以外的浏览器XMLHttpRequest是window的子对象

            xmlHttpReq = new XMLHttpRequest();//我们通常采用这种方式实例化一个XMLHttpRequest

        } 

        else if (window.ActiveXObject){//IE5 IE6是以ActiveXObject的方式引入XMLHttpRequest的

            xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");

                                           //IE5 IE6是通过这种方式

        }

        if(xmlHttpReq != null){//如果对象实例化成功 我们就可以干活啦

            xmlHttpReq.open("get","AjaxTest.aspx?s=1",true);

                                          //调用open()方法并采用异步方式

            xmlHttpReq.onreadystatechange=RequestCallBack; //设置回调函数

            xmlHttpReq.send(null);//因为使用get方式提交,所以可以使用null参调用

        }

        function RequestCallBack(){//一旦readyState值改变,将会调用这个函数

            if(xmlHttpReq.readyState == 4)

            {

                document.getElementById("iptText").value = xmlHttpReq.responseText;

                //将xmlHttpReq.responseText的值赋给iptText控件

            }

        }

    }

    -->

    </script>

</head>


    <form id="form1" runat="server">

        <div>

            <input id="iptText" type="text" value="" />

            <input type="button" id="" value="Ajax提交" onclick="GetInfo();" />

            <!--点击这个按钮调用-->

        </div>

    </form>

</body>

</html>

如果你在点击按钮的瞬间发现文本框内闪电般地出现了“Hello world!”,那么恭喜你,你已经完成了一个Ajax调用。如果你还对前台页面中那些和C#貌似神离的代码觉得不太明白,没关系,接下来我们就将来简单学习一下Ajax另外一个重要的部分——JavaScript。

5.2.2  JavaScript基础
大多数人看见JavaScript的第一感觉就是它和Java有关系,但实际上JavaScript是一种脚本语言,最初是由网景公司在LiveScript的基础上改进而来。它在很多地方都有应用,当然应用最广泛的地方当然还是浏览器上,大多数浏览器对JavaScript的支持都很好。相对于C#、Java来说,JavaScript更具灵活性,这也正是很多程序员觉得JavaScript难学的原因之一。此外JavaScript运行的平台是浏览器,各个浏览器对JavaScript的支持程度也不尽相同,程序员很多时候需要编写许多冗余的代码来适应各个浏览器不同的情况,一直以来这是最让Web程序员头疼的事情。在大多数情况下,JavaScript无疑是易用强大的,下面我们就从最基础的东西来认识这种语言。

我们通常在网页中使用JavaScript是将其写在成对的<script></script>标签之中,下面给出一个例子:

    <script language="javascript" type="text/javascript">

    <!--

        alert("OK");//这句话的作用是弹出一个写有 OK 的警告框

    -->

    </script>

language="javascript"和type="text/javascript"两个属性都标示了这个脚本类型是JavaScript,区别在于前面一个可以兼容老版本的浏览器,后面一种是W3C的推荐标准写法。我们可以把<script>标签放在整个文档的任何位置,其中的脚本都会起作用,但是通常情况下我们都把脚本写在<head>标签中,对于控制显示页面的脚本必须要写在
另外我们还可以将脚本写在一个后缀名为.js的单独的文件中,然后通过下面这种方式在网页中引用它:

<script language="javascript" type="text/javascript" src="ajax.js"></script>

这样写的好处是可以将一些公共的脚本都写在一个文件里面,然后通过给页面添加一个引用就可以在页面中使用了。

1.数据类型

JavaScript是一种弱类型的语言,在声明变量的时候不确定其类型,而通过赋值来确定变量的类型,并且我们可以对同一个变量赋不同类型的值,同时这个变量的类型也得以改变。在JavaScript中主要有四种数据类型,分别是数值型、字串型、布尔型和空,下面将简要介绍一下这四种类型:

数值型:数值型分为整型和实型,我们可以用八进制、十进制以及十六进制来表示一个整数,但是只能用十进制来表示一个小数。我们通常使用0作为前导来表示八进制数,如02435表示十进制的88,需要注意的是八进制数只能是0~7的数来表示;使用0x作为前导可以表示十六进制的数,如0x3e2d表示的是十进制的88343,十六进制使用0~9和a~e来表示0~15;我们可以使用parseInt(num,[radix])将八进制和十六进制数值转换成十进制的数,参数radix就是进制的值可以是8或16。对于实型,我们只能用十进制来表示,此外我们可以使用科学计数法表示一个实数,如3.456e2表示的是345.6,e后面的数字是表示这个数10的幂值,再如-7.89e-2表示-0.0789。

字串型:通常通过使用“符号或者’符号将一段字符括起来表示字串,比如“abc”,‘this is string’,“和’在使用上没什么区别,只是我们在一些时候需要嵌套使用,比如需要在一个字串中使用引号时,则可以使用”he say’hello!’”这种形式。此外还可以使用字符串转义符 \ 来对特殊字符进行表示,如”he say \”nothing\”.”,这样在字符串中就能显示”符号。同样地和C#类似,可以通过转义符表示在字符串中表示回车换行等特殊字符,如\n、\r。

布尔型:布尔型的值只能是true和flase,表示真和假,相对C#来说,JavaScript对布尔型的数值转换更灵活一些,在做判断的时候会自动将当前数值转化为布尔型,如果你定义一个没有赋值的便变量a,并且做判断if(a),解释器会自动将a的值null转化为布尔型并返回false,同样解释器会将0转化为flase,将0以外的数转化为true,这样使得我们在编写代码过程中有了很大的灵活性,并精简JavaScript代码量,降低页面大小加快了浏览速度。

空类型:一般来说对于没有定义的或未付值变量和未引用对象的值为null;此外对于数值类型计算错误(如除以一个字符)会返回一个NaN(Not a Number),这个值比较特殊一些,这个值表示不是数字,所以它和任何数字都不相等,有趣的是,NaN和自身也不相等,在判断的时候要注意。此外在访问一个未定义对象或者对象的一个未定义的属性时,会返回一个undefined来表示未定义。在判断时需要注意区分这几种空的不同情况,做相应的判断,不然很多时候会得到与我们预想相反的运行结果。

2.变量

我们已经知道JavaScript是一种弱类型的语言,在声明变量时不确定其类型,我们是通过关键字var声明一个变量,也可以在声明的同时给这个变量赋值。注意和其他脚本如VBScript不同,这里的变量命名是要区分大小写的。下面是一些例子:

    var a;//定义了一个null变量

    var b = 1;//定义了一个整形变量

    var C = "abc";//定义了一个字串型变量

    var d = true;//定义了一个布尔型变量

    var e = function(s) {alert(s)};//定义了一个函数型变量

这里需要说明的是变量e,它指向的是一个带参的函数,我们可以像调用函数一样调用这个变量,如e(“123”)运行后会发现弹出了一个警告框并显示了”123”,这也是JavaScript灵活性的表现之一。我们定义的变量可以赋以任何可引用到的值,不管是数值、对象还是函数。

我们声明一个变量的作用域和C#类似,在函数体外声明的变量在整个页面都能访问,包括在不同的<script>块,在函数体内部声明的局部变量只能在函数内可见。不同的是JavaScript对分支语句的变量作用域检测更为灵活一些,只要是在上文if语句、for语句中声明了的变量在下文中都能访问,如:

    for(var i=0;i< 2;i++)

    {

        if(1==1)

            var a = "123";

    }

    alert(a);

最后在弹出框里会显示出123,说明JavaScript与C#不同的是,对于分支语句中声明的对象其作用于不仅限于分支语句中。

3.运算符

JavaScript的运算符和C# 类似,在这里将各类运算符按照运算优先级的从高到低列举出来作为参考:

— 单目运算符:-取反、~取补、++自增、--自减;

— 双目运算符:+加、-减、*乘、/除、%取余、|按位或、&按位与、^按位异或、<<按位左移、>>按位右移、>>>按位右移填充零;

— 比较与算符:<小于、>大于、<=小于等于、>=大于等于、==等于;

— 布尔运算符:!取反、&&与、||或;

— 三目运算符:?: 如 a= a>b?a:b; 这是取a、b之间较大的数赋值给a;

— 赋值、复合运算符:=赋值、+=加后赋值、-=减后赋值、*=乘后赋值、/=除后赋值、%=求余后赋值、&=求与后赋值、|=求或后赋值、^=求异或后赋值。

4.语句

语句是程序的灵魂,JavaScript的语句和C系的语句风格大体一致,但由于其作用的特殊位置和极佳的灵活性又自成风格。

注释:与C一样,JavaScript支持两种注释方式,使用//对单行代码进行注释,从//开始直到行尾都为注释内容,使用/*和*/对多行进行注释,解释器对从/*开始直到*/结束之间的内容都会忽略为注释内容。

—  if语句:与C#也非常类似地,if也可以使用多级的条件判断。我们用几个例子来说明:

    if(a==b)

        alert("a等于b");

    else//如果条件不成立

        alert("a不等于b");

    //下面是多级判断的情况

    if(a==b)

    {

        c=a;

        alert("a、b、c都相等");

    }

    else if(b==c)//如果a==b不成立将会执行下一个else if

    {

        c++;

        alert("a、b、c都不相等");

    }

if语句后的else块在不需要的时候是可以不写的,当有多个判断条件的时候,if语句后可以跟连多个else if做多级判断,但是太多的else if会使得程序的可读性降低,此时最好的方式使用switch语句。

—  switch语句:和if语句多级判断最大的不同点是,switch语句将会用值去匹配case后的值,找到相等的值后会从这个值往下执行,直到遇见break语句或是switch末端的};如果没有找到匹配的值会执行defalut分支中的代码,如果没有defalut分支那么将会不执行任何操作,下面是一个例子。

    switch(weekday)

    {

        case 1:

        case 2:

        case 3:

        case 4:

        case 5:

            alert("工作日");

            break;

        case 6:

        case 0:

            alert("假日");

            break;

        default:

            alert("输入数据有误!");

            break;

    }

这是一个通过输入周数判断是工作日还是假日的代码段,在这里我们将等于1~5的分支都定义为工作日,周6和周日为休息日,对于超出0~6范围的值我们进入默认执行的语句并定义为数据错误,这样比我们写一连串的if和else if来得更清楚和简单一些。

循环体:JavaScript提供了两种循环体,for和while两种循环的效果都是一样的,主要是在适应的场合使用相应的循环体,for主要用于比较确定循环次数或是对循环次数比较肯定的场合,而while主要用于不确定循环次数或需要某一偶然因素来结束循环的情况,但这也不是绝对的,使用哪种循环体还是看个人的编码习惯。for的循环体包括下面几个部分for(<变量>=<初始值>;<循环条件>;<变量改变方法>){<执行语句块>},下面是一个使用for循环读取数组的例子:

    var ar = new Array("a","b","c","d");//声明一个有四个字串的数组

    for(var i=0;i<ar.length;i++)//循环执行条件是小于数组的长度

    {

        document.write(ar[i]);//将数组里的值打印在页面上

    }

同样我们也可以用while来完成这一循环:

    var ar = new Array("a","b","c","d"), i = 0;//声明一个有四个字串的数组和计数器i

    while(i < ar.length)//执行条件

    {

        document.write(ar[i++]);//在访问后一定要i++ 不然就死循环啦

    }

在这两个循环中只有一句语句,那么前后的{}就可以省去啦,这样又可以为页面节约几个字符的数据量,这是在编写JavaScript的时候的一个好习惯。

此外对于for循环还有用于遍历一个对象中所有属性的循环方法for(…in…),这个我们在后面将会提到,对于while循环也有另外一种方式do{}while(),区别在于这种写法是先执行循环体,再进行是否继续循环的判断。

在循环中还有两个不得不提及的关键字break和continue,和C#相同的break是跳出循环体,continue是结束本次循环进入下一次循环。

5.函数

在JavaScript中声明一个函数的方法是使用语句function <函数名> (<参数列表>){}。关键字function不但可以用来声明一个函数,也可以用来声明一个类,稍后我们将提到。下面给出声明一个函数的两种方式:

    function fun1()//声明一个无参函数fun1

    {

        alert("this is fun1");

    }

    var fun2 = function()//声明一个变量指向一个匿名函数

    {

        alert("this is fun2");

    }

这两种声明方式虽然调用方式相同,都可以通过fun1()的方式调用,但是在访问规则上有着区别。第一种可以在该访问层级范围的上下文任何地方调用函数,但是第二种只能在声明变量的下文才能对该函数进行调用,通常情况我们采用第一种方式声明函数。

如果函数要带参数,也可以使用带参的函数声明,下面就是一个例子。

    function fun1(a,b)//声明一个带有两个参数a,b的函数fun1

    {

        alert(a+b);//打印出a+b的和

    }

fun1(2,3);//这是调用

也可以不确定函数传入参数的个数,而随意传入参数调用函数:

    function fun2()//没有定义参数列表

    {

        var sum = 0;

        for(var i=0;i< arguments.length;i++)//循环arguments属性集合

            sum+=arguments[i];//获取参数并相加

        alert(sum);

    }

fun2(2,3,4);

在这里我们使用了arguments属性,这是函数的一个成员属性,用于返回调用时传入的参数列表,就可以通过这个属性获取到传入的全部参数,这个属性在很多地方是十分有用的。

函数的返回值是用return关键字,对于没有使用return的函数,我们对该函数获取值将会得到undefined。下面是一个返回值的例子:

    function fun1(a,b)

    {

        return a+b;//返回a+b的和

    }

    alert(fun1(2,3));

可见,在JavaScript中对函数的操作是相当灵活的,这也表现出了JavaScript的灵活性和易用性,但是对于习惯了严格格式的程序员来说,这反而让人觉得有些无所适从,其实只要明白了函数的本质,我们就能更好地运用JavaScript。

6.函数的本质与对象

为了能更好地认识函数的本质,首先来看一个例子:

    var fun1 = new Function("a","b","return a+b");

alert(fun1(2,3));

运行后发现,得到了结果5,这说明使用new Function方法实例出了一个函数,并运行得到了正确的结果。这也是我们声明一个函数的另外一种方式,虽然这种方式基本上并不使用,但是它揭示了函数的本质——函数也是一种JavaScript内置对象实例化以后得到的,只是通常的编码方式让我们察觉不到而已。

JavaScript除了Function这个内置对象之外还提供了一下几种内置对象:

Array数组:可以通过var ar = new Array()的方法得到一个实例化以后的数组对象,和上面的类似使用了new关键字,这个关键字是为对象分配内存并返回这个对象的实例。JavaScript为我们的数组对象提供了许多有用的内置函数,能够通过这些方法方便地对数组进行各种操作,这些操作在很多时候都是十分有用的,下面用一个例子来解析这些常用的方法:

    var ar = new Array();//实例一个空数组

    ar = new Array(3);//实例一个长度为3的空数组

    ar = new Array("a","b","c");//实例一个包含三个元素的数组

    ar = ["a","b","c"];//和上面的效果相同

    ar.push("d");//在数组末尾添加元素 d

    var tin = ar.pop();//将末尾的元素 d 弹出并赋值给tin

    tin = ar.shift();//将头部的元素 a 弹出并赋值给tin

    //此时数组为 ["b","c"]

    ar.unshift("e");//在数组头部添加元素 e 

    //此时数组为 ["e","b","c"]

    ar = ar.concat(ar);//将数组ar自身与自身连接

    //此时数组为 ["e","b","c","e","b","c"]

    ar = ar.slice(2);//将数组从位置2开始(包含2)取出子数组 

    //这个方法还可以包含2个参数 ar.slice(1,4) 表示取出从1到4

    //参数为负时表示从末尾开始倒数

    //此时数组为 ["c","e","b","c"]

    ar = ar.reverse();//将数组反序

    //此时数组为 ["c","b","e","c"]

    var ar1 = ar.splice(1,2,"1","2","3")//这个方法比较复杂一些

    //这一句表示把ar从位置1开始长度为2的两个元素替换为"1,2,3"

    //并把删除的两个元素赋值给ar1 位置可以为负 长度不可以

    //此时数组ar为 ["c","1","2","3","c"]

    //此时数组ar1为 ["b","e"]

    tin = ar.toString();//将数组转化为字符串

    //tin的值为"c,1,2,3,c"

    tin = ar.join('_');//将数组转化为_连接的字符串

    //tin的值为"c_1_2_3_c"

Date日期:作为JavaScript的一个十分重要的内置对象,它提供了关于日期时间的大多数操作,可以使用这个对象来表示从0001年到9999年之间的任意时间,实际上Date对象是使用一个整型变量存储时间的,1970年1月1日之前为负,以后为正。如果没有指定时区,那么这个时间的使用是UTC世界时间,也就是格林威治时间。Date对象的每个get方法都会有一个对应的set方法,并且每个方法都有一个对应的存取UTC时间的方法。如getMonth()是获取月份的方法,对应的有设置月份的方法setMonth(),另外也有getUTCMonth()和setUTCMonth()的方法。下面是一些例子:

    var dt = new Date();//实例一个以当前时间为值得日期对象

    dt = new Date(2008,8,1,18,5,20,300);//实例一个以2008年8月1日为值得日期对象

    var mt = dt.getMonth();//获取dt的月份值为8

    var hr = dt.getHours();//获取dt的小时为18

    var utchr = dt.getUTCHours();//获取UTC小时为10 本机的时区为北京+8

    /*此外还有类似的方法 

    getYear() 获取年份

    getDate() 获取日期

    getMinutes() 获取分钟

    getSeconds() 获取秒

    getMilliseconds() 获取毫秒数

    下面是一些设置时间的方法*/

    dt.setYear(2007);//设置年份为2007 另外还有一个相同的方法 setFullYear

    dt.setYear(99);//这样会自动在前面添加19 使用setFullYear(99)会得到0099

    dt.setDate(15);//如果设置的日期超出了本月的最大限度 会在月份上累加

    //相应地set方法也有对应的各种方法

最后还有几个方法需要提及一下,get/setTime()是获取/设置从1970年1月1日与此时间相差的毫秒数,这个用来进行时间差计算十分有用。另外Date对象还提供了几个方法用于将日期转换为字串进行显示

    document.write(dt.toString());//直接显示当前日期如 "Wed Sep 15 18:05:20 UTC+0800 1999"

    document.write(dt.toLocaleString());//按照本地格式显示如 "1999年9月15日 18:05:20 "

    document.write(dt.toGMTString());//用GMT格式显示 如 "Wed, 15 Sep 1999 10:05:20 UTC "

    document.write(dt.toUTCString());//用UTC格式显示 如"Wed, 15 Sep 1999 10:05:20 UTC"

RegExp正则表达式:正则表达式的方便易用促使了它在较为广泛的领域内运用,在Web客户端,最常见的应用就是验证匹配字符串。下面我们简要介绍一下正则表达式的一些应用。

我们通常有两种方式声明正则表达式:第一种实例RegExp对象创建正则表达式,具体语法为var reg = new RegExp(表达式,传入参数);第二种是直接声明对象方式,这种方式看起来更为简洁一些 var reg = /表达式/传入参数。其中传入参数有g、i、m三个值,g代表全字符串匹配,i代表不区分大小写匹配,m代表进行多行匹配,下面是一些例子:

    var reg = new RegExp("abc");

    var reg = new RegExp("abc","g");

    var reg = /abc/;

    var reg = /abc/i;

正则表达式可以通过+、*、?、.、^、$等特殊符号与字符串的组合达到复杂匹配的效果,但此内容不在本书讨论范围以内,有兴趣的读者可以在网上查一查相关的信息。下面我们将着重讲一讲正则表达式在JavaScript中的应用。

—  exec(str),这个方法是将正则表达式匹配一个字符串,并以数组的形势返回匹配字符串,如果正则表达式里面有捕捉字符串的(),那么在返回结果中将会一并返回。

    var regx=/\d+/;

    var rs=regx.exec("3432ddf53");//返回的值为:{3432}

    var regx2=new RegExp("ab(\d+)c");

    var rs2=regx2.exec("ab234c44");//返回的值为:{ab234c,234}

—  test(str),这个方法返回一个布尔值,以表示字串中是否匹配了正则表达式。

    var regx=new RegExp("abc");

    var rs=regx.test("ab234c44");//返回的值为false

    var rs2=regx.test("abc234");//返回值为true

此外对于字符串(string)也有几个与正则表达式相关的方法。

—  str.match(reg)返回字符串的中与reg正则表达式相匹配的一个字符串,如果表达式中有参数g,则返回所有匹配的字符串

    var regx=new RegExp("abc");

    var str="abc11abc";//返回abc

    var str1 = str.match(regx);

    var str2 = str.match(/abc/g);//返回{abc,abc}

—  str.replace(reg,s)将字符串str中与表达式reg相匹配的字符串替换为s,如果表达式中含有参数g,则对整个字符串进行匹配并替换,否则只会替换第一个字符串。

    var str = "zzabc234abc456";

    var str1 = str.replace(/abc/,"0");//返回值为zz0234abc456

    var str2 = str.replace(/abc/g,"0");//返回值为zz02340456

—  str.split(reg)将根据与表达式相匹配的字符串为分隔符对字串str进行分割成字符数组。这个方法不管是否添加参数g,所得结果都是相同的。

    var str = "zzabc234abc456";

    var str1 = str.split(/abc/);//返回值为{zz,234,456}

    var str2 = str.split(/abc/g);//返回值为{zz,234,456} 可见此方法与参数g无关

—  str.search(reg)返回字符串中与表达是相同的第一个字符串在整个字符串中的索引位置。这个方法与表达式中的参数g无关。

    var str = "zzabc234abc456";

    var idx = str.search(/abc/);//返回值为2

这里我们简单介绍了JavaScript的一些基础类型,运算符以及几个主要对象的部分属性和方法以及简单的引用,主要的作用还是抛砖引玉,希望了解更为深入的读者可以参考一些JavaScript方面的书籍,或者在网络上搜索一些专题文章,相信会有更多帮助。

5.2.3  DOM模型基础
DOM的全称是Document Objet Module即文档对象模型,在Web上把页面的HTML表现看作一个有树型结构的对象模型,可以通过一些操作接口来对Document的每一个子对象节点进行访问和操作,这就为Ajax在不刷新页面的情况下改变页面显示数据成为了可能。

先来看一个简单的HTML片段:

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

    <title>DOM模型</title>

</head>


    <label title="title1">DOM模型节点</label>

</body>

</html>

在这个HTML页面中,只有一个Label控件,可以根据节点的层次画出这个页面的层次结构图。

通常来说,在HTML文档中的每一个标签都表示一个对象节点。而像上面<Lable>这样的标签是我们的HTML元素节点,而标签中的title=“title1”是一个属性节点,而“DOM模型节点”这样的文本构成了一个文本节点。

那么,怎么才能对DOM模型中的一个节点进行操作呢,首先要做的是对这个节点进行引用。

1.对文档节点的引用

下面列举一些常用的对文档元素节点的引用方法。

—  document.GetElementById()方法直接引用节点,这个是我们在实际应用中最常用的一种方法,在HTML文档中每一个元素节点都可以定义一个唯一的id属性,然后使用GetElementById方法就可以准确地得到对这个节点的引用。

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

    <title>DOM模型</title>

</head>


<div id="Div1">

    <label title="title1">Dom模型节点</label>

</div>

</body>

</html>

<script language="javascript" type="text/javascript">

<!--

    var _div1 = document.getElementById("Div1");

    alert(_div1.innerHTML);//弹出警告框显示了标签div中的HTML内容

                           //<label title="title1">Dom模型节点</label>

-->

</script>

HTML文档中每一个元素节点都有innerHTML这个属性,我们通过对这个属性的访问可以获取或者设置这个元素节点标签内的HTML内容,自IE4.0以来越来越多的浏览器支持了这一属性,通过使用这一属性使许多繁杂的动态生成HTML的工作变得简单。需要注意的是,我们如果对单标记标签,如<img>这一类标签的innerHTML属性读取会得到一个空字符串,而写将会得到一个错误。

此外document对象还有一个类似的方法GetElementByName,我们可以通过form标签的name属性对表单元素节点进行引用,但返回的通常是一个数组,因为表单中的节点name属性的值不是唯一的,可以通过索引器得到每一个元素的引用。

—  document.getElementByTagName()

可以得到一个指定标记名称节点引用的数组集合,可以通过索引器对每个节点的引用进行访问。

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

    <title>DOM模型</title>

</head>


    <div id="Div1">节点1</div>

    <div id="Div2">节点2</div>

</body>

</html>

<script language="javascript" type="text/javascript">

<!--

    var _divs = document.getElementsByTagName("div");

    for(var i = 0; i < _divs.length;i++)

        alert(_divs[i].innerHTML);//依次显示了"节点1"和"节点2"

-->

</script>

这个方法通常在要对整个文档的某一类元素节点进行操作时用到,比如说为全部的图片添加一个鼠标掠过时发生位移的效果,这时就可以通过这个方法对文档所有的节点进行引用。

—  parentNode和childNodes,可以通过访问这两个属性获得当前节点的父节点和子节点集合的引用。

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

    <title>DOM模型</title>

</head>


    <div id="Div1">

        <span id="sp1">节点1</span>

        <span id="sp2">节点2</span>

    </div>

</body>

</html>

<script language="javascript" type="text/javascript">

<!--

    var _nod = document.getElementById("sp1");//得到对sp1的引用

    var _pNod = _nod.parentNode;//得到对Div1的引用

    alert(_pNod.innerHTML);//显示父节点内容

    for(var i = 0; i < _pNod.childNodes.length;i++)//循环子节点

        alert(_pNod.childNodes[i].innerHTML);//依次显示了每一个节点的内容

-->

</script>

在这里问题出现了,我们发现在IE和FF下面对属性_pNod.childNodes.length,即子节点的数量解释不同,在IE中为4,而在FF中为5。得到这样的结果是因为两种浏览器对文档中换行产生的文本节点的解释不统一造成的,IE没有把父节点与子节点之间那个换行作为一个文本节点,如果要使用这个属性就不得不在HTML文档编写的时候避免出现换行,可以将上面的结构改为下面的形式:

    <div id="Div1"

    ><span id="sp1">节点1</span

    ><span id="sp2">节点2</span

    ></div>

虽然这样写以后FF和IE都能很好地统一解释为两个子节点,但是损失了文档的美观性和易读性,所以一般都不推荐使用访问子节点的方法来引用节点。

类似的previousSibling和nextSibling也存在类似的问题。这两个属性是用来引用上一个或者下一个兄弟节点的,使用这两个属性时也存在空白文本节点的问题,我们也应该尽量避免使用这两个属性。

2.文档元素节点的操作

得到一个文档元素节点的引用之后,就可以对这个节点进行一些控制和操作,以达到对HTML显示进行更新的目的。

(1)  DOM标准操作,在DOM模型中定义了一套能够对文档结构进行更新的方法,我们可以通过这些方法创建文档节点,并将节点添加到文档中或者从文档中删除。

—  document.createElement(elmName) 根据标记名称创建一个节点。

—  document.createTextNode(text) 根据一段文本创建一个文本节点。

—  node.appendChild(childNode) 将节点添加到一个节点下子节点的末尾。

—  node.insertBefor(newNode,oldNode) 将节点插入到指定节点之前,newNode为新节点,oldNode为指定的节点,此节点必须为node的已经存在的一个子节点。

—  node.Replace(newNode,oldNode) 用新节点取代一个旧节点,与上面方法类似,oldNode必须为node的一个已近存在的子节点。

—  node.cloneNode(cloneChild) 复制一个节点,参数cloneChild是一个布尔值,表示是否复制子节点。

—  node.removeChild(childNode) 删除一个子节点,需要注意的是该方法将返回被删除节点的引用。

下面我们用一个例子来说明这些方法的使用:

    var _div1 = document.getElementById("div1");//获取Div1节点

    var _sp3 = document.createElement("span");//创建一个<span>元素节点

    _sp3.id="span3";//将新节点的属性id设为"span3"

    var _txt1 = document.createTextNode("节点3");//创建一个文本节点

    _sp3.appendChild(_txt1);//将文本节点添加到新元素节点下

    _div1.appendChild(_sp3);//将元素节点添加到节点Div1下

    //此时界面显示 节点1 节点2 节点3

    var _sp4 = _sp3.cloneNode(true);//将元素节点复制

    _sp4.id="span4";//为新复制的节点设置id属性

    var _txt2 = document.createTextNode("节点4");//新建一个文本节点

    _sp4.replaceChild(_txt2,_sp4.childNodes[0]);//将节点_sp4的文本节点替换

    _sp3.parentNode.insertBefore(_sp4,_sp3);//将节点_sp4添加到节点_sp3之前

    //此时界面显示 节点1 节点2 节点4 节点3

    _sp4.parentNode.removeChild(_sp4);//删除节点_sp4

(2)Table的操作

我们发现如果通过以上的方法对表格对象<table>进行操作的话,在IE下将得不到正确的结果,在IE下必须使用DOM1的方法对表格进行操作。

—  tab.insertRow(idx) 在表格指定索引位置添加一行空行,idx为索引位置。

—  tab.deleteRow(idx) 在表格指定索引位置删除一行。

—  row.insertCell(idx) 在行的指定索引位置添加一个空单元格。

—  row.deleteCell(idx) 在行的指定位置删除一个单元格。

可以通过document.createElement(“table”)创建一个表格,通过索引器可以访问talbe的各个行和单元格,如tab.rows[1].cells[3],这样我们就能得到表格的第二行第四列的引用,我们可以向操作普通节点一样来对这个单元格对象进行操作。下面是一个表格操作的例子,假定这个表格原来有2行2列。

    var tab = document.getElementById("tab");//得到对表格的引用

    var row2 = tab.insertRow(2);//新增第三行

    var cell20 = row2.insertCell(0);//为第三行添加第一个单元格

    cell20.innerHTML = "20";//

    var cell21 = row2.insertCell(1);//为第三行添加第二个单元格

    cell21.innerHTML = "21";

    tab.rows[1].deleteCell(1);//删除第二行第二列

    tab.deleteRow(1);//删除第二行

(3)innerHTML 的灵活使用

在IE 4.0以后,elm.innerHTML这个属性得到大部分浏览器的广泛支持,其易用性使得我们对文档的操作得到了很大程度的简化,下面来看一个操作文档节点的例子,假设要对一个节点添加两个子节点,并设置一些属性,下面是DOM标准创建方法:

    var _div1 = document.getElementById("div1");//得到父节点

    var _sp1 = document.createElement("span");//创建span节点

    _sp1.id="span1";

    var _txt1 = document.createTextNode("节点1");//创建文本节点

    _sp1.appendChild(_txt1);//将文本加入到span节点下

    _div1.appendChild(_sp1);//将span节点加入到父节点下

这样写我们通过六行代码完成了功能的实现,下面来看使用innerHTML的情况:

    var _div2 = document.getElementById("div2");

    _div2.innerHTML = "<span id='span1'>节点2</span>";

运行后发现,只使用了两行代码而得到了完全相同的效果,并且这种方法还更为直观一些,可读性还更强。可见使用innerHTML属性,可以更为方便高效地改变文档结构,这使得在大多数情况下都使用innerHTML来操作文档,但是标准的DOM方法在特定的环境下也有不可取代的作用,在编码时要灵活判断,选择合适的方法解决问题。

5.2.4  XML与JSON
XML的全称是eXtensible Markup Language ,即可扩展标记语言,作为一种广泛推广的数据传输的标准手段,在Ajax设想之初就被确定为XMLHttpRequest传输数据的方式,了解XML技术的本质对于我们更好地运用Ajax也是很有意义的。

对于所熟悉的超文本标记语言,XML也是一种类似的标记语言,不同的是,在HTML的标记是在标准中已经规定了有些什么标记,各种标记代表什么意义,如<p>代表一个段落,它控制着文档在浏览器中应该如何显示;而在XML中,标记以及标记的值是由我们自己定义的,但是,XML不像HTML那样会控制浏览器的显示,它其实只是用来封装数据,并不关心界面的显示如何,实际上我们最后控制界面的显示还是由HTML来控制的,在很长一段时间内我们还是会使用HTML来展示数据,而XML提供了一种可以使页面显示与数据分离的方法,这样为我们面向对象的模块化编程提供了方便。下面是一段XML文档。

<?xml version="1.0" encoding="utf-8" ?>

<person>

  <name>Dr Li</name>

  <sex>male</sex>

  <age>24</age>

</person>

看到我们为文档的根节点定义为person,之下有name、sex、age三个子节点,从这一点可以看出,我们定义了一个XML文档之后,通过对文档标签的自定义,可以得到一个很具结构化并具有一定意义的数据文档,这为我们提供了一种很便捷的方式来包裹和交换数据。

    var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");//声明一个XML对象

    xmlDoc.async="false";//是否与数据源同步

    xmlDoc.load("XML文档.xml");//读取XML文档

    nodes = xmlDoc.documentElement.childNodes;//获取节点对象

    document.getElementById("name").innerText = nodes.item(0).text;//为DOM节点赋值

    document.getElementById("sex").innerText = nodes.item(1).text;

    document.getElementById("age").innerText = nodes.item(2).text;

在使用XMLHttpRequest发出请求以后,在服务端可以使程序返回一个XML文档,然后在前端脚本中解析XML,并使用DOM方法改变界面显示。

JSON:它的名字看起来似乎很亲切,全称为JavaScript Object Notation,可以看出它和JavaScript的关系非常密切。JSON是一种与JavaScript自然匹配的对象,它实际上是一种名称与值的集合,我们使用JavaScript中通常的对象访问方法就能对对象进行访问。

    var jobj = {"person":{"name":"Dr Li","sex":"male","age":"24"}}//声明一个JSON对象

    document.getElementById("name").innerText = jobj.person.name;//为DOM节点赋值

    document.getElementById("sex").innerText = jobj.person.sex;

    document.getElementById("age").innerText = jobj.person.age;

通过大括号、冒号、逗号、引号等符号实例了一个JSON对象,每个冒号左边的是键,右边是值,JSON对象实际上就是一个键值对的集合。可以方便地通过类似jobj.person.name的方式访问对象中的每一个键对应的值。

还发现JSON对象相对于XML更为简洁,XML必须是封闭的标签对,而JSON只需要用一个打括号或者逗号就能说明元素的作用范围。这一点对于需要高效运行的Ajax是很重要的,一般来说使用JSON要比XML更节约带宽一些。

JSON在JavaScript中的灵活性与易用性,使得大多数Ajax编程人员都更愿意使用JSON作为Ajax传输数据的方法。在Ajax.NET提供的程序包里,我们会发现一个JSON版本的dll,使用这个dll回传数据使用的就是JSON。

5.2.5  xHTML和CSS
xHTML的全称是The Extensible HyperText Markup Language,即可扩展超文本标记语言,从名称上可以看出它似乎和XML有着什么联系。事实上的确如此XHTML实际就是XML,只不过它是用来代替HTML的一种XML,它实际上也是一种过渡语言,它和HTML基本上类似,但是有一些重要的区别,XHTML比HTML更为严禁,要求更为严格,因为XML本来就是一种要求严格的语言。

在使用VS建立页面的时候,发现VS自动在HTML页首添加了如下的一行代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3. org/TR/xhtml1/

DTD/xhtml1-transitional.dtd">

这个实际上就是为XHTML文档添加的一个XML文档标准化验证,默认添加的是XHTML 1.0的过渡标准,这个是现在主流浏览器执行的W3C推荐XHTML标准。要如何才能编写出符合XHTML标准的页面文档呢?通过VS自带的文档验证工具就能让我们编写出符合标准的文档,如图5-3所示。



图5-3

我们打开工具栏中的“HTML源编辑”工具条,在“验证目标框架”中选取我们希望验证的文档框架,编辑器就能随时提示我们在编写代码过程中没有符合标准或不能够兼容的地方。

在XHTML中已经不推荐使用Table标签来定位文档,现在流行的适用“Div+CSS”定位也就是XHTML的一种典型应用,使用CSS来定位文档,在很多时候会显得非常烦琐,我们不得不手工编写大量代码来控制界面的显示,毕竟现在大多数界面编辑器都不能很好地理解错综复杂的CSS定位,但是使用CSS定位对于文档结构的优化有着很重要的意义。使用“Div+CSS”定位并不是只是在字面上的意义,它不仅仅只是放弃Table,而只使用Div和CSS编码,更重要的是它提供了一种能将页面结构和样式控制分离开来,这样很有利于我们结构化和模块化的程序设计,使得我们在控制文档的时候更能专注于结构或者样式的控制。下面是一个简单的例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3. org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Div+CSS实践</title>

    <style>

        .style1 {font-size:12pt; font-weight:bold;}

        .style1 div {font-size:9pt;font-weight:normal;display:block;border: solid 1px #eee;}

        .style1 div div{border:none;display:inline;}

    </style>

</head>


    <div class="style1">

        员工列表

        <div>

            姓名:老张

            <div>

                <div>

                    性别:男</div>

                <div>

                    年龄:30</div>

                <div>

                    职位:经理</div>

            </div>

        </div>

        <div>

            姓名:小李

            <div>

                <div>

                    性别:女</div>

                <div>

                    年龄:24</div>

                <div>

                    职位:秘书</div>

            </div>

        </div>

        <div>

            姓名:小王

            <div>

                <div>

                    性别:男</div>

                <div>

                    年龄:25</div>

                <div>

                    职位:职员</div>

            </div>

        </div>

    </div>

</body>

</html>

可以看出文档结构中没有出现繁杂的样式控制标记,而样式控制都在Title标签里简短的CSS段落里实现了。这种分离样式的方法在数据页面,特别是具有大量繁杂数据的列表页面中更能够体现出它的优势。这对于Ajax的意义也是很大的,我们在DOM模型操作时,如果能很好地将文档结构和样式分离,将能减少很多样式控制的脚本,使逻辑更为清晰,脚本更为简洁。

如果读者对“Div+CSS”的内容有兴趣可以到网上找一找相关的文章,或者到网上一些相关的社区获取一些资料,相信将对你Web前端编码的认识有所加深。




Ajax.NET简介及缺陷



谈到Ajax技术相信很多人都不会陌生,Ajax技术已经由一股热潮演变成为了一种主流的Web应用技术。相信大多数人都感受到过Ajax优秀的用户体验和独特的魅力。现今网络中Ajax应用也越来越多,各种新的应用理念与方式也层出不穷,如Google Map 的兴起,掀起了Ajax应用热潮的旋风,Gmail的成功应用,证明了Ajax技术大规模应用的可靠性与可行性,在国内优秀的Ajax应用也为数众多,如超越传统电子地图的“e都市”电子地图让我们看到了国内开发人员优秀理念与高超的设计能力。对于一个刚接触Ajax的读者来说,似乎Ajax技术比起其他技术更显得高深莫测,应该怎么理解和学习这种技术呢?下面即将带你一步一步走近Ajax技术。

5.1  什么是Ajax
Ajax一个奇怪的单词,对于一个初接触到这门技术的读者来说不免地产生疑惑。下面将解析这一让人费解的单词。

Ajax其实是一个缩写词,其全称是“Asynchronous JavaScript and XML”即异步JavaScript和XML。从字面上来看Ajax至少包含了三个部分。

—  Asynchronous异步:说明Ajax的交互方式是异步的。传统的网页交互方式都是通过用户填写表单(form),通过用户提交表单时向远端Web服务器发送一个请求,服务端接收表单,并从请求对象(Request)中取出用户提交的表单的信息并处理然后返回结果并呈现,至此用户在网页上完成一次网页刷新。而Ajax采用的异步交互技术,是浏览器使用内置JavaScript对象XmlHttpRequest(这是一个非常重要的对象,在后面将会对它做详细阐述)向服务器端发起一个异步的请求,在请求发起于返回期间并不刷新页面,用户在当前页面的一切操作并不会受到阻塞,用户可以在此期间操作页面上的一切元素,这样使许多以前不能实现的应用得以实现,如Google Map的地图拖动后的地图异步加载。假想如果每拖动一次页面将会刷新一次来加载地图,可以预见得到的用户体验将是难以忍受的。

—  JavaScript:此处的JavaScript指的是应用于浏览器上的客户端脚本。JavaScript作为一种主流的浏览器客户端脚本技术,具有良好的开放性、易用性及灵活性,因此也奠定了它在Ajax技术中的核心地位,这也使得好多人觉得Ajax技术实际上是JavaScript脚本技术的一种拓展。实际上Ajax技术正是许多Web技术揉合的结果,JavaScript正是其中一项占有核心地位的应用技术。要学习Ajax首先了解和学习JavaScript是很重要的。

—  XML:作为近年来热门技术之一,在众多领域发挥了其巨大的优势与潜能。同样在Ajax技术中XML占有至关重要的作用,浏览器在使用XmlHttpRequest向服务端发送和返回请求的时候正是使用了XML来包裹和传送数据。Ajax的提出者Jesse James Garrett在最初提出此概念的时候希望XML能作为标准的Ajax数据传输方式,但在实际运用中人们发现后来提出的JSON(JavaScript Object Notation)在Ajax应用中比XML更为易用和高效,特别是JSON在JavaScript中的灵活使得人们在实际应用中更为乐于使用JSON来传送数据。

Ajax技术除了主要包括这三方面外还包括许多重要的重要的技术,如DOM(Document Object Model),XHTML+CSS等在此就不一一列举。可以说Ajax技术是集多种技术为一体的综合性技术解决方案,提出了一种Web技术应用的新方式,利用它我们可以从一个新的角度来看待Web应用程序,能向一个新的方向去发展Web应用。

5.1.1  Ajxa的工作方式
对于一个初学者来说,了解Ajax的工作方式是很有必要的,前面已经叙述了Asynchronous(异步)是Ajax的基本工作方式。但具体来说Ajax在浏览器和服务端之间是怎么样个异步法,和传统的synchronous(同步)交互方式又有怎么样的区别呢?

首先我们通过一个图来说明两种交互方式的区别(图5-1)对于传统模式来说客户端在浏览器中进行了一系列的操作以后通过提交表单向服务端发起请求,此时客户端网页刷新等待服务端返回结果,在这期间客户端浏览器存在一个“空白期”,即等待服务端返回页面数据的时间,在此期间用户除了等待之外不能进行任何操作,直到服务端处理完逻辑返回结果,用户才能继续操作。这样的模式是在万维网服务和浏览器诞生之初就已确定的标准交互模式,对于简单的Web应用,如注册用户是填写提交表单,这样的模式能很好达到目的并完成业务,对于用户来说短暂的等待和并不频繁的提交动作也是合理并且能够接受的。但对于需要频繁与为服务端交互数据的情况,如前面所说的电子地图拖动加载图片的情况,传统的刷新方式就显得笨拙而低效,此时Ajax就有了用武之地。在Ajax的异步交互模式中,我们看到在客户端除了用户能看得见Browser UI元素外,还存在一个Ajax engine的层级,Browser UI将用户的操作提交给Ajax engine处理,Ajax engine将用户的数据和操作分析后再与远端服务器交互,并得到远端服务器返回的数据,Ajax engine将这些返回的数据处理以后再将这些数据呈现在Browser UI上。在整个过程中可以看到客户端浏览器并没有存在一个“空白期”,这样用户并没有感觉到在交互的整个过程中有任何的等待期,用户可以在交互过程中浏览页面上的任何信息,或者对页面元素进行任何操作,这样对于用户来说具有较好的体验。相比之下两种交互方式,Ajax只是在客户端多了一个Ajax engine的层级,只是在客户端计算机多了一些额外的资源消耗。实际上Ajax技术对于节约服务端、客户端以及网络带宽资源都有重要意义。



图5-1

5.1.2  Ajax的优势
对于Ajax技术来说,它的优越性不仅来源于它优秀的用户体验,同样它对于提高Web程序的性能,提高可靠性也有着相对于传统Web交互方式有着巨大的优势。

Ajax对提高程序性能的优势来源于何处?我们可以通过下面这个图来分析(图5-2):



图5-2

传统模式相对于Ajax模式在性能上的最大区别就在于传输过数据的方式,在传统模式中,数据提交是通过表单(form),获取数据是整页的HTML+CSS,而Ajax模式只是通过XMLHttpRequest向服务端提交希望提交的数据,并通过XML返回需要的少量数据或者HTML Text片段,这样相对于传统模式来说无疑节约了很多资源。下面我们从三方面分析Ajax对资源的节约。

首先我们谈谈服务端处理数据,在传统交互模式中,服务端对客户端的数据处理都是以页面为单位的,客户端提交页面表单以后,服务器分析提交数据后会将整个页面重新生成为HTML+CSS发回给客户端呈现。很多时候程序其实只是希望提交很小一部分数据,并改变一小部分的数据显示,如投票时为赞成加上一票并将原来的“4票”显示为“5票”,这时却不得不将整个表单提交,并在服务器端处理整个页面并回传,这极大地浪费了宝贵的服务器资源。

借助于Ajax技术,我们有了一种新的选择,我们只回传用户对赞成投了一票这个信息,可能这个信息只包含几个字节,返回时我们也只返回投票成功这个信息,返回结果也只包含一个状态位,通过Ajax engine改变页面并显示数据,在服务端只是执行了一个简单的业务逻辑,并没有处理页面显示回传的问题,这样对于服务端来说减少了很多数据处理压力。

我们再来看看网络带宽的情况,前面其实已经提到,使用Ajax的方式处理和传输的数据量都相对于传统方式小很多。我们可以简单算一算,通常我们一个页面的HTML Text大小在30KB到100KB的样子,如果控件多一些,页面的大小会大得让你吃惊,特别在ASP.NET中一个页面控件多一点,并且控件都打开了ViewState,页面文本的大小是延迟Web相应得罪魁祸首,曾经笔者帮助朋友调整一个业务页面,这个页面是一个GridView,单元格里都是DropDownList,需要用户频繁地选择项并回传数据,开发人员觉得回传和刷新页面都非常困难,响应变得十分缓慢。经过笔者的分析发现回传的HTML Text大小高达1MB多,这是服务器响应缓慢的主要原因,用户实际上只是想回传一个控件的数据,但是不得不把所有控件的数据回传并又一次加载基本上相同的数据。这样的用户体验当然是无法让人忍受的。经过笔者引入Ajax技术并调整以后,整个页面执行效率变得很高,页面在最终转跳之前的数据获取都没有刷新页面,使之得到了较好的用户体验。以前通常需要刷新几十次页面,与服务器之间上传下载近百兆数据才能完成的业务,变成在仅仅交互几百KB就完成了业务,这样对带宽的节约得到的显著的性能提高在很多地方都得以一见。

最后我们再看看客户端的情况,通常我们觉得在客户浏览器端多维护了一个Ajax engine会比传统方式消耗更多的客户端资源,但是我们细想一下,在传统方式中回传加载页面时呈现页面需要消耗处理机资源,在刷新完成后保存前进后退的锚点数据同样需要消耗客户端的内存资源,相对于这些资源来说一个小小的Ajax engine对象所耗费的资源是可以忽略不计的。相反地使用Ajax技术后避免了页面的频繁刷新,反而能较好地避免浏览器因加载分析HTML引起的假死而长期占用处理机资源的情况。

从这三点可以看出,Ajax相对于传统的交互方式在各方面都有着很大的优势,因此Ajax在现今有着越来越广泛的应用,我们在越来越多的地方能看见Ajax的身影,Ajax也带给我们越来越优秀的用户体验。

但是,为什么Ajax相对于传统模式有着那么多优越性却没有完全取代传统模式,成为最优开发模式呢?这不得不让我们提起Ajax的不足,以及它与生俱来的缺陷,世界上并没有完美的事物,同样Ajax也并不是一项完美的技术。

5.1.3  Ajax的缺陷
事物都会有它的两面性,既然Ajax有着那么多优势,相应地它也会有很多不足之处。其实Ajax的不足大多数来自于外部,相信随着Ajax技术的发展,这些不足在有一天会消失。

首先,Ajax碰到最大的问题就来自于浏览器。对于XMLHttpRequest对象的支持,Internet Explorer是在5.0版本才支持的,Mozilla﹑Netscape等浏览器支持XMLHttpRequest则更在其后,如果说使用较老版本的浏览器访问Ajax页面是不可能得到正确结果的。如果说要使得老版的浏览器能够正确访问,我们不得不多写一个传统方式版本,并嗅探浏览器版本来显示相应的内容,无疑这样大大增加了开发难度及成本。另外一个问题就是在Internet历史长河中一直存在并一直没能解决的问题——浏览器之争。对于各个浏览器阵营来说,各行其道已经不是一年两年了,程序员在客户端脚本开发中顾此失彼也是常有的事,为了兼顾Ajax应用能在各个浏览器中都能正常运行,程序员必须花费大量的精力来比较各个浏览器之间的差别来使得Ajax应用能够很好地兼容各个浏览器。这本来是浏览器兼容客户端脚本的问题,但谁让JavaScript是Ajax的重要组成部分呢。这使得Ajax开发的难度高出普通Web开发很多,也是许多程序员对Ajax望而生畏的原因之一。

其次,Ajax改变了我们通常的Web浏览习惯。最显著的一个改变就是在Ajax中前进和后退按钮的失效,虽然可以通过一定的方法来添加锚点使得可以使用前进和后退(Gmail在这一点上似乎做得不错),但相对于传统的方式却麻烦了很多,对于大多数的程序员来说宁可放弃前进后退的功能,也不愿意在繁琐的逻辑中去处理这个问题。对于用户来说经常会碰到这种情况,当点击一个按钮触发一个Ajax交互后又觉得不想这样做,接着就去习惯性地点击后退按钮,结果发生了最不愿意看到的结果,浏览器后退到了先前的一个页面,通过Ajax交互得到的内容完全消失了。其次用户经常在点击一个按钮后对页面没有刷新而感到奇怪,在多次点击后才观察到页面微小的变化或出现的提示。如果用户想在Ajax应用的页面上通过拷贝链接来与朋友分享资源,相信你朋友看到的内容和你看到的并不是一回事。用户对于Ajax技术的不适应相信随着Ajax应用的增多会慢慢改善。

再次,对于互联网上搜索引擎的支持也是Ajax的一块心病。通常搜索引擎都是通过爬虫程序来对互联网上的数以亿计的海量数据来进行搜索整理的,可惜与Flash应用在搜索爬虫上遇到的问题类似,爬虫程序现在还不能理解人们那奇怪的JS代码和因此引起的页面内容的变化,这使得应用Ajax的站点在网络推广上相对于传统站点明显处于劣势。但是相信随着Ajax技术的大面积应用,Web 2.0标准的推广,以及新的搜索引擎技术的发展,Ajax一定能够在今后发展得更好。

最后,我们就要谈谈关于开发的问题。因为Ajax是一个综合的技术,是集多种技术为一体的边缘技术,这无疑为对其基础库支持相对于其他Web技术滞后很多,长期以来Ajax的开发人员从事着刀耕火种的工作,或者不得不自己开发大量的基础类来应对自己大量的开发工作,毕竟Ajax在开发上的灵活很难让像ASP.NET那样提供一个很好的基础库来支持开发工作。随着Ajax的发展现今国外很多公司都相继推出自己的针对ASP.NET的Ajax控件或者基础库产品。国外比较完善的Ajax成套控件有Telerik r.a.d Ajax,这套控件提供了大量的Ajax控件以及丰富的Ajax应用,在稳定性和应用效果上都是一流的,但可惜是非开源的收费软件,并且效能不敢恭维。同样微软也不为人后地推出了针对ASP.NET的Ajax框架代号为Atlas(后更名为ASP.NET Ajax Extensions),目的是简化Ajax在ASP.NET中的应用,但是由于Atlas在实现方式上较为累赘,并且自身Bata版本频出漏洞,使得人们对它不感不冒,现在大规模应用它的项目少之又少。对于ASP.NET的Web应用人们找到了一个更好的选择Ajax.NET。

5.1.4  Ajax.NET简介
Ajax.NET(AjaxPro)是由Google group推行的一个精简的Ajax For ASP.NET 1.1/2.0的框架,现在最新的版本已经到了AjaxPro 7.7.31.1,读者可以在这个网址找到自己想要的东西http://www.codeplex.com/。AjaxNET作为一个优秀的Ajax框架在执行效率和易用性方面都做得很出色,此外它还是一个开源的框架,在网上很容易能下载到源码,如果你有耐心可以细细分析一下Ajax的处理方式,主要就是靠处于/ajax/目录下的*.ashx文件发起的Ajax的回传请求被IHttpHandlerFactory类拦截并处理之,在Ajax回传处理时并不涉及页面元素,有较高的执行效率和可复用性,在返回数据时支持常见的服务端对象,如string、Array甚至DataSet这一类的大型对象,并且能很好地支持被标记为“[Serializable()]”(可序列化)的自定义类型。可以说Ajax.NET在现今针对ASP.NET的框架中是最优的解决方案之一。

Ajax.NET封装了隐藏了XMLHttpRequest的具体实现方式,用户只用在想要在客户端调用的函数上加上标记,就能像在服务端调用函数一样在客户端JS脚本中调用服务端函数。实际上用户一旦给任意Public函数作上标记,页面在第一次被加载的时候,Ajax会给这个函数在客户端脚本中生成一个代理,用户就通过这个代理调用服务端的函数并返回结果。Ajax.NET在一定程度上实现了编写脚本的自动化,使程序员告别了刀耕火种的Ajax开发,使程序开发者更能专注于业务逻辑。

使用Ajax.NET,我们可以惊奇地发现编写一个Ajax应用将变得如此简单。

Ajax基础之Javascript篇、Ajax.NET简介及缺陷(本文完毕)
下一篇:三层架构
上一篇:DirectoryEntry使用方法大全