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)。
- 当 for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象。
- 当 for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。
- 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()
总结
在 Map 和 Set 中迭代总是按照值插入的顺序进行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其编号来获取元素。
练习
//问题 过滤字谜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) );