Programming Field

symbol - TypeScriptキーワード一覧

スポンサーリンク

「symbol」は基本型(プリミティブ型)の1つである「Symbol型」として用いられます。

概要

(特筆の無いものは TypeScript/spec.md 「3.2.4 The Symbol Type」での定義によります。)

キーワード情報

詳細

symbol 型はES2015で導入された基本型(プリミティブ型)であり、四則演算などによる変更が不可能の不変なデータを持つ『Symbolプリミティブ型』(「symbol 型」)の値を扱います。symbol 型はユニーク性(一意性)のあるデータとして利用され、とりわけ特殊なデータをオブジェクトに保持する際の「プロパティー キー」として利用することができます。number 型に対する「数値」、string 型に対する「文字列」のようなものは symbol 型には存在せず、「Symbol」を関数として呼び出すことで新たな symbol 型データを得るか、「Symbol.for」メソッドの利用、および「Symbol」に定義されている各種スタティックフィールドを利用するなどで値を得ます。

※ 以下のコードは実行環境(スクリプトエンジン)がES2015に準拠していることを前提としています。そのため、これをJavaScriptに変換したコードはそのままではIE11などで動作しません。

let x: symbol;             // symbol 型変数
let y: symbol;
x = Symbol('mySymbol');    // 新たな symbol 型のデータを作成
y = Symbol.for('someSym'); // グローバルレジストリの「someSym」シンボルを取得(無ければ自動で作成)
let z = Symbol.iterator;   // エンジンが定義済みの「iterator」シンボルを取得
                           // ※ コンパイラーオプションの lib に「lib.es2015.iterable」の設定などが必要

// 配列の「iterator」オブジェクトを取得してその「next」を呼び出す(結果「0」を出力)
// ※ 2018年7月時点で「Symbol.iterator」の代わりに「z」を利用することは出来ない
console.log([0, 1, 2][Symbol.iterator]().next().value);

なお、symbol 型はグローバルの「Symbol」型として扱うことは出来ますが、「Symbol」型は symbol 型として使用することができません。また、「Symbol」型の変数に symbol 型データを代入しても実体は「Symbol」オブジェクトのデータではなくあくまで symbol 型データであるため、「instanceof Symbol」で検出することは出来ません。そのため、特別な理由が無い限りは「Symbol」型やそのオブジェクトを利用するのではなく symbol 型を利用するのが推奨されます。(関数としての「Symbol」の利用や、「Symbol」自身に存在するプロパティー(スタティックフィールド)を利用するのは問題ありません。)

※ NumberやStringなどと異なり、「new Symbol(...)」のような「Symbol」に対するコンストラクター呼び出しはできません(ES2015の仕様、TypeScriptでも型定義なし)。どうしても「Symbol」型のオブジェクトを取得したい場合は「Object」を関数として、symbol 型の値を引数として呼び出せばそのオブジェクトを取得することは出来ますが、多くの場合役に立ちません。

let s: Symbol | null;      // 「Symbol」型または null 型として定義(strictNullChecksが有効の場合)
if (process.env.NODE_ENV === 'DEVELOPMENT') {
    s = Symbol('hogeSym');
} else {
    s = null;
}
// 「Symbol」型かどうかののチェック
if (s instanceof Symbol) {
    // (型解析上は s は Symbol になるが実際にはここに入らない)
    console.log('Symbol', s);
} else {
    // (型解析上は s は null になるが実際にはここに入る)
    console.log('null', s);
}

symbol 型かどうかは typeof x === "symbol" でチェックできます。また、この式は以下のように型ガード(type guard)としても利用できます。

let unk: any;       // 型が不明な変数
.
.
.
if (typeof unk === 'symbol') {
    // このブロックでは unk を symbol 型の変数として扱える
    console.log(someVar[unk]);
}

※ 「Symbol」型は内部的にはオブジェクトであるため、x が(オブジェクトラップされた)「Symbol」型であるとき typeof x === "symbol" は false になります。

symbol 型データの利用

定義済みシンボルの利用

「Symbol」のオブジェクトにはいくつかのプロパティー(「Symbol」クラスとしてのスタティックフィールド)が存在します。これらはプロパティーのキーとして利用することで、ECMAScript準拠の処理においてスクリプトエンジン既定の処理を上書きしたり、独自の処理を与えることでクラスやオブジェクトの利便性を高めたりすることが出来ます。

