開始之前: 首先一個問題就是通常 XMLHttpRequest 默認的編碼都是UTF-8的, 所以我們建議所有頁面, 客戶端和服務器端都使用 UTF-8 作為編碼.
1. base64 encode 和 decode
??? 這個方案依賴于 JavaScript 實現的 base64 編碼/解碼方法, 在客戶端發送參數的時候用 base64 進行編碼, 服務器端通過 base64 進行解碼后還原出原來的字符, 這個解決方案可以滿足需要, 但是有個問題就是一是增加了客戶端代碼量, 還有個大問題就是編碼后的內容比原始內容會大很多, 另外如果找到的 base64 JS 算法不夠標準的話, 服務器端就無法還原原來的值了. 現在網上有很多種 base64 的 JS 實現代碼, 例如如下的一個算法實現:
<HTML>
<HEAD>
<TITLE>Base64</TITLE>
<script language=javascript>
var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var base64DecodeChars = new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3,? 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
function base64encode(str) {
var out, i, len;
var c1, c2, c3;
len = str.length;
i = 0;
out = "";
while (i < len) {
?c1 = str.charCodeAt(i++) & 0xff;
?if(i == len)
?{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
out += "==";
break;
?}
?c2 = str.charCodeAt(i++);
?if(i == len)
?{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
out += "=";
break;
?}
?c3 = str.charCodeAt(i++);
?out += base64EncodeChars.charAt(c1 >> 2);
?out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
?out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
?out += base64EncodeChars.charAt(c3 & 0x3F);
}
return out;
}
function base64decode(str) {
var c1, c2, c3, c4;
var i, len, out;
len = str.length;
i = 0;
out = "";
while (i < len) {
?/* c1 */
?do {
c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
?} while(i < len && c1 == -1);
?if(c1 == -1)
break;
?/* c2 */
?do {
c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
?} while(i < len && c2 == -1);
?if(c2 == -1)
break;
?out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
?/* c3 */
?do {
c3 = str.charCodeAt(i++) & 0xff;
if(c3 == 61)
return out;
c3 = base64DecodeChars[c3];
?} while(i < len && c3 == -1);
?if(c3 == -1)
break;
?out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
?/* c4 */
?do {
c4 = str.charCodeAt(i++) & 0xff;
if(c4 == 61)
return out;
c4 = base64DecodeChars[c4];
?} while(i < len && c4 == -1);
?if(c4 == -1)
break;
?out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
}
return out;
}
function utf16to8(str) {
var out, i, len, c;
out = "";
len = str.length;
for(i = 0; i < len; i++) {
?c = str.charCodeAt(i);
?if ((c >= 0x0001) && (c <= 0x007F)) {
out += str.charAt(i);
?} else if (c > 0x07FF) {
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
?} else {
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
?}
}
return out;
}
function utf8to16(str) {
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while (i < len) {
?c = str.charCodeAt(i++);
?switch(c >> 4)
?{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += str.charAt(i-1);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
?}
}
return out;
}
function doit() {
var f = document.f
f.output.value = base64encode(utf16to8(f.source.value))
f.decode.value = utf8to16(base64decode(f.output.value))
}
</script>
</HEAD>
<BODY>
<H1>Base64</H1>
<FORM NAME="f">
原碼& lt;BR>
<TEXTAREA NAME="source" ROWS=4 COLS=60 WRAP="soft"></TEXTAREA><BR><BR>
Base64 encode<BR>
<TEXTAREA NAME="output" ROWS=4 COLS=60 WRAP="soft"></TEXTAREA><BR><BR>
Base64 decode<BR>
<TEXTAREA NAME="decode" ROWS=4 COLS=60 WRAP="soft"></TEXTAREA><BR><BR>
<INPUT TYPE=BUTTON VALUE="轉換" ONCLICK="doit()">
</FORM>
</BODY>
在每個表單值被提交之前調用 base64encode, 然后在服務器端調用 base64 解碼器即可.
在 JSP 中可以通過這樣做來實現:
??? ??? sun.misc.BASE64Decoder base64decoder = new sun.misc.BASE64Decoder();
??? ??? byte[] data =?base64decoder.decodeBuffer(request.getParameter("input"));
??? ??? String result = new String(data, "UTF-8");// 注意建議這里制定字符集來歡迎到原來的字符串
在這種情況下服務器端返回的字符也可以通過先 base64 編碼的方式傳遞到客戶端, 客戶端之后調用 JS 形式的解碼器即可還原到原來的字符串. 服務器端可以使用 sun.misc.BASE64Encoder (不要用 java.netURLEncoder).
2.使用 JS 自帶的 escape() & encodeURI() & encodeURIComponent()
escape() & encodeURI() & encodeURIComponent()這三個函數都可以用來對URI進行encode或過濾特殊字符(#/$&+=?/等)。我的經驗是最好用encodeURIComponent()(需要IE 5.5以上,FireFox當然沒問題),因為對UTF-8支持比較好,不會遇到中文亂碼問題,否則還需要進行編碼轉換,很麻煩的。使用其它兩個函數都會發生丟失特殊字符的問題,例如空格變+號或者空格,引號,&=?等丟失的問題, 至少使用 JSP 作為服務器端的話會發生這種情況, 有興趣的朋友可以將本文最后的例子代碼中的編碼部分修改后做個測試.
下面是MSDN上對這三個函數的解釋:
escape(charString)
The escape method returns a string value (in Unicode format) that contains the contents of charstring. All spaces, punctuation, accented characters, and any other non-ASCII characters are replaced with %xx encoding, where xx is equivalent to the hexadecimal number representing the character. For example, a space is returned as "%20."
Characters with a value greater than 255 are stored using the %uxxxx format.
Note?? The escape method should not be used to encode Uniform Resource Identifiers (URI). Use encodeURI and encodeURIComponent methods instead.
http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthescape.asp
?encodeURI(URIString)
The encodeURI method returns an encoded URI. If you pass the result to decodeURI, the original string is returned. The encodeURI method does not encode the following characters: ":", "/", ";", and "?". Use encodeURIComponent to encode these characters.
http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthfencodeuri.asp
encodeURIComponent(encodedURIString)
3. 使用一個簡便的客戶端數據解析方案The encodeURIComponent method returns an encoded URI. If you pass the result to decodeURIComponent, the original string is returned. Because the encodeURIComponent method encodes all characters, be careful if the string represents a path such as /folder1/folder2/default.html. The slash characters will be encoded and will not be valid if sent as a request to a web server. Use the encodeURI method if the string contains more than a single URI component.
http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthencodeuricomponent.asp
最偷懶的辦法就是返回一段 HTML 顯示出來了. 至于如果是想帶一些數據, 解析處理的話, 方案很多, 利用 XML 啊, JSON 啊什么的不一而足. 我這里呢就給出一個相當簡便的方案: 使用 JS 內置的 eval 方法來解析. 這個方案是在幫助一個同事想最快的已最短的代碼解析返回的對象的多個變量的時候提出的.
服務器端返回一個字符串:
var _dataObject = {
?? username : "beansoft",
??????? age : 24
};
客戶端在得到這個字符串后可以通過下面一段代碼搞定:
var responseText = xmlhttp.responseText;
eval(responseText);
alert("_dataObject.username=" + _dataObject.username);
好了, 解析出來了!
如果要傳遞多個變量呢, 就用 var _dataObject1, var _dataObject2...這樣就可以了, 客戶端就依次是 _dataObject1.username, _dataObject2.username...
等等: 我的變量里寫了特殊字符怎么辦? 例如我用的字符串是 'abc"'', 這時候我不得不拋出殺手锏了, 這就是用 Java 實現的 escape(), unescape() 方法, 其實本例中只需要 escape() 的 Java 版本就可以了(這個方案也幫助另一個同事解決了從JSP端傳遞的變量含有'號結果導致客戶端沒法顯示的問題):
??? public static String escape(String src) {
??? ??? int i;
??? ??? char j;
??? ??? StringBuffer tmp = new StringBuffer();
??? ??? tmp.ensureCapacity(src.length() * 6);
??? ??? for (i = 0; i < src.length(); i++) {
??? ??? ??? j = src.charAt(i);
??? ??? ??? if (Character.isDigit(j) || Character.isLowerCase(j)
??? ??? ??? ??? ??? || Character.isUpperCase(j))
??? ??? ??? ??? tmp.append(j);
??? ??? ??? else if (j < 256) {
??? ??? ??? ??? tmp.append("%");
??? ??? ??? ??? if (j < 16)
??? ??? ??? ??? ??? tmp.append("0");
??? ??? ??? ??? tmp.append(Integer.toString(j, 16));
??? ??? ??? } else {
??? ??? ??? ??? tmp.append("%u");
??? ??? ??? ??? tmp.append(Integer.toString(j, 16));
??? ??? ??? }
??? ??? }
??? ??? return tmp.toString();
??? }
??? public static String unescape(String src) {
??? ??? StringBuffer tmp = new StringBuffer();
??? ??? tmp.ensureCapacity(src.length());
??? ??? int lastPos = 0, pos = 0;
??? ??? char ch;
??? ??? while (lastPos < src.length()) {
??? ??? ??? pos = src.indexOf("%", lastPos);
??? ??? ??? if (pos == lastPos) {
??? ??? ??? ??? if (src.charAt(pos + 1) == 'u') {
??? ??? ??? ??? ??? ch = (char) Integer.parseInt(src
??? ??? ??? ??? ??? ??? ??? .substring(pos + 2, pos + 6), 16);
??? ??? ??? ??? ??? tmp.append(ch);
??? ??? ??? ??? ??? lastPos = pos + 6;
??? ??? ??? ??? } else {
??? ??? ??? ??? ??? ch = (char) Integer.parseInt(src
??? ??? ??? ??? ??? ??? ??? .substring(pos + 1, pos + 3), 16);
??? ??? ??? ??? ??? tmp.append(ch);
??? ??? ??? ??? ??? lastPos = pos + 3;
??? ??? ??? ??? }
??? ??? ??? } else {
??? ??? ??? ??? if (pos == -1) {
??? ??? ??? ??? ??? tmp.append(src.substring(lastPos));
??? ??? ??? ??? ??? lastPos = src.length();
??? ??? ??? ??? } else {
??? ??? ??? ??? ??? tmp.append(src.substring(lastPos, pos));
??? ??? ??? ??? ??? lastPos = pos;
??? ??? ??? ??? }
??? ??? ??? }
??? ??? }
??? ??? return tmp.toString();
??? }
這樣, 在服務器端的時候可以變成:
<%
String username = "'abc\"''";// 其實這個普通子串轉換成 Java 語言中的字符串也有工具可以用的, 例如本人開發的 Native2JavaString, 改日再講.
%>
var _dataObject = {
?? username : "<%=escape(username)%>",
??????? age : 24
};
客戶端呢, 就可以簡單的來JS自帶的 unescape() 函數來取出原來的字符串:
var responseText = xmlhttp.responseText;
eval(responseText);
alert("_dataObject.username=" + unescape(_dataObject.username));
就是服務器端用 Java 寫的 escape(), 客戶端呢就用 JS 自帶的 unescape().
4.?實例代碼
好了, 說了這么多, 就推出個人的解決方案吧. 簡單的講就是我寫了一個腳本對象 AjaxFormer, 使用的是 escape來自動的將原來的 POST/GET 方式的提交代碼自動的轉換成 AJAX 的方式.
/**
?* @constructor
?* This is a ajax form helper class.
?*
?* @param form - the document form
?* @param resultDivId - the result div id
?*/
function AjaxFormer (form, resultDivId);
構造器的第一個參數是個 form 對象, 第二個是個可選的結果 DIV 對象, 也就是說你可以指定服務器端返回的 HTML 代碼顯示的地方, 如果保持為空的話, 那么返回的 HTML?會被附加到文檔的末尾. 本對象有一個名字為 ajaxSubmitForm() 的方法來自動的遍歷所有表單元素, 然后將結果拼成一個字符串, 最后根據原來的表單的提交方式(get/post)來自動再客戶端用 AJAX 模擬提交這個表單到原來的表單的 action 屬性所指定的頁面中去.
用法示例:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>AJAX Form Submit Test</title>
<script src='ajax_common.js'></script>
</head>
<body>
<h3>AJAX Form Submit Test</h3>
Fill the form and then click submit
<form method="POST" id="form1" name="form1"
action="form_action.jsp"
onSubmit="former.ajaxSubmitForm();return false;">
??? <p><input type="hidden" name="hidden1" value="hiddenValue">
??? text:<input type="text" name="textf&1" size="20" value="text1">
??? checkbox:<input type="checkbox" name="checkbox1" value="ON" checked>
??? radio:<input type="radio" value="V1" checked name="radio1">
??? select:<select size="1" name="select1">
??? <option selected value="option1">D1</option>
??? </select>
??? <br>
??? <br>
??? <input type="submit" name="B1" value="submit">
??? <input type="reset" name="B2" value="reset">
??? </p>
</form>
<script type="text/javascript">
var former = new AjaxFormer($('form1'));
</script>
</body>
</html>
紅色的字體就是您從一個非 AJAX 的表單提交改變成一個 AJAX 的表單提交所需要做的工作, 看上去夠簡單吧?
運行時的效果如圖所示:

下載本文的源碼:?
將源碼解壓縮到JSP服務器的任意目錄下即可, 例如 $TOMCAT_HOME\webapps\ROOT 下, 然后在瀏覽器里鍵入:
http://localhost:8080/ajax_form_submit.htm
作者: beansoft@126.com?2006.12.25