【今更ES2015を追ってみるシリーズ 3】Symbol編
不定期連載その3です。
今回はSymbolについて調べたのでその辺を書きます。
前回までの記事
Symbolとは?
ES2015から追加された新しいプリミティブ型です。目的としては以下が考えられます。
主目的
- アプリケーション内のオブジェクトで利用する一意のキーを作成する
- 何かしらの名前として利用するもの
副次効果
- オブジェクトのアクセスコントロールの観点からprivateなプロパティを作成する
- 後述しますが完全なprivateというわけにはいかないよう
公式によると
- The Symbol type is the set of all non-String values that may be used as the key of an Object property (6.1.7).
- Each possible Symbol value is unique and immutable.
- Each Symbol value immutably holds an associated value called "Description" that is either undefined or a String value.
ECMAScript 2015 Language Specification – ECMA-262 6th Edition
- キーとして利用する
- 変更不可(イミュータブル)で一意な値
といったところですね。
作り方
var sym1 = Symbol(); var sym2 = Symbol('hoge');
hogeはdescription(説明)なのでオプションです。sym1.toStringしたら初期化したきのdescriptionが読めます。
これはTypeError
var sym = new Symbol('hoge');
使い方
オブジェクトのキーにした例です。下記例の場合当然ですがobj['hoge']ではアクセスできません。
var sym = Symbol('hoge'); var obj = {}; obj[sym] = 'fuga' console.log(obj[sym]); // fuga console.log(obj['fuga']); // undefined
同じdescriptionで初期化しても別のキーとして扱われます。(やらないけど)
var sym1 = Symbol('hoge'); var sym2 = Symbol('hoge'); var obj = {}; obj[sym1] = 'fuga' obj[sym2] = 'piyo' console.log(obj[sym1]); // fuga console.log(obj[sym2]); // piyo
for..in構文で回してもキーはでてきません
var sym1 = Symbol(); var sym2 = Symbol(); var obj = {}; obj[sym1] = 'fuga' obj[sym2] = 'piyo' obj['foo'] = 1; for (var key in obj) { console.log(key); // foo }
Object.keysでも出てこない
// 略 console.log(Object.keys(obj)); // Array [ "foo" ]
プライベートプロパティを作る
Symobolをキーにしたオブジェクトの動作を応用することで簡単にprivateメンバーを持つオブジェクト作成することができます。
var Person = (function() { var nameSymbol = Symbol('name'); function Person(name) { this[nameSymbol] = name; } Person.prototype.getName = function() { return this[nameSymbol]; }; return Person; }()); var p = new Person('John'); console.log(p.getName()); // John delete p.name console.log(p.getName()); // John
※出典 - Private properties in JavaScript — Curiosity driven
なのですが... Object.getOwnPropertySymbols(object)というメソッドでobject内に含まれるキーとなっているSymbolを配列形式で取得することができます。つまり、下のコードのようにSymbolを取得してdeleteすることができるので完全にはprivateになっているわけではなさそうです。
var Person = (function() { var nameSymbol = Symbol('name'); function Person(name) { this[nameSymbol] = name; } Person.prototype.getName = function() { return this[nameSymbol]; }; return Person; }()); var p = new Person('John'); var pSym = Object.getOwnPropertySymbols(p)[0]; delete p[pSym]; console.log(p[pSym]); // undefined
Global領域で利用する
Global領域で利用するには以下の2つのメソッドを利用します。 またこの方法で作成されたSymbolはGlobalSymbolRegistryに登録され複数のGlobalにまたがるコードの実行環境全てで共有されます。
- Symbol.for('key名');
- 登録
- Symbol.keyFor(symbol);
- 利用
実行環境全てで共有されというのがどういうことかと言うと、ブラウザでいうと親ウィンドウとフレームでデータを共有することができます。
親ウィンドウ
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <iframe id="frame" src="test2.html"></iframe> <script> (function() { var frame = document.getElementById('frame'); var sym = Symbol.for('hoge'); frame.addEventListener('load', function() { console.log(Symbol.keyFor(sym)) console.log(frame.contentWindow.sym === sym); // ここがtrueになる }); })(); </script> </body> </html>
フレーム
<html lang="ja"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> window.sym = Symbol.for('hoge'); </script> </body> </html>
Well-Known Symbols
Well-Known SymbolsはES組み込みのシンボルであり、ES内部のメソッドであったりプリミティブ値を表しています。
特定のオブジェクトを拡張する際のプロパティのキーに利用することが典型的な使い方のようです。
例を見てみましょう。
var obj = { a: 1, b: 2, c: 3, [Symbol.iterator]() { var i = 0; var keys = Object.keys(this); var keyLen = keys.length; var that = this; return { next() { var ret = (i <= keyLen) ? {value: that[keys[i]], done: false} : {value: undefined, done: true}; i++; return ret; } } } } for (var i of obj) { console.log(i); }
「for..in文でループしろよ」というツッコミはさておき、上記の例ではobjを拡張してfor..of文でループできるようにしています。
iteratorに関する細かい説明はまた書きますが、for..of文のES内部の動作は[Symbol.iterator]メソッドをコールそのメソッドが返すiteratorが持つnextメソッドをコールします。そのため[Symbol.iterator]且つそのメソッドが返すiteratorに含まれるnextメソッドが実装されていない状態のObjectではfor..of文を利用することができません。
つまることろ上記の[Symbol.iterator]はインターフェイスに近いかたちになります。ES2015公式の仕様書には書かれていませんがiterator protocolといたるところで説明されているのはそのためです。
こことか。
Iteration protocols - JavaScript | MDN
@@表記について
Well-Known Symbolsについてい調べていると@@toPrimitiveの様に@@付きの表記で書かれている説明をよく目にします。 最初protocolとしてオブジェクトに組み込むときにこの書式で実装できるようになるのかと思ったのですがどうやら仕様書上だけの表記で単にWell-known Symbolでかあることを明示的にしてあるだけの話でした。
まとめ
Symbolでキーを作成するとなるとES2015が主流なれば、オブジェクト作成時の書き方がかなり変わってきそうですね。ただ、そうなるとリテラルで書けるようになってほしいですが。 またWell-known Symbolを利用してオブジェクトに様々な拡張ができるようになればより表現の幅が広がってきそうなイメージを得られます。(いよいよプログラマとしてのスキルがついていかない...)