JavaScript——编码问题
本文内容
- Unicode 编码
- 本地 / Unicode
- 本地 / UTF-8
- 本地 / ASCII
- 本地 / URI 编码
Unicode 编码
最近搞蜘蛛程序,经常遇到编码问题,原因有两个:一是各站点所使用的传输编码不同。我特别使用了“传输编码”这个说法,后面会解释;二是站点自己做的手脚。因为,谁也不想自己的资源,被蜘蛛搞走。网络上运行的蜘蛛实在很多。第一个问题很好解决,但是第二个问题,就有点麻烦——做手脚的手段千奇百怪。
本文之所以用 Unicode 开头,而不是 ASCII 或 UTF-8 ,是因为 Unicode 编码包含各个国家的字符(ASCII 当然在其范围),得到系统内核支持,而其他常见编码都是建立在该编码基础上的。应该从两方面认识 Unicode 编码:编码方式和其实现方式。Unicode 对各个国家的字符进行了编码,但未规定它的实现方式。Unicode 实现方式不同于其编码方式。一个字符的 Unicode 编码是确定的。但在实际存储和传输过程中,由于不同系统的设计不一定一致,对 Unicode 编码的实现方式有所不同。Unicode 的实现方式称为“Unicode 转换格式(Unicode Transformation Format,UTF)”。
实现方式的不同,体现在,比如,用多个字节表示一个汉字,存在字节顺序的问题——大端(Big-Endian,BE)和小端(Little-Endian,LE)。Mac 机是小端,而 Windows 机则是大端。若有十六进制 4E59,Mac 机认为是 594E,汉字为“奎”;而 Windows 机认为是 4E59,汉字为“乙”。再如,Unicode 是定长编码,可一个英文字母用一个字节就能表示,这就存在存储代价。实现时,如 UTF-8 把定长编码变成变长的。这就是为什么 XML 和 HTML 这样需要网络传输的把 UTF-8 作为标准字符集的主要原因。
目前通用的实现方式是 UTF-16 Big-Endian、UTF-16 Little-Endian 和 UTF-8。比如,Windows 7 的 记事本,在“另存为”可以看到四种编码方式:ANSI(即英文系统为 ASCII,中文系统为 GB2312 或 Big5),Unicode(即 UTF-16 Little-Endian)、Unicode big endian(即 UTF-16 Big-Endian)和 UTF-8。
现在,很多浏览器只能显示 UCS-2 完整字符集(Unicode 版本中的一个小子集),这部分是由于 Unicode 版本发展原因。
本地 / Unicode
你可能对“本地”有点迷惑。刚开始,我也不理解,应该是编码之间转换才对。因为本地字符有编码,那么把这个编码转换成 Unicode 编码的说法很合理。其实,所有国家字符的 Unicode 编码都是确定的,无论是你输入的是哪国语。
示例 1:
例子lizi
Unicode 十进制和十六进制编码分别为:
例子lizi
例子lizi
本地到 Unicode 转换的 JavaScript 代码如下所示:
function nativeToUnicode(str) {
var des = [];
for (var i = 0; i < str.length; i++)
des.push("&#" + str.charCodeAt(i) + ";");
//des.push("&#x" + str.charCodeAt(i).toString(16) + ";");
return des.join("");
}
Unicode 到本地转换的 JavaScript 代码如下所示:
function unicodeToNative(str) {
var src = str.match(/&#(\d+);/g);
if (src != null) {
var des = [];
for (var i = 0; i < src.length; i++)
des.push(String.fromCharCode(src[i].replace(/[&#;]/g, "")));
return des.join("");
}
return "";
}
说明:
- String.charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。
- String.fromCharCode() 方法可接受一个指定的 Unicode 值,然后返回一个字符串。
本地 / UTF-8
UTF-8 是 Unicode 的实现方式之一,把定长编码改成变长的,减少了存储和传输的开销。从下面示例可以看到,中文是编码,而对用一个字节就能表示的英文字符,没有变化。
示例 2:
例子lizi
UTF-8 编码为:
例子lizi
本地转换 UTF-8 的 JavaScript 代码如下所示:
function navtiveToUTF8(str) {
return str.replace(/[^\u0000-\u00FF]/g, function ($0) { return escape($0).replace(/(%u)(\w{4})/gi, "&#x$2;") });
}
UTF-8 转换本地的 JavaScript 代码如下所示:
function utfToNative(str) {
return unescape(str.replace(/&#x/g, '%u').replace(/;/g, ''));
}
说明:
- escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串。
- unescape() 函数可对通过 escape() 编码的字符串进行解码。
本地 / ASCII
示例 3:
例子lizi
ASCII 码转换或不转换 ASCII 的编码为:
\u4f8b\u5b50lizi
\u4f8b\u5b50\u006c\u0069\u007a\u0069
本地转换 ASCII 的 JavaScript 代码如下所示:
function nativeToASCII(str, isIgnoreLetter) {
var character = str.split("");
var ascii = [];
for (var i = 0; i < character.length; i++) {
var code = Number(character[i].charCodeAt(0));
if (!isIgnoreLetter || code > 127) {
var charAscii = code.toString(16);
charAscii = new String("0000").substring(charAscii.length, 4) + charAscii;
ascii.push("\\u" + charAscii);
}
else {
ascii.push(character[i]);
}
}
return ascii.join("");
}
ASCII 转换本地的 JavaScript 代码如下所示:
function asciiToNative(str) {
var character = str.split("\\u");
var res = character[0];
for (var i = 1; i < character.length; i++) {
var code = character[i];
res += String.fromCharCode(parseInt("0x" + code.substring(0, 4)));
if (code.length > 4) {
res += code.substring(4, code.length);
}
}
return res;
}
本地 / URI 编码
URI 编码不用以上编码,它的主要是出于安全性考虑,让链接的内容不能一目了然地看到是什么,防止恶意的偷看者。下面是 cnblogs 的“找找看”检索链接,对其进行编码。
示例 4:
http://zzk.cnblogs.com/s?w=例子lizi&t=
URI 编码和组件编码分别为:
http://zzk.cnblogs.com/s?w=%E4%BE%8B%E5%AD%90lizi&t=
http%3A%2F%2Fzzk.cnblogs.com%2Fs%3Fw%3D%E4%BE%8B%E5%AD%90lizi%26t%3D说明:
- encodeURI() 函数可把字符串作为 URI 进行编码。
- decodeURI() 函数可对 encodeURI() 函数编码过的 URI 进行解码。
- encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。
- decodeURIComponent() 函数可对 encodeURIComponent() 函数编码的 URI 进行解码。