先日、O'Reillyから出版されている「JavaScript 第7版」を読んでいると見慣れぬプロパティを見つけました。
それが Symbol です。
文字列ではないプロパティとしてES6から導入されたもののようで、
今まで見たことがありませんでした。
今回はSymbolとは一体何なのか、
どのように使用するのかを調べてまとめてみました。
Symbolとは
Symbolとは何なのか、まずは「MDN Web Docs」を確認してみました。
JavaScriptでは、シンボルはプリミティブ値です。
Symbol データ型を持つ値は「シンボル値」として見ることができます。 JavaScript の実行時環境では、シンボル値は Symbol 関数を呼び出すことで生成され、動的に無名の一意の値を生み出します。シンボルはオブジェクトプロパティとして使用されることがあります。
Symbol は任意で説明文を持つことができますが、これはデバッグ目的のみに利用されます。
いまいち分からないので、サンプルを見てみます。
let Sym1 = Symbol("Sym");
let Sym2 = Symbol("Sym");
console.log(Sym1 === Sym2); // falseを返す
なるほど。
一意の値を表すので、中身の説明文(値)が同じでも判定はfalse
になるのか。
JavaScript の大部分の値は文字列への暗黙的な変換に対応しています。例えば alert はほぼすべての値で表示することができ、動作します。シンボルは特殊です。自動変換は行われません。
let Sym = Symbol("Sym");
alert(Sym); // TypeError: Cannot convert a Symbol value to a string
alert(Sym.toString()); // Symbol(Sys)
Symbol()
関数に文字列の引数を渡した場合、toString()
メソッドを使用することでシンボルを表示することができるようです。
let Sym = Symbol("Sym");
alert(Sym.description); // Sys
そして文字列の値を参照したい場合は、description
というプロパティを使うようです。
Symbol()
は一意の値を生み出すというのがメリットのようです。
グローバルシンボルレジストリー
Symbol()
に対し、同じ文字列を使って呼び出した場合、常に同じ値を返すSymbol.for()
およびSymbol.keyFor()
という関数があります。
グローバルシンボルレジストリーを扱うメソッドは Symbol.for() および Symbol.keyFor() があります。これらは、グローバルシンボルテーブル(または「レジストリー」)と実行時環境の間を仲介します。シンボルレジストリーは、主に JavaScript コンパイラーインフラストラクチャが構築しており、その中のシンボルの内容は、 JavaScript 実行時インフラストラクチャでは上記のメソッド以外で扱えません。
Symbol.for(tokenString)はレジストリー内のシンボル値を返し、
Symbol.keyFor(symbolValue)はレジストリーからトークンの文字列を返します。
let s = Symbol.for("sample");
let t = Symbol.for("sample");
console.log(s === t); // ture
s.toString(); // Symbol(sample)
Symbol.keyFor(t); // sample
Symbol.for()
の場合、tokenStringが同じの場合は、同じ値を返します。
では実際、これらをどのように使用すればよいでのでしょうか。
使い方
プロパティキーとして使用する
Symbol()
は、オブジェクトのプロパティキーとして使用することができます。
const lunch = Symbol();
const order = Symbol();
const obj = {
lunch: "オムライス", // lunchという名のプロパティ
[lunch]: "カレーライス", // シンボル値lunchのプロパティ
order: function(){ console.log( this.lunch + "を1つお願いします。" ) },
[order]: function(){ console.log( this[lunch] + "を1つお願いします。" ) },
}
console.log( obj.lunch ); //オムライス
console.log( obj[lunch] ); //カレーライス
obj.order(); //オムライスを1つお願いします。
obj[order](); //カレーライスを1つお願いします。
プロパティのキーとして利用する場合、下記のメリットがあります。
- キーの衝突の回避
- 改変などをされるリスクの低減
また、プロパティを表示したくない場合、Symbol()
を使うことでキーを隠蔽することができます。
下記の例では、キーである「wordCount」「javascript」ともに表示されています。
const dictionary = {
wordCount: 1,
javascript: "a programming language..."
};
Object.keys(dictionary); // Array["wordCount", "javascript"]
しかし、Symbol()
を使用する場合と下記のようになります。
const wordCountSymbol = Symbol('dictionaryWordCount');
const dictionary = {
[wordCountSymbol]: 1,
javascript: "a programming language..."
};
Object.keys(dictionary); // Array["javascript"]
このように、Symbol()
を使用するとObject.keys()
では表示されません。
と思ったのですが、実はReflect.ownKeys()
を使用することでSymbol()
のキーでも表示できてしまうようです。。。
const wordCountSymbol = Symbol('dictionaryWordCount');
const dictionary = {
[wordCountSymbol]: 1,
javascript: "a programming language..."
};
console.log(Reflect.ownKeys(dictionary));
// [Symbol('dictionaryWordCount'), "javascript"]
導入の理由
名前の衝突を避けたり、隠蔽したいだけならわざわざSymbol()
を使用しなくても回避することは可能です。
ではなぜES6で導入されたのでしょうか。
詳しい理由が下記の記事で説明されています。
どうやら「互換性」がSymbol()
導入の理由のようです。
例えば、既存のメソッドと同じ名前のメソッドを使用してしまうと、実装しているプログラムに影響を与えてしまいます。
しかしSymbol()
を使用した場合、メソッドを一意の値で識別することができるため、メソッド名の重複を気にせずに拡張することができます。
const hobby = Symbol("hobby");
String.prototype[hobby] = function() {
console.log("My hobby is" + this);
}
export {hobby as default};
const hobby = Symbol("hobby");
String.prototype[hobby] = function() {
console.log("My hobby is" + this + "★");
}
export {hobby as default};
import hobby1 from "./hobby1.js";
import hobby1 from "./hobby2.js";
"counting stars"[hobby1](); // My hobby is counting stars
"counting stars"[hobby2](); // My hobby is counting stars★
最後に
正直、頻繁に使用することはないかなという印象を受けました。
今回紹介した使用方法以外に、
便利な使用方法があれば教えてください。
それでは。
参考サイト