JS-基础-迭代/set/map集合

1,126 阅读5分钟

Iterable object(可迭代对象)

可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。

Symbol.iterator

let range = {
  from: 1,
  to: 5
};

// 我们希望 for..of 可以正常遍历
for(let num of range){
    console.log(num)
}  
 //输出1 2 3 4 5

为了让 range 对象可迭代(也就让 for..of 可以运行)我们需要为对象添加一个名为 Symbol.iterator 的方法(一个专门用于使对象可迭代的内置 symbol)。

  1. 当 for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象。
  2. 当 for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。
  3. next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当 done=true 时,表示迭代结束,否则 value 是下一个值。
let range = {
  from: 1,
  to: 5
};

// 1. 通过for..of 调用,会调用 iterator 这个迭代方法
// 2. next() 在 for..of 的每一轮循环迭代中被调用    
// 3. 它将会返回 {done:.., value :...} 格式的对象,
// 4. 直到next方法返回:done:true结束
range[Symbol.iterator] = function() {
  return {
    current: this.from,
    last: this.to,

    next() {
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

//方法2 直接定义把迭代器写在对象里面
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
}; 

字符串是可迭代的

字符串内置了迭代器

for (let char of "test") {
  // 触发 4 次,每个字符一次
  alert( char ); // t, then e, then s, then t
}

直接掉用-迭代器

let str = "Hello";
// 和 for..of 做相同的事
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value); // 一个接一个地输出字符
}

Array.from

类数组对象

有索引属性和 length 属性的对象 。具有其他属性和方法,但是没有数组的内建方法。

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};
let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World(pop 方法有效)

// 假设 range 实现iterable的对象
let arr = Array.from(range); 
alert(arr); // 1,2,3,4,5 (数组的 toString 转化方法生效)

Array.from(obj[, mapFn, thisArg])

// 假设 range 来自上文例子中
// 求每个数的平方
let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25

把字符串转化成字符数组

let str = '2356';
// 将 str 拆分为字符数组
let chars = Array.from(str);
alert(chars[0]); // 2
alert(chars[1]); // 3
alert(chars.length); // 4

Map

是一个带键的数据项的集合,和 Object 一样。 唯一区别: Map 允许任何类型的键(key)。

  • new Map() —— 创建 map。
  • map.set(key, value) —— 根据键存储值。
  • map.get(key) —— 根据键来返回值,如果 map 中不存在对应的 key,则返回 undefined
  • map.has(key) —— 如果 key 存在则返回 true,否则返回 false
  • map.delete(key) —— 删除指定键的值。
  • map.clear() —— 清空 map。
  • map.size —— 返回当前元素个数。
let map = new Map();
map.set('1', 'str1');   // 字符串键
map.set(1, 'num1');     // 数字键
map.set(true, 'bool1'); // 布尔值键
// 还记得普通的 Object 吗? 它会将键转化为字符串
// Map 则会保留键的类型,所以下面这两个结果不同:
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3

特点:

1.键不会被转换成字符串。键可以是任何类型

2.Map 还可以使用对象作为键

let john = { name: "John" };
// 存储每个用户的来访次数
let visitsCountMap = new Map();
// john 是 Map 中的键
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123

//而对象则是
let john = { name: "John" };
let visitsCountObj = {}; // 尝试使用对象
visitsCountObj[john] = 123; // 尝试将 john 对象作为键
alert( visitsCountObj["[object Object]"] ); // 123

`不建议使用 map[key] = 2 这种写法,会当作 plain object处理

链式调用

map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

Map 迭代

map.keys(),map.values() ,map.entries()

let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);
// 遍历所有的键(vegetables)
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}
// 遍历所有的值(amounts)
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}
// 遍历所有的实体 [key, value]
for (let entry of recipeMap) { // 与 recipeMap.entries() 相同
  alert(entry); // cucumber,500 (and so on)
}

保留顺序

插入的顺序与遍历一致

let map = new Map()
map.set(1,1)
map.set(2,2)
map.set(3,3)
for (let item of map.entries()) {
  console.log(item)
}
//输出1,2,3

创建对象

new Map([ [key,val],[key2,val2]])//带有键值对的数组

// 键值对 [key, value] 数组
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);
alert( map.get('1') ); // str1

Object.entries(obj)

通过Object.entries(obj) 可以返回带有键值对的数组,就可以传入map初始化。

let obj = {
  name: "John",
  age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John

Object.fromEntries

给定一个具有 [key, value] 键值对的数组,它会根据给定数组创建一个对象

let prices = Object.fromEntries([
  ['banana', 1],
  ['orange', 2],
  ['meat', 4]
]);
// 现在 prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2

Map转对象

Object.fromEntries 期望得到一个可迭代对象作为参数,而不一定是数组。并且 map 的标准迭代会返回跟 map.entries() 一样的键/值对。

let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // 创建一个普通对象(plain object)
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2

Set

Set 是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。

  • new Set(iterable) —— 创建一个 set,如果提供了一个 iterable 对象(通常是数组),将会从数组里面复制值到 set 中。
  • set.add(value) —— 添加一个值,返回 set 本身
  • set.delete(value) —— 删除值,如果 value 在这个方法调用的时候存在则返回 true ,否则返回 false
  • set.has(value) —— 如果 value 在 set 中,返回 true,否则返回 false
  • set.clear() —— 清空 set。
  • set.size —— 返回元素个数。

特点:

重复使用同一个值调用 set.add(value) 并不会发生什么改变。这就是 Set 里面的每一个值只出现一次的原因。

let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits,一些访客来访好几次
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set 只保留不重复的值
alert( set.size ); // 3
for (let user of set) {
  alert(user.name); // John(然后 Pete 和 Mary)
}

创建

new Map([iterable])—— 创建 map,可选择带有[key,value]对的iterable`(例如数组)来进行初始化。

let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);

Set 迭代(iteration)

let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// 与 forEach 相同:
set.forEach((value, valueAgain, set) => {
  alert(value);//value, valueAgain 两个值是一样,只为了和map方法对应,方便替换
});

注意:forEach 的回调函数有三个参数:一个 value,然后是 同一个值 valueAgain,最后是目标对象。没错,同一个值在参数里出现了两次。

遍历方法

set.keys()

set.values()

set.entries()

总结

MapSet 中迭代总是按照值插入的顺序进行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其编号来获取元素。

练习

//问题  过滤字谜Anagrams,是具有相同数量相同字母但是顺序不同的单词。
//写一个函数 `aclean(arr)`,它返回被清除了字谜(anagrams)的数组。
//对于所有的字谜(anagram)组,都应该保留其中一个词,但保留的具体是哪一个并不重要。
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era"

//方法
function aclean(arr) {
  let map = new Map();
  for (let word of arr) {
    // 将单词 split 成字母,对字母进行排序,之后再 join 回来
    let sorted = word.toLowerCase().split('').sort().join(''); // (*)
    map.set(sorted, word);
  }
  return Array.from(map.values());
}

let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) );