信息网站建设方案,网站维护工单,怎么制作公司网站,免费发布招聘的网站目录 1map
1.1含义和基本用法
1.2实例的属性和操作方法
1.2.1size属性
1.2.2set(key, value)
1.2.3get(key)
1.2.4has(key)
1.2.5delete(key)
1.2.6clear#xff08;#xff09;
1.3遍历方法
1.4与其他数据结构的互相转换
1.4.1Map 转为数组
1.4.2数组 转为 Map…目录 1map
1.1含义和基本用法
1.2实例的属性和操作方法
1.2.1size属性
1.2.2set(key, value)
1.2.3get(key)
1.2.4has(key)
1.2.5delete(key)
1.2.6clear
1.3遍历方法
1.4与其他数据结构的互相转换
1.4.1Map 转为数组
1.4.2数组 转为 Map
1.4.3Map 转为对象
1.4.4对象转为 Map
1.4.5Map 转为 JSON
1.4.6JSON 转为 Map
2WeakMap
2.1含义
2.2WeakMap 的语法
2.3WeakMap 的示例
2.4WeakMap 的用途
总结
“睡服“面试官系列之各系列目录汇总建议学习收藏 1map
1.1含义和基本用法
JavaScript 的对象Object本质上是键值对的集合Hash 结构但是传统上只能用字符串当作键。这给它的使用带来了很大的限制
const data {};
const element document.getElementById(myDiv);
data[element] metadata;
data[[object HTMLDivElement]] // metadata
上面代码原意是将一个 DOM 节点作为对象 data 的键但是由于对象只接受字符串作为键名所以 element 被自动转为字符串 [object HTMLDivElement] 。 为了解决这个问题ES6 提供了 Map 数据结构。它类似于对象也是键值对的集合但是“键”的范围不限于字符串各种类型的值包括对象都可以 当作键。也就是说Object 结构提供了“字符串—值”的对应Map 结构提供了“值—值”的对应是一种更完善的 Hash 结构实现。如果你需要“键值 对”的数据结构Map 比 Object 更合适。
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 添加成员。作为构造函数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 。 Map 构造函数接受数组作为参数实际上执行的是下面的算法。
const items [
[name, 张三],
[title, Author]
];
const map new Map();
items.forEach(
([key, value]) map.set(key, value)
);
事实上不仅仅是数组任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构详见《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
上面代码对键 1 连续赋值两次后一次的值覆盖前一次的值。 如果读取一个未知的键则返回 undefined 。
new Map().get(asfddfsasadf)
// undefined
注意只有对同一个对象的引用Map 结构才将其视为同一个键。这一点要非常小心。
const map new Map();
map.set([a], 555);
map.get([a]) // undefined
上面代码的 set 和 get 方法表面是针对同一个键但实际上这是两个值内存地址是不一样的因此 get 方法无法读取该键返回 undefined 。 同理同样的值的两个实例在 Map 结构中被视为两个键。
const map new Map();
const k1 [a];
const k2 [a];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
上面代码中变量 k1 和 k2 的值是一样的但是它们在 Map 结构中被视为两个键。 由上可知Map 的键实际上是跟内存地址绑定的只要内存地址不一样就视为两个键。这就解决了同名属性碰撞clash的问题我们扩展别人的库的 时候如果使用对象作为键名就不用担心自己的属性与原作者的属性同名。 如果 Map 的键是一个简单类型的值数字、字符串、布尔值则只要两个值严格相等Map 将其视为一个键比如 0 和 -0 就是一个键布尔值 true 和字符串 true 则是两个不同的键。另外 undefined 和 null 也是两个不同的键。虽然 NaN 不严格相等于自身但 Map 将其视为同一个键。
let map new Map();
map.set(-0, 123);
map.get(0) // 123
map.set(true, 1);
map.set(true, 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123
1.2实例的属性和操作方法
Map 结构的实例有以下属性和操作方法。
1.2.1size属性
size 属性返回 Map 结构的成员总数
const map new Map();
map.set(foo, true);
map.set(bar, false);
map.size // 2
1.2.2set(key, value)
set 方法设置键名 key 对应的键值为 value 然后返回整个 Map 结构。如果 key 已经有值则键值会被更新否则就新生成该键
const m new Map();
m.set(edition, 6) // 键是字符串
m.set(262, standard) // 键是数值
m.set(undefined, nah) // 键是 undefined
set 方法返回的是当前的 Map 对象因此可以采用链式写法。
let map new Map()
.set(1, a)
.set(2, b)
.set(3, c)
1.2.3get(key)
get 方法读取 key 对应的键值如果找不到 key 返回 undefined
const m new Map();
const hello function() {console.log(hello);};
m.set(hello, Hello ES6!) // 键是函数
m.get(hello) // Hello ES6!
1.2.4has(key)
has 方法返回一个布尔值表示某个键是否在当前 Map 对象之中。
const m new Map();
m.set(edition, 6);
m.set(262, standard);
m.set(undefined, nah);
m.has(edition) // true
m.has(years) // false
m.has(262) // true
m.has(undefined) // true
1.2.5delete(key)
delete 方法删除某个键返回 true 。如果删除失败返回 false 。
const m new Map();
m.set(undefined, nah);
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
1.2.6clear
let map new Map();
map.set(foo, true);
map.set(bar, false);
map.size // 2
map.clear()
map.size // 0
1.3遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
keys() 返回键名的遍历器。 values() 返回键值的遍历器。 entries() 返回所有成员的遍历器。 forEach() 遍历 Map 的所有成员。 需要特别注意的是Map 的遍历顺序就是插入顺序
const map new Map([
[F, no],
[T, yes],
]);
for (let key of map.keys()) {
console.log(key);
}
// F
// T
for (let value of map.values()) {
console.log(value);
}
// no
// yes
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// F no
// T yes
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// F no
// T yes
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// F no
// T yes
上面代码最后的那个例子表示 Map 结构的默认遍历器接口 Symbol.iterator 属性就是 entries 方法。
map[Symbol.iterator] map.entries
// true
Map 结构转为数组结构比较快速的方法是使用扩展运算符 ...
const map new Map([
[1, one],
[2, two],
[3, three],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// [one, two, three]
[...map.entries()]
// [[1,one], [2, two], [3, three]]
[...map]
// [[1,one], [2, two], [3, three]]
结合数组的 map 方法、 filter 方法可以实现 Map 的遍历和过滤Map 本身没有 map 和 filter 方法
const map0 new Map()
.set(1, a)
.set(2, b)
.set(3, c);
const map1 new Map(
[...map0].filter(([k, v]) k 3)
);
// 产生 Map 结构 {1 a, 2 b}
const map2 new Map(
[...map0].map(([k, v]) [k * 2, _ v])
);
// 产生 Map 结构 {2 _a, 4 _b, 6 _c}
此外Map 还有一个 forEach 方法与数组的 forEach 方法类似也可以实现遍历。
map.forEach(function(value, key, map) {
console.log(Key: %s, Value: %s, key, value);
});
forEach 方法还可以接受第二个参数用来绑定 this
const reporter {
report: function(key, value) {
console.log(Key: %s, Value: %s, key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
上面代码中 forEach 方法的回调函数的 this 就指向 reporter 。
1.4与其他数据结构的互相转换
1.4.1Map 转为数组
前面已经提过Map 转为数组最方便的方法就是使用扩展运算符 ... 。
const myMap new Map()
.set(true, 7)
.set({foo: 3}, [abc]);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ abc ] ] ]
1.4.2数组 转为 Map
将数组传入 Map 构造函数就可以转为 Map。
new Map([
[true, 7],
[{foo: 3}, [abc]]
])
// Map {
// true 7,
// Object {foo: 3} [abc]
// }
1.4.3Map 转为对象
如果所有 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)
// { yes: true, no: false }
1.4.4对象转为 Map
function objToStrMap(obj) {
let strMap new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {yes true, no false}
1.4.5Map 转为 JSON
Map 转为 JSON 要区分两种情况。一种情况是Map 的键名都是字符串这时可以选择转为对象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap new Map().set(yes, true).set(no, false);
strMapToJson(myMap)
// {yes:true,no:false}
另一种情况是Map 的键名有非字符串这时可以选择转为数组 JSON
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap new Map().set(true, 7).set({foo: 3}, [abc]);
mapToArrayJson(myMap)
// [[true,7],[{foo:3},[abc]]]
1.4.6JSON 转为 Map
JSON 转为 Map正常情况下所有键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap({yes: true, no: false})
// Map {yes true, no false}
但是有一种特殊情况整个 JSON 就是一个数组且每个数组成员本身又是一个有两个成员的数组。这时它可以一一对应地转为 Map。这往往是数 组转为 JSON 的逆操作
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap([[true,7],[{foo:3},[abc]]])
// Map {true 7, Object {foo: 3} [abc]}
2WeakMap
2.1含义
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 除外不接受其他类型的值作为键名。
const map new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key
上面代码中如果将数值 1 和 Symbol 值作为 WeakMap 的键名都会报错。 其次 WeakMap 的键名所指向的对象不计入垃圾回收机制。 WeakMap 的设计目的在于有时我们想在某个对象上面存放一些数据但是这会形成对于这个对象的引用。请看下面的例子。
const e1 document.getElementById(foo);
const e2 document.getElementById(bar);
const arr [
[e1, foo 元素],
[e2, bar 元素],
]; 上面代码中 e1 和 e2 是两个对象我们通过 arr 数组对这两个对象添加一些文字说明。这就形成了 arr 对 e1 和 e2 的引用。 一旦不再需要这两个对象我们就必须手动删除这个引用否则垃圾回收机制就不会释放 e1 和 e2 占用的内存。
// 不需要 e1 和 e2 的时候
// 必须手动删除引用
arr [0] null;
arr [1] null
上面这样的写法显然很不方便。一旦忘了写就会造成内存泄露。 WeakMap 就是为了解决这个问题而诞生的它的键名所引用的对象都是弱引用即垃圾回收机制不将该引用考虑在内。因此只要所引用的对象的其他 引用都被清除垃圾回收机制就会释放该对象所占用的内存。也就是说一旦不再需要WeakMap 里面的键名对象和所对应的键值对会自动消失不用 手动删除引用。 基本上如果你要往对象上添加数据又不想干扰垃圾回收机制就可以使用 WeakMap。一个典型应用场景是在网页的 DOM 元素上添加数据就可 以使用 WeakMap 结构。当该 DOM 元素被清除其所对应的 WeakMap 记录就会自动被移除。
const wm new WeakMap();
const element document.getElementById(example);
wm.set(element, some information);
wm.get(element) // some information
上面代码中先新建一个 Weakmap 实例。然后将一个 DOM 节点作为键名存入该实例并将一些附加信息作为键值一起存放在 WeakMap 里面。 这时WeakMap 里面对 element 的引用就是弱引用不会被计入垃圾回收机制。 也就是说上面的 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 内部的引用依然存在
2.2WeakMap 的语法
WeakMap 与 Map 在 API 上的区别主要是两个一是没有遍历操作即没有 key() 、 values() 和 entries() 方法也没有 size 属性。因为没有办法 列出所有键名某个键名是否存在完全不可预测跟垃圾回收机制是否运行相关。这一刻可以取到键名下一刻垃圾回收机制突然运行了这个键名就没 了为了防止出现不确定性就统一规定不能取到键名。二是无法清空即不支持 clear 方法。因此 WeakMap 只有四个方法可用 get() 、 set() 、 has() 、 delete()
const wm new WeakMap();
// size、forEach、clear 方法都不存在
wm.size // undefined
wm.forEach // undefined
wm.clear // undefined
2.3WeakMap 的示例
WeakMap 的例子很难演示因为无法观察它里面的引用会自动消失。此时其他引用都解除了已经没有引用指向 WeakMap 的键名了导致无法证实 那个键名是不是存在。 贺师俊老师提示如果引用所指向的值占用特别多的内存就可以通过 Node 的 process.memoryUsage 方法看出来。根据这个思路网友vtxf补充了下面 的例子。 首先打开 Node 命令行。
$ node --expose-gc
上面代码中 --expose-gc 参数表示允许手动执行垃圾回收机制。 然后执行下面的代码
// 手动执行一次垃圾回收保证获取的内存使用状态准确global.gc();
undefined
// 查看内存占用的初始状态heapUsed 为 4M 左右process.memoryUsage();
{ rss: 21106688,
heapTotal: 7376896,
heapUsed: 4153936,
external: 9059 }let wm new WeakMap();
undefined
// 新建一个变量 key指向一个 5*1024*1024 的数组let key new Array(5 * 1024 * 1024);
undefined
// 设置 WeakMap 实例的键名也指向 key 数组
// 这时key 数组实际被引用了两次
// 变量 key 引用一次WeakMap 的键名引用了第二次
// 但是WeakMap 是弱引用对于引擎来说引用计数还是1wm.set(key, 1);
WeakMap {}global.gc();
undefined
// 这时内存占用 heapUsed 增加到 45M 了process.memoryUsage();
{ rss: 67538944,
heapTotal: 7376896,
heapUsed: 45782816,
external: 8945 }
// 清除变量 key 对数组的引用
// 但没有手动清除 WeakMap 实例的键名对数组的引用key null;
null
// 再次执行垃圾回收global.gc();
undefined
// 内存占用 heapUsed 变回 4M 左右
// 可以看到 WeakMap 的键名引用没有阻止 gc 对内存的回收process.memoryUsage();
{ rss: 20639744,
heapTotal: 8425472,
heapUsed: 3979792,
external: 8956 }
上面代码中只要外部的引用消失WeakMap 内部的引用就会自动被垃圾回收清除。由此可见有了 WeakMap 的帮助解决内存泄漏就会简单很 多。
2.4WeakMap 的用途
前文说过WeakMap 应用的典型场合就是 DOM 节点作为键名。下面是一个例子。
let myElement document.getElementById(logo);
let myWeakmap new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener(click, function() {
let logoData myWeakmap.get(myElement);
logoData.timesClicked;
}, false)
上面代码中 myElement 是一个 DOM 节点每当发生 click 事件就更新一下状态。我们将这个状态作为键值放在 WeakMap 里对应的键名就是 myElement 。一旦这个 DOM 节点删除该状态就会自动消失不存在内存泄漏风险。 WeakMap 的另一个用处是部署私有属性
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)();
}
}
}
const c new Countdown(2, () console.log(DONE));
c.dec()
c.dec()
// DONE
上面代码中 Countdown 类的两个内部属性 _counter 和 _action 是实例的弱引用所以如果删除实例它们也就随之消失不会造成内存泄漏
总结
本博客源于本人阅读相关书籍和视频总结创作不易谢谢点赞支持。学到就是赚到。我是歌谣励志成为一名优秀的技术革新人员。
欢迎私信交流一起学习一起成长。
推荐链接 其他文件目录参照
“睡服“面试官系列之各系列目录汇总建议学习收藏