JS笔记集合引用类型(上)

4.4 Map
1 基本API
// 1.构造方式
const map = new Map(); // 关键字和构造函数创建一个空映射
const map = new Map([ // 嵌套数组构造时初始化
  ["key1","v1"],
  ["key2","v2"],
  ["key3","v3"]
]);
console.log(map.size); // 3
console.log(map); // Map(3) { 'key1' => 'v1', 'key2' => 'v2', 'key3' => 'v3' }

// 2.方法
// set()用于添加键值对
map.set("key4","heyun");
console.log(map); /*Map(4) {
  'key1' => 'v1',
  'key2' => 'v2',
  'key3' => 'v3',
  'key4' => 'heyun'
}*/ 
// 也可以链式使用
const map = new Map().set("k1","v1");
map.set("k2","v2")
   .set("k3","v3");
console.log(map); /*Map(3) { 'k1' => 'v1', 'k2' => 'v2', 'k3' => 'v3' }*/

// get()用于取值,has()用于查询
console.log(map.get("key4")); // heyun
console.log(map.has("key4")); // true

// delete()和clear()用于删值
map.delete("key2") /*Map(3) { 'key1' => 'v1', 'key3' => 'v3', 'key4' => 'heyun' }*/
map.clear(); /*Map(0) {}*/

Map与Object的区别:

  • Object 的键必须是数值、字符串或者符号,而Map可以使用任何JS数据类型作为键,内部使用SameValueZero比较操作,相当于使用严格对象相等。
  • Map会维护键值对的插入顺序

Map与Object的相同点:

  • 映射的值没有限制
// SameValueZero出现冲突的情况
const m = new Map();
const a = 0/"",
      b = 0/"",
      pz = +0,
      nz = -0;
console.log(a === b); // false
console.log(pz === nz); // true
m.set(a,"bar");
m.set(pz,"foo");
console.log(m.get(b)); // bar
console.log(m.get(nz)); // foo
2 顺序与迭代

​ entries()是默认迭代器,可以直接对映射实例进行扩展操作,把映射转换为数组。

const map = new Map([ // 嵌套数组构造时初始化
  ["key1","v1"],
  ["key2","v2"],
  ["key3","v3"]
]);

console.log(map.entries === map[Symbol.iterator]); // true

for(let m of map.entries()) {
  console.log(m); /*[ 'key1', 'v1' ]
[ 'key2', 'v2' ]
[ 'key3', 'v3' ]
*/
}
console.log([...map]); /*[ [ 'key1', 'v1' ], [ 'key2', 'v2' ], [ 'key3', 'v3' ] ]*/

使用回调方式也可以实现同样的效果

const map = new Map([ // 嵌套数组构造时初始化
  ["key1","v1"],
  ["key2","v2"],
  ["key3","v3"]
]);

map.forEach((v,k) => console.log(`${v} -> ${k}`)) /*v1 -> key1
v2 -> key2
v3 -> key3*/

​ 键、值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改,这不会影响修改做为键、值对象的内部属性

const map = new Map([ // 嵌套数组构造时初始化
  ["key1","v1"],
]);
for(let key of map) {
  key = "newKey";
  console.log(key); // newKey
  console.log(map.get("key1")); // v1
}
3 Object VS Map
  1. 内存上看Map大约可以比Object多存储50%的值
  2. 如果代码设计到大量的插入操作,那么选择Map会更合适
  3. 如果代码设计大量的查找操作,Object更适合
  4. 对于大多数的浏览器来说,Map的delete()操作比插入和查找来得更快,所以如果有大量的代码删除操作,选择Map
4.5 WeakMap
1 基本API

​ Map的兄弟类型,其API也是Map 的子集,weak描述的是JavaScript垃圾回收程序对待“弱映射”中键的方式, 弱映射 中的键只能是Object类型或者继承Object的类型(Map没有限制),值的类型没有限制。

const map = new WeakMap([ 
  ["key1","v1"],
]);
console.log(map); // 如果按照Map的构造方式会报错

const key1 = {}; // 给key1绑定成对象
const map = new WeakMap([ 
  [key1,"v1"],
]);
console.log(map.get(key1)); // v1

// 其它的set(),has(),delete()与Map一致
2 弱键

意思就是这些键不属于正式的引用,不会阻止垃圾回收

const wm = new WeakMap()
wm.set({},"v"); // 这里进行初始化,因为没有指向这个键的其它引用,所以这段代码执行完就会被回收,然后成为一个空映射,同时由于值也没有被引用,所以值也会成为被回收的目标

// -------
const wm = new WeakMap()
const container = {
    key:{}
}
wm.set(container.key,"v"); // 这里由于key一直被引用着,所以就不会成为垃圾回收的目标,只有手动赋值为null,然后垃圾回收程序再清理。
3 不可迭代键

​ 因为WeakMap中的键值对任何时候都可能被销毁,所以没有必要提供迭代器的能力。WeakMap之所以限制只能使用对象作为主键,是为了保证只有通过键对象的引用才能取到值

4 使用弱映射
  • 私有变量的实现
const wm = new WeakMap();

class User {
  constructor(id) {
    this.idProperty = Symbol('id');
    this.setId(id);
  }

  setPrivate(property,value) {
    const privateMembers = wm.get(this) || {};
    privateMembers[property] = value;
    wm.set(this,privateMembers);
  }

  getPrivate(property) {
    return wm.get(this)[property];
  }

  setId(id) {
    this.setPrivate(this.idProperty,id);
  }

  getId() {
    return this.getPrivate(this.idProperty);
  }
}

