【JavaScript】lenghtを使う時はサロゲートペアに気をつけよう

プログラミング

この記事はJavaScriptでlenghtプロパティを使う時はサロゲートペアに気をつけようというテーマで書いています。

サロゲートペアが含まれているとlenghtプロパティで取得した数と、実際の文字数が違ってしまうんですね。

例えばこんな感じです。

const chars = '楽しい😀';
console.log(chars.length); //5

4文字のはずが、lenghtプロパティで調べると5と判定されます。

絵文字を使うことはあまりないかもですが、漢字でもいくつかサロゲートペアがあるので気をつけたいところです。

有名なところではお魚のホッケとか。

サロゲートペアとは

サロゲートペアがなんだか分からないとあれなんで、簡単に説明をしておきます。

まず、コンピューターは文字をそのまま理解することができません。

そこで、符号化文字集合と言われる、文字と文字に割り当てた番号を対応させてコンピューターに理解させるわけです。

例えば、「あ」→「12354」みたいな感じです。

この文字コードにはUTF-16というのがあり、基本的に2byte(0 ~ 65,535)に対応しているのですが、それでは世の中の文字に対応しきれなくなってしまいました。

そこで、一部の文字は4byte(2byte×2)で対応することにしました。

ここで大事なのが、2byte×2というところです。

以下のコードで見てみましょう。

// 通常の文字列 --------------------//
let str1 = 'あいうえお';
let code1 = [];

for (let i = 0; i < str1.length; i++) {
  code1.push(str1.charCodeAt(i));
}
console.log(code1); //[12354, 12356, 12358, 12360, 12362]

// サロゲートペアありの文字列 --------------------//
let str2 = '😀';
let code2 = [];

for (let i = 0; i < str2.length; i++) {
  code2.push(str2.charCodeAt(i));
}
console.log(code2); //[55357, 56832]

スマイルマーク1つですが、コンソールで見ると2byteの数字が2つ配列に入っていることがわかります。

これが、冒頭で「楽しい😀」をlenghtプロパティで調べた時に「5」と表示された理由です。

以下のサイトでサロゲートペアの一覧を見る事ができます。

山本ワールド サロゲートペア

lenghtプロパティで文字数をカウントする時のサロゲートペア対策

文字数カウントでサロゲートペア対策をする方法として2つ紹介します。

  • ①splitでサロゲートペアを基準に分割してカウントする
  • ②正規表現でサロゲートペアを文字として配列に入れてカウントする

では1つずつ解説します。

①splitでサロゲートペアを基準に分割してカウントする

let str = 'プログラミング🔁は楽しい😀ね!';
let SurrogatePair = str.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length - 1;
let num = str.length - SurrogatePair;
console.log(num); // 15

こんな感じです。

str変数にサロゲートペアが含まれる可能性がある文字列が入るようにします。

ただ、このままだとサロゲートペアの分だけlengthプロパティの数値が実際の文字数よりも増えてしまいます。

そこで、サロゲートペアを基準に分割して、その数を指標にlengthプロパティから引き算するという計算方法です。

ちょっとわかりにくいので、もう少し詳しいのを載せておきます。

let str = 'プログラミング🔁は楽しい😀ね!';
console.log(str.length); // 17

let SurrogatePair = str.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
console.log(SurrogatePair); // ['プログラミング', 'は楽しい', 'ね!']

let SurrogatePairNum = SurrogatePair.length - 1;
console.log(SurrogatePairNum); // 2

let num = str.length - SurrogatePairNum;
console.log(num); // 15

splitで分割すると、要素数はサロゲートペアの数+1になります。

なので、そこから1を引いてた数を、lengthプロパティで取得した数から引くという感じです。

②正規表現でサロゲートペアを文字として配列に入れてカウントする

let str = 'プログラミング🔁は楽しい😀ね!'.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S]/g) || [];
console.log(str); // ['プ', 'ロ', 'グ', 'ラ', 'ミ', 'ン', 'グ', '🔁', 'は', '楽', 'し', 'い', '😀', 'ね', '!']
console.log(str.length); // 15

こっちはシンプルですね。

文字列をいきなりlengthプロパティにかけるとサロゲートペアは2カウントされてしまいますが、配列の要素にしてしまえば、その要素数をカウントするだけです。

サロゲートペアの文字を使うことはあまりないと思いますが、思わぬバグが起きないためにも、このような知識は持っておくとよいかと。