Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

40.ES6 笔记四 - Set Map #41

Open
ccforward opened this issue Sep 9, 2016 · 0 comments
Open

40.ES6 笔记四 - Set Map #41

ccforward opened this issue Sep 9, 2016 · 0 comments

Comments

@ccforward
Copy link
Owner

ES6 笔记四 - Set Map

Set

基本用法

类似数组,但是成员都是唯一的没有重复值。

Set本身是一个构造函数用来生成Set类型的数据

Set可以接受一个数组或者类数组的对象作为参数来初始化

var s = new Set([1,2,3,3,3,4,5])
s.size  // 5
[...s]  // [1,2,3,4,5]

var ss = new Set([...document.querySelectorAll('div')]);
ss.size // div的个数

// 相当于
[...document.querySelectorAll('div')].forEach(div => ss.add(div))

使用 Set 数组去重 [...new Set(array)]

使用 Set 加入值时不会转换类型 5 和 '5' 是两个不同的值。虽然 NaN === NaN 返回 false, 但是在 Set 内部 NaN 是等于自身的。

var set = new Set();
var a = NaN;
var b = NaN;

set.add(a);
set.add(b);

set.size // 1  Set 内部 两个 NaN 是相等的

但是两个对象总是不等的

var set = new Set();

set.add({});
set.add({});

set.size // 2

属性 方法

1. 属性

  • Set.prototype.constroctor 构造函数
  • Set.prototype.size Set 实例的成员总数

2.1 操作方法

  • add(value) 返回 Set 本身
  • delete(value) 返回 Booolean
  • has(value) 返回 Booolean
  • clear()

Array.from 可以将 Set 结构转为数组

var s = new Set([1,2,3,4]);

var arr = Array.from(s);

arr // [1, 2, 3, 4]

数组去重的另一种方法

var unique = function(arr){
    return Array.from(new Set(arr))
}

unique([1,2,2,3,3,4]);

2.2 遍历方法

  • keys() 返回键名
  • values() 返回键值
  • entries() 返回键值对
  • forEach() 用回调遍历所有成员

Set 遍历的顺序就是插入顺序。如果使用Set保存一个回调函数的列表,调用时就能按照添加的顺序来调用。

1) keys(),values(),entries()

key()、values()、entries()返回的都是遍历器对象。因为 Set 没有键名(或者键名和键值是同一个值),所以key()和value()的行为完全一致。

var set = new Set(['aa', 'bb', 'cc']);

for (var item of set.keys()) {
  console.log(item);
}
// aa
// bb
// cc

for (var item of set.values()) {
  console.log(item);
}
// aa
// bb
// cc

for (var item of set.entries()) {
  console.log(item);
}
// ["aa", "aa"]
// ["bb", "bb"]
// ["cc", "cc"]

Set 结构的实例默认可以遍历 它的默认遍历器生成函数就是它的 values 方法

Set.prototype[Symbol.iterator] === Set.prototype.values

那么就可以直接用 for...of 来遍历Set。省略 values 方法。

var set = new Set(['aa', 'bb', 'cc']);

for(s of set) {
    console.log(s);
}
// aa
// bb
// cc
2) forEach()
var set = new Set(['aa', 'bb', 'cc']);

set.forEach((val,key) => console.log(val + ' - sss'))
3) 使用遍历

扩展运算符 (...) 内部使用 for...of 循环,所以也可以是用 Set 结构。

var set = new Set(['a', 'b', 'c']);

var arr = [...set];

// ['a', 'b', 'c']

数组的 map filter 方法也可用于 Set

var set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x*2));
// Set {2, 4, 6}

var set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x%2) ==0 ));
// Set {2, 4}

Set 很容易实现并集(Union)、交集(Intersect)和差集(Difference)

var a = new Set([1,2,3]);
var b = new Set([2,3,4]);

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

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

var diff = new Set([...a].filter(x => !b.has(x) ));
// Set {1}

WeakSet

与 Set 区别

  • WeakSet 的成员只能是对象
  • WeakSet 中的对象都是弱引用。
    如果其他对象不再引用该对象,那 GC 会自动回收该对象占用的内存,不考虑该对象还存在于 WeakSet 中。
    那就无法引用 WeakSet 的成员,因此 WeakSet 不可遍历。
var ws = new WeakSet();
ws.add(1); // Uncaught TypeError: Invalid value used in weak set
ws.add(Symbol()); // Uncaught TypeError: Invalid value used in weak set
var a = [[1,2], [3,4]];
var ws = new WeakSet(a);

上面代码中 a 数组的成员成为 WeakSet 的成员,而不是 a 数组本身。因此数组的成员只能是对象。

WeakSet 没有 size forEach 属性,没有办法遍历它的成员。

WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

Map

基本用法

对象(Object) 本质上是键值对的集合(Hash结构), 但是只能用字符串当键(字符串 - 值)。

Map 数据结构中各种类型的值(包括对象)都可以当作键(值 - 值)。

var m  = new Map();
var o = {a: 'abcd'};

m.set(o, 'content');
m.get(o); // 'content'

