ゆるふわ技術日誌

エンジニア見習いの悪戦苦闘日記

ES6で追加されたSymbolって結局なんなのか

JavaScript基礎力強化を図っています。

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

この本を読み返しながら、知識のないところを学習するシリーズ。


ES6から新たにSymbolという型のデータが追加されました。

developer.mozilla.org

Symbol() 関数は常に一意の値を返します。symbol 値はオブジェクトのプロパティ識別子として使われます。symbol 型はこの目的のためだけに使われます。

とのことなので、実際にどんな感じで使われるのかをみていこうと思います。

シンボルの特徴

シンボルはSymbol()関数で作ることができます。

先頭が大文字なのでクラスっぽいですが、new Symbol()は使えず、 エラーになります。

Symbol()は呼ばれる度に一意な値を返します。つまり

Symbol() === Symbol() // -> false

となります。

使われ方 その①

MDNにもあるように、シンボルはオブジェクトのプロパティ識別子(=キー)として用いることができます。具体的には

const sym = Symbol()
const obj = {
  [sym]: 'hoge'
}

ということができます。([sym]としないと、symという名前のキーになってしまうので注意)

これが具体的に役に立つケースは、既存のオブジェクトやクラスに対して、プロパティを追加したりするパターン。

Symbol登場以前は既存のオブジェクトやクラスに対してプロパティを追加する際に、キーが競合する可能性があるのを回避する手段がありませんでしたが、Symbolは作成する度に一意な値となることが保証されているので、安全に行うことができます。

const TATEGAKI = Symbol();

// Stringのインスタンスメソッドに縦書き(1文字ごとに改行)にするメソッドを追加する
String.prototype[TATEGAKI] = function () {
    return Array.from(this).join('\n')
}

console.log("文字列"[TATEGAKI]())

これでESの仕様で縦書きにするメソッドが実装されても(笑)自分の作った縦書きメソッドと衝突する恐れはないので、実行系が新しくなった瞬間に既存のコードが壊れるといった現象を回避することができます。

使われ方 その②

シンボルにはSymbol()を使って自分で作る値だけではなく、事前に定義された値があります。

その中の一つに、Symbol.iteratorという値があります。この値は、イテレート可能なオブジェクト(StringやArray、Map、Setなど)がイテレータを持つときのキーとして使われています。

"Stringはイテレータを持っています"[Symbol.iterator]; // -> function
new Date()[Symbol.iterator]; // -> undefined

なぜシンボルをキーに使うかについては、以下の記事が大変わかりやすかったです。

qiita.com

ざっくりとまとめると、

  • JSには特殊メソッド(使用者が上書きしてはいけないメソッド)を定義する方法がない
  • いきなり、イテレータを返すメソッドの名前を仕様で決め打ちにしてしまうと、正常に動かないプログラムが発生する可能性がある
  • そこで定義済みのシンボルを用意し、その値をキーとして用いることで、安全に特殊メソッドを実装することにした。

という感じ。言語の仕様を考えるような人たちって、本当に頭いいんですね…と思いました。