size
而不是 length
?ECMAScript 6 中新增了以下四种数据结构:Map
、WeakMap
、Set
和 WeakSet
。
映射的键可以是任意值
> 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
您可以使用包含 [键,值] 对的数组(或任何可迭代对象)来设置映射中的初始数据
const
map
=
new
Map
([
[
1
,
'one'
],
[
2
,
'two'
],
[
3
,
'three'
],
// trailing comma is ignored
]);
集合是唯一元素的集合
const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]
如您所见,如果将可迭代对象(示例中的 arr
)传递给构造函数,则可以使用元素初始化集合。
弱映射是一种映射,它不会阻止对其键进行垃圾回收。这意味着您可以将数据与对象相关联,而不必担心内存泄漏。例如
//----- 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 中,您能获得的最佳选择是字符串到任意值的映射,方法是滥用对象。即使那样,也有一些陷阱可能会让您绊倒。
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
确定映射的大小并清除它
> const map = new Map();
> map.set('foo', true);
> map.set('bar', false);
> map.size
2
> map.clear();
> map.size
0
您可以通过键值“对”(包含 2 个元素的数组)的可迭代对象来设置映射。一种可能性是使用数组(它是可迭代的)
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
大多数映射操作都需要检查一个值是否等于其中一个键。它们通过内部操作 SameValueZero 来实现,该操作的工作方式类似于 ===
,但将 NaN
视为与其自身相等。
让我们首先看看 ===
如何处理 NaN
> NaN === NaN
false
相反,您可以像使用任何其他值一样在映射中使用 NaN
作为键
> const map = new Map();
> map.set(NaN, 123);
> map.get(NaN)
123
与 ===
一样,-0
和 +0
被视为相同的值。这通常是处理两个零的最佳方式(详细信息在“Speaking JavaScript”中解释)。
> map.set(-0, 123);
> map.get(+0)
123
不同的对象始终被视为不同的。这是无法配置的(目前),如常见问题解答中稍后所述。
> new Map().set({}, 1).set({}, 2).size
2
获取未知键会产生 undefined
> new Map().get('asfddfsasadf')
undefined
让我们设置一个映射来演示如何对其进行迭代。
const
map
=
new
Map
([
[
false
,
'no'
],
[
true
,
'yes'
],
]);
映射记录插入元素的顺序,并在迭代键、值或条目时遵循该顺序。
keys()
返回映射中键的可迭代对象
for
(
const
key
of
map
.
keys
())
{
console
.
log
(
key
);
}
// Output:
// false
// true
values()
返回映射中值的可迭代对象
for
(
const
value
of
map
.
values
())
{
console
.
log
(
value
);
}
// Output:
// no
// yes
entries()
将映射的条目作为 [键,值] 对(数组)的可迭代对象返回。
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
);
}
迭代映射的默认方式是 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 ]
映射也是可迭代的,这意味着展开运算符可以将映射转换为数组
> 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()
数组,但映射没有这样的操作。解决方案是
我将使用以下映射来演示它是如何工作的。
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 由展开运算符 (...
) 执行,我之前已经解释过。
没有用于合并映射的方法,这就是为什么必须使用上一节中的方法来实现的原因。
让我们合并以下两个映射
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
,我通过展开运算符 (...
) 将它们转换为数组,并将这些数组连接起来。之后,我将结果转换回映射。所有这些都在第一行完成。
> const combinedMap = new Map([...map1, ...map2])
> [...combinedMap] // convert to Array to display
[ [ 1, 'a1' ],
[ 2, 'b2' ],
[ 3, 'c2' ],
[ 4, 'd2' ] ]
如果映射包含任意(JSON 兼容)数据,我们可以通过将其编码为键值对数组(2 元素数组)将其转换为 JSON。让我们首先研究如何实现这种编码。
展开运算符 允许您将映射转换为键值对数组
>
const
myMap
=
new
Map
()
.
set
(
true
,
7
)
.
set
(
{
foo
:
3
}
,
[
'abc'
]
);
>
[
...myMap
]
[
[
true
,
7
]
,
[
{
foo
:
3
},
[
'abc'
]
]
]
Map
构造函数允许您将键值对数组转换为映射
> new Map([[true, 7], [{foo: 3}, ['abc']]])
Map {true => 7, Object {foo: 3} => ['abc']}
让我们使用这些知识将任何包含 JSON 兼容数据的映射转换为 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'
]
}
只要映射仅包含字符串作为键,就可以通过将其编码为对象将其转换为 JSON。让我们首先研究如何实现这种编码。
以下两个函数在字符串映射和对象之间转换
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
;
}
让我们使用这两个函数
> 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
,则会创建一个空映射。如果您确实提供了 [键,值] 对的可迭代对象,则这些对将用于向映射添加条目。例如
const
map
=
new
Map
([
[
1
,
'one'
],
[
2
,
'two'
],
[
3
,
'three'
],
// trailing comma is ignored
]);
处理单个条目
Map.prototype.get(key) : any
key
映射到的 value
。如果此映射中没有键 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.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 添加条目,则键必须是对象。
const
wm
=
new
WeakMap
()
wm
.
set
(
'abc'
,
123
);
// TypeError
wm
.
set
({},
123
);
// OK
WeakMap 中的键是弱引用的:通常,任何存储位置(变量、属性等)都没有引用的对象都可以被垃圾回收。从这个意义上说,WeakMap 键不计为存储位置。换句话说:一个对象作为 WeakMap 中的键不会阻止该对象被垃圾回收。
此外,一旦键消失,其条目也将消失(最终会消失,但无论如何都无法检测到何时消失)。
无法检查 WeakMap 的内部结构,也无法获取它们的概览。这包括无法迭代键、值或条目。换句话说:要从 WeakMap 中获取内容,您需要一个键。也没有办法清除 WeakMap(作为一种解决方法,您可以创建一个全新的实例)。
这些限制实现了一种安全属性。引用 Mark Miller 的话:“只有同时拥有 WeakMap 和键的人才能观察或影响从 WeakMap/键对到值的映射。使用 clear()
,只有 WeakMap 的人就可以影响 WeakMap 和键到值的映射。”
此外,迭代将难以实现,因为您必须保证键保持弱引用。
WeakMap 对于将数据与您无法(或不想)控制其生命周期的对象相关联非常有用。在本节中,我们将看两个例子
使用 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
一起使用,您可以看到结果仅在第一次调用时计算,而第二次调用则使用缓存值
>
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
这两个函数 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
的构造函数和四种方法的工作方式与其 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 数据结构。有两种可能的解决方法
indexOf()
检查它是否包含元素,通过 filter()
删除元素等。这不是一个非常快的解决方案,但它很容易实现。需要注意的一个问题是 indexOf()
找不到值 NaN
。ECMAScript 6 具有数据结构 Set
,它适用于任意值,速度快并且可以正确处理 NaN
。
管理单个元素
> 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()
最多只有一个参数 Set
构造函数有零个或一个参数
其他参数将被忽略,这可能会导致意外结果
> Array.from(new Set(['foo', 'bar']))
[ 'foo', 'bar' ]
> Array.from(new Set('foo', 'bar'))
[ 'f', 'o' ]
对于第二个 Set,仅使用 'foo'
(它是可迭代的)来定义 Set。
与 Map 一样,元素的比较方式类似于 ===
,但 NaN
与任何其他值一样。
> const set = new Set([NaN]);
> set.size
1
> set.has(NaN)
true
再次添加元素无效
> const set = new Set();
> set.add('foo');
> set.size
1
> set.add('foo');
> set.size
1
与 ===
类似,两个不同的对象永远不会被认为是相等的(目前无法自定义,稍后将在常见问题解答中解释)
> 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,其中包含 Set a
中也存在于 Set b
中的元素。
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,其中包含 Set a
中不存在于 Set b
中的元素。此操作有时也称为减法 (-
)。
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
添加到此 Set。此方法返回 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
的对称性:以下两种方法仅存在是为了使 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
的构造函数和三种方法的工作方式与其 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
用于主要无序的集合,例如映射和集合。
如果有一种方法可以配置哪些映射键和哪些集合元素被认为是相等的,那就太好了。但该功能已被推迟,因为它难以正确有效地实现。
如果您使用键从映射中获取内容,您有时希望指定一个默认值,以便在键不在映射中时返回该值。ES6 映射不允许您直接这样做。但您可以使用 Or
运算符 (||
),如下面的代码所示。countChars
返回一个将字符映射到出现次数的映射。
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
。
如果您将字符串以外的任何内容映射到任何类型的数据,则别无选择:您必须使用映射。
但是,如果您要将字符串映射到任意数据,则必须决定是否使用对象。一个粗略的通用准则是
obj.key
map.get(theKey)
如果映射键是按值比较的(相同的“内容”意味着两个值被认为是相等的,而不是相同的身份),那么它们才有意义。这排除了对象。有一种用例——将数据外部附加到对象,但这种用例最好由 WeakMap 来处理,在 WeakMap 中,当键消失时,条目也会消失。