es6入门(四)

一、Set

1.1 基本用法

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成 Set 数据结构。

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4

Set可以接收一个数组或实现了Iterable接口的对象作为参数,用来初始化Set。例如:

const s1 = new Set([1, 2, 3, 4, 4]);
const s2 = new Set(document.querySelectorAll('div'));

利用上面Set特点,可以实现把数组重复的元素去除。

var s = [...new Set([1, 1, 2, 2, 3, 3])]
s.map(x => console.log(x)) // 分别输出1, 2, 3

Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===)。因此,5和’5’被认为是两个不同的元素。

1.2 基本属性和方法

Set提供了以下属性:
Set.prototype.constructor:构造函数,默认是Set函数;
Set.prototype.size:返回set实例的成员个数;

Set提供了以下方法:
Set.prototype.add(value):向Set添加一个元素,并返回Set本身;
Set.prototype.delete(value):删除某个元素,返回一个布尔值,表示是否删除成功;
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set成员;
Set.prototype.clear():清除Set所有成员,没有返回值;

Set.prototype.values():返回所有值的遍历器;
Set.prototype.keys():返回所有键的遍历器,但是由于Set没有键只有值,所以该方法和values方法的行为是一致的;

var s = new Set()
s.add(10).add(20).add(30)

// 方式一
for (let i of s.keys()) {
	alert(i)
}

// 方式二
for (let i of s.values()) {
	alert(i)
}

// 方式三
for (let i of s) {
	alert(i)
}

Set 结构的实例默认可遍历,所以上面三种方式遍历Set,得到的结果相同。

Set.prototype.entries():返回所有键值对的遍历器,所以每次输出一个数组,它的两个成员完全相等;
Set.prototype.forEach():使用回调函数遍历Set中每个成员;

var arr = [1, 1, 2, 3, 4, 4];
new Set(arr).forEach(x => alert(x))

需要特别指出的是,Set的遍历顺序就是插入顺序。

另外,数组的map和filter方法也可以间接用于 Set 了。

let arr = [1, 1, 2, 3, 4, 4]
let s1 = new Set(arr.map(x => x * 2))
s1.forEach(x => alert(x)) // 2 4 6 8

let s2 = new Set(arr.filter(x => (x % 2) == 0))
s2.forEach(x => alert(x)) // 2 4

1.3 Set与数组之间的转换:

将数组转换成Set:

new Set(Array对象)

将Set转换成数组:

// 方式一:使用展开运算符
[...Set对象]

// 方式二:使用Array.from函数
Array.from(Set对象)

1.4 Set和WeakSet的区别

WeakSet与Set的结构,它里面的元素也是不重复,它们在用法上也非常类似。但是与Set不同,WeakSet的成员只能够是对象,而不能是其他类型的值。

const ws = new WeakSet();
ws.add(1) // TypeError: Invalid value used in weak set

其次,WeakSet中的对象都是弱引用。如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

基于上面特点,WeakSet的成员不适合被引用。因此,ES6规定WeakSet不可遍历。因此遍历机制无法保证成员的存在。

二、Map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

2.1 Map的基本使用

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

上面代码使用 Map 结构的set方法,将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。

Map也可以接受一个数组作为参数。每一个数组元素都是一个表示键值对的数组。例如:

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

上面代码在新建 Map 实例时,就指定了两个键name和title。

事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。

const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

上面代码中,我们分别使用 Set 对象和 Map 对象,当作Map构造函数的参数,结果都生成了新的 Map 对象。

如果对同一个键多次赋值,后面的值将覆盖前面的值。

const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"

如果读取一个未知的键,则返回undefined。

new Map().get('asfddfsasadf')
// undefined

注意,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。

如果 Map 的键是一个简单类型的值(如数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

2.2 Map属性和方法

(1)size属性
size属性返回Map中成员个数。

(2)Map.prototype.set(key, value)
set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

(3)Map.prototype.get(key)
get方法读取key对应的键值,如果找不到key,返回undefined。

(4)Map.prototype.has(key)
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

(5)Map.prototype.delete(key)
delete方法删除某个键,返回true。如果删除失败,返回false。

(6)Map.prototype.clear()
clear方法清除所有成员,没有返回值。

(7)遍历方法keys()、values()、entries()和forEach()

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
	</head>
	<body>
		<script>
			const map = new Map([
			  ['F', 'no'],
			  ['T',  'yes'],
			]);
			
			// 遍历map所有key
			for (let key of map.keys()) {
			  console.log(key);
			}
			
			// 遍历map所有value
			for (let value of map.values()) {
			  console.log(value);
			}
			
			// 遍历map所有键值对
			for (let item of map.entries()) {
			  console.log(item[0], item[1]);
			}
			
			// 或者
			for (let [key, value] of map.entries()) {
			  console.log(key, value);
			}
			
			// 等同于使用map.entries()
			for (let [key, value] of map) {
			  console.log(key, value);
			}
		</script>
	</body>
</html>

Map 提供了遍历器属性(Symbol.iterator),因此遍历map出来的就是键值对。

需要特别注意的是,Map 的遍历顺序就是插入顺序。

2.3 Map与其他数据结构的转换

(1)Map转数组
把map转换成数组最简单方式是使用展开运算符(…)。

const myMap = new Map()
  .set(true, 7)
  .set({foo: 3}, ['abc']);
[...myMap]

(2)数组转Map
将数组传入 Map 构造函数,就可以转为 Map。

new Map([
  [true, 7],
  [{foo: 3}, ['abc']]
])

每一个数组元素都包含两个元素,第一个元素是键,第二个元素是值。

(3)Map转对象
如果所有 Map 的键都是字符串,它可以无损地转为对象。

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)

(4)对象转Map

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

let map = objToStrMap({yes: true, no: false})
alert(map.get('yes')); // true

(5)Map转JSON

function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
let json = strMapToJson(myMap);
alert(json); 

(6)JSON转Map
JSON 转为 Map,正常情况下,所有键名都是字符串。

function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')

但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')

2.4 WeakMap

WeakMap与Map的结构类似,也是用于生成键值对的集合。例如:

// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap();
const key = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2

// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "bar"

WeakMap与Map的区别有两点:
第一,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
第二,WeakMap的键名所指向的对象,不计入垃圾回收机制。

例如:

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

上面的 DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key) // Object {foo: 1}

上面代码中,键值obj是正常引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 内部的引用依然存在。所以最后一行代码仍然能够输出{foo: 1}。

WeakMap的基本语法

WeakMap 与 Map 在 API 上的区别主要是两个,一是没有keys()、values()和entries()等遍历方法,也没有size属性。WeakMap只有四个方法可用:get()、set()、has()、delete()。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章