• 原文:JavaScript 如何正确处理 Unicode 编码问题!
  • 作者:前端小智

Fundebug经授权转载,版权归原作者所有。

JavaScript 处理 Unicode 的方式至少可以说是令人惊讶的。本文解释了 JavaScript 中的 处理 Unicode 相关的痛点,提供了常见问题的解决方案,并解释了ECMAScript 6 标准如何改进这种情况。

Unicode 基础知识

在深入研究 JavaScript 之前,先解释一下 Unicode 一些基础知识,这样在 Unicode 方面,我们至少都了解一些。

Unicode 是目前绝大多数程序使用的字符编码,定义也很简单,用一个 码位(code point) 映射一个字符。码位值的范围是从 U+0000U+10FFFF,可以表示超过 110 万个字符。下面是一些字符与它们的码位。

  • A 的码位 U+0041
  • a 的码位 U+0061
  • © 的码位 U+00A9
  • ☃ 的码位 U+2603
  • ? 的码位 U+1F4A9

码位 通常被格式化为十六进制数字,零填充至少四位数,格式为 U +前缀

Unicode 最前面的 65536 个字符位,称为 基本多文种平面(BMP-—Basic Multilingual Plane),又简称为“零号平面”, plane 0),它的 码位 范围是从 U+0000U+FFFF。最常见的字符都放在这个平面上,这是 Unicode 最先定义和公布的一个平面。

剩下的字符都放在 **辅助平面(Supplementary Plane)**或者 星形平面(astral planes) ,码位范围从 U+010000 一直到 U+10FFFF,共 16 个辅助平面。

辅助平面内的码位很容易识别:如果需要超过 4 个十六进制数字来表示码位,那么它就是一个辅助平面内的码。

现在对 Unicode 有了基本的了解,接下来看看它如何应用于 JavaScript 字符串。

转义序列

在谷歌控制台输入如下:

    >> '\x41\x42\x43''ABC'>> '\x61\x62\x63''abc'

以下称为十六进制转义序列。它们由引用匹配码位的两个十六进制数字组成。例如,\x41 码位为 U+0041 表示大写字母 A。这些转义序列可用于 U+0000U+00FF 范围内的码位。

同样常见的还有以下类型的转义:

    >> '\u0041\u0042\u0043''ABC'>> 'I \u2661 JavaScript!''I ♡ JavaScript!

这些被称为 Unicode转义序列。它们由表示码位的 4 个十六进制数字组成。例如,\u2661 表示码位为 \U+2661 表示一个心。这些转义序列可以用于 U+0000U+FFFF 范围内的码位,即整个基本平面。

但是其他的所有辅助平面呢? 我们需要 4 个以上的十六进制数字来表示它们的码位,那么如何转义它们呢?

在 ECMAScript 6中,这很简单,因为它引入了一种新的转义序列: Unicode 码位转义。例如:

    >> '\u{41}\u{42}\u{43}''ABC'>> '\u{1F4A9}''?' // U+1F4A9 PILE OF POO

在大括号之间可以使用最多 6 个十六进制数字,这足以表示所有 Unicode 码位。因此,通过使用这种类型的转义序列,可以基于其代码位轻松转义任何 Unicode 码位。

为了向后兼容 ECMAScript 5 和更旧的环境,不幸的解决方案是使用代理对:

    >> '\uD83D\uDCA9''?' // U+1F4A9 PILE OF POO

在这种情况下,每个转义表示代理项一半的码位。两个代理项就组成一个辅助码位。

注意,代理项对码位与原始码位全不同。有公式可以根据给定的辅助码位来计算代理项对码位,反之亦然——根据代理对计算原始辅助代码位。

辅助平面(Supplementary Planes)中的码位,在 UTF-16 中被编码为一对16 比特长的码元(即32bit,4Bytes),称作代理对(surrogate pair),具体方法是:

  • 码位减去 0x10000,得到的值的范围为 20 比特长的 0..0xFFFFF.
  • 高位的 10 比特的值(值的范围为 0..0x3FF)被加上 0xD800 得到第一个码元或称作高位代理
  • 低位的 10 比特的值(值的范围也是 0..0x3FF)被加上 0xDC00 得到第二个码元或称作低位代理(low surrogate),现在值的范围是 0xDC00..0xDFFF.