m.has(o); // true
m.delete(o); // true
m.has(o); // false

Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

var  m = new Map([
    ['name1', 'a'],
    ['name2', 'b']    
]);

m.size // 2
m.has('name1') // true

true 和 字符串 'true' 在 Map 表示两个值

var  m = new Map([
    [true, 'a'],
    ['true', 'b']    
]);

m.get(true)  // 'a'
m.get('true')  // 'b'

重复赋值会覆盖

var m = new Map();
m
 .set('a', 'a')
 .set('a', 'b')

m.get('a') // 'b'

读取一个未知的键,则返回undefined new Map().get('abc')

只有对用一个对象的引用 Map 才会视为同一个键

var m = new Map();

m.set(['a'], 123);

m.get(['a']) // undefined


var map = new Map();

var k1 = ['a'];
var k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222

// 变量k1和k2的值是一样的,但是它们在Map结构中被视为两个键

所以说,Map 的键是和内存地址绑定的,内存地址不同,就视为两个键。这样就解决了同名属性的问题。

在 Map 中 NaN 视为同一个键 0-0 也是同一个键,因为 0 === -0

属性 方法

  • size属性 返回Map结构的成员总数
  • set(key, value) 返回的是Map本身,因此可以采用链式写法。
  • get(key)
  • has(key)
  • delete(key) 删除失败,返回false
  • clear() 没有返回值

遍历方法

  • keys() 返回键名
  • values() 返回键值
  • entries() 返回所有成员
  • forEach() 遍历Map的所有成员

Map的遍历顺序就是插入顺序。

let map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

[...map.values()]
// ["no", "yes"]

[...map.keys()]
// ["F", "T"]

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// F no
//T yes

使用扩展运算符(...)Map结构可转为数组结构

var map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

结合数组的map方法、filter方法,可以实现Map的遍历和过滤(Map本身没有map和filter方法)

var map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

var map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
// 产生Map结构 {1 => 'a', 2 => 'b'}

var map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}

与其他数据结构的互相转换

  1. Map转为数组

    使用扩展运算符

    var m = new Map().set(true, 7).set({foo: 3}, ['abc']);
    [...m]
    //  [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  2. 数组转为Map

    数组直接放入Map的构造函数

    var m = new Map([ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]);
    m
    // Map {true => 7, Object {foo: 3} => ["abc"]}
  3. Map转为对象

    所有的键为字符串的Map可转换为数组

    function strMapToObj(map) {
      let obj = Object.create(null);
      for(let [k,v] of map){
        obj[k] = v;
      }
    
      return obj;
    }
    var m = new Map().set('a', 'a1a2a3').set('true', true);
    strMapToObj(m);
    // Object {a: "a1a2a3", true: true}
  4. 对象转为Map

    function objToStrMap(obj){
      let m = new Map();
      for(let [k,v] of obj){
        m.set(k,v)
      }
      return m
    }
  5. Map转为JSON

    Map的键名都是字符串,可以直接转为JSON

    function mapToJson(map){
      return JSON.stringify(strMapToObj(map));
    }
    var m = new Map().set('yes', true).set('no', false);
    strMapToJson(m)
    // '{"yes":true,"no":false}'

    Map的键名如果有非字符串,可选择转为数组JSON

      function mapToArrayJson(m){
        return JSON.stringify([...m])
      }
      var mm = new Map().set(true, 7).set({foo: 3}, ['abc']);
      mapToArrayJson(mm)
      // "[[true,7],[{"foo":3},["abc"]]]"
  6. JSON转为Map

    正常情况下所有键名都是字符串

    function jsonToStrMap(json){
      return objToStrMap(json);
    }
    jsonToStrMap('{"yes":true,"no":false}')
    // Map {'yes' => true, 'no' => false}

    有一种特殊情况 整个 JSON 是一个数组,每个数组成员本身又是一个有两个成员的数组,这时课一一对应的转为Map

    function jsonToMap(jsonStr) {
      return new Map(JSON.parse(jsonStr));
    }
    
    jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
    // Map {true => 7, Object {foo: 3} => ["abc"]}

WeakMap

WeakMap 与 Map 类似,只接受对象作为键名(null 除外),而且键名所指向的对象,不计入垃圾回收机制。

WeakMap 的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap 自动移除对应的键值对。典型应用是,一个对应 DOM 元素的WeakMap 结构,当某个 DOM 元素被清除,其所对应的 WeakMap 记录就会自动被移除。基本上,WeakMap 的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

var wm = new WeakMap();
var element = document.querySelector(".element");

wm.set(element, "Original");
wm.get(element) // "Original"

element.parentNode.removeChild(element);
element = null;
wm.get(element) // undefined

WeakMap 没有遍历操作,无法清空,只有四个方法可用:get()、set()、has()、delete()

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++;
  myWeakmap.set(myElement, logoData);
}, false);

myElement 是一个 DOM 节点,每当发生 click 事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是 myElement 。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

WeakMap的另一个用处是部署私有属性。

let _counter = new WeakMap();
let _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)();
    }
  }
}

let c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()

Countdown 类的两个内部属性 _counter 和 _action ,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant