?
在用 AJAX 開發(fā)的過程中, 不可避免的會遇到中文問題. 很多原來可以通過表單進行 POST 提交的字符, 到了用 AJAX 實現(xiàn)的時候, 就會出現(xiàn)煩人的亂碼和丟特殊字符的現(xiàn)象. 另外服務(wù)器端返回值如何解析, 也是一個很煩人的問題. 本文將就個人的一點實踐經(jīng)驗作出總結(jié), 并給出一個盡量簡單可行, 復(fù)用性高的方案. 目的不是替代你喜歡的 AJAX 框架, 而是希望幫助您理解和處理可能遇到的問題.
開始之前: 首先一個問題就是通常 XMLHttpRequest 默認的編碼都是UTF-8的, 所以我們建議所有頁面, 客戶端和服務(wù)器端都使用 UTF-8 作為編碼.
1. base64 encode 和 decode??? 這個方案依賴于 JavaScript 實現(xiàn)的 base64 編碼/解碼方法, 在客戶端發(fā)送參數(shù)的時候用 base64 進行編碼, 服務(wù)器端通過 base64 進行解碼后還原出原來的字符, 這個解決方案可以滿足需要, 但是有個問題就是一是增加了客戶端代碼量, 還有個大問題就是編碼后的內(nèi)容比原始內(nèi)容會大很多, 另外如果找到的 base64 JS 算法不夠標準的話, 服務(wù)器端就無法還原原來的值了. 現(xiàn)在網(wǎng)上有很多種 base64 的 JS 實現(xiàn)代碼, 例如如下的一個算法實現(xiàn):
<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="轉(zhuǎn)換" ONCLICK="doit()"></FORM></BODY>在每個表單值被提交之前調(diào)用 base64encode, 然后在服務(wù)器端調(diào)用 base64 解碼器即可.
在 JSP 中可以通過這樣做來實現(xiàn):??? ??? sun.misc.BASE64Decoder base64decoder = new sun.misc.BASE64Decoder();??? ??? byte[] data =?base64decoder.decodeBuffer(request.getParameter("input"));??? ??? String result = new String(data, "UTF-8");// 注意建議這里制定字符集來歡迎到原來的字符串在這種情況下服務(wù)器端返回的字符也可以通過先 base64 編碼的方式傳遞到客戶端, 客戶端之后調(diào)用 JS 形式的解碼器即可還原到原來的字符串. 服務(wù)器端可以使用 sun.misc.BASE64Encoder (不要用 java.netURLEncoder).
2.使用 JS 自帶的 escape() & encodeURI() & encodeURIComponent()escape() & encodeURI() & encodeURIComponent()這三個函數(shù)都可以用來對URI進行encode或過濾特殊字符(#/$&+=?/等)。我的經(jīng)驗是最好用encodeURIComponent()(需要IE 5.5以上,F(xiàn)ireFox當(dāng)然沒問題),因為對UTF-8支持比較好,不會遇到中文亂碼問題,否則還需要進行編碼轉(zhuǎn)換,很麻煩的。使用其它兩個函數(shù)都會發(fā)生丟失特殊字符的問題,例如空格變+號或者空格,引號,&=?等丟失的問題, 至少使用 JSP 作為服務(wù)器端的話會發(fā)生這種情況, 有興趣的朋友可以將本文最后的例子代碼中的編碼部分修改后做個測試.
下面是MSDN上對這三個函數(shù)的解釋:
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)
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
3. 使用一個簡便的客戶端數(shù)據(jù)解析方案
最偷懶的辦法就是返回一段 HTML 顯示出來了. 至于如果是想帶一些數(shù)據(jù), 解析處理的話, 方案很多, 利用 XML 啊, JSON 啊什么的不一而足. 我這里呢就給出一個相當(dāng)簡便的方案: 使用 JS 內(nèi)置的 eval 方法來解析. 這個方案是在幫助一個同事想最快的已最短的代碼解析返回的對象的多個變量的時候提出的.
服務(wù)器端返回一個字符串:
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 實現(xiàn)的 escape(), unescape() 方法, 其實本例中只需要 escape() 的 Java 版本就可以了(這個方案也幫助另一個同事解決了從JSP端傳遞的變量含有'號結(jié)果導(dǎo)致客戶端沒法顯示的問題):
??? 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();??? }
這樣, 在服務(wù)器端的時候可以變成:
<%String username = "'abc\"''";// 其實這個普通子串轉(zhuǎn)換成 Java 語言中的字符串也有工具可以用的, 例如本人開發(fā)的 Native2JavaString, 改日再講.
%>
var _dataObject = {?? username : "<%=escape(username)%>",??????? age : 24};客戶端呢, 就可以簡單的來JS自帶的 unescape() 函數(shù)來取出原來的字符串:
var responseText = xmlhttp.responseText;eval(responseText);alert("_dataObject.username=" + unescape(_dataObject.username));就是服務(wù)器端用 Java 寫的 escape(), 客戶端呢就用 JS 自帶的 unescape().
4.?實例代碼好了, 說了這么多, 就推出個人的解決方案吧. 簡單的講就是我寫了一個腳本對象 AjaxFormer, 使用的是 escape來自動的將原來的 POST/GET 方式的提交代碼自動的轉(zhuǎn)換成 AJAX 的方式.
/**?* @constructor?* This is a ajax form helper class.?*?* @param form - the document form?* @param resultDivId - the result div id?*/function AjaxFormer (form, resultDivId);構(gòu)造器的第一個參數(shù)是個 form 對象, 第二個是個可選的結(jié)果 DIV 對象, 也就是說你可以指定服務(wù)器端返回的 HTML 代碼顯示的地方, 如果保持為空的話, 那么返回的 HTML?會被附加到文檔的末尾. 本對象有一個名字為 ajaxSubmitForm() 的方法來自動的遍歷所有表單元素, 然后將結(jié)果拼成一個字符串, 最后根據(jù)原來的表單的提交方式(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 的表單提交所需要做的工作, 看上去夠簡單吧?
運行時的效果如圖所示:

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