有两本书让我对网页和Web服务器更加了解,一本是 《HeadFirst Ajax》 一本是谢孟军写的 《Go Web 编程》 前者主要讲解了现在的(因为过去的网页都是每次请求重新加载整个页面)网页是如何和服务器交互的,这种交互方式就叫Ajax。后者主要讲解了服务器 的逻辑,一个Web服务是如何处理网页请求的。
Ajax其实是一种网页设计的理念,过去的网页每次刷新都要重新加载整个网页,网页加载过程中无法继续操作页面,而网页加载过程又很慢,整个 用户体验很差。Ajax 通过javascript脚本(以后简称js)异步的请求网页中需要更新的部分,然后局部的刷新网页。这种至少有两个优点
下面通过文件上传来解释Ajax过程,先从过去表单(Form)请求开始
表单用于向服务器传输数据,所有的浏览器都支持,表单大概张成这样
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="file" name="upload" multiple="multiple"/>
<input type="submit" value="Upload file" />
</form>
最外层是一个 <form />
标签,里面可以嵌入一些 <input />
标签,<input/>
标签可以设置多种不同的类型比如
<input/>
标签可以设置各种类型,见w3c的教程,点击提交按钮后,
会把其他input标签的内容同步提交给服务器,其实就是一个post请求,注意到 <input type="text" name="fname" />
每个input都有一个
name
属性,服务器可以通过它区分不同字段的值。
Ajax 请求分成两部分,一部分是网页提供一个表单用来选择需要上传的文件,一部分是js用来上传文件
<form id="apk_form" enctype="multipart/form-data" method="post" action="upload">
<input type="file" name="upload" id="fileToUpload"/>
</form>
注意没有 submit
类型的按钮,但是我们会用js监测文件选择按钮,等文件ready之后,就直接用js提交到服务器
//在网页加载的时候找到选择文件的按钮
var fileUploader = document.getElementById('fileToUpload')
//监测事件
fileUploader.onchange = function(){
//上传选择的第一个文件
var file = fileUploader.files[0];
if (file) {
//new一个form对象
var fd = new FormData();
fd.append("upload", file);
//new一个XMLHttpRequest请求
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function(evt){
alert("上传成功:" + evt.target.responseText)
}, false);
xhr.addEventListener("error", function(evt){
alert("上传失败")
}, false);
xhr.open("POST", "url to server");
xhr.send(fd);
}
}
一般不会把大片的js代码嵌入到HTML页面(<script/>
标签) 这样会使HTML文件太大且比较复杂,大多时候是把js代码单独写到一个文件中,然后使用在HTML页面中加载js代码,比如 index.html
<!DOCTYPE html>
<html>
<head>
<title>Beego</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="static/js/main.js"></script>
</head>
<body>
...
</body>
</html>
这样在加载HTML网页(有一种更好的方法是把js文件放到HTML页面的末尾)的时候会自动下载 main.js
这个文件,我们会在这个文件中写一些js代码,比如
window.onload = initPage;
function initPage() {
// do somethign
}
这样在页面加载的时候会执行 initPage
方法,可以在这里面执行一些初始化的事情。
像上面那样直接书写 var xhr = new XMLHttpRequest();
在一些浏览器上可能会无法工作,要考虑所有浏览器的情况就要多做一些工作,专门创建一个函数用来
创建一个http请求,这个函数会考虑各种浏览器的兼容问题。下面详细的演示Ajax请求一个图片。
function createRequest() {
try {
request = new XMLHttpRequest();
} catch (tryMS) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (otherMS) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = null;
}
}
}
return request;
}
XMLHttpRequest
是大部分浏览器支持的请求,后面的两个 ActiveXObject
是微软特定的 http 请求(所以知道为啥大家痛恨IE了吗?)
function getDetails(url) {
request = createRequest();
if (request==null) {
alert("Unable to create request");
return;
}
request.open("GET",url,true);
request.onreadystatechange = displayDetails;
request.send(null);
}
function displayDetails() {
if (request.readyState == 4) {
if (request.status == 200) {
//更新页面
//detailDiv = document.getElementById("description");
//detailDiv.innerHTML = request.responseText;
}
}
}
request.open
有三个参数
GET
、 POST
等等request.onreadystatechange
设置请求结束之后的回调函数, request.send(null)
可以用这个函数发送一些额外的表单数据,如果没有
可以直接填 null
, 发送表单数据在前面有演示过。
request
对象拥有几个属性可以用来获取服务器返回的信息
在上面的请求行为中有一段注释掉的代码
//更新页面
//detailDiv = document.getElementById("description");
//detailDiv.innerHTML = request.responseText;
这段代码在js中直接修改 HTML 页面中的元素,这样做是非常不好的,以后很难再从一片js代码中找到这句话来再次修改样式,如果把展示 单独放到css文件中,就可以通过css来统计修改样式了。假如有一个表单需要用户输入用户名,然后使用js代码对输入的用户名进行验证,根据 验证结果显示不同的状态,css 中设定三种状态
首先在 .css
文件中定义三种状态的样式,然后在js代码中设定状态。
//.html
<input type="text" name="usr_name" id="username" />
//.css
#username { padding: 0 20px 0 2px; width: 198px; }
#username.thinking { background: url("../images/inProcess.png"); }
#username.approved { background: url("../images/okay.png"); }
#username.denied { background: url("../images/inUse.png"); }
//.js
if (request.status == 200) {
if (request.responseText == "okay") {
document.getElementById("username").className = "approved";
}else {
alert("Sorry, that user name is taken.");
document.getElementById("username").className = "denied";
}
}
一般情况下我们会这样处理一些事件,比如鼠标悬在某个标签上面的时候,给这个标签设计事件回调 currentBtn.onmouseover = showHint;
显示一段说明文字,但是同时又想改变标签的状态 currentBtn.onmouseover = buttonOver;
这个时候这两个事件只有最后一个设计的会执行,后一个覆盖了前一个回调函数。我们可以把这两件事放到一个函数里来完成,但是这样做并不是很好,因为本来就是两件事逻辑会弄得不清晰。解决这个问题可以给标签注册事件。
currentBtn.addEventListener("mouseover", showHint, false);
currentBtn.addEventListener("mouseover", buttonOver, false);
addEventListener
方法会给当前标签注册回调函数,有三个参数
在IE上执行执行这样的代码会报错,因为IE无法设别 addEventListener
这样的方法,在IE上他是 currentBtn.attachEvent("onmouseover", showHint);
, 虽然干了同一件事,但是IE和其他大部分浏览器都不一样(呵呵)。注意他们的不同
addEventListener
vs attachEvent
on
开头的( eg: onmouseover)但是问题来了,如何同时兼容IE和非IE浏览器,他们的差异这个大 ?
同 createRequest
方法一样,写一个工具方法判断不同类型浏览器来实现,以后都通过工具方法来添加事件。
function addEventHandler(obj, eventName, handler) {
if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
} else if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
}
}
function buttonOver() {
//在非IE浏览器 this 指向了 btn
this.className = "active";
}
addEventHandler(btn, "mouseover", buttonOver);
在Firefox上运行这段代码是没有问题的,但是在 IE 上问题又来了,this.className = "active";
这句话好像失效了,是因为在IE上
this.
指向的不是回调函数的拥有者(这个btn) 而是IE的事件处理框架(event framework). 所以我们需要新的工具函数来获取事件拥有者了
function getActivatedObject(e) {
var obj;
if (!e) {
// early version of IE
obj = window.event.srcElement;
} else if (e.srcElement) {
// IE 7 or later
obj = e.srcElement;
} else {
// DOM Level 2 browser
obj = e.target;
}
return obj;
}
所以现在的代码变成这样了
function addEventHandler(obj, eventName, handler) {
if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
} else if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
}
}
function buttonOver(e) {
getActivatedObject(e).className = "active";
}
addEventHandler(btn, "mouseover", buttonOver);
现在比较流行的web架构大都使用REST API 来实现,一般都使用JSON最为中间交换数据的协议, JSON 是一种字符格式在js中还可以表示成js对象.
{
"id":"itemGuitar",
"description":"Pete Townshend once played this guitar ...",
"price":5695.99,
"urls":["http://www.thewho.com/",
"http://en.wikipedia.org/wiki/Pete_Townshend"]
}
使用eval()
函数可以对字符串进行求值,比如 alert(eval("2 + 2"));
会得到 4 ,调用 eval({"id":"img","url":"xxxx"})
会得到一个js对象。eval({"id":"img","url":"xxxx"}).id
会打印 “img”。如果传入的是一个json字符串,需要这样做告诉js这是一个
单一对象,在字符串外围加上 ‘(‘ 和 ‘)’。
var a = eval('(' + "{\"id\":\"img\",\"url\":\"xxxx\"}" + ')')
alert(a.id)
alert(a.url)
但是 eval()
函数太过强大,它会执行js代码,而有时候服务器返回的可能并不是json字符串,所以我们需要一个更安全的函数来解析服务器返回的字符串,http://www.json.org 提供了一个脚本 json2.js
可以用来解析json字符串
var a = JSON.parse("{\"id\":\"img\",\"url\":\"xxxx\"}")
alert(a.id)
alert(a.url)
PS:以上内容大部分来自 《HEADFirstAjax》
nTop 16 November 2014