size
プロパティを持ち、length
プロパティを持たないのか?ECMAScript 6では、Map
、WeakMap
、Set
、WeakSet
など、いくつかの新しいデータ構造が導入されました。
Mapのキーは任意の値にすることができます。
> const map = new Map(); // create an empty Map
> const KEY = {};
> map.set(KEY, 123);
> map.get(KEY)
123
> map.has(KEY)
true
> map.delete(KEY);
true
> map.has(KEY)
false
[キー、値]のペアを含む配列(または任意のイテラブル)を使用して、Mapの初期データを設定できます。
const
map
=
new
Map
([
[
1
,
'one'
],
[
2
,
'two'
],
[
3
,
'three'
],
// trailing comma is ignored
]);
Setは、一意の要素のコレクションです。
const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]
ご覧のように、コンストラクタにそれらの要素に対するイテラブル(例ではarr
)を渡すと、要素でSetを初期化できます。
WeakMapは、キーのガベージコレクションを妨げないMapです。つまり、メモリリークを心配することなく、オブジェクトにデータを関連付けることができます。例えば…
//----- Manage listeners
const
_objToListeners
=
new
WeakMap
();
function
addListener
(
obj
,
listener
)
{
if
(
!
_objToListeners
.
has
(
obj
))
{
_objToListeners
.
set
(
obj
,
new
Set
());
}
_objToListeners
.
get
(
obj
).
add
(
listener
);
}
function
triggerListeners
(
obj
)
{
const
listeners
=
_objToListeners
.
get
(
obj
);
if
(
listeners
)
{
for
(
const
listener
of
listeners
)
{
listener
();
}
}
}
//----- Example: attach listeners to an object
const
obj
=
{};
addListener
(
obj
,
()
=>
console
.
log
(
'hello'
));
addListener
(
obj
,
()
=>
console
.
log
(
'world'
));
//----- Example: trigger listeners
triggerListeners
(
obj
);
// Output:
// hello
// world
JavaScriptは常に非常に簡素な標準ライブラリを持っていました。値を値にマッピングするためのデータ構造がひどく不足していました。ECMAScript 5で得られる最善の方法は、オブジェクトを悪用することで、文字列から任意の値へのMapです。それでも、いくつかの落とし穴があります。
ECMAScript 6のMap
データ構造を使用すると、任意の値をキーとして使用できるようになり、非常に歓迎されます。
単一のエントリを操作する
> const map = new Map();
> map.set('foo', 123);
> map.get('foo')
123
> map.has('foo')
true
> map.delete('foo')
true
> map.has('foo')
false
Mapのサイズを決定し、クリアする
> const map = new Map();
> map.set('foo', true);
> map.set('bar', false);
> map.size
2
> map.clear();
> map.size
0
キーと値の「ペア」(2つの要素を持つ配列)に対するイテラブルを介してMapを設定できます。1つの可能性は配列(イテラブル)を使用することです。
const
map
=
new
Map
([
[
1
,
'one'
],
[
2
,
'two'
],
[
3
,
'three'
],
// trailing comma is ignored
]);
あるいは、set()
メソッドはチェーン可能です。
const
map
=
new
Map
()
.
set
(
1
,
'one'
)
.
set
(
2
,
'two'
)
.
set
(
3
,
'three'
);
オブジェクトも含め、任意の値をキーにすることができます。
const
map
=
new
Map
();
const
KEY1
=
{};
map
.
set
(
KEY1
,
'hello'
);
console
.
log
(
map
.
get
(
KEY1
));
// hello
const
KEY2
=
{};
map
.
set
(
KEY2
,
'world'
);
console
.
log
(
map
.
get
(
KEY2
));
// world
ほとんどのMap操作では、値がキーのいずれかと等しいかどうかをチェックする必要があります。それらは内部操作SameValueZeroを使用して行われ、===
のように動作しますが、NaN
はそれ自身と等しいとみなします。
まず、===
がNaN
をどのように処理するかを見てみましょう。
> NaN === NaN
false
逆に、他の値と同様に、NaN
をMapのキーとして使用できます。
> const map = new Map();
> map.set(NaN, 123);
> map.get(NaN)
123
===
と同様に、-0
と+0
は同じ値とみなされます。通常、2つのゼロを処理する最善の方法です(“Speaking JavaScript”で詳細が説明されています)。
> map.set(-0, 123);
> map.get(+0)
123
異なるオブジェクトは常に異なるものとみなされます。これは(まだ)設定できません。後でFAQで説明されているとおりです。
> new Map().set({}, 1).set({}, 2).size
2
不明なキーを取得するとundefined
になります。
> new Map().get('asfddfsasadf')
undefined
Mapの反復処理方法を示すために、Mapを設定してみましょう。
const
map
=
new
Map
([
[
false
,
'no'
],
[
true
,
'yes'
],
]);
Mapは要素が挿入された順序を記録し、キー、値、またはエントリを反復処理するときにその順序を尊重します。
keys()
は、Map内のキーに対するイテラブルを返します。
for
(
const
key
of
map
.
keys
())
{
console
.
log
(
key
);
}
// Output:
// false
// true
values()
は、Map内の値に対するイテラブルを返します。
for
(
const
value
of
map
.
values
())
{
console
.
log
(
value
);
}
// Output:
// no
// yes
entries()
は、Mapのエントリを[キー、値]ペア(配列)に対するイテラブルとして返します。
for
(
const
entry
of
map
.
entries
())
{
console
.
log
(
entry
[
0
],
entry
[
1
]);
}
// Output:
// false no
// true yes
デストラクチャリングを使用すると、キーと値に直接アクセスできます。
for
(
const
[
key
,
value
]
of
map
.
entries
())
{
console
.
log
(
key
,
value
);
}
Mapを反復処理するデフォルトの方法はentries()
です。
> map[Symbol.iterator] === map.entries
true
したがって、前のコードスニペットをさらに短くすることができます。
for
(
const
[
key
,
value
]
of
map
)
{
console
.
log
(
key
,
value
);
}
スプレッド演算子(...
)は、イテラブルを配列に変換できます。これにより、Map.prototype.keys()
(イテラブル)の結果を配列に変換できます。
> const map = new Map().set(false, 'no').set(true, 'yes');
> [...map.keys()]
[ false, true ]
Mapもイテラブルであるため、スプレッド演算子を使用してMapを配列に変換できます。
> const map = new Map().set(false, 'no').set(true, 'yes');
> [...map]
[ [ false, 'no' ],
[ true, 'yes' ] ]
Map
メソッドforEach
は、次のシグネチャを持っています。
Map
.
prototype
.
forEach
((
value
,
key
,
map
)
=>
void
,
thisArg
?
)
:
void
最初の引数のシグネチャはArray.prototype.forEach
のコールバックのシグネチャを反映しているため、値が最初に来ます。
const
map
=
new
Map
([
[
false
,
'no'
],
[
true
,
'yes'
],
]);
map
.
forEach
((
value
,
key
)
=>
{
console
.
log
(
key
,
value
);
});
// Output:
// false no
// true yes
配列をmap()
およびfilter()
できますが、Mapにはそのような操作はありません。解決策は次のとおりです。
これがどのように機能するかを示すために、次のMapを使用します。
const
originalMap
=
new
Map
()
.
set
(
1
,
'a'
)
.
set
(
2
,
'b'
)
.
set
(
3
,
'c'
);
originalMap
のマッピング
const
mappedMap
=
new
Map
(
// step 3
[...
originalMap
]
// step 1
.
map
(([
k
,
v
])
=>
[
k
*
2
,
'_'
+
v
])
// step 2
);
// Resulting Map: {2 => '_a', 4 => '_b', 6 => '_c'}
originalMap
のフィルタリング
const
filteredMap
=
new
Map
(
// step 3
[...
originalMap
]
// step 1
.
filter
(([
k
,
v
])
=>
k
<
3
)
// step 2
);
// Resulting Map: {1 => 'a', 2 => 'b'}
ステップ1は、以前に説明したスプレッド演算子(...
)によって実行されます。
Mapを結合するためのメソッドがないため、前のセクションの方法を使用する必要があります。
次の2つのMapを結合してみましょう。
const
map1
=
new
Map
()
.
set
(
1
,
'a1'
)
.
set
(
2
,
'b1'
)
.
set
(
3
,
'c1'
);
const
map2
=
new
Map
()
.
set
(
2
,
'b2'
)
.
set
(
3
,
'c2'
)
.
set
(
4
,
'd2'
);
map1
とmap2
を結合するには、スプレッド演算子(...
)を使用して配列に変換し、それらの配列を連結します。その後、結果をMapに戻します。これらすべてが最初の行で行われます。
> const combinedMap = new Map([...map1, ...map2])
> [...combinedMap] // convert to Array to display
[ [ 1, 'a1' ],
[ 2, 'b2' ],
[ 3, 'c2' ],
[ 4, 'd2' ] ]
Mapに任意の(JSON互換の)データが含まれている場合、キーと値のペア(2要素の配列)の配列としてエンコードすることで、JSONに変換できます。まず、そのエンコード方法を調べましょう。
スプレッド演算子を使用すると、Mapをペアの配列に変換できます。
>
const
myMap
=
new
Map
()
.
set
(
true
,
7
)
.
set
(
{
foo
:
3
}
,
[
'abc'
]
);
>
[
...myMap
]
[
[
true
,
7
]
,
[
{
foo
:
3
},
[
'abc'
]
]
]
Map
コンストラクタを使用すると、ペアの配列をMapに変換できます。
> new Map([[true, 7], [{foo: 3}, ['abc']]])
Map {true => 7, Object {foo: 3} => ['abc']}
この知識を使用して、JSON互換のデータを持つ任意のMapをJSONに変換し、元に戻してみましょう。
function
mapToJson
(
map
)
{
return
JSON
.
stringify
([...
map
]);
}
function
jsonToMap
(
jsonStr
)
{
return
new
Map
(
JSON
.
parse
(
jsonStr
));
}
次のインタラクションは、これらの関数の使用方法を示しています。
>
const
myMap
=
new
Map
()
.
set
(
true
,
7
)
.
set
(
{
foo
:
3
}
,
[
'abc'
]
);
>
mapToJson
(
myMap
)
'
[
[
true
,
7
]
,
[
{
"foo"
:
3
},
[
"abc"
]
]]'
>
jsonToMap
(
'
[
[
true
,
7
]
,
[
{
"foo"
:
3
},
[
"abc"
]
]]'
)
Map
{
true
=>
7,
Object
{
foo
:
3
}
=>
[
'abc'
]
}
Mapにキーとして文字列のみが含まれている場合は、オブジェクトとしてエンコードすることでJSONに変換できます。まず、そのエンコード方法を調べましょう。
次の2つの関数は、文字列Mapとオブジェクトを変換します。
function
strMapToObj
(
strMap
)
{
const
obj
=
Object
.
create
(
null
);
for
(
const
[
k
,
v
]
of
strMap
)
{
// We don’t escape the key '__proto__'
// which can cause problems on older engines
obj
[
k
]
=
v
;
}
return
obj
;
}
function
objToStrMap
(
obj
)
{
const
strMap
=
new
Map
();
for
(
const
k
of
Object
.
keys
(
obj
))
{
strMap
.
set
(
k
,
obj
[
k
]);
}
return
strMap
;
}
これらの2つの関数を使用してみましょう。
> const myMap = new Map().set('yes', true).set('no', false);
> strMapToObj(myMap)
{ yes: true, no: false }
> objToStrMap({yes: true, no: false})
[ [ 'yes', true ], [ 'no', false ] ]
これらのヘルパー関数を使用すると、JSONへの変換は次のようになります。
function
strMapToJson
(
strMap
)
{
return
JSON
.
stringify
(
strMapToObj
(
strMap
));
}
function
jsonToStrMap
(
jsonStr
)
{
return
objToStrMap
(
JSON
.
parse
(
jsonStr
));
}
これはこれらの関数の使用方法の例です。
> const myMap = new Map().set('yes', true).set('no', false);
> strMapToJson(myMap)
'{"yes":true,"no":false}'
> jsonToStrMap('{"yes":true,"no":false}');
Map {'yes' => true, 'no' => false}
コンストラクタ
new Map(entries? : Iterable<[any,any]>)
iterable
パラメータを提供しない場合、空のMapが作成されます。[キー、値]ペアに対するイテラブルを提供する場合、それらのペアを使用してMapにエントリが追加されます。例:
const
map
=
new
Map
([
[
1
,
'one'
],
[
2
,
'two'
],
[
3
,
'three'
],
// trailing comma is ignored
]);
単一のエントリの処理
Map.prototype.get(key) : any
key
がマップされているvalue
を返します。このMapにキーkey
がない場合、undefined
が返されます。Map.prototype.set(key, value) : this
key
であるエントリが既に存在する場合は、更新されます。それ以外の場合は、新しいエントリが作成されます。このメソッドはthis
を返すため、チェーンできます。Map.prototype.has(key) : boolean
Map.prototype.delete(key) : boolean
key
であるエントリがある場合、削除され、true
が返されます。それ以外の場合は、何も起こらず、false
が返されます。すべてエントリの処理
get Map.prototype.size : number
Map.prototype.clear() : void
**反復処理とループ:**エントリがMapに追加された順序で行われます。
Map.prototype.entries() : Iterable<[any,any]>
Map.prototype.forEach((value, key, collection) => void, thisArg?) : void
thisArg
が指定されている場合、各呼び出しにおいて`this`はそれに設定されます。それ以外の場合は、this
はundefined
に設定されます。Map.prototype.keys() : Iterable<any>
Map.prototype.values() : Iterable<any>
Map.prototype[Symbol.iterator]() : Iterable<[any,any]>
Map.prototype.entries
を参照します。WeakMapは、主にMapと同様に機能しますが、以下の違いがあります。
以降のセクションでは、これらの違いについてそれぞれ説明します。
WeakMapにエントリを追加する場合、キーはオブジェクトでなければなりません。
const
wm
=
new
WeakMap
()
wm
.
set
(
'abc'
,
123
);
// TypeError
wm
.
set
({},
123
);
// OK
WeakMapのキーは弱参照されます。通常、いかなる記憶領域(変数、プロパティなど)からも参照されていないオブジェクトは、ガベージコレクションの対象となります。WeakMapのキーは、そのような意味での記憶領域とは見なされません。言い換えると、WeakMapのキーであるオブジェクトは、そのオブジェクトがガベージコレクションされるのを防ぎません。
さらに、キーがなくなると、そのエントリも(最終的には)消えます(ただし、いつ消えるかを検出する方法はありません)。
WeakMapの中身を検査したり、概要を取得したりすることはできません。これには、キー、値、またはエントリを反復処理できないことも含まれます。言い換えると、WeakMapからコンテンツを取得するには、キーが必要です。WeakMapをクリアする方法もありません(回避策として、まったく新しいインスタンスを作成できます)。
これらの制限により、セキュリティ特性が有効になります。Mark Millerの言葉を引用すると、「weakmap/keyペアの値からのマッピングは、weakmapとキーの両方を持っている人だけが観測または変更できます。clear()
を使用すると、WeakMapしか持っていない人がWeakMapとキーから値へのマッピングに影響を与えることができてしまいます。」
さらに、キーが弱参照された状態を保証する必要があるため、反復処理の実装は困難になります。
WeakMapは、ライフサイクルを制御できない(または制御したくない)オブジェクトにデータを関連付けるのに役立ちます。このセクションでは、2つの例を見ていきます。
WeakMapを使用すると、メモリ管理を心配することなく、事前に計算された結果をオブジェクトに関連付けることができます。次の関数countOwnKeys
は例です。これは、WeakMap cache
に以前の結果をキャッシュします。
const
cache
=
new
WeakMap
();
function
countOwnKeys
(
obj
)
{
if
(
cache
.
has
(
obj
))
{
console
.
log
(
'Cached'
);
return
cache
.
get
(
obj
);
}
else
{
console
.
log
(
'Computed'
);
const
count
=
Object
.
keys
(
obj
).
length
;
cache
.
set
(
obj
,
count
);
return
count
;
}
}
この関数をオブジェクトobj
で使用すると、結果は最初の呼び出しに対してのみ計算され、2回目の呼び出しではキャッシュされた値が使用されることがわかります。
>
const
obj
=
{
foo
:
1
,
bar
:
2
}
;
>
countOwnKeys
(
obj
)
Computed
2
>
countOwnKeys
(
obj
)
Cached
2
オブジェクトを変更せずにオブジェクトにリスナーをアタッチしたいとしましょう。オブジェクトobj
にリスナーを追加できます。
const
obj
=
{};
addListener
(
obj
,
()
=>
console
.
log
(
'hello'
));
addListener
(
obj
,
()
=>
console
.
log
(
'world'
));
そして、リスナーをトリガーできます。
triggerListeners
(
obj
);
// Output:
// hello
// world
2つの関数addListener()
とtriggerListeners()
は、次のように実装できます。
const
_objToListeners
=
new
WeakMap
();
function
addListener
(
obj
,
listener
)
{
if
(
!
_objToListeners
.
has
(
obj
))
{
_objToListeners
.
set
(
obj
,
new
Set
());
}
_objToListeners
.
get
(
obj
).
add
(
listener
);
}
function
triggerListeners
(
obj
)
{
const
listeners
=
_objToListeners
.
get
(
obj
);
if
(
listeners
)
{
for
(
const
listener
of
listeners
)
{
listener
();
}
}
}
ここでWeakMapを使用する利点は、オブジェクトがガベージコレクションされると、そのリスナーもガベージコレクションされることです。言い換えると、メモリリークは発生しません。
次のコードでは、WeakMap _counter
と_action
を使用して、Countdown
のインスタンスの仮想プロパティのデータを格納します。
const
_counter
=
new
WeakMap
();
const
_action
=
new
WeakMap
();
class
Countdown
{
constructor
(
counter
,
action
)
{
_counter
.
set
(
this
,
counter
);
_action
.
set
(
this
,
action
);
}
dec
()
{
let
counter
=
_counter
.
get
(
this
);
if
(
counter
<
1
)
return
;
counter
--
;
_counter
.
set
(
this
,
counter
);
if
(
counter
===
0
)
{
_action
.
get
(
this
)();
}
}
}
この手法の詳細については、クラスに関する章を参照してください。
WeakMap
のコンストラクタと4つのメソッドは、対応するMap
のメソッドと同様に機能します。
new
WeakMap
(
entries
?
:
Iterable
<
[
any
,
any
]
>
)
WeakMap
.
prototype
.
get
(
key
)
:
any
WeakMap
.
prototype
.
set
(
key
,
value
)
:
this
WeakMap
.
prototype
.
has
(
key
)
:
boolean
WeakMap
.
prototype
.
delete
(
key
)
:
boolean
ECMAScript 5にもSetデータ構造はありません。2つの回避策があります。
indexOf()
を使用して要素が含まれているかどうかを確認し、filter()
を使用して要素を削除するなどします。これは非常に高速なソリューションではありませんが、実装は容易です。注意すべき点として、indexOf()
は値NaN
を見つけることができません。ECMAScript 6には、任意の値に対して機能し、高速でNaN
を正しく処理するSet
データ構造があります。
単一要素の管理
> const set = new Set();
> set.add('red')
> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false
Setのサイズを決定し、クリアする
> const set = new Set();
> set.add('red')
> set.add('green')
> set.size
2
> set.clear();
> set.size
0
Setを構成する要素を反復処理するイテラブル(たとえば、配列)を使用して、Setを設定できます。
const
set
=
new
Set
([
'red'
,
'green'
,
'blue'
]);
または、add
メソッドはチェーン可能です。
const
set
=
new
Set
().
add
(
'red'
).
add
(
'green'
).
add
(
'blue'
);
new Set()
は最大で1つの引数です Set
コンストラクタは、0個または1個の引数を取ります。
それ以降の引数は無視されるため、予期しない結果につながる可能性があります。
> Array.from(new Set(['foo', 'bar']))
[ 'foo', 'bar' ]
> Array.from(new Set('foo', 'bar'))
[ 'f', 'o' ]
2番目のSetでは、'foo'
(イテラブル)のみを使用してSetが定義されます。
Mapと同様に、要素は===
と同様に比較されますが、NaN
は他の値と同じように扱われます。
> const set = new Set([NaN]);
> set.size
1
> set.has(NaN)
true
要素を2回目に追加しても、効果はありません。
> const set = new Set();
> set.add('foo');
> set.size
1
> set.add('foo');
> set.size
1
===
と同様に、2つの異なるオブジェクトは決して等しいとは見なされません(現在カスタマイズできません。FAQで後述)。
> const set = new Set();
> set.add({});
> set.size
1
> set.add({});
> set.size
2
Setはイテラブルであり、for-of
ループは期待どおりに機能します。
const
set
=
new
Set
([
'red'
,
'green'
,
'blue'
]);
for
(
const
x
of
set
)
{
console
.
log
(
x
);
}
// Output:
// red
// green
// blue
ご覧のとおり、Setは反復処理の順序を保持します。つまり、要素は常に挿入された順序で反復処理されます。
以前に説明したスプレッド演算子(...
)はイテラブルで動作するため、Setを配列に変換できます。
const
set
=
new
Set
([
'red'
,
'green'
,
'blue'
]);
const
arr
=
[...
set
];
// ['red', 'green', 'blue']
これで、配列をSetに変換し、元に戻す簡潔な方法ができました。これにより、配列から重複が削除されます。
const
arr
=
[
3
,
5
,
2
,
2
,
5
,
5
];
const
unique
=
[...
new
Set
(
arr
)];
// [3, 5, 2]
配列とは異なり、Setにはmap()
メソッドとfilter()
メソッドがありません。回避策としては、配列に変換して元に戻す方法があります。
マッピング
const
set
=
new
Set
([
1
,
2
,
3
]);
set
=
new
Set
([...
set
].
map
(
x
=>
x
*
2
));
// Resulting Set: {2, 4, 6}
フィルタリング
const
set
=
new
Set
([
1
,
2
,
3
,
4
,
5
]);
set
=
new
Set
([...
set
].
filter
(
x
=>
(
x
%
2
)
==
0
));
// Resulting Set: {2, 4}
ECMAScript 6のSetには、和集合(例:addAll
)、積集合(例:retainAll
)、差集合(例:removeAll
)を計算するためのメソッドがありません。このセクションでは、その制限を回避する方法について説明します。
和集合(a
∪ b
):Set a
とSet b
の両方の要素を含むSetを作成します。
const
a
=
new
Set
([
1
,
2
,
3
]);
const
b
=
new
Set
([
4
,
3
,
2
]);
const
union
=
new
Set
([...
a
,
...
b
]);
// {1,2,3,4}
パターンは常に同じです。
スプレッド演算子(...
)は、イテラブルなもの(Setなど)の要素を配列に挿入します。したがって、[...a, ...b]
は、a
とb
が配列に変換され、連結されることを意味します。これは[...a].concat([...b])
と同等です。
積集合(a
∩ b
):Set b
にも含まれるSet a
の要素を含むSetを作成します。
const
a
=
new
Set
([
1
,
2
,
3
]);
const
b
=
new
Set
([
4
,
3
,
2
]);
const
intersection
=
new
Set
(
[...
a
].
filter
(
x
=>
b
.
has
(
x
)));
// {2,3}
手順:a
を配列に変換し、要素をフィルタリングし、結果をSetに変換します。
差集合(a
\ b
):Set b
に含まれていないSet a
の要素を含むSetを作成します。この操作は、マイナス(-
)と呼ばれることもあります。
const
a
=
new
Set
([
1
,
2
,
3
]);
const
b
=
new
Set
([
4
,
3
,
2
]);
const
difference
=
new
Set
(
[...
a
].
filter
(
x
=>
!
b
.
has
(
x
)));
// {1}
コンストラクタ
new Set(elements? : Iterable<any>)
iterable
パラメータを指定しない場合、空のSetが作成されます。指定した場合、イテレートされた値がSetの要素として追加されます。例:
const
set
=
new
Set
([
'red'
,
'green'
,
'blue'
]);
単一Set要素
Set.prototype.add(value) : this
value
を追加します。このメソッドはthis
を返すため、チェーン可能です。Set.prototype.has(value) : boolean
value
がこのSetに含まれているかどうかを確認します。Set.prototype.delete(value) : boolean
value
を削除します。すべてのSet要素
get Set.prototype.size : number
Set.prototype.clear() : void
反復処理とループ
Set.prototype.values() : Iterable<any>
Set.prototype[Symbol.iterator]() : Iterable<any>
Set.prototype.values
を指します。Set.prototype.forEach((value, key, collection) => void, thisArg?)
value
とkey
はどちらも要素に設定されるため、このメソッドはMap.prototype.forEach
と同様に動作します。thisArg
が提供された場合、各呼び出しでthis
はそれに設定されます。それ以外の場合は、this
はundefined
に設定されます。Map
との対称性: 以下の2つのメソッドは、SetのインターフェースをMapのインターフェースと似せるためだけに存在します。各Set要素は、キーと値が要素であるMapエントリとして扱われます。
Set.prototype.entries() : Iterable<[any,any]>
Set.prototype.keys() : Iterable<any>
entries()
を使用すると、SetをMapに変換できます。
const
set
=
new
Set
([
'a'
,
'b'
,
'c'
]);
const
map
=
new
Map
(
set
.
entries
());
// Map { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
WeakSet
は、要素のガベージコレクションを妨げないSetです。WeakSetがイテレーション、ループ、クリアを許可しない理由については、WeakMap
に関するセクションを参照してください。
要素を反復処理できないため、WeakSetのユースケースはそれほど多くありません。オブジェクトにマークを付けることができます。
たとえば、プロキシのファクトリ関数がある場合、WeakSetを使用して、そのファクトリによって作成されたオブジェクトを記録できます。
const
_proxies
=
new
WeakSet
();
function
createProxy
(
obj
)
{
const
proxy
=
···
;
_proxies
.
add
(
proxy
);
return
proxy
;
}
function
isProxy
(
obj
)
{
return
_proxies
.
has
(
obj
);
}
完全な例はプロキシに関する章に示されています。
_proxies
はWeakSetである必要があります。通常のSetを使用すると、プロキシが参照されなくなってもガベージコレクションが妨げられるためです。
Domenic Denicolaは、クラスFoo
が、そのメソッドを自身によって作成されたインスタンスのみに適用する方法を示しています。
const
foos
=
new
WeakSet
();
class
Foo
{
constructor
()
{
foos
.
add
(
this
);
}
method
()
{
if
(
!
foos
.
has
(
this
))
{
throw
new
TypeError
(
'Incompatible object!'
);
}
}
}
WeakSet
のコンストラクタと3つのメソッドは、対応するSet
のものと同じように動作します。
new
WeakSet
(
elements
?
:
Iterable
<
any
>
)
WeakSet
.
prototype
.
add
(
value
)
WeakSet
.
prototype
.
has
(
value
)
WeakSet
.
prototype
.
delete
(
value
)
size
プロパティを持ち、length
プロパティを持たないのですか? 配列はエントリの数を数えるためにlength
プロパティを持ちます。MapとSetは異なるプロパティsize
を持ちます。
この違いの理由は、length
がシーケンス(配列のようにインデックス可能なデータ構造)用であり、size
が主に順序付けられていないコレクション(MapとSetのような)用であるためです。
MapのキーとSetの要素の等価性を構成する方法があれば便利ですが、その機能は適切かつ効率的に実装するのが困難なため、延期されています。
キーを使用してMapから何かを取得する場合、キーがMapにない場合に返されるデフォルト値を指定したいことがあります。ES6のMapでは、これを直接行うことはできません。しかし、次のコードに示すように、Or
演算子(||
)を使用できます。countChars
は、文字を出現回数にマッピングするMapを返します。
function
countChars
(
chars
)
{
const
charCounts
=
new
Map
();
for
(
const
ch
of
chars
)
{
ch
=
ch
.
toLowerCase
();
const
prevCount
=
charCounts
.
get
(
ch
)
||
0
;
// (A)
charCounts
.
set
(
ch
,
prevCount
+
1
);
}
return
charCounts
;
}
A行では、ch
がcharCounts
にない場合、get()
がundefined
を返すため、デフォルトの0
が使用されます。
文字列以外のものをあらゆる種類のデータにマッピングする場合は、選択肢がありません。Mapを使用する必要があります。
ただし、文字列を任意のデータにマッピングする場合は、オブジェクトを使用するかどうかを決定する必要があります。大まかな一般的なガイドラインは次のとおりです。
obj.key
map.get(theKey)
Mapのキーは、主に値で比較する場合に意味があります(同じ「内容」は、同じ同一性ではなく、2つの値が等しいと見なされることを意味します)。これにはオブジェクトは含まれません。1つのユースケースは、オブジェクトに外部からデータを添付することですが、このユースケースは、キーが消えるとエントリも消えるWeakMapの方が適しています。