概要: このチュートリアルでは、JavaScriptのプリミティブ型であるSymbolについて、そしてSymbolを効果的に使用する方法について学びます。
Symbolの作成
ES6では、新しいプリミティブ型としてSymbol
が追加されました。 数値、真偽値、null、undefined、文字列などの他のプリミティブ型とは異なり、Symbol型にはリテラル形式がありません。
新しいSymbolを作成するには、以下の例のようにグローバル関数Symbol()
を使用します。
let s = Symbol('foo');
Code language: JavaScript (javascript)
Symbol()
関数は、呼び出されるたびに新しい一意の値を作成します。
console.log(Symbol() === Symbol()); // false
Code language: JavaScript (javascript)
Symbol()
関数は、オプションの引数としてdescription
を受け入れます。 description
引数は、Symbolをより記述的にします。
次の例では、firstName
とlastName
の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); // symbol
Code language: JavaScript (javascript)
Symbolはプリミティブ値であるため、new
演算子を使用してSymbolを作成しようとすると、エラーが発生します。
let s = new Symbol(); // error
Code 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); // true
Code 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)); // undefined
Code language: JavaScript (javascript)
Symbolの使用方法
A) 一意の値としてのSymbolの使用
コードで文字列または数値を使用する場合は、代わりにSymbolを使用する必要があります。たとえば、タスク管理アプリケーションでステータスを管理する必要があるとします。
ES6以前は、タスクのさまざまなステータスを表すために、open
、in progress
、completed
、canceled
、on 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)
その後、obj
がtype
オブジェクトのインスタンスかどうかを判断するメソッドに依存します。次の例を参照してください。
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では、すべてのコレクションオブジェクト(配列、Set、Map)と文字列は反復可能オブジェクトです。
ES6は、次の例のように反復可能オブジェクトで動作するfor…ofループを提供します。
var numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}
// 1
// 2
// 3
Code 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
// C
Code 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)); // 799USD
Code language: JavaScript (javascript)
このチュートリアルでは、JavaScriptのSymbolについて、そして一意の値とオブジェクトプロパティにSymbolを使用する方法について学びました。また、Well-known Symbolを使用してオブジェクトの動作を変更する方法も学びました。