ES6——之数据存储结构 Set、Map基本使用及实现原理
使用时必须要new:
1、Set:是ES6提供给我们的构造函数,能构造出一种新的储存数据的结构;
特点:只有属性值,成员值唯一(不重复)
用途:可以转换成数组,进行去重、取交集、并集、差集等操作;
Set的基本使用:创建时可传[]、’'等迭代数据;
const oS = new Set([1,2,false,1]); //自带去重方法
const oS1 = new Set('abcdc');
//Set.prototype上的方法:
oS.add(4); //增加值;
oS.add([1, 2]);
oS.delete(false); //删值;
// oS.clear(); //清空Set;
oS.has(10); //判断oS中有没有10,返回false;
oS.forEach(val => { //遍历属性值;
console.log(val)
});
for (const prop of oS){ //ES6-for of方法同样可以遍历迭代数据;
console.log(prop)
}
console.log(Array.from(oS)); //将Set转换位数组;
console.log([...oS]); //还可用...展开迭代数据;
//去重:
const o = {
name : 'll'
};
const arr = [1,2,3,4,5,o, o, {name: 'ly'}];
const oS2 = new Set(arr);
//Set方法去重,很好的将对象一并去重,比自己写的方法功能完善;
//并集、交集、差集:
const arr1 = [1,2,3,2,3];
const arr2 = [2,3,4,5,4,5];
const arrB = new Set([...arr1, ...arr2]) //取并集;
const arr11 = new Set(arr1);
const arr22 = new Set(arr2); //使arr2去重,并有has方法;
const arrJ = [...arr11].filter( ele => arr22.has(ele) );
//[ ].filter进行筛选,先把arr1数据进行去重;//return出arr22中有arr11中的值的值;取交集;
const arrC1 = [...arr11].filter( ele => !arr22.has(ele)); //找出arr1中arr2没有的值;
const arrC2 = [...arr22].filter( ele => !arr11.has(ele)); //找出arr2中zrr1没有的值;
const arrC = [...arrC1, ...arrC2] //取出并集即可取差集;
2、Map:与Set相似,只不过本质上是键值对的集合;
特点:key对应value,key和value唯一,任何值都可以当属性;
用途:可以让对象当属性,储存,去重等;
Map的基本使用:创建时可传参,注意格式 ([[’’,’’],[’’,’’]]);
const oM = new Map([['name', 'll'], ['age', 18], [123, true], [{}, 123] ]);
//Map.prototype上的方法:
const oM1 = new Map();
oM1.set('name', 'll') //增加值;
oM1.set('age', '18');
const obj = {};
oM1.set(obj, 123);
oM1.get(obj); //取值(注意:如果取引用值时,现将引用值存好在取)
// oM1.delete('name'); //删除
// oM1.clear(); //初始化‘’
oM1.has(10); //判断oM1中有没有10,返回false;
oM1.forEach((ele, index, self) => { //遍历Map;
console.log(ele, index, self)
})
for(const val of oM1){ //ES6-for of方法同样可以遍历迭代数据;
console.log(val[0], val[1])
}
Set、Map原理思想:(邻接链表思想)(Set原理相同,只是少了对value值的处理)
//1、链表的储存方式:
const node3 = {
next: null
}
const node2 = {
next: node3
}
const node1 = {
next: node2
}
console.log( node1.next.next.next ); ==> null;
拓:
邻接链表储存解构详解:
class Node { //创建一个Node的构造函数,函数中有next指针;
constructor(key){
this.key = key;
this.next = null;
}
}
// var node1 = new Node(1)
// node1.next = new Node(2)
// node1.next.next = new Node(3) //这是一个链表,但调用方法太麻烦;
//封装inserNode方法:在链表中进行插入操作(生成链表);
function inserNode (startNode, node, num = Infinity) { //参数num:有值时在该值后拆入节点,没值时=在最后插入节点;
if (num < 1) {
return;
}
while (startNode.next && --num >0){ //找到想传入节点的位置;
startNode = startNode.next; //让当前节点指向下一个节点;直到下一个节点为null时跳出循环
}
let nextNode = startNode.next; //保存下一个节点的指针;
startNode.next = node //让当前节点指向当前传入的node;
node.next = nextNode; //让传入节点指向原来的下一个节点;即可实现任意位置插入节点;
}
let nodeHead = {type: 'head', next: null}
inserNode(nodeHead, new Node(1)) //每一步都可以更改链表;
inserNode(nodeHead, new Node(2))
inserNode(nodeHead, new Node(3))
inserNode(nodeHead, new Node(4))
inserNode(nodeHead, new Node(5))
// inserNode(nodeHead, new Node(6), 1)//在第一个节点后插入新节点;
var node = new Node(6);
inserNode(nodeHead, node, 1000) //插入最后
node.next = nodeHead.next.next; //让最后一个node的next指向第三个node,形成链表中的环结构;
//面试题:判断链表结构中有没有环的方法;
//原理:取一个步长为1,一个步长为2的指针
//当这两个指针相等,并且不为null时,则链表中有环;
function findRing (nodeHead) {
let point1 = nodeHead.next;
let point2 = point1 && point1.next; //nodeHead.next.next;
while (point1 && point2) { //point1 和 point2 都存在并且值不为null;
if(point1 == point2) {
console.log('有环')
return true;
}
point1 = point1.next;
point2 = point2.next;
point2 = point2 && point2.next;
}
console.log('无环');
return false
}
findRing(nodeHead); //输出结果:有环;
//2、hash算法:将一些不定的值转换为特定的值(因为传入的值有可能是{}、’’、132等,通过hash算法将所有的{}存到1对象中,将所有的’ '存到2对象中,将所有的数字存到3对象中;)
//3、桶思想:用数组规定一个桶,规定桶的长度,每个长度时一个对象{ },并且里面有next属性;
Map的实现源码:
function myMap () {
this.bLenght = 8; //规定桶的长度;
this.init();
}
myMap.prototype.init = function () { //初始化一个桶:有8个对象;
this.bucket = new Array(this.bLenght);
for (var i = 0; i < this.bLenght; i++){
this.bucket[i] = { //让每一个长度为对象;
type: '对象-' + i,
next: null
}
}
}
myMap.prototype.makeHash = function (key) { //编写hash算法,传入的值变成[0,8)区间的值;
let hash = 0;
if(typeof key !== 'string'){
if(typeof key == 'number'){ //传入的是数字或者NaN时,不是NaN时直接赋给hash;
hash = Object.is(key, NaN) ? 0 : key;
}else if(typeof key == 'object'){ //处理传入的是对象时: []、{}、 null等;
hash = 1;
}else if(typeof key == 'boolean'){ //boolean类型时,true转化为1;false转换为0;
hash = key;
}else{ //剩下的 undefined function(){} 类型直接转化为2;
hash = 2;
}
}else{ //转换string形式的key;
for(let i = 0; i < 3; i++){ //将传入的字符串转化为特定的数字;
hash += key[i] ? key[i].charCodeAt(0) : 0;
}
}
return hash % 8; //将hash转化为[0,8)区间的值;可以分不到不同的桶里的对象中去;
}
myMap.prototype.set = function (key, value) {
let hash = this.makeHash(key); //找到自己对应的hash值;
let oTempB = this.bucket[hash]; //找到自己对应的桶里的对象;
while (oTempB.next) { //当桶里对象的next有值时:
if (oTempB.next.key == key){ //如果传入的key和桶里的key相同;
oTempB.next.value = value;//直接覆盖原来的值;
return; //结束set任务;
}else{ //否则让它直接指向下一个next=对象,跳出循环,进行下面的操作;
oTempB = oTempB.next;
}
}
oTempB.next = {
//跳出循环时:当桶里对象的next没有值时,让next为一个{},直接赋值,没并且让其next为null;
key : key,
value : value,
next : null
}
}
myMap.prototype.get = function (key) {
let hash = this.makeHash(key);
let oTempB = this.bucket[hash]; //找到自己对应的桶里的对象;
while (oTempB.next) {
if (oTempB.key == key ){
return oTempB.value;
}else{
oTempB = oTempB.next;
}
}
return '没有找到该值';
}
myMap.prototype.delete = function (key) {
let hash = this.makeHash(key);
let oTempB = this.bucket[hash];
while (oTempB.next) {
if(oTempB.next.key == key){ //如果当前桶中对象的next中的key是传入的key时;
oTempB.next = oTempB.next.next;
//直接将上一层的next对象,连接到下一层next对象上,即可达到删除功能;
return '删除成功'
}else{ //如果不是;
oTempB = oTempB.next;
//将链表进入下一层;直到找到传入的key的那一层next对象,进行上面操作;
}
}
return '该值不存在';
}
myMap.prototype.has = function (key) {
let hash = this.makeHash(key);
let oTempB = this.bucket[hash];
while(oTempB.next){
if (oTempB.next.key == key){
return '有该值';
}else{
oTempB = oTempB.next;
}
}
return "没有该值";
}
myMap.prototype.clear = function (key) {
this.init(); //初始化函数;
}
//测试MyMap:
const oMM = new myMap();
oMM.set('name2', 'l6');
oMM.set('name2', 'l5');
oMM.set('fly', 'l6');
oMM.set({}, 456);
oMM.get('name1');
console.log(oMM);
//与原Map方法结果一致;
利用myMap()可实现ES6中的Map();