Ajax个人开发心得

 Ajax个人开发心得  

 首先,我们先来看一个很简单的也很常用的ajax模块实例,在我们所做的后台系统开发中,我们经常会遇到这样的需求:抽象的说起来,有两种。

(1)在前端页面上有一个文本框子或者文本区,当我们键入内容后,可能需要对于键入的内容有一个复杂业务逻辑的判断,客户希望他所输入的内容能即时通过这个业务判断,并希望能看到即时的判断结果,而不希望是当表单页面的所有信息都填写完毕才能知道刚才填写的内容是否正确。

(2)在页面上有一些可以辅助自动完成的功能,比如说一些多级联动,区号电话号码邮编自动填写,查找以当前区域内容为关键字的相关信息。

对于这些需求来说,Ajax技术具有两点优势,一个是异步(不影响当前页面的流程),二是流量小,页面应用的开销小(拿城市省份联动来说,如果是基于页面JS的实现的话,需要一个完整的城市省份的数据集合,需要对于这个大容量的数据进行遍历,开销在浏览器,采用Ajax方式,处理过程的开销在于服务器后端,两者对于结果的处理开销是一样的)三是对于传统的Request/Response方式来说,所有的数据处理都是由服务端生成最终的HTML页面,现在通过Ajax模式,部分对于DOM数据生成操作改由客户端完成,略降了应用服务器的压力。

一个常见的Ajax模块的例子是这样的(判断当前是否有此公司ID存在)

Test.htm

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

<input type=”text” name=”comId” value=”请输入公司ID” οnblur=”isExistCurComID(this.value)”>

Test.js

function isExistCurComID(comId) {

         var xmlhttp_request;

         // 创建XMLHTTP

         try {

                   if (window.ActiveXObject) {

                            for (var i = 5; i; i--) {

                                     try {

                                               if (i == 2) {

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

                                               } else {

                                                        xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP."

                                                                           + i + ".0");

                                                        xmlhttp_request.setRequestHeader("Content-Type",

                                                                           "text/xml");

                                                        xmlhttp_request.setRequestHeader("Charset", "gb2312");

                                               }

                                               break;

                                     } catch (e) {

                                               xmlhttp_request = false;

                                     }

                            }

                   } else if (window.XMLHttpRequest) {

                            xmlhttp_request = new XMLHttpRequest();

                            if (xmlhttp_request.overrideMimeType) {

                                     xmlhttp_request.overrideMimeType('text/xml');

                            }

                   }

         } catch (e) {

                   xmlhttp_request = false;

         }

         // 使用GET方式提交

         var URL = 'companychk.do?action=isExistComId&comId=' + comId;

         xmlhttp_request.open('GET', URL, true);

         xmlhttp_request.send(null);

         xmlhttp_request.onreadystatechange = function() {

                   if (xmlhttp.readyState == 4) {

                            if (xmlhttp.status == 200) {

                                     responseStr = eval(xmlhttp.responseText);

                                     // 处理返回信息

                                     if (responseStr == true) {

                                               var div = document.createElement('div');

                                               document.getElementByName('comId')[0].parentNode

                                                                 .appendChild(div);

                                               div.innerHTML = '该公司ID已经存在';

                                     }

                            } else {

                                     // alert("读取失败");

                            }

                   }

         }

}

从上面的源代码中可以看到,我们给页面的文本框控件增加onblur事件的处理方法isExistCurComID,参数为当前文本框的值内容。该事件定义在test.js文件中。

在test.js文件中,我们需要用JavaScript来创建XMLHttpRequest 类向服务器发送一个HTTP请求,这里要用到XMLHttpRequest 类,它首先是由Internet Explorer以ActiveX对象引入,被称为XMLHTTP。后来Mozilla﹑Netscape﹑Safari和其他浏览器也提供了XMLHttpRequest类,不过它们创建XMLHttpRequest类的方法不同。

对于Internet Explorer浏览器,创建XMLHttpRequest 方法如下:

xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP.3.0"); //3.0或4.0, 5.0,6.0,7.0,8.0

xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP");

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

