JavaScript Symbol完全ガイド

概要: このチュートリアルでは、JavaScriptのプリミティブ型であるSymbolについて、そしてSymbolを効果的に使用する方法について学びます。

Symbolの作成

ES6では、新しいプリミティブ型としてSymbolが追加されました。 数値真偽値nullundefined文字列などの他のプリミティブ型とは異なり、Symbol型にはリテラル形式がありません。

新しいSymbolを作成するには、以下の例のようにグローバル関数Symbol()を使用します。

let s = Symbol('foo');Code language: JavaScript (javascript)

Symbol()関数は、呼び出されるたびに新しい一意の値を作成します。

console.log(Symbol() === Symbol()); // falseCode language: JavaScript (javascript)

Symbol()関数は、オプションの引数としてdescriptionを受け入れます。 description引数は、Symbolをより記述的にします。

次の例では、firstNamelastNameの2つのSymbolを作成します。

let firstName = Symbol('first name'),
    lastName = Symbol('last name');Code language: JavaScript (javascript)

Symbolのdescriptionプロパティには、toString()メソッドを使用してアクセスできます。 console.log()メソッドは、次の例に示すように、SymbolのtoString()メソッドを暗黙的に呼び出します。

console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)Code language: JavaScript (javascript)

Symbolはプリミティブ値であるため、typeof演算子を使用して、変数がSymbolかどうかを確認できます。 ES6では、Symbol変数を渡すとsymbol文字列を返すようにtypeofが拡張されました。

console.log(typeof firstName); // symbolCode language: JavaScript (javascript)

Symbolはプリミティブ値であるため、new演算子を使用してSymbolを作成しようとすると、エラーが発生します。

let s = new Symbol(); // errorCode language: JavaScript (javascript)

Symbolの共有

ES6は、Symbolをグローバルに共有できるグローバルシンボルレジストリを提供します。共有されるSymbolを作成する場合は、Symbol()関数を呼び出す代わりに、Symbol.for()メソッドを使用します。

Symbol.for()メソッドは、次の例に示すように、Symbolの説明に使用できる単一のパラメーターを受け入れます。

let ssn = Symbol.for('ssn');Code language: JavaScript (javascript)

Symbol.for()メソッドは、最初にグローバルシンボルレジストリでキーがssnのSymbolを検索します。存在する場合は、既存のSymbolを返します。それ以外の場合は、新しいSymbolを作成し、指定されたキーでグローバルシンボルレジストリに登録し、Symbolを返します。

後で、同じキーを使用してSymbol.for()メソッドを呼び出すと、Symbol.for()メソッドは既存のSymbolを返します。

let citizenID = Symbol.for('ssn');
console.log(ssn === citizenID); // trueCode language: JavaScript (javascript)

この例では、Symbol.for()メソッドを使用して、キーがssnのSymbolを検索しました。グローバルシンボルレジストリにすでに含まれているため、Symbol.for()メソッドは既存のSymbolを返しました。

Symbolに関連付けられたキーを取得するには、次の例に示すように、Symbol.keyFor()メソッドを使用します。

console.log(Symbol.keyFor(citizenID)); // 'ssn'Code language: JavaScript (javascript)

Symbolがグローバルシンボルレジストリに存在しない場合、System.keyFor()メソッドはundefinedを返します。

let systemID = Symbol('sys');
console.log(Symbol.keyFor(systemID)); // undefinedCode language: JavaScript (javascript)

Symbolの使用方法

A) 一意の値としてのSymbolの使用

コードで文字列または数値を使用する場合は、代わりにSymbolを使用する必要があります。たとえば、タスク管理アプリケーションでステータスを管理する必要があるとします。

ES6以前は、タスクのさまざまなステータスを表すために、openin progresscompletedcanceledon holdなどの文字列を使用していました。 ES6では、次のようにSymbolを使用できます。

let statuses = {
    OPEN: Symbol('Open'),
    IN_PROGRESS: Symbol('In progress'),
    COMPLETED: Symbol('Completed'),
    HOLD: Symbol('On hold'),
    CANCELED: Symbol('Canceled')
};
// complete a task
task.setStatus(statuses.COMPLETED);
Code language: JavaScript (javascript)

B) オブジェクトの計算されたプロパティ名としてのSymbolの使用

Symbolを計算されたプロパティ名として使用できます。次の例を参照してください。

let status = Symbol('status');
let task = {
    [status]: statuses.OPEN,
    description: 'Learn ES6 Symbol'
};
console.log(task);Code language: JavaScript (javascript)

オブジェクトのすべての列挙可能なプロパティを取得するには、Object.keys()メソッドを使用します。

console.log(Object.keys(task)); // ["description"]Code language: JavaScript (javascript)

プロパティが列挙可能かどうか関係なく、オブジェクトのすべてのプロパティを取得するには、Object.getOwnPropertyNames()メソッドを使用します。

console.log(Object.getOwnPropertyNames(task)); // ["description"]Code language: JavaScript (javascript)

オブジェクトのすべてのプロパティシンボルを取得するには、ES6で追加されたObject.getOwnPropertySymbols()メソッドを使用します。

console.log(Object.getOwnPropertySymbols(task)); //[Symbol(status)]Code language: JavaScript (javascript)

Object.getOwnPropertySymbols()メソッドは、オブジェクトからの固有のプロパティシンボルの配列を返します。

Well-known Symbol

ES6は、Well-known Symbolと呼ばれる定義済みのSymbolを提供します。 Well-known Symbolは、JavaScriptの一般的な動作を表します。各Well-known Symbolは、Symbolオブジェクトの静的プロパティです。

Symbol.hasInstance