例えば「Symbol.iterator」は以下のように利用することができます。

/// <reference lib='es2015.iterable' />

// 「Symbol.iterator」をサポートするクラス
class MyIterable {
    constructor(public count: number) { }

    // Iterator オブジェクトを返すメソッド
    public [Symbol.iterator](): Iterator<number> {
        const count = this.count;
        const iter = {
            _index: 0,
            // next メソッドは「done」と「value」を持つオブジェクト(IteratorResult オブジェクト)を返す
            next() {
                const i = this._index;
                if (i < count) {
                    ++this._index;
                    return { value: i, done: false };
                } else {
                    return { value: -1, done: true };
                }
            }
        };
        return iter;
    }
}

const m = new MyIterable(10);
// 「Symbol.iterator」をサポートするので for...of が使える
// ※ 「target」が「es5」以下の場合「downlevelIteration」の有効化が必要
for (const x of m) { // (x は自動で number 型となる)
    console.log(x);  // 順に「0」「1」「2」...「9」を出力
}

※ 「target: 'es5'」以下で「downlevelIteration: true」とするとJavaScriptではPolyfillコードが生成されますが、「target: 'es2015'」でコンパイルするとほぼそのままのコードが出力され、それを実行すると独自の「Symbol.iterator」定義がスクリプトエンジンから呼び出されて適切にfor...ofが実行されることが分かります。

unique symbol

[参考: TypeScript PR #15473]

symbol 型のデータは基本的には「不変なデータ」という扱いであり、ユニーク性(一意性)を保障したい場合に有用なデータです。

「Symbol」を関数として呼び出した場合のデータは常にユニークであるため、

const mySym1 = Symbol('mySym1');

という形で「mySym」変数を作った場合、mySym1 === someVar が true になる someVar の値は「mySym1」の値のみになります。(mySym1 === Symbol('mySym1') は false になります。)

一方、「Symbol.for」を使って取得した symbol データは、引数の文字列が一致すれば同一の symbol データとなりますが、それでも別の文字列であれば不一致となるため、ほぼユニーク性のある symbol データとして用いることができます。

const mySym2 = Symbol.for('mySym2');
console.log(mySym2 === Symbol.for('mySym2')); // true

※ Symbol.for はスクリプトエンジン内部に1つのみ存在する「グローバルsymbolレジストリ」に文字列と symbol データのペアを保持します。そのため、同一環境上では異なるスクリプトファイルでも同じ名前を共有できます。

また、symbol 型では「ユニークなシンボルである」ということを表すために unique symbol という特別な型を利用することができます。

const mySym1: unique symbol = Symbol('mySym1');

※ 「Symbol(...)」および「Symbol.for(...)」で代入される const / readonly な値は自動的に「unique symbol」と扱われるため、上記では「unique symbol」という型指定は省略できます。

このとき、「mySym1」の型は symbol 型を特殊化した「typeof mySym1」というユニークな型になります。ユニークな型であるため、「symbol 型」の変数に「typeof mySym1 型」の値は代入できますが、「typeof mySym1 型」の変数に「symbol 型」の値を代入することは出来ません。

const someSym1: symbol = mySym1;                   // OK
const someSym2: typeof mySym1 = Symbol('mySym1');  // ERROR: TS2322

このことは、特定のシンボルをプロパティーのキーとしてユニークに利用するために非常に有用であり、インターフェイス定義や型推論で役に立ちます。

const mySym1: unique symbol = Symbol('mySym1');
// const で定義された symbol 型変数に「Symbol(...)」または「Symbol.for(...)」の値を代入すると自動的に unique symbol として扱われる
const mySym2 = Symbol.for('mySym2');
// 「Symbol(...)」や「Symbol.for(...)」を使わない場合は普通の symbol として扱われる
const otherSym1 = mySym1;

// プロパティー名(プロパティーシンボル)として型定義に使う場合の例
interface SomeStruct {
    [mySym1]: string;     // OK
    [mySym2]: number;     // OK
    [otherSym1]: object;  // ERROR: TS1169 (プロパティー名に式を入れる場合は結果の型が文字列リテラル型または「unique symbol」でなければならない)
    [key: string]: any;
}

// プロパティー名(プロパティーシンボル)から型推論をする例
declare var someObj: SomeStruct;
const x = someObj[mySym1];     // x は string 型
const z = someObj[otherSym1];  // ERROR: TS2538 ('otherSym1' をインデックス値として使用できない)