编码 Unicode 及其在 JavaScript 中的使用

一、javascript 使用 unicode16 字符集,可以使用中文变量名和函数名

计算机使用 8 位(bit)二进制表示一个字节(Byte),计算机内存最小寻址单位就是 1 字节。早期为了在计算机上使用同一的方式使用字符,使用无符号整数来标记字符。

ANSI(美国国家标准局)制订了ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),使用一个字节大小的二进制数来编码每个字符。ASCII已经被国际标准化组织(ISO)定为国际标准,称为ISO 646标准。

一个字节为8位二进制,2的8次方为256,因此有256个字符可以用一个字节来表示(0~255),但ASCII字符集只设计了128个字符(字母、数字、一些标点符号和控制字符),因此实际上只用到7位二进制,第八位设置为0,剩下的128个编码位置是闲置的。

有的计算机厂商可能会利用闲置的128个空位来制订一些字符的编码,称为OEM字符集。例如,IBM使用多出来的128位扩展了一个ASCII 扩展表,包含了一些控制符和制表符等等,被广泛使用在电子元件的数据通讯和存储中,但OEM字符集不是通用的标准。

为了编码更多的字符,2个研究字符编码的机构合并研究成果,制订了 unicode 字符集。unicode 字符集使用使用多个字节来为字符编码,按使用的字节数不同制订了不同方案,所有 unicode 编码方案前 1 个字节(256个码位)的编码对应的字符都是 ASCII 字符集中的字符。

目前 unicode 编码已经达到 64 位,使用 8 个字节标记一个字符。

如果每个字符用2个字节(16位二进制数)来标记,可以编码 65536 个字符(2 的16次方),这基本上已经可以标记世界上所有国家的语言符号,因此,在实际中通用的是UCS-2通用字符集(Universal Character Set,UCS),由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义。UCS 为第一字节的128个空位增补了一个字符集,称为 C1控制符及拉丁文补充-1 (C1 Control and Latin 1 Supplement)。

UCS-2字符集编码法有17个位面,每个位面都用2个字节来标记字符,17个位面可以映射 1,112,064个字符,其中最常用最重要的是编号为 0 的位面,里面包含了最常用的字符编码,称为基本多国语言平面BMP(Basic Multilingual Plane)。

Unicode 第 0 平面(BMP)中的编码被划分为不同区段,各国文字符号、控制符、制表符、图形字符等 都有连续的分布,其中中文简繁体区段是 4E00-9FBF。

  • 0000-007F:C0控制符及基本拉丁文 (C0 Control and Basic Latin)  
  • 0080-00FF:C1控制符及拉丁文补充-1 (C1 Control and Latin 1 Supplement)  
  • 0100-017F:拉丁文扩展-A (Latin Extended-A)  
  • 0180-024F:拉丁文扩展-B (Latin Extended-B)  
  • 0250-02AF:国际音标扩展 (IPA Extensions)  
  • 02B0-02FF:空白修饰字母 (Spacing Modifiers)  
  • 0300-036F:结合用读音符号 (Combining Diacritics Marks)
  • 4E00-9FBF:CJK 统一表意符号 (CJK Unified Ideographs)
  • (......还有很多国家语言和甲骨文等已经不用或极少使用的语言或字符的专用区段)

从第1位面开始,字符的unicode编码已经超出16位二进制数的范围,因此UCS-2无法使用2个字节直接编码BMP位面之外的字符。但是,在 UCS-2 编码中,区段 UD800 到 UDFFF 的码位是闲置的保留位,因此,可以使用这个区段中的码位通过一定的转换方式映射到其他位面的 unicode 编码。

在实际的字符传输和存储行为中,为了节省字节数,可能不会直接传输 unicode 编码,而是使用 Unicode转换格式(Unicode Transformation Format,简称为UTF),目前常见的 UTF格式有UTF-7, UTF-7.5, UTF-8, UTF-16, 以及 UTF-32,他们是由 ITTF(Internet Engineering Task Force,互联网工程任务组)组织进行标准化的,UTF-8 和 UTF-16 编码使用比较广泛。

UTF-16 编码:该编码法在 UCS-2 第0位面字符集的基础上利用 D800-DFFF 区段的码位通过一定转换方式将超出2字节的字符编码为一对16比特长的码元(即32bit,4Bytes),称作代理码对 (surrogate pair)。

编码 Unicode 及其在 JavaScript 中的使用

例如字符“编码 Unicode 及其在 JavaScript 中的使用”,他处于编号'2'的位面(总共17个位面,位面编号为16进制数0-10,第0位面可以舍去编号0直接用4位16进制数编码),码位是A6A5,即unicode编码为 2A6A5,它在UTF-16中的代理码对为 d869 dea5,但是通过 js 的charCodeAt()函数只能得出高位码对,但是这并不影响解码软件对字符编码进行定位,因为这些字符的代理码对都是成对地分布在  UD800-UDFFF 区段内的,并不存在交叉的现象,知道高位码对也可以简单地搜索到低位码对。

编码 Unicode 及其在 JavaScript 中的使用

UTF-16 编码出现以前,UD800-UDFFF 区段的码位可能会被一些计算机产品设计者利用,而且其他位面的字符极少用到,因此,一些软件可能无法正确识别代理码对,这可能会导致一些BUG。例如,Python 2.6 在 UNIX 平台上便无法正确识别代理码对。如果一个软件声称自己支持UCS-2,那么他很可能是不支持UTF-16的。

