写在最前:js中包含的迭代器(遍历器)比较多,博主认为整理在一块一起学习,进行对比,有助于记忆,便整理出这么一篇长文,文章有点长,强烈建议收藏,反复查阅!
目录
1.定义
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
JavaScript中内置的有关迭代器的使用非常多,接下来我们来看看都有哪些?
2.js中内置的迭代器(语句篇)
以下为迭代器语句篇,其中包括Iterator、for...in、for...of、for await ... of。
-
Iterator
Iterator 是 ES6 引入的一种新的迭代机制,是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现。
使用示例:
let arr = ['hello','been']
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // {value: "hello", done: false}
console.log(iterator.next()); // {value: "been", done: false}
console.log(iterator.next()); // {value: undefined, done: true}
由上我们可以看出,迭代的过程如下:
- 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置;
- 随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束;
- 当 done 为 true 时则遍历结束。
-
for...in
for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性。
使用示例:
Object.prototype.writeBlog = function () {
console.log("hello world");
}
let obj = {
age: 22,
name: 'been'
};
for ( key in obj) {
console.log(key);
}
由于for...in会遍历到原型上继承来的属性,导致输出结果为:age name writeBlog;
当我们不需要遍历到继承的属性时,可以使用hasOwnProperty() 进行判断,如下:
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key);
}
}
// 输出:age name
-
for...of
for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。ES6 新引入的迭代器,并且支持新的迭代协议。
使用示例:
let arr = ['hello','been'];
for (let val of arr) {
console.log(val);
}
//输出:
// hello
// been
let set = new Set();
set.add('hello');
set.add('been');
for (let val of set) {
console.log(val);
}
//输出:
// hello
// been
以上遍历数组和集合差不多,都是输出属性值。而遍历Map对象则是输出[key,value],如下
let map = new Map();
map.set(0, "zero");
map.set(1, "one");
for (let item of map) {
console.log(item);
}
// 输出:
// [0,"zero"]
// [1,"one"]
for (let [key,val] of map) {
console.log(`${key}:${val}`);
}
// 输出:
// 0:"zero"
// 1:"one"
遍历Map对象得到的值为数组,下标0和1分别为key和value,所以我们可以数组解构赋值方式对应赋值。
注:for...of不能遍历object对象,为什么呢?
let obj = {
age: 22,
name: 'been'
};
for (let key of obj) {
console.log(key);
}
// 报错 Uncaught TypeError: obj is not iterable
因为for...of语句用在可迭代对象上,即需要实现迭代器Iterator。而Object对象并没有实现这个Symbol.iterator接口,使得它无法被for...of遍历。
那怎么让对象可以被for...of 遍历?缺啥就补啥咯,代码如下:
Object.prototype[Symbol.iterator] = function*() {
let index = 0;
let arr = Object.entries(this);
let length = arr.length;
while (true) {
if (index >= length) {
return false
} else {
let key = arr[index] && arr[index][0];
let val = arr[index] && arr[index][1];
let result = { [key]: val };
index++;
yield result
}
}
};
我们给Object的原型绑了Symbol.iterator接口,用的是ES6引入的Generator函数,这时再用for...of遍历obj时,则输出:
-
for await ... of
for await...of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如arguments 或者NodeList),TypedArray,Map, Set和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。像await表达式一样,这个语句只能在 async function内使用。
虽然说for await ... of在异步或同步都可用,但一般用于异步的情况,同步用for ... of。
使用示例:
Symbol.asyncIterator 可指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环,代码如下:
// 创建异步可迭代对象
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
i: 0,
next() {
if (this.i < 2) {
return Promise.resolve({
value: this.i++,
done: false
});
}
return Promise.resolve({
done: true
});
}
};
}
};
// 执行迭代
(async function () {
for await (num of asyncIterable) {
console.log(num);
}
})();
// 输出:
// 0
// 1
async function * 可用来创建异步生成器,其中已经实现了异步迭代器协议, 所以可以用for await ... of迭代。代码如下:
// 创建异步生成器
async function* asyncGenerator() {
var i = 0;
while (i < 2) {
yield i++;
}
}
// 执行迭代
(async function() {
for await (num of asyncGenerator()) {
console.log(num);
}
})();
// 输出:
// 0
// 1
3.js中内置的迭代器(函数篇)
-
forEach()
forEach() 方法对数组的每个元素执行一次给定的函数。
参数 | 描述 | ||||||||
callback( currentValue ,index ,array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||
thisArg | 可选。当执行回调函数 callback 时,用作 this 的值。 |
使用示例:
const arr = [1,2,3,4];
const res = arr.forEach((v,i,a)=>{
arr[i] = v*2;
})
console.log(arr,res);
// 输出:[2, 4, 6, 8] undefined
我们发现,原数组中每个元素都结果回调函数的操作之后,都变为原来的俩倍;forEach是没有返回值的。
注意: 除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果在forEach中使用return,只能达到continue的效果,代码如下:
const arr = [1,2,3,4];
arr.forEach((val,i,eachArr)=>{
if(val===2){
return
}
eachArr[i] = val*2;
})
console.log(arr);
// 输出:[2, 2, 6, 8]
由上可见,forEach()不适合需要中止或跳出循环的情况。
-
map()
map()
方法创建一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
参数 | 描述 | ||||||||
callback( currentValue ,index ,array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||
thisArg | 可选。当执行回调函数 callback 时,用作 this 的值。 |
使用示例:
const arr = [1,2,3,4];
const res = arr.map(v=> v*2)
console.log(arr,res);
// 输出:
// [1, 2, 3, 4]
// [2, 4, 6, 8]
我们发现,原数组没有改变,返回的数组为原来的俩倍;map()中修改val值,不会修改到原数组,而是修改返回的新数组;
如果不使用返回的新数组,可以用forEach()或for...of替代。
注意: 和forEach()一样,除了抛出异常以外,没有办法中止或跳出 map() 循环。如果在map中return没有内容时,会让新数组对应值为undefined,代码如下:
const arr = [1, 2, 3, 4];
const res = arr.map(v => {
if(v === 2){
return
}
return v * 2
})
console.log(res);
// 输出: [2, undefined, 6, 8]
-
every()
every()方法测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。
参数 | 描述 | ||||||||
callback( currentValue ,index ,array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||
thisArg | 可选。当执行回调函数 callback 时,用作 this 的值。 |
使用示例:
const result1 = arr.every(v=>{
console.log(v);
return v<=4
})
console.log(result1);
// 输出:1 2 3 4 true
const result2 = arr.every(v=>{
console.log(v);
return v>2
})
console.log(result2);
// 输出:1 false
从上面俩个输出对比,可以发现:every 在迭代过程中,当callback返回有false,将会立即返回 false,不再继续迭代。相反地,当每一个元素执行都返回 true,every才会返回 true。
和map()一样,every()中操作val值,不会改变原数组。代码如下:
const arr = [1,2,3,4];
const res = arr.every(v=>{
v=v*2
return v>1
})
console.log(res,arr);
// 输出:
// true [1, 2, 3, 4]
-
some()
some() 方法测试数组中是不是至少有1个元素通过指定函数的测试。它返回一个布尔值。
参数 | 描述 | ||||||||
callback( currentValue ,index ,array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||
thisArg | 可选。当执行回调函数 callback 时,用作 this 的值。 |
使用示例:
const arr = [1,2,3,4];
const result1 = arr.some(v=>{
console.log(v);
return v<=4
})
console.log(result1);
// 输出:
// 1 true
const result2 = arr.some(v=>{
console.log(v);
return v>4
})
console.log(result2);
// 输出:
// 1 2 3 4 false
从上面俩个输出对比,可以发现:some在迭代过程中,当callback返回有true,将会立即返回 true,不再继续迭代。相反地,当每一个元素执行都返回 false,some才会返回 false。
和every()一样,some()中操作val值,不会改变原数组。代码如下:
const res = arr.some((v,i,a)=>{
v=v*2
return v<2
})
console.log(res,arr);
// 输出:
// false [1, 2, 3, 4]
-
find()
find()方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
参数 | 描述 | ||||||||
callback( currentValue ,index ,array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||
thisArg | 可选。当执行回调函数 callback 时,用作 this 的值。 |
使用示例:
const arr = [5, 12, 130, 44];
const found1 = arr.find(v =>{
console.log(v);
return v > 10
});
console.log(found1);
// 输出:5 12 12
const found2 = arr.find(v =>{
console.log(v);
return v > 130
});
console.log(found2);
// 输出:5 12 130 44 undefined
从上面俩个输出对比,可以发现:find在迭代过程中,当callback返回有true,将会立即返回 符合值,不再继续迭代。相反地,当每一个元素执行都返回 false,find会返回 undefined。
-
findIndex()
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。
findIndex()和find()用法类似,区别:前者返回索引,后者返回值。所以不再赘述。
-
filter()
filter()方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
参数 | 描述 | ||||||||
callback( currentValue ,index ,array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||
thisArg | 可选。当执行回调函数 callback 时,用作 this 的值。 |
使用示例:
const arr = [5, 12, 8, 130];
const filterArr = arr.filter(v => v > 10);
console.log(filterArr,arr);
// 输出: [12, 130] [5, 12, 8, 130]
我们发现,新数组中的元素都为回调函数返回true的元素,而且原数组不会改变。
-
reduce()
reduce()
方法对数组中的每个元素执行回调函数(顺序从左到右),将其结果汇总为单个返回值。
参数 | 描述 | ||||||||||
callback( accumulator, currentValue, index, array ) | 必需。为数组中每个元素执行的函数。 该函数参数:
| ||||||||||
initialValue | 可选。初始值:作为第一次调用 callback 函数时的accumulator 值。 如果没有提供,则使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。 |
使用示例:
// reduce()
const arr = [1, 2, 3, 4];
let log = [];
const reducer = (accumulator, currentValue, index , arr) => {
log.push({
accumulator,
currentValue,
index ,
arr:arr.join(',')
});
return accumulator + currentValue
};
// 1 + 2 + 3 + 4 没有初始值
const result1 = arr.reduce(reducer)
console.table(log);
console.log("返回:",result1);
为方便查看执行过程中各参数变化,我们用console.table打印结果如下:
我们发现,当没有初始值initialValue时,各变量会有以下规则:
- 首次执行时,accumulator为数组第一个值,往后都是前一次执行的返回结果;
- currentValue和index都是从第二个元素开始;
- arr原数组不改变;
- 返回值为最后一次调用回调结果。
再来看看,在同一个回调函数,设置初始值initialValue的情况,代码如下:
log = [];
const result2 = arr.reduce(reducer,5);
console.table(log);
// 5 + 1 + 2 + 3 + 4 有初始值
console.log("返回:",result2);
打印结果如下:
我们发现,当设置初始值initialValue时,各变量会有以下规则:
- 首次执行时,accumulator为初始值initialValue,往后都是前一次执行的返回结果;
- currentValue和index都是从第一个元素开始;
- arr原数组不改变;
- 返回值为最后一次调用回调结果。
-
reduceRight()
reduceRight()
方法对数组中的每个元素执行回调函数(顺序从右到左),将其结果汇总为单个返回值。
reduceRight()和reduce()用法类似,主要是执行顺序不同:前者从右到左,后者从左到右。所以不再赘述。
非常感谢您的耐心阅读,整理不易,喜欢点赞、评论、收藏、关注哦,您是最棒的!