keyof
とPropertyKey
型
keyof
演算子はTypeScript の機能の1つで、オブジェクトのプロパティキーをユニオン型で取得できます。
このkeyof
によって得られる型はすべてPropertyKey
型の部分型、つまりstring | number | symbol
型の部分型となります。
declare type PropertyKey = string | number | symbol;
オブジェクトのプロパティキーとしてsymbol
型を使うこともできるわけですが、どのような用途があるのでしょうか。
この記事ではsymbol
型のキーを持つオブジェクトについてみていきたいと思います。
symbol
型
symbol
はプリミティブ値の1つで、「動的に一意の識別値を生成」「文字列への自動変換がされない」などの特徴を持ちます。
symbol
の識別値はJavaScriptから直接アクセスや表示ができない内部的な値となっています。
オブジェクトとsymbol
型
symbol
は一意の値となるため、オブジェクトのプロパティキーとして使用しても他のキーと競合しないというメリットがあります。
const key1 = Symbol('foo')
const key2 = Symbol('foo')
const obj = {
[key1]: 123,
[key2]: 234
}
console.log(obj[key1])
console.log(obj[key2])
//@ts-ignore
console.log(obj[Symbol('foo')])
123
234
undefined
プロパティキーの競合はしなくなるものの、可読性はあまり高くなく、普段の開発で見かけることはあまりなさそうですね。
主にライブラリやフレームワーク開発で活用されています。
symbol
プロパティは列挙されない
オブジェクトにはObject.keys()
やObject.entries()
といった列挙メソッドがあります。
「列挙可能な」プロパティキーや値を配列の形で返しますので、オブジェクトの各プロパティぞれぞれに処理を行いたいときなどに便利なメソッドです。
const fish = {
tuna: "maguro",
salmon: "sake",
octopus: "tako",
}
console.log(Object.keys(fish))
// [ 'tuna', 'salmon', 'octopus' ]
console.log(Object.entries(fish))
// [ [ 'tuna', 'maguro' ], [ 'salmon', 'sake' ], [ 'octopus', 'tako' ] ]
symbol
型のプロパティキーと列挙
symbol
型のプロパティキーを持つオブジェクトに列挙メソッドを適用しても、symbol
型のキーを持つプロパティは列挙されません。
symbol
型のキーは、「列挙可能な」プロパティキーではないわけです。
(ご指摘いただいた通り、列挙メソッドによって列挙されることと「列挙可能」であることは異なるものです。)
const squidSymbol = Symbol('squid')
const fish = {
tuna: "maguro",
salmon: "sake",
[squidSymbol]: 'ika',
octopus: "tako",
}
console.log(Object.keys(fish))
// [ 'tuna', 'salmon', 'octopus' ]
console.log(Object.entries(fish))
// [ [ 'tuna', 'maguro' ], [ 'salmon', 'sake' ], [ 'octopus', 'tako' ] ]
このプログラムにはsymbol
型のキーをもつプロパティを追加していますが、コンソール上に表示される内容は先ほどのプログラムと同じものとなっています。
列挙させたくないプロパティをオブジェクトに持たせたいときには、symbol
型のキーをもたせることで隠蔽できるわけですね。
ちなみに、Object.getOwnPropertyNames()
というメソッドを使用すると「列挙不可能な」キーを含めて取得することができますが、このメソッドを使用した場合にも、symbol
型のキーをもつプロパティは列挙されません。
const squidSymbol = Symbol('squid')
const fish = {
tuna: "maguro",
salmon: "sake",
[squidSymbol]: 'ika',
octopus: "tako",
}
console.log(Object.getOwnPropertyNames(fish))
// [ 'tuna', 'salmon', 'octopus' ]
symbol
型のキーを列挙するには
symbol
型のキーはあらゆるメソッドから隠蔽されるわけではなく、Reflect.ownKeys()
などを使うと確認することができます。
const squidSymbol = Symbol('squid')
const fish = {
tuna: "maguro",
salmon: "sake",
[squidSymbol]: 'ika',
octopus: "tako",
}
Object.defineProperty(fish, 'crub', {
value: 'kani'
});
console.log(Object.keys(fish))
// [ 'tuna', 'salmon', 'octopus' ]
console.log(Object.getOwnPropertyNames(fish))
// [ 'tuna', 'salmon', 'octopus', 'crub' ]
console.log(Reflect.ownKeys(fish))
// [ 'tuna', 'salmon', 'octopus', 'crub', Symbol(squid) ]
このプログラムでは、キーがsymbol
型ではない列挙不可能なプロパティをObject.defineProperty()
によって追加しています1。
symbol
型のキーを使用したとしてもプロパティを完全に隠蔽できるわけではないため、ライブラリやフレームワーク開発など内部のみでしか使用しないプロパティが出てくる場面では有用ですが、セキュリティの向上を目的に使用することにはあまり適していなさそうですね。
-
defineProperty()
のenumerable
のデフォルト値はfalse
なので、このメソッドでプロパティを追加するだけで列挙不可能なプロパティとなります。 ↩