非数值型索引——Set和Map
在JS中只存在一种集合类型,就是数组类型。数组只可以使用数值型的索引,有时候我们需要非数值型的索引。在过去我们一般采用非数组对象的键值对来模拟,这种方法虽然可以近似实现,但也存在一些问题。为此ES6推出了Set和Map这两种集合类型来解决非数值型索引需求。
ES5的模拟带来的问题
在ES5中对象属性的类型必须为字符串(ES6中增加了Symbol支持),由于JS的强制转型,我们就必须保证任意两个键不能被转化为相同的字符串。例如:
let map = {};
map[0] = 'a';
console.log(map["0"]); // a
一般来说我们希望不同键应该存储不同的值,当我们想将数值和字符串都作为键来用时,就难免会出现问题。而当我们想使用对象作为键时,由于强制转型都会调用toString()方法转化为[object Object],不同的对象作为键名时,又被转化为了同一字符串。所以ES5的模拟在键名上有很大的局限。
ES6的Set
Set集合类型是无重复值的有序列表,可以更加方便的索引离散值。
Set的初始化
我们可以通过new Set( )的方法初始化一个空的Set集合,也可以传入一个数组将数组转化为Set集合。由于Set的无重复值特性,会自动剔除重复值,这在数组去重时十分有用。例子如下:
let set = new Set([1,2,3,3,3]),
arr = [...set];
console.log(arr); //[1,2,3]
Set的操作方法
通过调用add()方法可以扩充Set,并且可以检查size属性查看其中包含多少项。由于无重复值特性,如果对相同值进行多次调用,第一次之后的调用会被忽略。
let set = new Set();
set.add(1);
set.add(1);
console.log(set.size); // 1
通过调用delete( )方法来删除单个值,调用clear( )方法来删除所有值。调用has( )方法来检测某个值是否存在。
let set = new Set();
set.add(1);
set.add(2);
console.log(set.has(1)); // true
set.delete(2);
console.log(set.has(2)); // false
set.clear();
console.log(set.size); // 0
遍历Set可以使用forEach方法,forEach接受两个参数。和数组的forEach方法相同,第一个参数为回调函数。该函数接受三个参数:遍历位置的键,遍历位置的值,Set集合本身。由于Set集合本身的键就是值,所以前两个参数相等。第二个参数为this,传递当前作用域给回调函数使用。例子如下:
let set = new Set([1, 2]);
let obj = {
output(val) {
console.log(val);
},
process(set) {
set.forEach(function(val) {
this.output(val);
}, this);
}
};
obj.process(set);
Weak Set
由于Set存储对象时相当于直接把对象引用存储在变量中,因此只要对Set的实例引用存在,存储对象即使取消原始引用,也无法被垃圾回收机制回收。这在大部分时候都没有问题,但有时我们需要当原始对象引用消失时,Set内部引用也能消失来防止内存泄漏。针对这个需求,ES6推出了Weak Set来解决。
Weak Set由WeakSet()构造函数创建,区别只在于Weak Set是对象的弱引用。大部分使用方法和Set相同。关键差异如下:
- WeakSet的实例调用add, has,delete等方法时不能传入非对象参数。
- WeakSet不可迭代。
- WeakSet没有size属性。
ES6的Map
Map集合类型是无重复值的有序键值对,键和值都可以是任何类型。和对象不同,键不会被强制转换为字符串。
Map的初始化
和Set类似,仍然通过Map构造函数创建一个Map集合。可以传入一个数组初始化,该数组每一项都必须是数组,内部数组的首个项会成为键,第二项为值。例子如下:
let map = new Map([['a', 1],['b', 2]]);
console.log(map.get('a')); // 1
console.log(map.size); // 2
Map的操作方法
和Map类似,同样也有has,delete,clear方法,size属性,forEach遍历,用法也相同,这里不再赘述。不同的是add方法修改为set方法,该方法接受两个参数,即为键值对。
Weak Map
同样。Weak版本都是存储对象弱引用的方法。所有的键都必须是对象,但是值没有限制。Weak版本的操作方法只保留了has和delete,删除了clear方法,同样没有size属性,不能迭代。
应用场景示例
缓存数据
假如我们通过ajax的方式获取数据并缓存,一般我们这么做:
ajax.cache = {};
ajax.getData = function(key , fn) {
let cache = this.cache[key];
if (cache) {
return fn(cache);
else {
// ajax code
}
}
}
但是以上代码存在一些问题,因为缓存数据是存在一个对象模拟的map结构上,所以对象中一些从原型上继承的方法或属性就会影响到缓存,如果我们请求的key是toString,那么条件语句总会返回true。
通过ES6的Map,就可以更干净的实现缓存。
ajax.cache = new Map;
ajax.getData = function (key, fn) {
let cache = this.cache.get(key);
if (cache) {
return fn (cache);
}
else {
// ajax code
}
}
关联DOM元素到对象
有些时候我们可能会维护一个自定义对象用于引用所需要的每个DOM元素。这里有个难点,就是如何判断一个DOM已经不在网页中,以便在对象中同步移除。如果不能做到将会在对象中保持对DOM元素的无效引用导致无法垃圾回收。通过Weak Map我们就可以解决这个问题。
let map = new WeakMap(),
ele = document.querySelector('.ele');
map.set(ele, 'string');
console.log(map.get(ele)) // string
ele.parentNode.removeChild(ele);
ele = null;
console.log(map.get(ele)) // 取到无效值
保存私有变量
在ES5中我们可以利用闭包创建私有变量,但最大的问题在于闭包中私有变量的数据永不会消失,因为在对象实例被销毁时没有任何方法取到保存私有变量的对象数据。现在我们可以用Weak Map来解决。举例如下:
let Person = (function() {
let privateData = new WeakMap();
function Person(name) {
privateData.set(this, {name: name});
}
Person.prototype.getName = function() {
return privateData.get(this).name;
};
})();
该例子中使用了实例对象本身作为键,当实例被销毁时,私有变量同步被销毁。
总结
Set是无重复值的有序列表,和数组间可以简单的相互转化,所以数组去重变得十分简单。WeakSet只能包含弱引用对象,最好用来追踪可以被分类到一起的对象。Map是有序的键值对,相对于对象的模拟键值对存储,更为干净和严格。WeakMap的弱引用特性使其相对于普通的存储,更擅长于内存管理。除了以上的个人见解,应该还有更多的地方可以应用,就等到以后去挖掘了。
-- EOF --
前端开发
」下,并被添加
「JavaScript」
标签。