Map 与 Set,WeakSet...
Published on Jan 07, 2023, with 21 view(s) and 0 comment(s)
Ai 摘要:本文介绍了JavaScript中的Map、Set、WeakSet和WeakMap数据结构。Map用于存储键值对,提供快速查找;Set存储唯一值,常用于数组去重和集合运算。WeakSet和WeakMap分别与Set、Map类似,但仅接受对象作为元素/键,并采用弱引用机制,利于垃圾回收。文章还通过两数求和算法对比了不同解法效率,并展示了WeakSet/WeakMap在对象监听等场景的实际应用,强调其在内存管理方面的优势。

Map对象

Map对象保存键值对,任何值(对象或者原始值)都可以作为一个键或者一个值。 使用JavaScript创建一个Map如下:

let m = new Map();

可以向Map里放入键值对:

m.set("aaa",1);
m.set("bbb",2);
m.set("ccc",3);

或者利用二维数组初始化:

let m = new Map([["aaa",1],["bbb",2],["ccc",3]])

常用方法

let m = new Map();
m.set("aaa",1); //添加键值对
m.has("aaa"); //是否存在key为"aaa":true
m.get("bbb"); // 2
m.delete("ccc"); // 删除key "ccc"

注:一个key只能对应一个value,如果重复放入key-value,后面的value会把前面的value替换。

Map对象的相关操作

  • 和Array的转化:

let m = new Map([["aaa",1],["bbb",2],["ccc",3]])
console.log(Array.from(m));  //使用Array.from()可将一个Map对象转化为一个二维键值对数组
  • Map的克隆:

let m = new Map([["aaa",1],["bbb",2],["ccc",3]])
let m1 = new Map(m)

console.log(m===m1);  //false
  • Map的合并:

let m = new Map([
  ["aaa", 1],
  ["bbb", 2],
]);
let m1 = new Map([["bbb", 3]]);
let m2 = new Map([...m, ...m1]);
console.log(m2);  //{ "aaa" => 1, "bbb" => 3 }后面的value会覆盖前面同key的value

Set对象

  • Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

let s = new Set();
s.add(1);
s.add("abc");
s.add({ aaa: 1 });
  • Set对象的作用:

  1. 数组去重

var s = new Set([1, 2, 3, 4, 4]);
[...s]; // [1, 2, 3, 4]
  1. 取并集

var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var union = new Set([...a, ...b]); // {1, 2, 3, 4}
  1. 取交集

var a = new Set([1, 2, 3]);
var b = new Set([4, 3, 2]);
var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}

算法实例演示

两数求和:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。解法1:双循环暴力求解 时间复杂度: O(n^2)O(n 2) 空间复杂度: O(1)O(1)

var twoSum = function(nums, target) {
    for(let i = 0, len = nums.length;i < len;i++){
        // 因为同一元素不允许重复出现,所以从i的下一位开始遍历
        for(let j = i + 1;j < len;j++) {
            if(nums[i] + nums[j] === target) {
                return [i, j];
            }
        }
    }
    // 所有样例都是有返回结果的,这里无所谓
    return [-1, -1];
};

解法2:一次循环,使用HashMap进行记录 时间复杂度:O(n)O(n) 空间复杂度:O(n)O(n)

var twoSum = function(nums, target) {
    const map = new Map();
    for(let i = 0, len = nums.length;i < len;i++) {
        if(map.has(target - nums[i])) {
            return [map.get(target - nums[i]), i];
        }
        map.set(nums[i], i);
    }
    return [];
};

WeakSet

和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。 WeakSet和Set的区别:

  • 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;

  • 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;

    上述示例中创建了先一个obj对象,又创建了一个set对象,并且假设向set对象中添加了obj,并且有个索引item1。此时如果使用obj = null 把obj置空,此时由于item1仍然指向0X100,所以obj不会被GC回收。但是如果时weakSet,item1对0X100的引用是弱引用,obj对象则会被回收掉。

  • Description

WeakSet常见的方法

  • add(value):添加某个元素,返回WeakSet对象本身;

  • delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;

  • has(value):判断WeakSet中是否存在某个元素,返回boolean类型

WeakSet的应用

const personSet = new WeakSet();
class Person {
  constructor() {
    personSet.add(this);
  }
  running() {
    if (!personSet.has(this)) {
      throw Error("该方法只能通过new对象调用");
    }
    console.log("i an running", this);
  }
}
const p = new Person();
p.running(); //i an running Person {}
// p.running.call({ name: "sun" }); //i an running { name: "sun" }
p.running.call({ name: "sun" }); //throw Error("该方法只能通过new对象调用");

上述案例中创建一个Person类,Person类中有一个running方法。我们可以通过构造方法创建出来的p调用running方法。此时this指向p。还可以通过call来调用来使Perosn里的this指向 {name:"sun"}。 此时如果有一个需求:不能通过非构造方法创建出来的对象来调用running。此时就可以使用先创建一个WeakSet,在创建对象时会先执行constructor把this存入WeakSet,在调用running时先判断是否存在this。 为啥不用new Set(),因为创建了一个对象p,如果执行P=null;此时的p是不会被销毁的,因为set是强引用。

WeakMap

和Map类型相似的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。和Map区别

  • 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;

  • 区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;常见的方法

  • set(key, value):在Map中添加key、value,并且返回整个Map对象;

  • get(key):根据key获取Map中的value;

  • has(key):判断是否包括某一个key,返回Boolean类型;

  • delete(key):根据key删除一个键值对,返回Boolean类型;

WeakMap的应用

const obj1 = {
  name: "sun",
  age: 20,
};
function obj1NameFn1() {
  console.log("obj1namefn1执行");
}
function obj1NameFn2() {
  console.log("obj1namefn2执行");
}
function objAgeFn1() {
  console.log("obj1agefn1执行");
}
function objAgeFn2() {
  console.log("obj1agefn2执行");
}

const obj2 = {
  height: 190,
};
function obj2HeightFn1() {
  console.log("obj2heightfn1执行");
}
function obj2HeightFn2() {
  console.log("obj2heightfn2执行");
}
// 需求:监听对象的变化执行相应操作
// 1,创建weakMap
const weakMap = new WeakMap();

// 2,收集依赖结构
// 2.1对obj1收集的数据结构
const map1 = new Map();
map1.set("name", [obj1NameFn1, obj1NameFn2]);
map1.set("age", [objAgeFn1, objAgeFn2]);
weakMap.set(obj1, map1);

// 2.2对obj2收集的数据结构
const map2 = new Map();
map2.set("height", [obj2HeightFn1, obj2HeightFn2]);
weakMap.set(obj2, map2);

// 3,如果obj.name发生了变化
// proxy/Object.defineProterty
Object.defineProperty(obj1, "name", {
  set: function () {
    const targetMap = weakMap.get(obj1);
    const fns = targetMap.get("name");
    fns.forEach((ele) => {
      ele();
    });
  },
});
obj1.name = "lebron";
// obj1namefn1执行
// obj1namefn2执行