javascript 跟 java 一样使用UTF-16编码,因此, 实际上 javascript 程序中变量名和函数名可以使用ASCII 之外的字符,例如中文,不过网页文件保存的编码格式要注意,使用的编码格式对字符编码的范围应当不小于 UTF-16,比如保存为 utf-8 编码。

  1. function试试看(){
  2. var打个招呼={你好:'好你妹!'};
  3. alert(打个招呼.你好);
  4. }
  5. 试试看();

二、字符编码格式及其在 javascript 中的使用

1、unicode 16进制编码

unicode 16 使用 16位二进制编码字符,但是其编码格式在书面上使用16进制(二进制写起来太长了),在javascript中, \u 加 4个16进制字符表示一个字符的编码(每个字节 8 位二进制对应2位十六进制,2^8 = 256 = 16^2),不足4位16进制的,高位用0补足,比如 \u55B5 表示汉字 "喵",字母 "a" 的 ASCII 码是10进制 97,表示成 16 进制 unicode 编码格式就是 \u0061。试试打印出来: 

  1. document.write('\u55B5'); 
  2. document.write('\u0061');

2、javascript charCodeAt 和 String.fromChartCode 使用 10 进制编码

在 javascript 字符串的 charCodeAt 和 String.fromChartCode 中取得和使用的字节编码都是 10 进制的,因此在 document.write 和 这些方法配合使用时需要进行进制转换。
另外要注意的是,如果一个变量保存了一个字符的 unicode() 编码,你想用 document.write() 打印到页面上就需要注意了,不要将'\u' 转义成 '\\u' ,如果转义了,需要使用 eval() 来执行,否则将直接把编码打印出来:

  1. var code1 ='\\u0061';
  2. document.write(code1);// \u0061
  3. var code2 ='\u0061';
  4. document.write(code2);// a

但是在表达式中,也许你想拼接出 unicode 编码后打印字符串,这就要注意了,因为在字符串中 \u 后面必须接 4个十六进制字符才是合法的语法,所以不得不转义:

  1. var code ="\\u"+("0000"+('a').charCodeAt(0).toString(16)).slice(-4);
  2. document.write(code);// \u0061
  3. document.write(eval('"'+code+'"'));//正确做法 ,注意eval 时加上引号,因为 document.write 接受的参数是字符串,document.write("\u0061"),其中 \u0061 是单个字符,而不是可以分割的多个字符组成的字符串 ( "\\"+"u"+"0061" ), 而形如document.write(\u0061) 的语句是个语法错误.

 试试下面代码

  1. function \u0061(){ console.log(123)}
  2. \u0061();
  3. a();

3、javascript 中的单字节编码

在 js 中,可以使用 \x 加 2位16进制字符标记一个单字节的��符,例如字符 'a' 可以表示为 \x61,用法类似\u 加4位16进制编码。

  1. document.write('\x61');// a

\u 加四位十六进制数 或 \x 加2位十六进制数属于转义字符,在 js 字符串长度中只算 1 个,转义字符不能直接用于 HTML 文件(不会转换后输出,而是直接输出转义格式的字符串),但可以用 document.write()打印出来,因为在 js 语法范围内,虽然表达方式不一样,但是转义字符和直接的字符字面量都是指同一个东西:

  1. console.log("\u0061"==='a');//true
     
  2. ('123|u55b5abc').length //12
  3. ('123|u55b5abc').split('')//["1", "2", "3", "|", "u", "5", "5", "b", "5", "a", "b", "c"]
  4. ('123\u55b5abc').length //7
  5. ('123\u55b5abc').split('')// ["1", "2", "3", "喵", "a", "b", "c"]

4、 xss 与 字符编码

当向页面显示用户输入的内容是,通常我们要过滤或转义 script 标签以避免 XSS 攻击,

但是,需要注意的是, \u+16进制 、\x+16进制 、字符实体如果出现在模板中的 HTML 标签属性中就需要特别注意了,他们会正确解码后才赋给标签的属性,例如:

  1. document.body.innerHTML ='<img src="wrongUrl.gif" onerror="&#116;&#104;&#105;&#115;&#46;&#115;&#114;&#99;&#61;&#39;&#104;&#116;&#116;&#112;&#58;&#47;&#47;&#119;&#119;&#119;&#46;&#120;&#115;&#115;&#46;&#99;&#111;&#109;&#39;" >';

对标签属性之进行转义时,需要对注意,是比较下面2个做法:

  1. document.body.innerHTML ='<a onclick="\&\#116;\&\#104;\&\#105;\&\#115;">click me</a>';
  2. document.body.innerHTML ='<a onclick="\\&\\#116;\\&\\#104;\\&\\#105;\\&\\#115;">click me</a>';

5、八进制转义字符

js 字符串中,\ 开始后接正数,可能被解析为8进制转义字符。

一个八进制转义字符形成的条件是:斜线后面接的整数最长3位,至少1位,单个数字的字面值不大于8。

如果过不满足任意一项,都将结束一个字符的解释,开始新字符的解释。八进制转义字符的10进制数字字面值最大值为377,即 '\377' 被解析为一个字符,'\378' 被解析为2个字符: '\37' 和 '8' :

  1. '\377'.length //1
  2. console.log('\377')// ÿ
  3. '\378'.length //2
  4. '\128'.length //2
  5. console.log('\127')//W
  6. 'W'.charCodeAt(0)//87
  7. (87).toString(8)//'127'

相关推荐