由于在不同Internet Explorer浏览器中XMLHTTP版本可能不一致,为了更好的兼容不同版本的Internet Explorer浏览器,因此我们需要根据不同版本的Internet Explorer浏览器来创建XMLHttpRequest类,上面代码就是根据不同的Internet Explorer浏览器创建XMLHttpRequest类的方法。

对于Mozilla﹑Netscape﹑Safari等浏览器,创建XMLHttpRequest 方法如下:xmlhttp_request = new XMLHttpRequest();

如果服务器的响应没有XML mime-type header,某些Mozilla浏览器可能无法正常工作。 为了解决这个问题,如果服务器响应的header不是text/xml,可以调用其它方法修改该header。

xmlhttp_request = new XMLHttpRequest();

xmlhttp_request.overrideMimeType('text/xml');

在实际应用中,为了兼容多种不同版本的浏览器,一般将创建XMLHttpRequest类的方法写成如下形式:

//创建一个XMLHTTP请求

try {

         if (window.ActiveXObject) {

                   for (var i = 5; i; i--) {

                            try {

                                     if (i == 2) {

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

                                     } else {

                                               xmlhttp_request = new ActiveXObject("Msxml2.XMLHTTP." + i

                                                                 + ".0");

                                               xmlhttp_request

                                                                 .setRequestHeader("Content-Type", "text/xml");

                                               xmlhttp_request.setRequestHeader("Charset", "gb2312");

                                     }

                                     break;

                            } catch (e) {

                                     xmlhttp_request = false;

                            }

                   }

         } else if (window.XMLHttpRequest) {

                   xmlhttp_request = new XMLHttpRequest();

                   if (xmlhttp_request.overrideMimeType) {

                            xmlhttp_request.overrideMimeType('text/xml');

                   }

         }

} catch (e) {

         xmlhttp_request = false;

}

在定义了如何处理响应后,就要发送请求了。可以调用HTTP请求类的open()和send()方法,如下所示:

xmlhttp_request.open('GET', URL, true);

xmlhttp_request.send(null);

open()的第一个参数是HTTP请求方式—GET,POST或任何服务器所支持的您想调用的方式。 按照HTTP规范,该参数要大写;否则,某些浏览器(如Firefox)可能无法处理请求。第二个参数是请求页面的URL。第三个参数设置请求是否为异步模式。如果是TRUE,JavaScript函数将继续执行,而不等待服务器响应。这就是"AJAX"中的"A"。

(Tips)GET请求和POST请求的区别

一般说来Get请求比Post略快,Get请求的数据内容在request head,Post在request body,Get走URL方式,对于URL有字符限制的,提交参数也是明文在URL上。对于大数据量需要用Post

用JavaScript来创建XMLHttpRequest 类向服务器发送一个HTTP请求后,接下来要决定当收到服务器的响应后,需要做什么。这需要告诉HTTP请求对象用哪一个JavaScript函数处理这个响应。可以将对象的onreadystatechange属性设置为要使用的JavaScript的函数名,如下所示:

xmlhttp_request.onreadystatechange =FunctionName;

FunctionName是用JavaScript创建的函数名,注意不要写成FunctionName(),当然我们也可以直接将JavaScript代码创建在onreadystatechange之后,例如:

xmlhttp_request.onreadystatechange = function() {

         // JavaScript代码段

};

首先要检查请求的状态。只有当一个完整的服务器响应已经收到了,函数才可以处理该响应。XMLHttpRequest 提供了readyState属性来对服务器响应进行判断。

readyState的取值如下:

0 (未初始化)

1 (正在装载)

2 (装载完毕)

3 (交互中)

4 (完成)

所以只有当readyState=4时,一个完整的服务器响应已经收到了,函数才可以处理该响应。具体代码如下:

if (http_request.readyState == 4) { // 收到完整的服务器响应

}

else { // 没有收到完整的服务器响应

}

当readyState=4时,一个完整的服务器响应已经收到了,接着,函数会检查HTTP服务器响应的状态值。完整的状态取值可参见W3C文档。当HTTP服务器响应的值为200时,表示状态正常。

