精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

新聞 前端
JavaScript 處理 Unicode 的方式至少可以說是令人驚訝的。本文解釋了 JavaScript 中的 處理 Unicode 相關的痛點,提供了常見問題的解決方案,并解釋了ECMAScript 6 標準如何改進這種情況。

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

Unicode 基礎知識

在深入研究 JavaScript 之前,先解釋一下 Unicode 一些基礎知識,這樣在 Unicode 方面,我們至少都了解一些。

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

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

碼位通常被格式化為十六進制數字,零填充至少四位數,格式為 U +前綴 。

Unicode 最前面的 65536 個字符位,稱為 基本多文種平面(BMP-—Basic Multilingual Plane) ,又簡稱為“ 零號平面 ”, plane 0),它的 碼位 范圍是從 U+0000 到 U+FFFF 。最常見的字符都放在這個平面上,這是 Unicode ***定義和公布的一個平面。

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

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

現在對 Unicode 有了基本的了解,接下來看看它如何應用于 JavaScript 字符串。

轉義序列

在谷歌控制臺輸入如下:

  1. >> '\x41\x42\x43' 
  2. 'ABC' 
  3.  
  4. >> '\x61\x62\x63' 
  5. 'abc' 

以下稱為十六進制轉義序列。它們由引用匹配碼位的兩個十六進制數字組成。例如, \x41 碼位為 U+0041 表示大寫字母 A。這些轉義序列可用于 U+0000 到 U+00FF 范圍內的碼位。

同樣常見的還有以下類型的轉義:

  1. >> '\u0041\u0042\u0043' 
  2. 'ABC' 
  3.  
  4. >> 'I \u2661 JavaScript!' 
  5. 'I ♡ JavaScript!' 

這些被稱為 Unicode轉義序列 。它們由表示碼位的 4 個十六進制數字組成。例如, \u2661 表示碼位為 \U+2661 表示一個心。這些轉義序列可以用于 U+0000 到 U+FFFF 范圍內的碼位,即整個基本平面。

但是其他的所有輔助平面呢? 我們需要 4 個以上的十六進制數字來表示它們的碼位,那么如何轉義它們呢?

在 ECMAScript 6中,這很簡單,因為它引入了一種新的轉義序列: Unicode 碼位轉義 。例如:

  1. >> '\u{41}\u{42}\u{43}' 
  2. 'ABC' 
  3.  
  4. >> '\u{1F4A9}' 
  5. ':hankey:' // U+1F4A9 PILE OF POO 

在大括號之間可以使用最多 6 個十六進制數字,這足以表示所有 Unicode 碼位。因此,通過使用這種類型的轉義序列,可以基于其代碼位輕松轉義任何 Unicode 碼位。

為了向后兼容 ECMAScript 5 和更舊的環境,不幸的解決方案是使用代理對:

  1. >> '\uD83D\uDCA9' 
  2. ':hankey:' // 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+010000 到 U+10FFFF )都可以表示,但是使用一個轉義來表示基本平面的碼位,以及使用兩個轉義來表示輔助平面中的碼位,整個概念是令人困惑的,并且會產生許多惱人的后果。

使用 JavaScript 字符串方法來計算字符長度

例如,假設你想要計算給定字符串中的字符個數。你會怎么做呢?

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

  1. >> 'A'.length // 碼位: U+0041 表示 A 
  2.  
  3. >> 'A' == '\u0041' 
  4. true 
  5.  
  6. >> 'B'.length // 碼位: U+0042 表示 B 
  7.  
  8. >> 'B' == '\u0042' 
  9. true 

在這些例子中,字符串的 length 屬性恰好反映了字符的個數。這是有道理的:如果我們使用轉義序列來表示字符,很明顯,我們只需要對每個字符進行一次轉義。但情況并非總是如此!這里有一個稍微不同的例子:

  1. >> '  '.length // 碼位: U+1D400 表示 Math Bold 字體大寫 A 
  2.  
  3. >> '  ' == '\uD835\uDC00' 
  4. true 
  5.  
  6. >> '  '.length // 碼位: U+1D401 表示 Math Bold 字體大寫 B 
  7.  
  8. >> '  ' == '\uD835\uDC01' 
  9. true 
  10.  
  11. >> ':hankey:'.length // U+1F4A9 PILE OF POO 
  12.  
  13. >> ':hankey:' == '\uD83D\uDCA9' 
  14. true 