const users = new User(123);
console.log(users.getId()); // 123
users.setId(456);
console.log(users.getId()); //456 

console.log(wm.get(users)[users.idProperty]); // 456

​ 上面最后一行代码通过拿到对象的实例和弱映射就可以取得‘私有变量’,为了避免上诉情况发生,可以使用闭包。

const User = (() => {
  const wm = new WeakMap();

class User {
  constructor(id) {
    this.idProperty = Symbol('id');
    this.setId(id);
  }

  setPrivate(property,value) {
    const privateMembers = wm.get(this) || {};
    privateMembers[property] = value;
    wm.set(this,privateMembers);
  }

  getPrivate(property) {
    return wm.get(this)[property];
  }

  setId(id) {
    this.setPrivate(this.idProperty,id);
  }

  getId() {
    return this.getPrivate(this.idProperty);
  }
}
return User;
})();

const users = new User(123);
console.log(users.getId()); // 123
users.setId(456);
console.log(users.getId()); // 456

console.log(wm.get(users)[users.idProperty]); //那么到这里就拿不到wm的值,也就无法对私有变量进行访问。
  • DOM节点元数据

    ​ 因为WeakMap实例不会妨碍垃圾回收,所以非常适合保存关联元数据。

    const m = new Map();
    
    const loginBtn = document.querySelector('#login');
    
    m.set(loginBtn,{disabled:true});
    // 以上代码执行后,页面会发生变化,但是由于映射中还保存着按钮的引用,所以对应的DOM节点仍会逗留在内存中,除非手动删除,或者该映射自己被摧毁。
    
    const m = new WeakMap();
    // 但是如果是弱映射,当节点被删除后,垃圾回收程序会立即释放其内存。
    
4.6 Set
1 基本API
// 1.构造方法
const set1 = new Set(); // 空集合
const set2 = new Set(['v1','v2','v3']); // 使用数组初始化集合

// 2.方法
// add():往集合里面添加值
set2.add("heyun")
    .add("luoyu");
console.log(set2); // Set(5) { 'v1', 'v2', 'v3', 'heyun', 'luoyu' }

// has():判断集合中是否有目标元素
console.log(set2.has("luoyu")); // true
console.log(set2.has("hhh")); // false

// size:取得集合的长度
console.log(set2.size); // 5

// delete():删除指定元素,返回一个布尔值
set2.delete("heyun")
console.log(set2); // Set(4) { 'v1', 'v2', 'v3', 'luoyu' }

// clear():销毁集合实例中的所有值
set2.clear()
console.log(set2); // Set(0) {}

​ Set和Map类似,可以包含任何JS类型作为值,集合也可以使用SameValueZero操作。用作值的对象和其他‘集合’类型在自己的内容或属性被修改时也不会改变

2 顺序与迭代

​ Set会维护插入时的顺序,因此支持按顺序迭代。通过values()或者keys()取得迭代器(Symbol.iterator也行,它会引用values())

const set2 = new Set(['v1','v2','v3']); 

console.log(set2.values === set2[Symbol.iterator]); // true
console.log(set2.keys === set2[Symbol.iterator]); // true
const set2 = new Set(['v1','v2','v3']); 

for(let v of set2) {
  console.log(v); /*v1
v2
v3
*/
}
const set2 = new Set(['v1','v2','v3']); 

console.log([...set2]); // [ 'v1', 'v2', 'v3' ]

// 其它的方法和Map的大致类似,有一点比较熟悉的是set中的元素不能重复。
const set2 = new Set(['v1','v2','v3',"v1"]); 

console.log([...set2]); // [ 'v1', 'v2', 'v3' ]
3 定义正式集合操作(待解决

​ 开发中很多人喜欢自定义set函数库,这里有几个需要注意的点。

  • 某些set操作是关联的,因此设计的方法必须要支持处理任意多个集合实例
  • set保留插入顺序,所有方法返回的集合必须保持顺序。
  • 尽可能高效使用内存
  • 不修改已有的集合实例:如union()

​ 一些常见的集合实例

// 1.返回两个或更多集合的并集书上177页,有点问题,后面遇到了再解决

4.7 WeakSet
1 基本API

​ Set的兄弟类型 ,weak描述的是JS垃圾回收程序对待‘弱结合’中值的方式。通用弱集合中的值只能是Object或者继承Object的值。

const set1 = new WeakSet(['v1','v2','v3']); 
console.log(set1); // 报错
// ------------
const v1 = {str:'v1'},
      v2 = {str:'v2'},
      v3 = {str:'v3'}
const set1 = new WeakSet([v1,v2,v3]); 
console.log(set1); // WeakSet { <items unknown> }

​ WeakSet的方法与Set类似。

2 弱值

​ 这儿和WeakMap弱键中的操作相同,就是怎么让垃圾程序回收的问题(加个对弱值的引用就不会被回收)

3 不可迭代值

​ 无法使用迭代器,刚刚在上面的set1中试了下,直接报错,提示set1不是课迭代对象。

4 使用弱集合

​ 同WeakMap的使用弱映射。

4.8 迭代与扩展操作

​ 有四种原生集合类型定义了默认迭代器:Array、所有定型数组、Map、Set

let itetableItems = [
  Array.of(1,2),
  typedArr = Int16Array.of(3,4),
  new Map([5,6],[7,8]),
  new Set([9,10])
];

for (const iterator of itetableItems) {
  for (const x of iterator) {
    console.log(x); /*
    1
    2
    3
    4
    [5,6]
    [7,8]
    9
    10*/
  }
}

​ 所有的这些类型都兼容扩展操作符,因此在对可迭代对象进行浅复制时特别有用。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