当请求达到服务端,因为刚才的请求是/companychk.do?action=isExistComId&comId=' + comId,实际就相当于在浏览器的URL地址栏中输入/companychk.do?action=isExistComId&comId=3415,对于现有后台老系统,通过struct控制器映射,进入CompanyCheckAction这个Action类,寻找Form参数action等于isExistComId的对应方法:

If(“isExistComId”.equals(form.getAction)){

         isExistComId();

}



Public void isExistComId() throws Exception{

        

}

你可以看到在相应的后端业务代码中会根据传入参数,进行业务逻辑的判断,这里是判断是否存在该公司ID,调用MIC_CompanyCheck组件,通过传入ComId字段初始化,如果初始化不成功则证明该公司不存在。通过往Response输出流中写入返回结果,完成Ajax的前端数据(业务处理需要的参数)?à后端数据(业务处理的返回结果)之间的传递。在后端数据处理的过程中,与前端密切相关的,也是我们做一个Ajax模块必须着重考虑的是,如果组织一个前端脚本语言友好,数据容量小,易于理解的数据结构。一般来说,我们有三种方式:

以纯文本方式,既字符串的方式输出

以XML标准文档的方式

以JSON的方式

这里我个人推荐是JSON的方式,首先JSON相比XML具有体积更小,查询更快捷(XML一般采用XPATH的方式,而JSON本生就是原生的JS对象),同时JSON和Java对象之间可以形成互相转换,参考附件2。在上面的例子中我们只是为了举例,所以输出的这个JSON比较简单,就是一个true或者false的Boolean对象,注意这里不是字符串,字符串是需要加引号的,别搞混了。

接着继续看前端,在检查完请求的状态值和响应的HTTP状态值后,就可以处理从服务器得到的数据了。因为刚才定下来传递过来的是一个JSON格式的数据,我们就需要对于这个Response中的content做解析,在Javascript中,有个原生的eval方法,它的参数是一个字符串,执行eval(‘字符串内容’),可以把字符串内容解析为一个js对象。比如执行刚才的eval(‘true’)那它就会返回一个boolean值true。

当解析完返回结果后,我们需要做最后一步,就是根据返回结果,完成最终页面显示。这里我们的逻辑是当判断一个公司ID是否真的存在该公司时,如果返回true,那么在该文本框后面显示一段文字,叫做“该公司已经存在”。这里需要了解的知识是JS操作DOM,同时这里你看到的是document.getElementByName而不是常用的document.getElementById,用name的好处是,我们不必额外指定元素的ID,当然,对于一个DOM来说,每个Element的ID应该是唯一标示的。同样,对于一个表单来说,每个表单元素的name 都对应我们对应后端bean的一个属性一样,这里直接使用表单元素的name而不额外指定id是有好处的,我们的目的是尽量做到对于页面侵入越小越好,当然使用getElementByName 并不是我们最好的用法,在后面的Ajax操作DOM高级技巧中我们会进步一谈到。同时在这里,我们还采用动态创建页面元素的方法,不是在已有的页面先建立一块输出区域,我这样做的好处在于,此异步模块与现有页面模块是独立的,可分离的,当然细心的人会发现页面上还有一句onblur是耦合的,下面我们会讲一些进阶技巧,可以做ajax模块与当前页面模块进一步分离,只留下最基本的页面引用,让ajax开发做到复用,并且模块化。

讲到这里一个,很简单,很基本的ajax小模块就成型了。当然麻雀虽小,五脏俱全。无论是以后多么复杂的ajax应用,都离不开上诉的这些基本原理。但是如果每次开发都按照上面这样的话,是很雷人。作为新时代的程序员,我们一定要学会节约自己的时间,弄一些“偷懒”的方法,下面我们要讲一些进阶的ajax编程技巧。

转自: http://www.cnblogs.com/xueduanyang/archive/2008/07/29/1256026.html
 
