登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

淘尽网 官方博客

淘尽网http://www.tao3w.com做最好的比价网站

 
 
 

日志

 
 

javascript html 相关编码问题研究(http://stauren.net/log/fpev3c89q.html)  

2011-01-27 16:46:28|  分类: HTML |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
javascript html 相关编码问题研究
  • 8/2802
  • stauren
  • 821d ago
  •  
http://stauren.net 版权所有,转载请注明出处,谢谢

不敢说完全终极的,不过基本够用吧,里面可能有错的地方,欢迎指正。

说是javascript相关,其实也需要很多html相关和基础的编码知识,让我们开始吧!

在计算机中,我们储存的信息都是用二进制代码来表示的。比如说 'stauren.net' 和 ‘畅哥’ 这样的汉字。这样的非二进制的信息要被转换成二进制,则需要进行编码。

在这里需要先明白2个概念,charsetcharacter encoding

charset 是字符集,也就是某个符号和某个数字映射关系的一个表,也就是它决定了 97 是 stauren.net 的 'a',30021 是畅哥的“畅”,不同的表有不同的映射关系,如 ascii,gb2312,Unicode

chracter encoding 是编码方式,既,同是 30021 这个数,我们是用 \畅 表示呢,还是用 %E7%95%85 来表示呢?这就是 character encoding

对于 'stauren.net' 这样的 字符串来说,是美国人的常用字符,他们就制定了一个 ASCII 的字符集,全称是 american standard code of information interchange,把0--127这128个码,(2的8次方,0x00-0x7f) 编码了123abc这样的常用的127个字符。一共是 7 bits,原因是计算机的一个 byte 是8个字节,第一个是符号位,要用来去补码反码表示负数什么的,就剩下7位表示含义。唉,美国人就是小气,要是一开始就设计成一个 byte 是16 bits,世界上会少很多问题,不过当时,估计他们觉得 7 bits 就够了,127个不同的字符呢!

介于计算机这玩意儿是美国人搞出来的,所以丫的就图自己省事,把自家用的符号都编码好了,他们用的挺爽的。但当这个东西开始国际化的时候,问题出来了,拿中国举例吧,汉字就好几万,怎么办?后来ascii扩展了一次,扩展到了可以表示256个字符,多了一倍,可惜还是只是杯水车薪。

于是大家没办法,只好在现有的基础上解决这个问题,已有的 8 bits 的系统是基础,不能破坏,不能去改到 16 bits之类的…… 所以只能走另一条路:用多个 ascii 的字符去表示一个字符,也就是传说中的 MBCS ( Multi-Byte Character System,多字节字符系统)

有了这个 MBCS 的概念,我们可以表示更多个字符了,比如我们用 2 个 ascii 字符,就有 16 bits, 理论上有 2 的 16 次方 65536 个字符。但这些编码怎么分配到字符上呢?比如畅哥的"畅"的 Unicode 编码就是 30021,谁决定  是“畅”呢?charset,也就是字符集。ascii就是最基础的一个字符集,在此之上,我们有像 gb2312, big5这样的MBCS的字符集。这些都是小集,最终有个叫 Unicode Consortium 的机构,从1991年开始,发布了第一版 Unicode国际标准,ISBN 0-321-18578-1 什么的,国际标准化组织 ISO 也参与了这个的定制,叫什么 ISO/IEC 10646 : the Universal Character Set,历史我就不多讲了,总之,这个是个基本覆盖了所有已经存在的地球上的符号的字符集了,javascript的标准 ECMA xxxx里面也都规定,js的标准字符集是 Unicode。

对于我们在中国的开发者来说,可能碰到比较多的问题就是 gbk, gb2312, utf-8 互转之类的问题了。可惜的是,这个说法其实都不准确,gbk,gb2312都是字符集,而是是 MBCS中的双字符类的,2个ascii字符代表一个汉字。而utf-8 是一种character encoding,是使用Unicode字符集的一种编码方式,因为Unicode如今主要用UTF-8编码,所以大家就把这几个并列了…… 其实呢,错误的叫法。

有了Unicode这个字符集,大家都很高兴,至少人类文明没有碰到外星人之前,这是一把万能钥匙了,都用它吧。于是,UTF-8 (8-bit S/Unicode Transformation Format) 这种编码方法被发明了出来,它有几个特别好的地方,易于推广:
1,代表Unicode字符集,一劳永逸的事情
2,兼容ascii
第二点是个很大的优点,因为一些陈旧的英美人,觉得为了给第三世界的这些不会英语的穷人方便,需要把它们本来的1byte的编码方式变成2byte的(有种编码就是这样的,stauren我忘了是哪个了……),这样存储空间增大一倍,他们觉得很不划算,所以就一直抗拒抗拒的,加上一些老旧的系统也没法升级,所以…… 但是UTF-8的设计就很好,完全兼容ascii,该是ascii还是ascii,所以,大家都happy。

要把 UTF-8 说清楚呢,就必须引入一个表了:
 U-00000000 – U-0000007F:    0xxxxxxx
 U-00000080 – U-000007FF:    110xxxxx 10xxxxxx
 U-00000800 – U-0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx
 U-00010000 – U-001FFFFF:    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 U-00200000 – U-03FFFFFF:    111110xx 10xxxxxx 10xxxxxx 10xxxxxx  10xxxxxx
 U-04000000 – U-7FFFFFFF:    1111110x 10xxxxxx 10xxxxxx 10xxxxxx  10xxxxxx 10xxxxxx

要看懂这个表呢,我们看前两行就够了
U-00000000 – U-0000007F:    0xxxxxxx 第一行是这样的,意思是说,如果你发现一个utf-8编码是0xxxxxxx,是0开头的, 0-127之间,那么他就是单独的一byte代表一个符号,而且是一个和ascii码完全一样的一 byte。其他所有的utf8编码的二进制值都是1开头的1xxxxxxx,大于127的,而且都需要至少2 bytes才能代表一个符号。这个就是刚才谈到的兼容性,用英文来说,就是utf8编码的两个属性:
S characters U+0000 to U+007F (ASCII) are encoded simply as bytes 0x00 to 0x7F (ASCII compatibility). This means that files and strings which contain only 7-bit ASCII characters have the same encoding under both ASCII and UTF-8.
All S characters >U+007F are encoded as a seqnce of several bytes, each of which has the most significant bit set. Therefore, no ASCII byte (0x00-0x7F) can appear as part of any other character.

然后我们看第二行:
 U-00000080 – U-000007FF:    110xxxxx 10xxxxxx
先说英文定义的标准吧:
The first byte of a multibyte seqnce that represents a non-ASCII character is always in the range 0xC0 to 0xFD and it indicates how many bytes follow for this character. All further bytes in a multibyte seqnce are in the range 0x80 to 0xBF. This allows easy resynchronization and makes the encoding stateless and robust against missing bytes.
在一长串连续的二进制 byte 码中,可能由2个至6个bytes来表示一个符号,那么相比较于用一个 byte 表示符号的ascii码,我们需要空间来储存两个额外信息: 1,这个符号开始处,一个”starter“的位置,用生物学上的话来说,起始因子的位置了;2,这个符号的长度(其实如果每个符号都有 starter,这个长度是可以不提供的,但是提供长度信息增加了在部分bytes丢失时的容错能力)。解决方案是:第一个byte里面的第一位刚才已经被用了,是0表示ascii码,是1表示这不是ascii,所以,我们用第二位的1来代表这一bytes是一个starter,即,一个多字节符号的第一个bytes一定是 11xxxxxx,一个192到255之间的二进制数。接下来,从第三位开始,提供长度信息,没有1了就表示这个符号是2字节的,没多一个1,多一字节。所以utf-8最多定义到了 6 字节字符,需要比 110xxxxx 这样的表示2字节的starter多 4 个 1,所以这个starter就是 1111110x,如上表所示。
真正的信息位(即,真正的charset字符集中的数字信息),是直接用二进制的方式,放在上面这个表的x位上的。用我们中国程序员接触最多的汉字来说吧,它们的编码区间是在 U-00000800 – U-0000FFFF 之间的,所以,用三个字节来表示,还是用 http://stauren.net 的畅哥的"畅"字举例吧,畅字在unicode中的编号是这样的:
畅 : 30021 == 0x7454 ==  二进制 111010101000101

在 javascript 中,run这段代码:

  alert('\畅');
  alert(escape('畅'));
  alert(String.fromCharCode('30021'));
  alert('畅'.charCodeAt(0));
  alert(encodeURI('畅'));

可以看到,string直接量可以用\u+十六进制 Unicode 码的形式得到字符 ’畅‘,而fromCharCode 方法接受 10 进制的 Unicode 码,得到字符 '畅'。
其中第二个alert得到的是 '%畅' , 这是一种不标准的Unicode编码,是属于 URI 的 Percent encoding 一部分,但这种使用方法以及被W3C拒绝了,任何一个RFC中都没有这个标准,ECMA-262 标准中规定了 escape 的这种行为,估计也是暂时的。

比较有意思的是第五次alert得到的 %E7%95%85 这是什么呢?怎么得到的呢?

这就是,传说中的URI上用的比较多的 Percent encoding,百分号编码,RFC 3986 标准中规定的。

RFC 3986 规定,Percent encoding的非保留字如下:
 Unreserved characters, per RFC 3986 (Jan ry 2005)
 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
 a b c d e f g h i j k l m n o p q r s t u v w x y z
 0 1 2 3 4 5 6 7 8 9 - _ . ~

也就是说,这些字出现在 URI 中的时候,不进行编码,因为他们和URI的格式没有关系,只是表示原义的字符

另外,保留字如下:
 Reserved characters, per RFC 3986 (Jan ry 2005)
 ! * ' ( ) ; : @ & = + $ , / ? % # [ ]

这些字符,是有特殊意义的,如果在不代表那些特殊意义而代表原意的时候出现,必须经过编码,如下:
 Reserved characters after percent-encoding
 ! * ' ( ) ; : @ & = + $ , / ? % # [ ]
 %21 %2A %27 %28 %29 %3B %3A %40 %26 %3D %2B %24 %2C %2F %3F %25 %23 %5B %5D

而 % 号后面就是一个2位的十六进制数,这个数,基本就是 UTF-8 编码…… 具体可能还有详细的地方,stauren我懒得看了,呵呵

回到 '畅' 字为什么是 %E7%95%85 这个问题上来

现在我们知道 '畅' 的 Unicode 编码的二进制形式是:
 111010101000101

我们又知道,对于一个二进制的汉字,它的UTF-8编码的形式是:
 U-00000800 – U-0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx

现在我们做个填空题,把 '畅’ 二进制切开填进去替换掉 x:
 111010101000101 = 111 010101 000101

第一个字节少一位,左边加个0补齐,得到:
 11100111 - 10010101 - 10000101

让我们把这三个二进制数转换成16进制:

  alert(
      '%' + parseInt('11100111', 2).toString(16) +
      '%' + parseInt('10010101', 2).toString(16) +
      '%' + parseInt('10000101', 2).toString(16)
  )

怎么样,得到 %E7%95%85 了吧。

另外,再介绍一下 HTML 中的 Numeric character reference
相信大家都知道,HTML中的特殊字符是需要编码的,比如 & 需要被编码为: &  还有 ® 这样的特殊字符。其实HTML也是可以利用 Unicode 编码来显示任何一个字符的,编辑一个如下的html文件:

 <html>
 <body>
 &#30021;
 &#030021;
 &#x7545;
 </body>
 </html>

看到了吧,三个“畅”字。

以上基本就是 javascript 和 html 中常用到的编码和原理,最后再说一点,很多很多的黑客行为都和编码有关,用编码后的代码来通过一些简单的过滤,如下js代码:

    var a = '畅哥';
    \a = 'stauren.net';
    alert(a);

看出来了吧。
编码工具http://stauren.net/lab
  评论这张
 
阅读(804)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018