在內部,JavaScript 將輔助平面內的字符表示為代理對,并將單獨的代理對部分開為單獨的 “字符”。如果僅使用 ECMAScript 5 兼容轉義序列來表示字符,將看到每個輔助平面內的字符都需要兩個轉義。這是令人困惑的,因為人們通常用 Unicode 字符或圖形來代替。

計算輔助平面內的字符個數

回到這個問題:如何準確地計算 JavaScript 字符串中的字符個數 ? 訣竅就是如何正確地解析代理對,并且只將每對代理對作為一個字符計數。你可以這樣使用:

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

或者,如果你使用 Punycode.js ,利用它的實用方法在 JavaScript 字符串和 Unicode 碼位之間進行轉換。 decode 方法接受一個字符串并返回一個 Unicode 編碼位數組;每個字符對應一項。

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

在 ES6 中,可以使用 Array.from 來做類似的事情,它使用字符串的迭代器將其拆分為一個字符串數組,每個字符串數組包含一個字符:

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

或者,使用解構運算符 ...

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

使用這些實現,我們現在可以正確地計算碼位,這將導致更準確的結果:

  1. >> countSymbols('A'// 碼位:U+0041 表示 A 
  2.  
  3. >> countSymbols('  '// 碼位: U+1D400 表示 Math Bold 字體大寫 A 
  4.  
  5. >> countSymbols(':hankey:'// U+1F4A9 PILE OF POO 

找撞臉

考慮一下這個例子:

  1. >> 'mañana' == 'mañana' 
  2. false 

JavaScript告訴我們,這些字符串是不同的,但視覺上,沒有辦法告訴我們!這是怎么回事?

JavaScript轉義工具 會告訴你,原因如下:

  1. >> 'ma\xF1ana' == 'man\u0303ana' 
  2. false 
  3.  
  4. >> 'ma\xF1ana'.length 
  5.  
  6. >> 'man\u0303ana'.length 

***個字符串包含碼位 U+00F1 表示字母 n 和 n 頭上波浪號,而第二個字符串使用兩個單獨的碼位( U+006E 表示字母 n 和 U+0303 表示波浪號)來創建相同的字符。這就解釋了為什么它們的長度不同。

然而,如果我們想用我們習慣的方式來計算這些字符串中的字符個數,我們希望這兩個字符串的長度都為 6,因為這是每個字符串中可視可區分的字符的個數。要怎樣才能做到這一點呢?

在ECMAScript 6 中,解決方案相當簡單:

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

String.prototype 上的 normalize 方法執行 Unicode規范 化,這解釋了這些差異。 如果有一個碼位表示與另一個碼位后跟組合標記相同的字符,則會將其標準化為單個碼位形式。

  1. >> countSymbolsPedantically('mañana'// U+00F1 
  2. >> countSymbolsPedantically('mañana'// U+006E + U+0303 

為了向后兼容 ECMAScript5 和舊環境,可以使用 String.prototype.normalize polyfill 。

計算其他組合標記

然而,上述方案仍然不是***的——應用多個組合標記的碼位總是導致單個可視字符,但可能沒有 normalize 的形式,在這種情況下,normalize 是沒有幫助。例如:

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

如果需要更精確的解決方案,可以使用正則表達式從輸入字符串中刪除任何組合標記。

  1. //  將下面的正則表達式替換為經過轉換的等效表達式,以使其在舊環境中工作 
  2.  
  3. var regexSymbolWithCombiningMarks = /(\P{Mark})(\p{Mark}+)/gu; 
  4.  
  5. function countSymbolsIgnoringCombiningMarks(string) { 
  6.     // 刪除任何組合字符,只留下它們所屬的字符: 
  7.     var stripped = string.replace(regexSymbolWithCombiningMarks, function($0, symbol, combiningMarks) { 
  8.         return symbol; 
  9.     }); 
  10.      
  11.     return punycode.ucs2.decode(stripped).length; 

此函數刪除任何組合標記,只留下它們所屬的字符。任何不匹配的組合標記(在字符串開頭)都保持不變。這個解決方案甚至可以在 ECMAScript3 環境中工作,并且它提供了迄今為止最準確的結果:

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

計算其他類型的圖形集群

上面的算法仍然是一個簡化—它還是無法正確計算像這樣的字符:நி,漢語言由連體的 Jamo 組成,如 깍, 表情字符序列,如 :man:‍:woman:‍:girl:‍:boy: ((:man: U+200D + :woman: U+200D + :girl: + U+200D + :boy:)或其他類似字符。

Unicode 文本分段上的 Unicode 標準附件#29 描述了用于確定字形簇邊界的算法。 對于適用于所有 Unicode腳本的完全準確的解決方案 ,請在 JavaScript 中實現此算法,然后將每個字形集群計為單個字符。 有人建議將Intl.Segmenter(一種文本分段API)添加到ECMAScript中 。

JavaScript 中字符串反轉

下面是一個類似問題的示例:在JavaScript中反轉字符串。這能有多難,對吧? 解決這個問題的一個常見的、非常簡單的方法是:

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

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

  1. >> reverse('abc'
  2. 'cba' 
  3.  
  4. >> reverse('mañana'// U+00F1 
  5. 'anañam' 

然而,它完全打亂了包含組合標記或位于輔助平面字符的字符串。

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

要在 ES6 中正確反轉位于輔助平面字符,字符串迭代器可以與 Array.from 結合使用:

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

但是,這仍然不能解決組合標記的問題。

幸運的是,一位名叫 Missy Elliot 的聰明的計算機科學家提出了一個 防彈算法 來解釋這些問題。它看上去像這樣:

我把丁字褲放下,翻轉,然后倒過來。我把丁字褲放下,翻轉,然后倒過來。

事實上:通過將任何組合標記的位置與它們所屬的字符交換,以及在進一步處理字符串之前反轉任何代理對,可以成功避免問題。

  1. // 使用庫 Esrever (https://mths.be/esrever) 
  2.  
  3. >> esrever.reverse('mañana'// U+006E + U+0303 
  4. 'anañam' 
  5.  
  6. >> esrever.reverse(':hankey:'// U+1F4A9 
  7. ':hankey:' // U+1F4A9 

字符串方法中的 Unicode 的問題

這種行為也會影響其他字符串方法。

將碼位轉轉換為字符

String.fromCharCode 可以將一個碼位轉換為字符。 但它只適用于 BMP 范圍內的碼位 ( 即從 U+0000 到 U+FFFF )。如果將它用于轉換超過 BMP 平面外的碼位 ,將獲得意想不到的結果。

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

唯一的解決方法是自己計算代理項一半的碼位,并將它們作為單獨的參數傳遞。

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

如果不想計算代理項的一半,可以使用 Punycode.js 的實用方法:

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

幸運的是,ECMAScript 6 引入了 String.fromCodePoint(codePoint) ,它可以位于基本平面外的碼位的字符。它可以用于任何 Unicode 編碼點,即從 U+000000 到 U+10FFFF 。

  1. >> String.fromCodePoint(0x1F4A9) 
  2. ':hankey:' // U+1F4A9 

為了向后兼容ECMAScript 5 和更舊的環境,使用 String.fromCodePoint() polyfill 。

從字符串中獲取字符

如果使用 String.prototype.charAt(position) 來檢索包含字符串中的***個字符,則只能獲得***個代理項而不是整個字符。

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

有人提議在 ECMAScript 7 中引入 String.prototype.at(position) 。它類似于 charAt ,只不過它盡可能地處理完整的字符而不是代理項的一半。

  1. >> ':hankey:'.at(0) // U+1F4A9 
  2. ':hankey:' // U+1F4A9 

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

從字符串中獲取碼位

類似地,如果使用 String.prototype.charCodeAt(position) 檢索字符串中***個字符的碼位,將獲得***個代理項的碼位,而不是 poo 字符堆的碼位。

  1. >> ':hankey:'.charCodeAt(0) 
  2. 0xD83D 

幸運的是,ECMAScript 6 引入了 String.prototype.codePointAt(position) ,它類似于 charCodeAt ,只不過它盡可能處理完整的字符而不是代理項的一半。

  1. >> ':hankey:'.codePointAt(0) 
  2. 0x1F4A9 

為了向后兼容 ECMAScript 5 和更舊的環境,使用 String.prototype.codePointAt()_polyfill。

遍歷字符串中的所有字符

假設想要循環字符串中的每個字符,并對每個單獨的字符執行一些操作。

在 ECMAScript 5 中,你必須編寫大量的樣板代碼來判斷代理對。

  1. function getSymbols(string) { 
  2.     var index = 0; 
  3.     var length = string.length; 
  4.     var output = []; 
  5.     for (; index < length - 1; ++index) { 
  6.         var charCode = string.charCodeAt(index); 
  7.         if (charCode >= 0xD800 && charCode <= 0xDBFF) { 
  8.             charCode = string.charCodeAt(index + 1); 
  9.             if (charCode >= 0xDC00 && charCode <= 0xDFFF) { 
  10.                 output.push(string.slice(index, index + 2)); 
  11.                 ++index; 
  12.                 continue
  13.             } 
  14.         } 
  15.         output.push(string.charAt(index)); 
  16.     } 
  17.     output.push(string.charAt(index)); 
  18.     return output; 
  19.  
  20. var symbols = getSymbols(':hankey:'); 
  21. symbols.forEach(function(symbol) { 
  22.     console.log(symbol == ':hankey:'); 
  23. }); 

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

在 ECMAScript 6中,你可以簡單地使用 for…of 。字符串迭代器處理整個字符,而不是代理對。

  1. for (const symbol of ':hankey:') { 
  2.     console.log(symbol == ':hankey:'); 

不幸的是,沒有辦法對它進行填充,因為 for…of 是一個語法級結構。

其他問題

此行為會影響幾乎所有字符串方法,包括此處未明確提及的方法(如 String.prototype.substring , String.prototype.slice 等),因此在使用它們時要小心。

正則表達式中的 Unicode 問題

匹配碼位和 Unicode 標量值

正則表達式中的點運算符( . )只匹配一個“字符”, 但是由于JavaScript將代理半部分公開為單獨的 “字符”,所以它永遠不會匹配位于輔助平面上的字符。

  1. >> /foo.bar/.test('foo:hankey:bar'
  2. false 

讓我們思考一下,我們可以使用什么正則表達式來匹配任何 Unicode字符? 什么好主意嗎? 如下所示的, . 這w個是不夠的,因為它不匹配換行符或整個位于輔助平面上的字符。

  1. >> /^.$/.test(':hankey:'
  2. false 

為了正確匹配換行符,我們可以使用 [\s\S] 來代替,但這仍然不能匹配整個位于輔助平面上的字符。

  1. >> /^[\s\S]$/.test(':hankey:'
  2. false 

事實證明,匹配任何 Unicode 編碼點的正則表達式一點也不簡單:

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

當然,你不希望手工編寫這些正則表達式,更不用說調試它們了。為了生成像上面的一個正則表達式,可以使用了一個名為 Regenerate 的庫,它可以根據碼位或字符列表輕松地創建正則表達式:

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

從左到右,這個正則表達式匹配BMP字符、代理項對或單個代理項。

雖然在 JavaScript 字符串中技術上允許使用單獨的代理,但是它們本身并不映射到任何字符,因此應該避免使用。術語 Unicode標量值 指除代理碼位之外的所有碼位。下面是一個正則表達式,它匹配任何 Unicode 標量值:

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

Regenerate 作為構建腳本的一部分使用的,用于創建復雜的正則表達式,同時仍然保持生成這些表達式的腳本的可讀性和易于維護。

ECMAScript 6 為正則表達式引入一個 u 標志,它會使用 . 操作符匹配整個碼位,而不是代理項的一半。

  1. >> /foo.bar/.test('foo:hankey:bar'
  2. false 
  3.  
  4. >> /foo.bar/u.test('foo:hankey:bar'
  5. true 

注意 . 操作符仍然不會匹配換行符,設置 u 標志時, . 操作符等效于以下向后兼容的正則表達式模式:

  1. >> regenerate() 
  2.      .addRange(0x0, 0x10FFFF) // all Unicode code points 
  3.      .remove(  // minus `LineTerminator`s (https://ecma-international.org/ecma-262/5.1/#sec-7.3): 
  4.        0x000A, // Line Feed <LF> 
  5.        0x000D, // Carriage Return <CR> 
  6.        0x2028, // Line Separator <LS> 
  7.        0x2029  // Paragraph Separator <PS> 
  8.      ) 
  9.      .toString(); 
  10. '[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]' 
  11.  
  12. >> /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:hankey:bar'
  13. true 

位于輔助平面碼位上的字符

考慮到 /[a-c]/ 匹配任何字符從 碼位為 U+0061 的字母 a 到 碼位為 U+0063 的字母 c,似乎/[:hankey:-:dizzy:]/ 會匹配碼位 U+1F4A9 到碼位 U+1F4AB ,然而事實并非如此:

  1. >> /[:hankey:-:dizzy:]/ 
  2. SyntaxError: Invalid regular expression: Range out of order in character class 

發生這種情況的原因是,正則表達式等價于:

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

事實證明,不像我們想的那樣匹配碼位 U+1F4A9 到碼位 U+1F4AB ,而是匹配正則表達式:

  • U+D83D(高代理位)
  • 從 U+DCA9 到 U+D83D 的范圍(無效,因為起始碼位大于標記范圍結束的碼位)
  • U+DCAB(低代理位)
  1. >> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCA9'// match U+1F4A9 
  2. true 
  3.  
  4. >> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4A9}'// match U+1F4A9 
  5. true 
  6.  
  7. >> /[:hankey:-:dizzy:]/u.test(':hankey:'// match U+1F4A9 
  8. true 
  9.  
  10. >> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCAA'// match U+1F4AA 
  11. true 
  12.  
  13. >> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4AA}'// match U+1F4AA 
  14. true 
  15.  
  16. >> /[:hankey:-:dizzy:]/u.test(':muscle:'// match U+1F4AA 
  17. true 
  18.  
  19. >> /[\uD83D\uDCA9-\uD83D\uDCAB]/u.test('\uD83D\uDCAB'// match U+1F4AB 
  20. true 
  21.  
  22. >> /[\u{1F4A9}-\u{1F4AB}]/u.test('\u{1F4AB}'// match U+1F4AB 
  23. true 
  24.  
  25. >> /[:hankey:-:dizzy:]/u.test(':dizzy:'// match U+1F4AB 
  26. true 

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

  1. >> regenerate().addRange(':hankey:'':dizzy:'
  2. '\uD83D[\uDCA9-\uDCAB]' 
  3.  
  4. >> /^\uD83D[\uDCA9-\uDCAB]$/.test(':hankey:'// match U+1F4A9 
  5. true 
  6.  
  7. >> /^\uD83D[\uDCA9-\uDCAB]$/.test(':muscle:'// match U+1F4AA 
  8. true 
  9.  
  10. >> /^\uD83D[\uDCA9-\uDCAB]$/.test(':dizzy:'// match U+1F4AB 
  11. true 

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

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

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

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

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

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

責任編輯:張燕妮 來源: SegmentFault
相關推薦

2009-12-02 09:49:43

PHP Ajax亂碼

2010-05-21 11:07:55

MySQL 5 亂碼問

2010-09-30 10:31:28

2021-12-31 18:20:44

數據云端數據遷移

2017-10-10 15:30:20

JavaScript

2024-01-29 00:45:36

跨域后端接口

2009-12-07 15:50:27

WCF文件

2010-02-24 10:35:56

WCF鏈接服務超時

2024-05-29 00:00:01

字符串Python縮進

2024-01-29 00:21:14

環境變量數據

2020-09-14 18:47:03

云安全

2021-09-23 22:36:30

手機數據二手

2025-01-26 00:00:25

限流組件HTTP

2010-05-07 13:28:43

Oracle數據庫

2017-10-30 11:03:11

2021-04-11 10:19:45

Python編碼Unicode萬國碼

2024-01-04 12:53:00

Unicode字符UTF-8

2010-03-24 11:37:22

Python unic

2016-09-18 17:24:58

php函數json_encodeunicode

2014-12-12 10:13:12

JavaScript
點贊
收藏

51CTO技術棧公眾號

亚洲国产综合在线看不卡| a一区二区三区亚洲| 国产偷国产偷亚洲高清人白洁| 日本韩国在线不卡| youjizz亚洲女人| 欧美在线一级| 精品国产精品自拍| 日本不卡一区| www.我爱av| 丝袜美腿亚洲色图| 九九视频直播综合网| 久久久久久亚洲中文字幕无码| 懂色aⅴ精品一区二区三区| 亚洲图片欧美激情| 麻豆av一区| 国产高清视频免费| 日韩av中文在线观看| 色综合久久悠悠| 成人小视频免费看| 卡通动漫精品一区二区三区| 欧美日韩一区二区三区视频| 你真棒插曲来救救我在线观看| 91caoporm在线视频| 国产精品一区三区| 国产精品视频午夜| 久久久久久久黄色片| 91精品久久久久久久蜜月| 亚洲精品www久久久久久广东| 国产美女18xxxx免费视频| 天天免费亚洲黑人免费| 亚洲成人第一页| 日本黄色a视频| 国产系列在线观看| 99精品久久久久久| 亚洲a一级视频| 伊人网视频在线| 视频一区中文字幕| 欧美在线观看网站| 久久这里只有精品免费| 亚洲国产日韩欧美在线| 色婷婷综合久久久久中文字幕1| 免费黄色在线视频| 久久99偷拍| 日韩欧美高清dvd碟片| av中文字幕网址| 欧美成人高清视频在线观看| 91久久免费观看| 亚洲国产精品久久久久爰色欲| 国产一线二线在线观看| 亚洲激情在线激情| 超薄肉色丝袜足j调教99| 欧美成人性生活视频| 国产精品毛片无遮挡高清| 日韩精品国内| 啊v在线视频| 国产欧美日本一区视频| 日韩av不卡在线播放| 九色在线视频| 国产欧美精品在线观看| 亚洲激情一区二区| 午夜免费福利在线观看| **网站欧美大片在线观看| 综合久久国产| a毛片在线看免费观看| 亚洲激情一二三区| 国产精品一线二线三线| free性欧美16hd| 精品毛片网大全| 黄色片一级视频| 99久久伊人| 91精品久久久久久蜜臀| 又黄又爽又色的视频| 成人自拍在线| 国产视频欧美视频| 久久精品国产亚洲AV成人婷婷| 91影院成人| 欧美黄色三级网站| 成人免费a视频| 日韩精品电影在线观看| 国产欧美一区二区三区久久人妖 | 午夜精品久久久久久久久久| 国产高清av在线播放| 欧美三级网址| 91精品免费在线| 漂亮人妻被黑人久久精品| 国产99精品| 久久久国产精品视频| 日韩精品乱码久久久久久| 久色成人在线| 亚洲资源在线看| 午夜视频在线播放| 国产精品精品国产色婷婷| 精品国产一区二区三区无码| 黑人巨大亚洲一区二区久 | 亚洲va久久久噜噜噜无码久久| 成人动漫一区二区| 亚洲精品一区二区三区四区五区 | 日韩黄色大片| 欧美男插女视频| 亚洲av无码不卡| 国产盗摄女厕一区二区三区| 久久综合一区| 亚洲色图美国十次| 欧洲av一区二区嗯嗯嗯啊| 久久人妻少妇嫩草av蜜桃| 精品国产一区探花在线观看| 欧美第一页在线| 中文字幕精品一区二| 成人av免费在线| 中文字幕中文字幕在线中一区高清 | 99re8这里有精品热视频8在线| 亚洲欧美另类人妖| 欧美日韩人妻精品一区二区三区| 久久久久久婷| 国产日韩久久| 超碰个人在线| 欧美色网站导航| 国产精品久久AV无码| 久久久久久影院| 国产精品高清网站| 婷婷五月综合久久中文字幕| 亚洲色图欧洲色图婷婷| 亚洲国产精品三区| 啄木系列成人av电影| 欧美激情18p| 国产精品一区二区黑人巨大| 国产日韩欧美不卡| 国内性生活视频| 国产精品jk白丝蜜臀av小说| 美女啪啪无遮挡免费久久网站| jizz国产在线| 久久久电影一区二区三区| 国产精品裸体瑜伽视频| 亚洲三区欧美一区国产二区| 美女啪啪无遮挡免费久久网站| 一卡二卡在线观看| 欧美国产成人在线| av五月天在线| 精品国产乱码久久久久久1区2匹| 欧美在线亚洲一区| 亚洲av电影一区| 精品成人av一区| 影音先锋人妻啪啪av资源网站| 欧美日韩国内| 成人av资源网| 黄色小说在线播放| 日韩精品最新网址| 国产一级av毛片| 国产不卡视频在线观看| 国产精品69久久久| 成人av资源网址| 97国产精品视频| 欧美xxx.com| 色久综合一二码| 三年中国中文观看免费播放| 日韩在线a电影| 亚洲欧洲一区二区| 亚洲最大的免费视频网站| 久久久极品av| 国产1区在线观看| 午夜精品一区二区三区免费视频 | 欧美激情第99页| 免费观看毛片网站| 欧美色图在线视频| 天天干天天舔天天操| 精品亚洲成a人在线观看| 在线观看成人av电影| 日韩视频在线直播| 韩国精品久久久999| 人成在线免费视频| 欧美日韩一区二区三区视频| 免费看一级一片| 91丝袜国产在线播放| 538在线视频观看| 中文字幕av亚洲精品一部二部| 999热视频| 日韩脚交footjobhd| 中文字幕欧美精品在线| aa视频在线免费观看| 亚洲午夜日本在线观看| 亚洲AV无码国产精品| 美女一区二区视频| 欧美中日韩在线| 免费精品国产的网站免费观看| 国产精品成人在线| av毛片在线免费看| 亚洲片国产一区一级在线观看| 亚洲系列在线观看| 亚洲国产一区二区三区| b站大片免费直播| 国产一区二区三区四区五区美女 | 51精品在线| 日韩理论片久久| 99久久99久久久精品棕色圆| 精品电影在线观看| 紧身裙女教师波多野结衣| 99re这里只有精品首页| 中文字幕色网站| 国产精品日韩久久久| 一本一道久久a久久综合精品| 超碰成人在线观看| 成人免费淫片视频软件| 不卡av影片| 欧美寡妇偷汉性猛交| 成年网站在线| 亚洲精品国产电影| av免费在线不卡| 日本韩国欧美在线| 日韩精品视频播放| 亚洲欧美二区三区| 欧美波霸videosex极品| 成人av免费在线观看| 亚洲天堂一区二区在线观看| 日韩影院精彩在线| 18禁免费观看网站| 欧美福利在线| 午夜精品一区二区在线观看| 久久精品亚洲成在人线av网址| 成人性生交xxxxx网站| 我爱我色成人网| 久久久久久久久久国产| 成人免费在线| 日韩小视频网址| 姝姝窝人体www聚色窝| 91精品国产综合久久久蜜臀粉嫩 | 日本天码aⅴ片在线电影网站| 一区二区三区国产在线观看| 亚洲av电影一区| 精品欧美一区二区三区精品久久| 一区二区三区免费在线视频| 日本福利一区二区| 高清乱码免费看污| 欧美日韩精品中文字幕| www.av视频在线观看| 一区二区三区 在线观看视频| www.xx日本| 国产精品美女久久福利网站| 蜜桃av乱码一区二区三区| 91麻豆国产福利在线观看| 中文字幕在线播放一区| 成人小视频在线| 老女人性生活视频| 国产成人免费视频网站高清观看视频| 少妇一级淫免费播放| 日本伊人精品一区二区三区观看方式| 日韩久久一级片| 丝袜美腿一区二区三区| 亚洲精品高清无码视频| 日韩av中文字幕一区二区| 国产一线二线三线在线观看| 免费在线观看日韩欧美| 亚洲欧美在线精品| 捆绑调教一区二区三区| 超碰在线资源站| 国产盗摄一区二区三区| 欧美xxxxx精品| 久久在线观看免费| 亚洲一区二区自偷自拍| 欧美国产乱子伦| 午夜精品一区二区三级视频| 亚洲女人****多毛耸耸8| 91日韩中文字幕| 亚洲国产一区视频| 国内精品福利视频| 狠狠色狠狠色综合日日小说| 337p粉嫩色噜噜噜大肥臀| 欧美日韩国产一级| 正在播放亚洲精品| 9191国产精品| 日批视频在线播放| 一区二区中文字幕| 国产激情视频在线| 欧美.www| 欧美凹凸一区二区三区视频| 凹凸成人精品亚洲精品密奴| 国产卡一卡二在线| 亚洲国产国产亚洲一二三| 免费无码av片在线观看| 久久精品国产99久久6 | 成人精品动漫| 91嫩草在线| 一本色道久久综合狠狠躁的番外| 先锋影音亚洲资源| 国产精品v一区二区三区| 黄色片视频在线免费观看| 久久69国产一区二区蜜臀| 亚洲欧洲国产视频| 国产亚洲视频系列| 538任你躁在线精品视频网站| 精品久久久久久久久久ntr影视| 波多野结衣二区三区| 日韩欧美电影一二三| 男操女在线观看| 九九九久久久久久| 天然素人一区二区视频| 粉嫩精品一区二区三区在线观看| 国产日产一区 | 日日夜夜免费精品| 肉色超薄丝袜脚交| 久久影院视频免费| 精品无码免费视频| 在线电影院国产精品| 青青草娱乐在线| 欧美高跟鞋交xxxxxhd| 国产91在线精品| 麻豆av一区二区| 国内精品久久久久国产盗摄免费观看完整版| 69堂免费视频| 高清成人免费视频| 国产三级精品三级观看| 欧美日韩性视频| 成人午夜福利视频| 精品久久国产精品| 国产另类xxxxhd高清| 国产一区二区三区黄| 亚洲精品a级片| 亚洲黄色av网址| 久久久亚洲综合| 日韩精品一区二区三| 日韩视频免费观看高清完整版在线观看 | 在线观看三级网站| 美女一区二区三区| 久久国产精品影院| 天天操天天色综合| 高清乱码毛片入口| 欧美国产中文字幕| 精品国产三级| 欧美日韩视频免费在线观看| 日本不卡在线视频| 69视频在线观看免费| 欧美性xxxxxxx| 香蕉视频黄色片| 91国产在线精品| 高清精品视频| 成年人网站国产| 岛国一区二区三区| 青娱乐国产在线| 日韩久久免费av| 免费电影视频在线看| 99国精产品一二二线| 午夜精品久久| 性高潮免费视频| 亚洲.国产.中文慕字在线| 亚洲欧美另类一区| 久久久久久亚洲精品| 国产乱论精品| 免费毛片网站在线观看| 不卡一二三区首页| 欧美另类一区二区| 国产丝袜一区二区| 69堂免费精品视频在线播放| 天堂一区二区三区| 裸体在线国模精品偷拍| 免费成人深夜夜行网站| 日韩视频免费观看高清完整版在线观看| 26uuu亚洲电影在线观看| 国产精品国色综合久久| 日韩视频二区| 99久久人妻无码精品系列| 在线观看日韩av先锋影音电影院| 91在线观看| 96成人在线视频| 国产日韩一区二区三区在线| 中文字幕一区二区三区人妻电影| 欧美在线你懂的| 激情影院在线观看| 国产精品国产精品国产专区蜜臀ah| 亚洲人成免费| 国产视频三区四区| 91精品久久久久久久久99蜜臂| 蜜臀av在线播放| 免费成人av网站| 看片的网站亚洲| 久久久久久久久久久久久久免费看 | 欧美一级性视频| 欧美一区视频在线| 久久一区二区中文字幕| 国内精品免费视频| 色婷婷精品大视频在线蜜桃视频| av福利精品| 国产精品免费视频一区二区| 三级一区在线视频先锋| 朝桐光av在线| 亚洲毛片一区二区| 国产精品成人**免费视频| av免费观看国产| 国产精品美女www爽爽爽| 二区三区在线视频| 国产精品美女主播在线观看纯欲| 91精品1区| 国产精品久久久久无码av色戒| 3d成人动漫网站| 在线手机中文字幕| 干日本少妇视频| 国产日韩影视精品| 丰满少妇一级片| 国产精品视频永久免费播放| 精品成人在线| 亚洲女人久久久| 亚洲天天在线日亚洲洲精|