页面逻辑与页面表现的分离,让你的Ajax编程更加模块化  

 大家都知道,没有人喜欢步行,给你个轮子,你去一个目的地的速度会大大提升。编程也是一样,给你个可复用的类,比自己一遍又一遍写一拖代码要省事的多。我们在写Java代码的时候知道要分模块,当然写Javascript也一样,我们天天写Java代码都知道,当所有的代码都耦合在一起的时候,无论是开发还是维护都无比烦躁,特别是我们做的后台系统的维护工作,很多时候,对于新功能的引进最好是无侵入式的,对于原有代码(无BUG的那种)改动越小越好,就像盖楼一样,你接着别人盖好的继续往上盖可以,但是你要把别人的转头上打个洞,硬塞一些新的东西,这个楼房不倒才怪^_^。带着这样的疑问,我们开始第二章的讲解,表现层的分层技巧。

Ok,还是来看上面的例子,

Test.htm

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

<input type=”text” id=”comId” value=”请输入公司ID” οnblur=”isExistCurComID(this.value)”>

当我们需要对于页面上的某个控件增加某个事件监听的时候,我们一般采用的做法是在某个具体控件上写上“事件名”=“方法”,这样的字段。好,我们来假设一下,有一个旧的系统,页面上20个Input text控件,现在我们对于其中部分控件,增加ajax验证的功能模块,我需要怎么做?如果采用老方法的话,就是说我们要破坏原有的页面,我们需要在每个控件上增加onblur失去焦点事件(举例,不见得是onblur),当其他类似页面需要此功能时候,我们也需要在改一次,成本是每改一个页面,该页面有多少控件,我们就要改多少次,而且改动位置不确定,CVS同步后会发现千疮百孔,有没有更好的方法?我们可以将onblur抽象出来吗?在原有页面只维持一处引用,而将所有的该ajax模块功能的代码都剥离到一个独立模块中吗?答案是可以的。

Test.htm

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

<input type=”text” id=”comId” value=”请输入公司ID” οnblur=”isExistCurComID(this.value)”>

<script>

document.getElementsByName('comId')[0].onblur = function() {

         isExistCurComID(this.value);

}

</script>

Test.js保持不变

发现了把?原本在元素comId上的onblur事件定义被移到了元素下方的script区域中,原本元素和事件的紧耦合关系变得开始松散,相互间的依赖由原来的元素自身内部依赖转移到外部的元素的唯一标示符,打个比方就是一个会飞的猪,现在被拆解成为,猪+(猪)会飞,猪没有变,但是猪会飞由原来的内部定义,被单独拉出来放在了外面。聪明的你马上就会从中得到启示,对的,这样的好处很明显,当我们需要给一个旧有的页面添加新的功能模块时,我们不需要对于原有代码做任何改动,所有的新功能都可以统一在外层定义和执行,而与事件依附对象的联系由原本的元素本身变为通过元素的唯一标示符找到该元素,同时对于功能的拆解也变的方便,当不需要某个功能的时候,只需要把相应的功能代码块去掉,是不是颇有模块化开发的味道,呵呵^_^。

再来看看刚才的这段代码,刚才我们说了,事件和元素之间的纽带是元素的唯一标识符,在例子中也就是document.getElementsByName('comId')[0]中的comId,当然例子的这段代码是理想状态下的,也就是说,我们已经假定了,在当前的document中,所有的元素的name是唯一的,所以document.getElementsByName('comId')[0]取到的name为comId的第一个元素就是我们真正想要的那个元素。但是,在实际的开发环境中,这样可能不太显示,比如说radio框(他们的名称都是一样的),或者页面有多个form的情况,或者由于老版本的页面不规范的编程写法(很多。。。),你可能会发现同一个页面中有多个input,name=comId的输入框,选择框,这个时候用document.getElementsByName('elementName')[index]这样的写法可能就不是很通用了,对了,你会问,为什么要通用?对于不同的环境,我们硬编码去实现不就好了。从实现的角度来说是对的,我不反对这样的代码,但是如果能有一个通用的方式可以这种类似代码封装成一个模块,大家都能调用岂不是更方便,就好比城市省份联动,你画页面写一个,我画页面又写一个,这样冗余代码堆积在页面中,会造成维护极其不方便(代码分散,发现BUG后,所有应用到这段代码的地方都需要改),如果能统一在一处,不仅维护方便,以后代码重构,优化,都很方便,俗话说的好,有车坐,谁愿意走路^_^。所以我们解决的方法就是找一个一个可以唯一标示该元素的标记如ID,如document.getElementById('id'),或者通过元素的name结合其它元素上的属性来确定一个唯一的元素, 如