使用代理对,所有辅助平面中的码位(即从 U+010000U+10FFFF )都可以表示,但是使用一个转义来表示基本平面的码位,以及使用两个转义来表示辅助平面中的码位,整个概念是令人困惑的,并且会产生许多恼人的后果。

使用 JavaScript 字符串方法来计算字符长度

例如,假设你想要计算给定字符串中的字符个数。你会怎么做呢?

首先想到可能是使用 length 属性。

    >> 'A'.length // 码位: U+0041 表示 A1>> 'A' == '\u0041'true>> 'B'.length // 码位: U+0042 表示 B1>> 'B' == '\u0042'true

在这些例子中,字符串的 length 属性恰好反映了字符的个数。这是有道理的:如果我们使用转义序列来表示字符,很明显,我们只需要对每个字符进行一次转义。但情况并非总是如此!这里有一个稍微不同的例子:

    >> '?'.length // 码位: U+1D400 表示 Math Bold 字体大写 A2>> '?' == '\uD835\uDC00'true>> '?'.length // 码位: U+1D401 表示 Math Bold 字体大写 B2>> '?' == '\uD835\uDC01'true>> '?'.length // U+1F4A9 PILE OF POO2>> '?' == '\uD83D\uDCA9'true

在内部,JavaScript 将辅助平面内的字符表示为代理对,并将单独的代理对部分开为单独的 “字符”。如果仅使用 ECMAScript 5 兼容转义序列来表示字符,将看到每个辅助平面内的字符都需要两个转义。这是令人困惑的,因为人们通常用 Unicode 字符或图形来代替。

计算辅助平面内的字符个数

