V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
darluc
V2EX  ›  JavaScript

小议 Javascript 中的 UTF-8

  •  2
     
  •   darluc · 2016-12-30 02:56:05 +08:00 · 2696 次点击
    这是一个创建于 2913 天前的主题,其中的信息可能已经有所发展或是发生改变。

    直接去读原文

    在 Javascript 中处理编码问题有时会令人烦恼。我最近一次遭遇就是在使用浏览器的 atobbtoa 函数时。这些函数用于二进制字符串和 Base64 ASCII 码之间的转换。但是用它们处理 Unicode 字符串时就挂了:

    > btoa('\u0227');
    Error: INVALID_CHARACTER_ERR: DOM
    Exception 5
    

    MDN 上关于 btoa 的文档指出下面来自于 Johan Sundström 的函数可以解决 Unicode 问题:

    function encode_utf8(s) {
      return unescape(endcodeURIComponent(s));
    }
    
    function decode_utf8(s) {
      return decodeURIComponent(escape(s));
    }
    

    用上这些函数后,btoa 就能够给出期望的结果了:

    > btoa(encode_utf8('\u0227'));
    "yKc=="
    

    Johan 的函数能正常工作,只是让人觉得有点像是黑魔法。所以我决定研究一下 encode_utf8 函数的各个部分,搞明白它是如何工作的。

    encodeURIComponent

    encode_utf8 函数中先使用了 encodeURIComponentencodeURIComponent最近的 ECMAScript 规范中定义如下:

    encodeURIComponent 函数计算产生一个新的 URI ,其中的某些字符被替换为一个,二个,三个,或四个字节的 该字符 UTF-8 编码的转义序列。

    令我惊讶的是 encodeURIComponent 函数是与 UTF-8 编码绑定的。而不像其它语言的类似函数,编码方式是作为函数参数传入的。如果你本来就像使用 UTF-8 编码,当然没有问题,如果你想使用别的编码方式就不好使了(当然也有别的方法使用 ArrayBuffers 实现定长编码)。

    这儿有个例子是对 Unicode 字符 U+0227 (ȧ,小写的拉丁字母 A 顶上加个点)使用 encodeURIComponent 函数:

    > encodeURIComponent('\u0227');
    "%C8%A7"
    

    结果是一个百分号编码的字符串,每一段表示该字符串 UTF-8 编码的一个字节。查找 Unicode 字符 U+0227 的文档,我们可以验证该字符 UTF-8 编码的十六进制形式就是 0xC8 0xA7 。

    ECMAScript 的定义很模糊,它说 encodeURIComponent 会替换“某些字符”,却没说明这些字符。不过一个 快速的试验显示,任何大于 0x7E 的字符都会被编码,所以我觉得说任何非 ASCII 字符都会被 encocodeURIComponent 编码肯定不会错。由于百分号编码方式只使用了数字 0-9 ,字母 A-F 以及 ‘%’ 字符,可以保证输出结果一定是 ASCII 字符串。

    现在我们可以先暂停一下。btoa 函数所需要的就是 ASCII 字符串,encodeURIComponent 函数就已然够用了:

    > btoa(encodeURIComponent('\u0227'));
    "JUM4JUE3"
    

    不过这样做有一些缺陷。首先,输出的结果字符串与输入的字符串的二进制变了。这只是个原始字节的一个编码版本。这可能在与其它系统交互时造成困难和烦恼。

    第二个缺陷是,encodeURIComponent 函数生成了一个更长的字符串。单个 Unicode 字符采用 UTF-8 编码最多占用 4 个字节,经过 URI 编码后就能产生一个 12 个字符长的串。之后在经过 Base64 编码(能使字符串变得更长),最终输出的结果比输入的字符能长许多倍。为了解决字符串长度问题, Johan 找来了 unescape 函数。

    阅读完整文章

    4 条回复    2016-12-30 17:08:48 +08:00
    itkdqwzero
        1
    itkdqwzero  
       2016-12-30 08:06:45 +08:00 via Android
    赞一个先
    Vhc001
        2
    Vhc001  
       2016-12-30 10:34:34 +08:00
    非常感谢,真是受益匪浅!
    SuperMild
        3
    SuperMild  
       2016-12-30 10:35:14 +08:00 via iPad
    看到最后发现不是原创,是译文
    darluc
        4
    darluc  
    OP
       2016-12-30 17:08:48 +08:00
    @SuperMild 😅
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1125 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 18:25 · PVG 02:25 · LAX 10:25 · JFK 13:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.