for (var el in document.getElementByName('name')) {

         if(el.hasAttribute('attribute')){

                   el.getAttribute('attribute')=='value'

                   return el;

         }

}

第二个问题,我们刚才看到的是对于一个原本没有绑定onblur事件的元素,那么当我们面对一个已经有onblur事件的元素,如<input type=”text” id=”comId” name=”comId” οnblur=”function(){}”>这样的元素时候,如果我们再定义一次document.getElementById('comId').οnblur=function(){}那么就会把原来的onblur事件给盖掉,当然这个是我们不希望看到的,我们希望的结果是给该元素添加另外一个出发事件,同时不影响当前的事件。解决方法是:

if (navigator.userAgent.indexOf("MSIE") > 0) {

document.getElementById('comId').attachEvent('onblur', function() {

                   // IE下的事件代码

         });

} else {

         document.getElementById('comId').addEventListener('onblur', function() {

                   // FF下的事件代码

         })

}

代码里面考虑了IE和FF下的为元素添加监听事件方法的不同,所以有个浏览器的判断,然后分别调用IE或者FF的特有方法,给元素注册了一个新的监听事件,这个事件与原有事件,是没有冲突的,可以认为是并行存在且运行的。同时,你还会发现,原本是硬编码实现的onblur事件,而现在onblur是一个字符串作为参数传递到方法中,而我们刚才是说元素和事件做了分离,现在对于事件来说,事件的触发条件,事件的执行内容,也有了一层分离,可以单独做为参数来传递,进一步降低了代码的耦合度,在后面的Js操作DOM的高级技巧中,我们会进一步做阐述。

第三个问题是,我们刚才对于一个元素,是在它下面定义了一块script区,当页面有多个元素需要这样的功能模块时,我们怎么做到批量的添加Ajax事件呢,难道还是要在每一个元素后写一段script区域代码?答案是否定的。我们应该把类似的script代码统一集中在一个地方,这样维护起来也方便,所以修改如下:

Test.js文件内容修改

(function PageInit() {

         if (navigator.userAgent.indexOf("MSIE") > 0) {

                   document.attachEvent('onload', function() {

                            PageRegistedEvent();

                   })

         } else {

                   document.addEventListener('onload', function() {

                            PageRegistedEvent();

                   })

         }

         function PageRegistedEvent() {

                   if (navigator.userAgent.indexOf("MSIE") > 0) {

                            document.getElementById('comId').attachEvent('onblur', function() {

                                     // IE下的事件代码

                            });

                   } else {

                            document.getElementById('comId').addEventListener('onblur',

                                               function() {

                                                        // FF下的事件代码

                                               })

                   }

         }

})();

代码中定义了一个全局静态类PageInit(),该方法会在head加载Test.js时被执行,该方法注册了一个onload事件,当页面加载完毕后,会执行一个PageRegistedEventFor对应浏览器版本的方法,该方法里面定义了页面中所有元素的注册监听事件。

好了,到目前位置,我们已经把原本位于test.html文件中的所有Js脚本都被移到了test.js,在test.html中只在head里保留了对于test.js的引用,他们之间的联系建立在test.html的dom每个元素的唯一标记。相对于原本紧耦合的代码来说,这样的代码分离后,我们可以把test.js当作test.html的功能模块,可拆卸的,可复用的(基于页面的唯一标示符一直的理想情况下),易于维护和代码重构的(所有的逻辑代码都统一放在了test.js里面),从实现角度来说,看起来有点类似.net的code behind,只是这次分离的是表现层,把表现层分离为标准页面层和页面逻辑层。当然,仅仅是这样还是不够的,下面我们会从Javascirpt代码的角度来谈谈,对于表现层的页面逻辑层如何抽象和封装。

转自:http://www.cnblogs.com/xueduanyang/archive/2008/07/29/1256030.html


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值