回到这个问题:如何准确地计算 JavaScript 字符串中的字符个数 ? 诀窍就是如何正确地解析代理对,并且只将每对代理对作为一个字符计数。你可以这样使用:

    var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;function countSymbols(string) {return string// Replace every surrogate pair with a BMP symbol..replace(regexAstralSymbols, '_')// …and *then* get the length..length;}

或者,如果你使用 Punycode.js,利用它的实用方法在 JavaScript 字符串和 Unicode 码位之间进行转换。decode 方法接受一个字符串并返回一个 Unicode 编码位数组;每个字符对应一项。

    function countSymbols(string) {return punycode.ucs2.decode(string).length;}

在 ES6 中,可以使用 Array.from 来做类似的事情,它使用字符串的迭代器将其拆分为一个字符串数组,每个字符串数组包含一个字符:

    function countSymbols(string) {return Array.from(string).length;}

或者,使用解构运算符 ... :

    function countSymbols(string) {return [...string].length;}

使用这些实现,我们现在可以正确地计算码位,这将导致更准确的结果:

    >> countSymbols('A') // 码位:U+0041 表示 A1>> countSymbols('?') // 码位: U+1D400 表示 Math Bold 字体大写 A1>> countSymbols('?') // U+1F4A9 PILE OF POO1

找撞脸

考虑一下这个例子:

    >> 'mañana' == 'mañana'false

JavaScript告诉我们,这些字符串是不同的,但视觉上,没有办法告诉我们!这是怎么回事?

JavaScript转义工具会告诉你,原因如下:

    >> 'ma\xF1ana' == 'man\u0303ana'false>> 'ma\xF1ana'.length6>> 'man\u0303ana'.length7

第一个字符串包含码位 U+00F1 表示字母 n 和 n 头上波浪号,而第二个字符串使用两个单独的码位(U+006E表示字母 n 和 U+0303 表示波浪号)来创建相同的字符。这就解释了为什么它们的长度不同。

然而,如果我们想用我们习惯的方式来计算这些字符串中的字符个数,我们希望这两个字符串的长度都为 6,因为这是每个字符串中可视可区分的字符的个数。要怎样才能做到这一点呢?

在ECMAScript 6 中,解决方案相当简单:

    function countSymbolsPedantically(string) {// Unicode Normalization, NFC form, to account for lookalikes:var normalized = string.normalize('NFC');// Account for astral symbols / surrogates, just like we did before:return punycode.ucs2.decode(normalized).length;}

String.prototype 上的 normalize 方法执行 Unicode规范化,这解释了这些差异。 如果有一个码位表示与另一个码位后跟组合标记相同的字符,则会将其标准化为单个码位形式。

    >> countSymbolsPedantically('mañana') // U+00F16>> countSymbolsPedantically('mañana') // U+006E + U+03036

为了向后兼容 ECMAScript5 和旧环境,可以使用 String.prototype.normalize polyfill

计算其他组合标记

然而,上述方案仍然不是完美的——应用多个组合标记的码位总是导致单个可视字符,但可能没有 normalize 的形式,在这种情况下,normalize 是没有帮助。例如:

    >> 'q\u0307\u0323'.normalize('NFC') // `q̣̇`'q\u0307\u0323'>> countSymbolsPedantically('q\u0307\u0323')3 // not 1>> countSymbolsPedantically('Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞')74 // not 6

如果需要更精确的解决方案,可以使用正则表达式从输入字符串中删除任何组合标记。

    //  将下面的正则表达式替换为经过转换的等效表达式,以使其在旧环境中工作var regexSymbolWithCombiningMarks = /(\P{Mark})(\p{Mark}+)/gu;function countSymbolsIgnoringCombiningMarks(string) {// 删除任何组合字符,只留下它们所属的字符:var stripped = string.replace(regexSymbolWithCombiningMarks, function($0, symbol, combiningMarks) {return symbol;});return punycode.ucs2.decode(stripped).length;}

此函数删除任何组合标记,只留下它们所属的字符。任何不匹配的组合标记(在字符串开头)都保持不变。这个解决方案甚至可以在 ECMAScript3 环境中工作,并且它提供了迄今为止最准确的结果:

    >> countSymbolsIgnoringCombiningMarks('q\u0307\u0323')1>> countSymbolsIgnoringCombiningMarks('Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞')6

计算其他类型的图形集群

上面的算法仍然是一个简化—它还是无法正确计算像这样的字符:நி,汉语言由连体的 Jamo 组成,如 깍, 表情字符序列,如 ?‍?‍?‍? ((? U+200D + ? U+200D + ? + U+200D + ?)或其他类似字符。

Unicode 文本分段上的 Unicode 标准附件#29 描述了用于确定字形簇边界的算法。 对于适用于所有 Unicode脚本的完全准确的解决方案,请在 JavaScript 中实现此算法,然后将每个字形集群计为单个字符。 有人建议将Intl.Segmenter(一种文本分段API)添加到ECMAScript中。

JavaScript 中字符串反转

下面是一个类似问题的示例:在JavaScript中反转字符串。这能有多难,对吧? 解决这个问题的一个常见的、非常简单的方法是:

    function reverse(string) {return string.split('').reverse().join('');}

它似乎在很多情况下都很有效:

    >> reverse('abc')'cba'>> reverse('mañana') // U+00F1'anañam'

然而,它完全打乱了包含组合标记或位于辅助平面字符的字符串。

    >> reverse('mañana') // U+006E + U+0303'anãnam' // note: the `~` is now applied to the `a` instead of the `n`>> reverse('?') // U+1F4A9'��' // `'\uDCA9\uD83D'`, the surrogate pair for `?` in the wrong order

要在 ES6 中正确反转位于辅助平面字符,字符串迭代器可以与 Array.from 结合使用:

    function reverse(string) {return Array.from(string).reverse().join('');}

但是,这仍然不能解决组合标记的问题。

幸运的是,一位名叫 Missy Elliot 的聪明的计算机科学家提出了一个防弹算法来解释这些问题。它看上去像这样:

我把丁字裤放下,翻转,然后倒过来。我把丁字裤放下,翻转,然后倒过来。

事实上:通过将任何组合标记的位置与它们所属的字符交换,以及在进一步处理字符串之前反转任何代理对,可以成功避免问题。

    // 使用库 Esrever (https://mths.be/esrever)>> esrever.reverse('mañana') // U+006E + U+0303'anañam'>> esrever.reverse('?') // U+1F4A9'?' // U+1F4A9

字符串方法中的 Unicode 的问题

这种行为也会影响其他字符串方法。

将码位转转换为字符

String.fromCharCode 可以将一个码位转换为字符。 但它只适用于 BMP 范围内的码位 ( 即从 U+0000U+FFFF)。如果将它用于转换超过 BMP 平面外的码位 ,将获得意想不到的结果。

    >> String.fromCharCode(0x0041) // U+0041'A' // U+0041>> String.fromCharCode(0x1F4A9) // U+1F4A9'' // U+F4A9, not U+1F4A9

唯一的解决方法是自己计算代理项一半的码位,并将它们作为单独的参数传递。

    >> String.fromCharCode(0xD83D, 0xDCA9)'?' // U+1F4A9

如果不想计算代理项的一半,可以使用 Punycode.js 的实用方法:

    >> punycode.ucs2.encode([ 0x1F4A9 ])'?' // U+1F4A9

幸运的是,ECMAScript 6 引入了 String.fromCodePoint(codePoint),它可以位于基本平面外的码位的字符。它可以用于任何 Unicode 编码点,即从 U+000000U+10FFFF

    >> String.fromCodePoint(0x1F4A9)'?' // U+1F4A9

为了向后兼容ECMAScript 5 和更旧的环境,使用 String.fromCodePoint() polyfill

从字符串中获取字符

如果使用 String.prototype.charAt(position) 来检索包含字符串中的第一个字符,则只能获得第一个代理项而不是整个字符。

    >> '?'.charAt(0) // U+1F4A9'\uD83D' // U+D83D, i.e. the first surrogate half for U+1F4A9

有人提议在 ECMAScript 7 中引入 String.prototype.at(position)。它类似于charAt,只不过它尽可能地处理完整的字符而不是代理项的一半。

    >> '?'.at(0) // U+1F4A9'?' // U+1F4A9

为了向后兼容 ECMAScript 5 和更旧的环境,可以使用 String.prototype.at() polyfill/prollyfill。

从字符串中获取码位

类似地,如果使用 String.prototype.charCodeAt(position) 检索字符串中第一个字符的码位,将获得第一个代理项的码位,而不是 poo 字符堆的码位。

    >> '?'.charCodeAt(0)0xD83D

幸运的是,ECMAScript 6 引入了 String.prototype.codePointAt(position),它类似于 charCodeAt,只不过它尽可能处理完整的字符而不是代理项的一半。

    >> '?'.codePointAt(0)0x1F4A9

为了向后兼容 ECMAScript 5 和更旧的环境,使用 String.prototype.codePointAt()_polyfill。

遍历字符串中的所有字符

假设想要循环字符串中的每个字符,并对每个单独的字符执行一些操作。

在 ECMAScript 5 中,你必须编写大量的样板代码来判断代理对:

    function getSymbols(string) {var index = 0;var length = string.length;var output = [];for (; index < length - 1; ++index) {var charCode = string.charCodeAt(index);if (charCode >= 0xD800 && charCode <= 0xDBFF) {charCode = string.charCodeAt(index + 1);if (charCode >= 0xDC00 && charCode <= 0xDFFF) {output.push(string.slice(index, index + 2));++index;continue;}}output.push(string.charAt(index));}output.push(string.charAt(index));return output;}var symbols = getSymbols('?');symbols.forEach(function(symbol) {console.log(symbol == '?');});

或者可以使用正则表达式,如 var regexCodePoint = /[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g; 并迭代匹配

在 ECMAScript 6中,你可以简单地使用 for…of。字符串迭代器处理整个字符,而不是代理对。

    for (const symbol of '?') {console.log(symbol == '?');}

不幸的是,没有办法对它进行填充,因为 for…of 是一个语法级结构。

其他问题

此行为会影响几乎所有字符串方法,包括此处未明确提及的方法(如 String.prototype.substringString.prototype.slice 等),因此在使用它们时要小心。

正则表达式中的 Unicode 问题

匹配码位和 Unicode 标量值

正则表达式中的点运算符(.)只匹配一个“字符”, 但是由于JavaScript将代理半部分公开为单独的 “字符”,所以它永远不会匹配位于辅助平面上的字符。

    >> /foo.bar/.test('foo?bar')false

让我们思考一下,我们可以使用什么正则表达式来匹配任何 Unicode字符? 什么好主意吗? 如下所示的,. 这w个是不够的,因为它不匹配换行符或整个位于辅助平面上的字符。

    >> /^.$/.test('?')false

为了正确匹配换行符,我们可以使用 [\s\S] 来代替,但这仍然不能匹配整个位于辅助平面上的字符。

    >> /^[\s\S]$/.test('?')false

事实证明,匹配任何 Unicode 编码点的正则表达式一点也不简单:

    >> /[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/.test('?') // wtftrue

当然,你不希望手工编写这些正则表达式,更不用说调试它们了。为了生成像上面的一个正则表达式,可以使用了一个名为 Regenerate的库,它可以根据码位或字符列表轻松地创建正则表达式:

    >> regenerate().addRange(0x0, 0x10FFFF).toString()'[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]'

从左到右,这个正则表达式匹配BMP字符、代理项对或单个代理项。

虽然在 JavaScript 字符串中技术上允许使用单独的代理,但是它们本身并不映射到任何字符,因此应该避免使用。术语 Unicode标量值 指除代理码位之外的所有码位。下面是一个正则表达式,它匹配任何 Unicode 标量值:

    >> regenerate().addRange(0x0, 0x10FFFF)     // all Unicode code points.removeRange(0xD800, 0xDBFF) // minus high surrogates.removeRange(0xDC00, 0xDFFF) // minus low surrogates.toRegExp()/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/

Regenerate作为构建脚本的一部分使用的,用于创建复杂的正则表达式,同时仍然保持生成这些表达式的脚本的可读性和易于维护。

ECMAScript 6 为正则表达式引入一个 u 标志,它会使用 . 操作符匹配整个码位,而不是代理项的一半。

    >> /foo.bar/.test('foo?bar')false>> /foo.bar/u.test('foo?bar')true

注意 . 操作符仍然不会匹配换行符,设置 u 标志时,. 操作符等效于以下向后兼容的正则表达式模式:

    >> regenerate().addRange(0x0, 0x10FFFF) // all Unicode code points.remove(  // minus `LineTerminator`s (https://ecma-international.org/ecma-262/5.1/#sec-7.3):0x000A, // Line Feed <LF>0x000D, // Carriage Return <CR>0x2028, // Line Separator <LS>0x2029  // Paragraph Separator <PS>).toString();'[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]'>> /foo(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])bar/u.test('foo?bar')true

位于辅助平面码位上的字符

考虑到 /[a-c]/ 匹配任何字符从 码位为 U+0061 的字母 a 到 码位为 U+0063 的字母 c,似乎/[?-?]/ 会匹配码位 U+1F4A9 到码位 U+1F4AB,然而事实并非如此:

    >> /[?-?]/SyntaxError: Invalid regular expression: Range out of order in character class

发生这种情况的原因是,正则表达式等价于:

    >> /[\uD83D\uDCA9-\uD83D\uDCAB]/SyntaxError: Invalid regular expression: Range out of order in character class

事实证明,不像我们想的那样匹配码位 U+1F4A9 到码位 U+1F4AB,而是匹配正则表达式:

  • U+D83D(高代理位)

  • U+DCA9U+D83D 的范围(无效,因为起始码位大于标记范围结束的码位)

  • U+DCAB(低代理位)

    >> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCA9') // match U+1F4A9true>> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4A9}') // match U+1F4A9true>> /[?-?]/u.test('?') // match U+1F4A9true>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCAA') // match U+1F4AAtrue>> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4AA}') // match U+1F4AAtrue>> /[?-?]/u.test('?') // match U+1F4AAtrue>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCAB') // match U+1F4ABtrue>> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4AB}') // match U+1F4ABtrue>> /[?-?]/u.test('?') // match U+1F4ABtrue

遗憾的是,这个解决方案不能向后兼容 ECMAScript 5 和更旧的环境。如果这是一个问题,应该使用 Regenerate 生成 es5兼容的正则表达式,处理辅助平面范围内的字符:

    >> regenerate().addRange('?', '?')'\uD83D[\uDCA9-\uDCAB]'>> /^\uD83D[\uDCA9-\uDCAB]$/.test('?') // match U+1F4A9true>> /^\uD83D[\uDCA9-\uDCAB]$/.test('?') // match U+1F4AAtrue>> /^\uD83D[\uDCA9-\uDCAB]$/.test('?') // match U+1F4ABtrue

实战中的 bug 以及如何避免它们

这种行为会导致许多问题。例如,Twitter 每条 tweet 允许 140 个字符,而它们的后端并不介意它是什么类型的字符——是否为辅助平面内的字符。但由于JavaScript 计数在其网站上的某个时间点只是读出字符串的长度,而不考虑代理项对,因此不可能输入超过 70 个辅助平面内的字符。(这个bug已经修复。)

许多处理字符串的JavaScript库不能正确地解析辅助平面内的字符。

例如,Countable.js 它没有正确计算辅助平面内的字符。

Underscore.string 有一个 reverse 方法,它不处理组合标记或辅助平面内的字符。(改用 Missy Elliot 的算法)

它还错误地解码辅助平面内的字符的 HTML 数字实体,例如

JavaScript如何正确处理Unicode编码问题相关推荐

  1. JavaScript中字符串与Unicode编码的互相转换

    JavaScript中字符串与Unicode编码的互相转换 这段代码演示了JavaScript中字符串与Unicode编码的转换: // 为了控制台的演示方便, 变量没有添加 var 定义 // 实际 ...

  2. JavaScript中Unicode编码和中文相互转换

    Unicode转换 简介 官方中文名称为统一码,也译名为万国码.国际码.单一码,是计算机科学领域的业界标准.它整理.编码了世界上大部分的文字系统,使得电脑可以用更为简单的方式来呈现和处理文字. Uni ...

  3. js与html编码不同,js与html中unicode编码的使用

    [转]javascript和html中unicode编码和字符转义的详解 不是十分理解unicode和html转义的情况下,可能会误用,所以下面会对它们再做比较容易理解的解释: 1.html中的转义: ...

  4. python无法打印unicode编码_【整理】Python中实际上已经得到了正确的Unicode或某种编码的字符,但是看起来或打印出来却是乱码...

    [背景] Python中的字符编码,其实的确有点复杂. 再加上,不同的开发环境和工具中,显示的逻辑和效果又不太相同,尤其是,中文的,初级用户,最常遇到的: (1)在Python自带的IDE:IDLE中 ...

  5. JavaScript汉字Unicode编码相互转换

    关键字:JavaScript 汉字转换为Unicode编码 Unicode编码转换为汉字 JavaScript 汉字Unicode编码相互转换代码. JavaScript库 -JavaScript v ...

  6. 正确理解UNICODE UTF-8等编码方式

    [color=darkred]转载出处:[/color][url]http://blog.csdn.net/polarman/archive/2007/04/30/1593159.aspx[/url] ...

  7. JavaScript之Unicode编码转换小工具

    经常浏览别人的网页时,会看到重要的信息就被加密混淆了,常见的就类似\\u4faf\\u6587\\u658c .\u4faf\u6587\u658c.侯文斌等转换的字符串,相信正常人直接不会看懂的(火 ...

  8. 尚硅谷JavaScript学习笔记_P22_P23关系运算符和Unicode编码

    <!DOCTYPE html> <html><head><meta charset="UTF-8"><title>< ...

  9. JavaScript为unicode编码转换为中文

    代码laycode - v1.1 关于这样的数据转换为中文问题,常用的以下方法. 1. eval解析或new Function("'+ str +'")() str = eval( ...

最新文章

  1. 发送请求获取响应内容(c#)
  2. PaddleDetection支持的数据格式
  3. 曲苑杂坛--修改数据库名和文件组名
  4. CheckBoxList 全选(jquery版本)
  5. java线程的创建与执行_Java多线程的创建和运行
  6. 获取roi内的xld_提升ROI是王道,这几种推广获客渠道与形式你还不知道?
  7. 用反射实现简单的框架
  8. Google深度学习TensorFlow最好的入门文章!
  9. 如何使用3DMax球形化命令
  10. 电脑ps计算机磨皮,入门:PS最简单的磨皮方法
  11. 微信营销与微博营销的区别
  12. 推荐一本好书《 Java程序员 上班那点事儿》
  13. 微信公众平台 微接口 接口100 API100 接口大全(转) 开发微信功能简便了
  14. 计组作业【题目记录】
  15. (转载)c# winform comboBox的常用一些属性和用法
  16. java.lang.OutOfMemoryError: Java heap space解决办法
  17. 毫米波雷达(mmWave)基本原理
  18. Java实现 蓝桥杯 算法训练 My Bad(暴力)
  19. 迅雷看看看电影,画面是绿色的,不能看
  20. 《编译原理教程(第四版)胡元义》第二章 词法分析

热门文章

  1. Camtasia Studio2023喀秋莎免费实用的屏幕录像工具
  2. 小米正式宣布:这种手机以后买不到了…
  3. 【啃书C++Primer5】-c++有些理论基础需要了解,墙裂建议看看原书,有太多细节需要注意了
  4. 微型计算机原理第三版考试,微机原理试题及答案
  5. 投资因子(Investment factor)——投资组合分析(EAP.portfolio_analysis)
  6. 爬虫配套学习-前端学习笔记23-CSS相关
  7. 飞腾CPU体系结构之虚拟地址
  8. 《Python编程无师自通》第11章 练习
  9. 退役大学生的IT求学之路
  10. Babylonjs实现模型围栏效果