JavaScript

JavaScriptのcodePointAt():文字のコードポイントを調べて入力の違いを見つける

この記事でわかること

String.prototype.codePointAt()で文字のコードポイントを調べる方法を解説。ブラウザーとNode.jsの実行手順、UTF-16の添字、期待出力、絵文字や見えない文字を調査する際の注意点を学べます。

目的:見た目では分からない文字の違いを調べる

入力した名前が検索に一致しない、コピーした文字列に余分な空白がある、絵文字を含めると位置がずれる。このような問題では、画面に表示された文字だけを見ても原因を判断できません。

String.prototype.codePointAt()は、指定した位置から始まる文字のUnicodeコードポイントを数値で取得するメソッドです。たとえば半角のAは十進数で65、Unicodeでよく使う十六進表記ではU+0041です。文字を数値として確認すれば、見た目が似た別の文字を区別できます。MDNのcodePointAt()リファレンス

この記事では、文字列をコードポイントの一覧として出力する小さな診断処理を作ります。文字数のカウントや入力の自動修正ではなく、比較に失敗する理由を調べることが目的です。

動作環境と対応バージョン

2026年10月29日時点で確認したMDNでは、codePointAt()はBaselineのWidely availableに分類され、2015年9月から主要ブラウザーを横断して利用可能とされています。現在のChrome、Edge、Firefox、Safariで利用できます。古い組み込みブラウザーまで同じ対応状況とは限らないため、必要なら機能の有無を確認します。MDNの互換性情報

ブラウザー専用のAPIではないため、Node.jsでも動きます。本記事の実行環境はNode.js 22以降とします。これは記事の対象バージョンであり、このメソッドが初めて搭載されたバージョンを意味しません。追加パッケージ、DOM、通信処理は不要です。Node.jsはV8を利用し、安定したJavaScript機能を通常はフラグなしで提供します。Node.jsのECMAScript対応方針

次の式がfunctionを返せば、実行環境にメソッドが存在します。

JavaScript
console.log(typeof String.prototype.codePointAt);

引数と戻り値を理解する

添字はUTF-16のコード単位

基本の呼び出し方はtext.codePointAt(index)です。

  • textは調べる文字列です。元の文字列は変更しません。
  • indexは先頭を0とする位置です。見た目の文字数ではなく、UTF-16のコード単位を基準にします。
  • 戻り値はコードポイントを表す非負の整数です。範囲外の位置ではundefinedになります。
  • indexを省略するかundefinedを指定すると、位置0として扱われます。

たとえばA😀Bのlengthは4です。AとBはそれぞれ1コード単位、😀は2コード単位を使います。したがってBの位置は3です。

😀の先頭位置を指定すると絵文字全体のコードポイントを取得できますが、後半を指定すると後半のコード単位の値だけが返ります。この違いが、添字を扱う際の注意点です。MDNの引数・戻り値の説明

数値を取得してから表示形式を決める

codePointAt()自身がU+0041という文字列を返すわけではありません。取得した数値を十六進数へ変換し、必要なら大文字化やゼロ埋めを行います。範囲外では数値が得られないため、変換する前の判定が必要です。

コピーして動かす診断コード

次のコードは、文字列の各コードポイントと、その開始位置を出力します。入力例には、普通の空白と見た目が似たノーブレークスペースを含めています。違いを確実に再現するため、対象の空白はUnicodeエスケープで記述しました。

JavaScript
(() => {
  "use strict";

  if (typeof String.prototype.codePointAt !== "function") {
    console.log("この環境はcodePointAt()に対応していません");
    return;
  }

  function inspectCodePoints(text) {
    if (typeof text !== "string") {
      throw new TypeError("textには文字列を指定してください");
    }

    const lines = [];
    let offset = 0;

    for (const character of text) {
      const point = character.codePointAt(0);
      const hex = point.toString(16).toUpperCase().padStart(4, "0");
      lines.push(`${offset}: U+${hex}`);
      offset += character.length;
    }

    return lines;
  }

  const sample = "A\u0020\u00A0\u{1F600}B";
  console.log(`UTF-16 length: ${sample.length}`);

  for (const line of inspectCodePoints(sample)) {
    console.log(line);
  }

  console.log(`empty rows: ${inspectCodePoints("").length}`);

  try {
    inspectCodePoints(null);
  } catch (error) {
    console.log(`${error.name}: ${error.message}`);
  }
})();