Symbol.hasInstanceは、instanceof演算子の動作を変更するSymbolです。通常、instanceof演算子を使用する場合

obj instanceof type;Code language: JavaScript (javascript)

JavaScriptは次のようにSymbol.hasIntanceメソッドを呼び出します。

type[Symbol.hasInstance](obj);Code language: JavaScript (javascript)

その後、objtypeオブジェクトのインスタンスかどうかを判断するメソッドに依存します。次の例を参照してください。

class Stack {
}
console.log([] instanceof Stack); // false
Code language: JavaScript (javascript)

[]配列はStackクラスのインスタンスではないため、この例ではinstanceof演算子はfalseを返します。

[]配列がStackクラスのインスタンスになるようにするには、次のようにSymbol.hasInstanceメソッドを追加します。

class Stack {
    static [Symbol.hasInstance](obj) {
        return Array.isArray(obj);
    }
}
console.log([] instanceof Stack); // true
Code language: JavaScript (javascript)

Symbol.iterator

Symbol.iteratorは、関数がオブジェクトのイテレータを返すかどうかを指定します。

Symbol.iteratorプロパティを持つオブジェクトは、反復可能オブジェクトと呼ばれます。

ES6では、すべてのコレクションオブジェクト(配列SetMap)と文字列は反復可能オブジェクトです。

ES6は、次の例のように反復可能オブジェクトで動作するfor…ofループを提供します。

var numbers = [1, 2, 3];
for (let num of numbers) {
    console.log(num);
}

// 1
// 2
// 3Code language: JavaScript (javascript)

内部的には、JavaScriptエンジンは最初にnumbers配列のSymbol.iteratorメソッドを呼び出して、イテレータオブジェクトを取得します。

次に、iterator.next()メソッドを呼び出し、イテレータオブジェクトのvalueプロパティをnum変数にコピーします。

3回の反復の後、resultオブジェクトのdoneプロパティはtrueになり、ループは終了します。

次のように、System.iteratorシンボルを介してデフォルトのイテレータオブジェクトにアクセスできます。

var iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}Code language: JavaScript (javascript)

デフォルトでは、コレクションは反復可能ではありません。ただし、次の例に示すように、Symbol.iteratorを使用して反復可能にすることができます。

class List {
    constructor() {
        this.elements = [];
    }

    add(element) {
        this.elements.push(element);
        return this;
    }

    *[Symbol.iterator]() {
        for (let element of this.elements) {
            yield  element;
        }
    }
}

let chars = new List();
chars.add('A')
     .add('B')
     .add('C');

// because of the Symbol.iterator
for (let c of chars) {
    console.log(c);
}

// A
// B
// CCode language: JavaScript (javascript)

Symbol.isConcatSpreadable

2つの配列を連結するには、次の例に示すように、concat()メソッドを使用します。

let odd  = [1, 3],
    even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]Code language: JavaScript (javascript)

この例では、結果の配列には両方の配列の単一要素が含まれています。さらに、concat()メソッドは、以下に示すように、配列以外の引数も受け入れます。

let extras = all.concat(5);
console.log(extras); // [1, 3, 2, 4, 5]Code language: JavaScript (javascript)

数値5は配列の5番目の要素になります.

上記の例でわかるように、配列をconcat()メソッドに渡すと、concat()メソッドは配列を個々の要素に展開します。ただし、単一のプリミティブ引数は異なって扱われます。 ES6より前は、この動作を変更できませんでした.

これが、Symbol.isConcatSpreadableシンボルが登場する理由です。

Symbol.isConcatSpreadableプロパティは、オブジェクトがconcat()関数の結果に個別に追加されるかどうかを決定するブール値です。

次の例を考えてみましょう。

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]Code language: JavaScript (javascript)

listオブジェクトは['Learning']配列に連結されます。ただし、個々の要素は展開されません。

concat()メソッドに渡すときにlistオブジェクトの要素が配列に個別に追加されるようにするには、次のようにSymbol.isConcatSpreadableプロパティをlistオブジェクトに追加する必要があります。

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2,
    [Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]Code language: JavaScript (javascript)

Symbol.isConcatSpreadableの値をfalseに設定し、listオブジェクトをconcat()メソッドに渡すと、オブジェクト全体として配列に連結されることに注意してください。

Symbol.toPrimitive

Symbol.toPrimitiveメソッドは、オブジェクトがプリミティブ値に変換されたときに何が起こるかを決定します。

JavaScriptエンジンは、各標準タイプのプロトタイプにSymbol.toPrimitiveメソッドを定義します。

Symbol.toPrimitiveメソッドは、「number」、「string」、「default」の3つの値のいずれかを持つhint引数を取ります。 hint引数は、戻り値の型を指定します。 hintパラメータは、オブジェクトが使用されるコンテキストに基づいてJavaScriptエンジンによって入力されます。

Symbol.toPrimitiveメソッドの使用例を次に示します。

function Money(amount, currency) {
    this.amount = amount;
    this.currency = currency;
}
Money.prototype[Symbol.toPrimitive] = function(hint) {
    var result;
    switch (hint) {
        case 'string':
            result = this.amount + this.currency;
            break;
        case 'number':
            result = this.amount;
            break;
        case 'default':
            result = this.amount + this.currency;
            break;
    }
    return result;
}

var price = new Money(799, 'USD');

console.log('Price is ' + price); // Price is 799USD
console.log(+price + 1); // 800
console.log(String(price)); // 799USDCode language: JavaScript (javascript)

このチュートリアルでは、JavaScriptのSymbolについて、そして一意の値とオブジェクトプロパティにSymbolを使用する方法について学びました。また、Well-known Symbolを使用してオブジェクトの動作を変更する方法も学びました。

このチュートリアルは役に立ちましたか?