JavaScript 如何正確處理 Unicode 編碼問題!

JavaScript 處理 Unicode 的方式至少能夠說是使人驚訝的。本文解釋了 JavaScript 中的 處理 Unicode 相關的痛點,提供了常見問題的解決方案,並解釋了ECMAScript 6 標準如何改進這種狀況。javascript

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!前端

Unicode 基礎知識

在深刻研究 JavaScript 以前,先解釋一下 Unicode 一些基礎知識,這樣在 Unicode 方面,咱們至少都瞭解一些。java

Unicode 是目前絕大多數程序使用的字符編碼,定義也很簡單,用一個 碼位(code point) 映射一個字符。碼位值的範圍是從 U+0000U+10FFFF,能夠表示超過 110 萬個字符。下面是一些字符與它們的碼位。git

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

碼位 一般被格式化爲十六進制數字,零填充至少四位數,格式爲 U +前綴github

Unicode 最前面的 65536 個字符位,稱爲 基本多文種平面(BMP-—Basic Multilingual Plane),又簡稱爲「零號平面」, plane 0),它的 碼位 範圍是從 U+0000U+FFFF。最多見的字符都放在這個平面上,這是 Unicode 最早定義和公佈的一個平面。正則表達式

剩下的字符都放在 輔助平面(Supplementary Plane)或者 星形平面(astral planes) ,碼位範圍從 U+010000 一直到 U+10FFFF,共 16 個輔助平面。算法

輔助平面內的碼位很容易識別:若是須要超過 4 個十六進制數字來表示碼位,那麼它就是一個輔助平面內的碼。express

如今對 Unicode 有了基本的瞭解,接下來看看它如何應用於 JavaScript 字符串。segmentfault

轉義序列

在谷歌控制檯輸入以下:後端

>> '\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 表示 A
1

>> 'A' == '\u0041'
true

>> 'B'.length // 碼位: U+0042 表示 B
1

>> 'B' == '\u0042'
true

在這些例子中,字符串的 length 屬性剛好反映了字符的個數。這是有道理的:若是咱們使用轉義序列來表示字符,很明顯,咱們只須要對每一個字符進行一次轉義。但狀況並不是老是如此!這裏有一個稍微不一樣的例子:

>> '𝐀'.length // 碼位: U+1D400 表示 Math Bold 字體大寫 A
2

>> '𝐀' == '\uD835\uDC00'
true

>> '𝐁'.length // 碼位: U+1D401 表示 Math Bold 字體大寫 B
2

>> '𝐁' == '\uD835\uDC01'
true

>> '💩'.length // U+1F4A9 PILE OF POO
2

>> '💩' == '\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 表示 A
1

>> countSymbols('𝐀') // 碼位: U+1D400 表示 Math Bold 字體大寫 A
1

>> countSymbols('💩') // U+1F4A9 PILE OF POO
1

找撞臉

考慮一下這個例子:

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

JavaScript告訴咱們,這些字符串是不一樣的,但視覺上,沒有辦法告訴咱們!這是怎麼回事?

圖片描述

JavaScript轉義工具 會告訴你,緣由以下:

>> 'ma\xF1ana' == 'man\u0303ana'
false

>> 'ma\xF1ana'.length
6

>> 'man\u0303ana'.length
7

第一個字符串包含碼位 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+00F1
6
>> countSymbolsPedantically('mañana') // U+006E + U+0303
6

爲了向後兼容 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('💩') // wtf
true

固然,你不但願手工編寫這些正則表達式,更不用說調試它們了。爲了生成像上面的一個正則表達式,可使用了一個名爲 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+1F4A9
true

>> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4A9}') // match U+1F4A9
true

>> /[💩-💫]/u.test('💩') // match U+1F4A9
true

>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCAA') // match U+1F4AA
true

>> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4AA}') // match U+1F4AA
true

>> /[💩-💫]/u.test('💪') // match U+1F4AA
true

>> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCAB') // match U+1F4AB
true

>> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4AB}') // match U+1F4AB
true

>> /[💩-💫]/u.test('💫') // match U+1F4AB
true

遺憾的是,這個解決方案不能向後兼容 ECMAScript 5 和更舊的環境。若是這是一個問題,應該使用 Regenerate 生成 es5兼容的正則表達式,處理輔助平面範圍內的字符:

>> regenerate().addRange('💩', '💫')
'\uD83D[\uDCA9-\uDCAB]'

>> /^\uD83D[\uDCA9-\uDCAB]$/.test('💩') // match U+1F4A9
true

>> /^\uD83D[\uDCA9-\uDCAB]$/.test('💪') // match U+1F4AA
true

>> /^\uD83D[\uDCA9-\uDCAB]$/.test('💫') // match U+1F4AB
true

實戰中的 bug 以及如何避免它們

這種行爲會致使許多問題。例如,Twitter 每條 tweet 容許 140 個字符,而它們的後端並不介意它是什麼類型的字符——是否爲輔助平面內的字符。但因爲JavaScript 計數在其網站上的某個時間點只是讀出字符串的長度,而不考慮代理項對,所以不可能輸入超過 70 個輔助平面內的字符。(這個bug已經修復。)

許多處理字符串的JavaScript庫不能正確地解析輔助平面內的字符。

例如,Countable.js 它沒有正確計算輔助平面內的字符。

Underscore.string 有一個 reverse 方法,它不處理組合標記或輔助平面內的字符。(改用 Missy Elliot 的算法)

它還錯誤地解碼輔助平面內的字符的 HTML 數字實體,例如 &#x1F4A9;。 許多其餘 HTML 實體轉換庫也存在相似的問題。(在修復這些錯誤以前,請考慮使用 he 代替全部 HTML 編碼/解碼需求。)

原文:

https://mathiasbynens.be/note...

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

你的點贊是我持續分享好東西的動力,歡迎點贊!

歡迎加入前端你們庭,裏面會常常分享一些技術資源。

clipboard.png

相關文章
相關標籤/搜索