ブラウザーで実行する

対応ブラウザーで開発者ツールのConsoleを開き、コード全体を貼り付けて実行します。アプリの準備やHTMLファイルの作成は不要です。外側の即時実行関数により、繰り返し実行しても同じ変数名の宣言が衝突しにくい構成にしています。

Node.jsで実行する

Node.jsをインストールした環境で、コードをUTF-8のinspect-code-points.jsとして保存します。ターミナルでそのファイルがあるフォルダーへ移動し、次のコマンドを実行してください。ファイル名をnodeへ渡す方法は、Node.js公式のスクリプト実行手順に沿っています。

Shell
node --version
node inspect-code-points.js

期待出力

次は、仕様とコードの処理順から導いた期待出力です。本記事の作成時にコードの実機実行は行っていません。

CODE
UTF-16 length: 6
0: U+0041
1: U+0020
2: U+00A0
3: U+1F600
5: U+0042
empty rows: 0
TypeError: textには文字列を指定してください

U+0020とU+00A0が別々に表示されるため、空白の違いを確認できます。絵文字の開始位置が3、その次のBが5となる点にも注目してください。

コードではfor...ofでコードポイントごとに文字列を取り出し、その先頭にcodePointAt(0)を適用しています。offsetには取り出した文字列のUTF-16長を加算するため、元の文字列での位置を保てます。この走査方法はMDNのループに関する説明でも案内されています。

失敗例と制約

添字を1ずつ増やすと後半も取得する

次は単独で実行できる失敗例です。絵文字を1つ調べたい意図に対して、2回出力されます。

JavaScript
(() => {
  const text = "\u{1F600}";
  for (let index = 0; index < text.length; index += 1) {
    console.log(text.codePointAt(index).toString(16));
  }
})();
CODE
1f600
de00

2行目のde00は別の絵文字ではありません。サロゲートペアの後半を直接参照した結果です。全体の一覧を作る用途なら、先ほどのfor...ofを使う方が位置の扱いを誤りにくくなります。

範囲外の結果をそのまま変換しない

空文字列へのcodePointAt(0)はundefinedです。そのままtoString(16)を呼ぶとTypeErrorになります。位置を指定して調べる処理では、次のように分岐してください。

JavaScript
(() => {
  const point = "".codePointAt(0);
  console.log(point === undefined ? "対象なし" : point.toString(16));
})();
CODE
対象なし

メインコードでは、空文字列に対するループが一度も実行されないため、この問題を避けています。また、inspectCodePoints()は文字列以外を明示的に例外にします。これは記事の補助関数が定めた入力条件です。処理はすべて同期的であり、awaitやPromiseの拒否処理は不要です。

見た目の1文字や文字列の正しさは判定しない

結合文字、肌の色を伴う絵文字、国旗などは、複数のコードポイントで1つの表示単位になることがあります。今回の一覧の行数を、そのまま利用者向けの文字数として扱わないでください。

さらにJavaScriptの文字列には、対になっていないサロゲートが含まれる場合もあります。数値が取得できたことだけで、他のシステムへ安全に渡せる文字列と判断することはできません。表示単位とコードポイント、不正なサロゲートの違いは、MDNのString解説で確認できます。

活用事例と導入時の改善案

検索に一致しない文字列があれば、検索語と保存済みの値をそれぞれinspectCodePoints()へ渡し、一覧を比較できます。フォームから入力した値と、外部システムから取り込んだ値を調べる場合にも使えます。

実務では、違いを見つけた後に「どの文字を同じものとして扱うか」を決めることを勧めます。空白をすべて削除すると、商品名や識別子の意味まで変わる可能性があるためです。診断結果を基に、許容する入力と変換ルールを具体化すれば、意図しないデータ変更を避けやすくなります。

また、この例は入力全体の件数に比例して一覧を保持します。大量の文章へ組み込む場合は、問題のある位置の周辺に対象を絞るなど、調査範囲を決めて使ってください。

公式参考資料

PREFERRED SOURCES

Googleの優先するニュース提供元にSEの部屋を追加

Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。

Googleの設定画面で確認する 新しいタブで開きます

候補にSEの部屋が表示されない場合は、まだ追加できません。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →