ES6-Set and Map
依赖文件地址 :https://github.com/chanceLe/ES6-Basic-Syntax/tree/master/js
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>[es6]-12-Set和Map结构</title> 6 <script src="./js/browser.js"></script> 7 <script type="text/babel"> 8 /* 9 * Set 10 * Es6提供了新的数据结构Set,类似数组,成员都是唯一的,不重复。 11 * 本身是一个构造函数,用来生成Set数据结构。 12 */ 13 14 var s = new Set(); 15 [2,3,5,4,5,2,2].map(x=>s.add(x)); 16 console.log(s); //2,3,5,4 不会重复添加 17 18 //Set函数可以接收一个数组(或类似数组的对象)作为参数,用来初始化。 19 var set = new Set([1,2,3,4]); 20 console.log([...set]); //[1,2,3,4] 21 22 var set2 = new Set([1,2,3,4,5,5,5,5,5,5]); 23 console.log(set2.size); //5 没有重复值 24 25 var set3 = new Set([...document.querySelectorAll("div")]); 26 console.log(set3.size); //3 27 28 //上面代码中也展示了一种数组去重方法 [...new Set(array)] 29 console.log([...new Set([1,2,3,2,3,2,3])]); //[1,2,3] 30 31 /* 32 * 向Set加入值的时候,不会发生类型转换,所以5 和“5” 是不一样的。 33 * Set内部的比较算法是 same-value-equality。类似“===”主要的区别是NaN等于自身,三等号认为NaN不等于NaN。 34 * 在set内部,NaN等于NaN.但两个对象总是不相等。 35 */ 36 let set4 = new Set(); 37 set4.add({}); 38 console.log(set4.size); //1 39 set4.add({}); 40 console.log(set4.size); //2 表示两个对象在Set内是不相等的。 41 set4.add(NaN); 42 console.log(set4.size); //3 43 set4.add(NaN); 44 console.log(set4.size); //3 都是3表明NaN在Set内部是相等的。 45 46 /* 47 * Set实例的属性和方法 48 * 属性: Set.prototype.constructor: 构造函数,默认就是Set。 49 * Set.prototype.size : 返回Set成员数。 50 * 51 * 方法:分为两类,操作方法和遍历方法 52 * 四个操作: 53 * add(value) 添加某个值,返回Set结构本身。 54 * delete(value) 删除某个值,返回布尔值,表示删除是否成功。 55 * has(value) 返回一个布尔值,表示该值是否为Set的成员。 56 * clear() 清除所有成员,没有返回值。 57 */ 58 59 //Array.from()可以把Set结构转为数组,所以又有了一种去重的方法 60 function dedupe(arr){ 61 return Array.from(new Set(arr)); 62 } 63 console.log(dedupe([1,2,3,3,3,])); //[1,2,3] 64 65 /* 66 * Set结构的实例有四个遍历方法,可用于遍历成员。 67 * keys() 返回键名的遍历器 68 * values() 返回键值的遍历器 69 * entries() 返回键值对的遍历器 70 * forEach() 使用回调函数遍历每个成员 71 * 72 * 需要特别指出的是 Set的遍历顺序就是插入顺序。 73 * Set结构中,键名跟键值是同一个,所以keys 跟values的结果一样。 74 */ 75 let set5 = new Set(["green","blue","red"]); 76 for(let key of set5.keys()){ 77 console.log(key,"key") 78 } 79 for(let value of set5.values()){ 80 console.log(value,"value") 81 } 82 for(let entry of set5.entries()){ 83 console.log(entry,"entry") 84 } 85 86 // Set结构默认可遍历,他的默认遍历器就是values方法 87 console.log(Set.prototype[Symbol.iterator] == Set.prototype.values); //true 88 //这意味着可以省略values方法 直接用for of 循环 89 90 for(let value of set5){ 91 console.log(value,"value") //默认是values方法 92 } 93 94 //forEach 参数是一个处理函数。参数函数的参数依次是键值,键名,Set本身,forEach方法本身 95 //还可以有第二个参数,表示绑定的this对象。 96 97 //扩展运算符内部使用的是for...of,所以同样可以用于集合。 98 console.log([...set5]); //["green","blue","red"] 99 100 //数组的map和filter方法也可以用于Set 101 let set6 = new Set([1,2,3]); 102 set6 = new Set([...set6].map(x=>x*2)); 103 console.log(set6); //{2,4,6} 104 105 let set7 = new Set([1,2,3,4,5]); 106 set7 = new Set([...set7].filter(x=>(x % 2)==0)); 107 console.log(set7); //{2,4} 108 109 //使用Set可以很容易实现并集交集差集 110 111 let a = new Set([1,2,3]); 112 let b = new Set([4,3,2]); 113 114 //并集 115 let union = new Set([...a,...b]); 116 console.log(union); //{1,2,3,4} 117 118 //交集 119 let intersect = new Set([...a].filter(x=>b.has(x))); 120 console.log(intersect) //{2,3} 121 122 //差集 123 let diff = new Set([...a].filter(x=>!b.has(x))); 124 console.log(diff); //{1} 125 /* 126 * 如果想在操作中改变原来的Set结构,目前没有直接的方法,但有两个变通方法, 127 * 一种是利用原Set映射出一个新的结构,然后赋值给原来的Set结构; 128 * 一种是利用Array.from方法 129 */ 130 //方法一 131 let set8 = new Set([1,2,3]); 132 set8 = new Set([...set8].map(x=>x*2)); 133 console.log(set8); //{2,4,6} 134 135 //方法二 136 let set9 = new Set([1,2,3]); 137 set9 = new Set(Array.from(set,val=>val*2)); 138 console.log(set9); //{2,4,6} 139 140 /* 141 * WeakSet 142 * 结构与Set类似,但只能存放对象,不能存放其他类型的成员。 143 * WeakSet中的对象都是弱引用,即不在垃圾回收机制的考虑范围内。 144 * 这意味着无法引用WeakSet的成员,因此WeakSet不可遍历。 145 * 不能获取size和forEach属性。 146 * 一个用处是,储存DOM节点,而不担心这些节点从文档中移除时,会引发内存泄漏下面是另一个例子: 147 */ 148 const foos = new WeakSet(); 149 class Foo{ 150 constructor(){ 151 foos.add(this); 152 } 153 method(){ 154 if(!foos.has(this)){ 155 throw new TypeError("Foo.prototype.method 只能在Foo的实例上调用") 156 } 157 } 158 } 159 //上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用WeakSet的好处是foos对实例的引用, 160 //不会被计入内存回收机制。所以删除实例的时候不用考虑foos,也不会出现内存泄漏。 161 162 163 164 /* 165 * Map 166 * Map结构的目的和基本用法 167 * js的对象本质上是键值对的集合(hash结构),但是传统上只能用字符串当做键。这给使用带来了很大限制。 168 * Es6提供了Map数据结构,类似对象,也是键值对的结合,但键的范围不限于字符串,各种类型的值都可以当做键。 169 * 也就是说Object结构提供了“字符串-值”的对应。Map结构提供了“值-值”的对应。是一种更完善的哈希结构实现。 170 */ 171 var m = new Map(); 172 var o = {p:"hello world!"} 173 174 m.set(o,'content'); 175 console.log(m.get(o)); //content 176 console.log(m.has(o)); //true 177 console.log(m.delete(o)); //true 178 console.log(m.has(o)); //false 179 /* 180 * 上面代码使用set方法,将对象o当做m的一个键,然后使用get方法读取这个键,接着用delete方法删除了这个键。 181 * 作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 182 */ 183 var m2 = new Map([ 184 ["name","张丹"], 185 ["age",20] 186 ]) 187 console.log(m2.size); //2 188 console.log(m2.has("name")); //true 189 console.log(m2.get("name")); //张丹 190 191 //下面的例子中,字符串true和布尔true是两个不同的键 192 var m3 = new Map([ 193 [true,"1"], 194 ["true","string"] 195 ]); 196 console.log(m3.get("true")); //string 197 console.log(m3.get(true)); //1 198 199 //如果对同一个键多次赋值,后面的值将覆盖前面的值 200 m3.set(true,"number"); 201 console.log(m3.get(true)); //number 202 203 // 如果读取一个未知的键,则返回undefined。注意,只有对同一个对象的引用,Map 204 // 才将其视为同一个键,这一点要非常小心。 205 206 var m4 = new Map(); 207 m4.set(["s"],555); 208 console.log(m4.get(["s"])); //undefined 因为这俩不是同一个对象的引用。 209 210 //同理,同样的值的两个实例,在Map结构中被视为两个键。 211 212 var m5 = new Map(); 213 var k1 = ["a"]; 214 var k2 = ["a"]; //k1其实跟k2的内存地址不一样。 215 m5.set(k1,"1111") 216 m5.set(k2,"2222") 217 console.log(m5.size); //2 表明这是两个键。 218 219 //由上可知,Map实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名 220 //属性碰撞的问题。我们扩展别人库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者 221 //重名。 222 /* 223 * 如果Map的键是一个简单类型的值(数字,字符串,布尔值),则只要两个严格相等,Map将其视为一个键。 224 * 包括+0和-0。另外,虽然NaN不等于自身,但Map将其视为同一个键。 225 */ 226 227 var m6 = new Map(); 228 m6.set(NaN,"123"); 229 console.log(m6.get(NaN)); //123 230 m6.set(-0,"456"); 231 console.log(m6.get(0)); //456 232 233 /* 234 * 实例的属性和操作方法 235 * 属性: size 返回大小 236 * 方法: set 添加 get 获取,找不到返回undefined has 判断是否存在,返回布尔 237 * delete删除,返回布尔值 clear清空。 238 * 239 * 遍历方法跟Set一样,keys,values,entries,forEach 240 * Mapc的默认遍历器是entries,是键值对。 241 * 242 * Map转为数结构,比较快速的是用... 扩展运算符。 243 * 244 * Map还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。 245 * 这里的forEach接受第二个参数,绑定this。 246 */ 247 var reporter = { 248 report:function(key,value){ 249 console.log("key:%s,value :%s",key,value); 250 } 251 } 252 console.log(reporter.report); 253 var m7 = new Map([ 254 ["name","zhangdan"], 255 ["age",25] 256 ]) 257 m7.forEach(function(value,key,map){ 258 this.report(key,value); 259 },reporter) 260 261 //上面的handler函数不能用箭头函数,原因是箭头函数的this 是死的。 262 263 /* 264 Map与其他结构互转 265 */ 266 267 //1.map转为数组 ,...扩展运算符。 268 var m8 = new Map().set("add","+").set("plus","-"); 269 console.log([...m8]); 270 271 //2.数组转为Map,将数组传入构造函数。 272 var m9 =new Map([["name","张丹"],["age",26]]); 273 274 //3.如果map的所有键都是字符串,那就可以转成对象。 275 var strMaptoObj = (strMap) =>{ 276 let obj = Object.create(null); 277 for(let [k,v] of strMap){ 278 obj[k] =v; 279 } 280 return obj; 281 } 282 console.log( typeof(strMaptoObj(m9))); //object 283 284 //4.对象转为Map 285 var objToStrMap = (obj)=>{ 286 let strMap = new Map(); 287 for(let k of Object.keys(obj)){ 288 strMap.set(k,obj[k]); 289 } 290 return strMap; 291 } 292 console.log(objToStrMap({yes:true,no:false})); //Map 293 294 //5.Map转Json,分为两种情况。 295 //一种是 Map的键名都是字符串,这时可以转为对象JSON。 296 var strMapToJson = (strMap) =>JSON.stringify(strMaptoObj(strMap)); 297 let m10 = new Map().set("yes",true).set("no",false); 298 console.log(strMapToJson(m10)); //{"yes":true,"no":false} 299 300 //另一种是Map的键名含有非字符串,这时可以转为数组JSON。 301 var mapToArrayJson = (map) => JSON.stringify([...map]); 302 let m11 = new Map().set(true,"1").set({foo:3},["abc"]); 303 console.log(mapToArrayJson(m11)); //[[true,"1"],[{"foo":3},["abc"]]] 数组json 304 305 //6.JOSN转Map 306 //正常情况下,所有键名都是字符串 307 var jsonToStrMap = (jsonStr) => objToStrMap(JSON.parse(jsonStr)); 308 console.log(jsonToStrMap('{"yes":true,"no":false}')); //Map {"yes" => true, "no" => false} 309 310 //但有一个特殊情况,整个JSON是一个数组,且每个数组成员本身,又是一个有两个成员的数组,这时,可以一一对应的转为Map。 311 //这往往是数组转JSON的逆操作。 312 var jsonToMap = (jsonStr) => new Map(JSON.parse(jsonStr)); 313 console.log(jsonToMap('[["name","zhangdan"],["age",25]]')); 314 //Map {"name" => "zhangdan", "age" => 25} 315 316 317 /* 318 * WeakMap 319 * 跟WeakSet类似,也是只接受成员为对象(null除外),而且键名所指向的对象,不计入垃圾回收机制。 320 * WeakMap的键名可能会被自动回收,当对象被回收后,WeakMap自动移除对应的键值对。 321 * 典型的应用是,一个DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录自动被移除。 322 * 基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在未来消失。 323 * WeakMap有助于防止内存泄漏。 324 * 325 * WeakMap的另一个用处就是部署私有属性。 326 */ 327 { 328 let _counter = new WeakMap(); 329 let _action = new WeakMap(); 330 331 class Countdown{ 332 constructor(counter,action){ 333 _counter.set(this,counter); 334 _action.set(this,action); 335 } 336 dec(){ 337 let counter = _counter.get(this); 338 if(counter<1){ 339 return 340 } 341 counter--; 342 _counter.set(this,counter); 343 if(counter === 0){ 344 _action.get(this)(); 345 } 346 } 347 } 348 349 let c = new Countdown(2, ()=>{console.log("Done")}); 350 c.dec(); 351 c.dec(); 352 353 //Done 354 } 355 </script> 356 </head> 357 <body> 358 <div></div> 359 <div></div> 360 <div></div> 361 </body> 362 </html>