脚本宝典收集整理的这篇文章主要介绍了从forEach到迭代器,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
本文从使用 foreach
对数组进行遍历开始说起,粗略对比使用 forEach
, for...in
, for...of
进行遍历的差异,并由此引入 ES6 中 可迭代对象/迭代器 的概念,并对其进行粗略介绍。
forEach
forEach
方法按升序为数组中的有效值的每一项执行一次callback
** 函数
特点
-
forEach
的第二个参数 thisarg
传入后可以改变 callback
函数的 this
值,如果不传则为 undefined
。当然, callback
所拿到的 this
值也受普遍规律的支配:这意味着如果 callback
是个箭头函数,则 thisArg
会被忽略。(因为箭头函数已经在词法上绑定了this值,不能再改了)
- 不建议在循环中增/删数组内容:首先,
forEach
遍历的范围在第一次调用 callback
前就会确定,这意味着调用forEach
后添加到数组中的项不会被 callback
访问到。同时,由于 forEach
的遍历是基于下标的(可以这么理解,并能从 Polyfill 中看到这一实现),那么在循环中删了数组几项内容则会有奇怪的事情发生:比如下图中,在下标为 1 的时候执行了 shift()
,那么原来的第 3 项变为了第 2 项,原来的第 2 项则被跳过了。
缺点
-
无法中断或跳出:如果想要中断/跳出,可以考虑使用如下两种方式:
- 无法链式调用(因为返回了
undefined
)
for...in
for...in
循环实际是为循环 enumerable
对象而设计的:
for...in
语句以任意顺序遍历一个对象的 可枚举属性 。对于每个不同的属性,语句都会被执行。
特点
- 遍历可枚举属性:
for...in
循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)。
- 可以被中断
缺点
- 会访问到能访问到的所有的 可枚举属性 ,也就是说会包括那些原型链上的属性。如果想要仅迭代自身的属性,那么在使用
for...in
的同时还需要配合 getOwnPropertynames()
或 hasOwnProperty()
- 不能保证
for ... in
将以任何特定的顺序返回索引,比如在 IE 下可能会乱来。
-
不建议用于迭代 Array
:
- 不一定保证按照下标顺序
- 遍历得到的下标是字符串而不是数字
for...of
for...of
的本质是在 可迭代对象(ITerable objects) 上调用其 迭代方法 创建一个迭代循环,并执行对应语句。可以迭代 数组/字符串/类型化数组(TyPEdArray)/Map/Set/generators/类数组对象(nodelist/arguments) 等。需要注意的是, Object
并不是一个可迭代对象。
for...of
是 ES6 中新出炉的,其弥补了 forEach
和 for...in
的诸多缺点:
区别
那么我们简单的几句话说明一下 for...of
和 for...in
的区别:
- for...in 语句以原始插入顺序(还不一定保证)迭代对象的 可枚举属性 。
-
for...of
语句遍历 可迭代对象 定义要迭代的数据。
-
for...of
得到的是 值(value), 而 for...in
得到的是 键(key)。
那么扯到了 可迭代对象 ,就不得不说说 ES6 中新增的与 可迭代对象/迭代器 有关东西了。
可迭代对象/__迭代器__
iteration 是 ES6 中新引入的遍历数据的机制,其核心概念是:iterable(可迭代对象) 和 iterator(迭代器):
-
iterable(可迭代对象):一种希望被外界访问的内部元素的数据结构,实现了
Symbol.iterator
方法
-
iterator(迭代器):用于遍历数据结构元素的指针
可迭代协议(iterable PRotocol)
首先,可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为。而如 Array
或 Map
等内置可迭代对象有默认的迭代行为,而如 Object
则没有。(所以为什么不能对 Object
用 for...of
)再具体一点,即对象或其原型上有 [Symbol.iterator]
的方法,用于返回一个对象的无参函数,被返回对象符合迭代器协议。然后在该对象被迭代的时候,调用其 [Symbol.iterator]
方法获得一个在迭代中使用的迭代器。
首先可以看见的是 Array 和 Map 在原型链上有该方法,而 Object 则没有。这印证了上面对于哪些可以用于 for...of
的说法。
如果我们非要想用 for...of
对 Object
所拥有的属性进行遍历,则可使用内置的 Object.keys()
方法:
for (const key of @H_338_304@Object.keys(someObject)) {
console.LOG(key + ": " + someObject[key]);
}
或者如果你想要更简单得同时得到键和值,可以考虑使用 Object.entries()
:
for (const [key, value] of Object.entries(someObject)) {
console.log(key + ": " + value);
}
其次,有如下情况会使用可迭代对象的迭代行为:
迭代器协议(iterator protocol)
上文说到返回了一个迭代器用于迭代,那我们就来看看符合什么样规范的才算一个 迭代器 。
只需要实现一个符合如下要求的 next
方法的对象即可:
本质上,在使用一个迭代器的时候,会不断调用其 next()
方法直到返回 done: true
。
自定义迭代行为
既然符合可迭代协议的均为可迭代对象,那接下来就简单自定义一下迭代行为:
@H_867_406@// 让我们的数组倒序输出 value
const myArr = [1, 2, 3];
myArr[Symbol.iterator] = function () {
const that = this;
let index = that.length;
return {
next: function () {
if (index > 0) {
index--;
return {
value: that[index],
done: false
};
} else {
return {
done: true
};
}
},
};
};
[...myArr]; // [3, 2, 1]
Array.from(myArr) // [3, 2, 1]
一句说明可迭代对象和迭代器的关系
当一个__可迭代对象__需要被迭代的时候,它的 Symbol.iterator
方法被无参调用,然后返回一个用于在迭代中获得值的迭代器。
换句话说,一个对象(或其原型)上有符合标准的 Symbol.iterator
接口,那他就是 可迭代的(Iterator)
,调用这个接口返回的对象就是一个 迭代器
关闭迭代器
上文提到说 for...of
比 forEach
好在其可以被“中断”,那么对于在 for...of
中中断迭代,其本质是中断了迭代器,迭代器在中断后会被关闭。说到这里,就继续说一下迭代器关闭的情况了。
首先,迭代器的关闭分为两种情况:
- Exhaustion:当被持续调用
next()
方法直到返回 done: true
,也就是迭代器正常执行完后关闭
- Closing:通过调用
return()
方法来告诉迭代器不打算再调用 next()
方法
那么什么时候会调用迭代器的 return
方法呢:
- 首先,
return()
是个可选方法,只有具有该方法的迭代器才是 可关闭的(closable)
- 其次,只有当没有 Exhaustion 时才应该调用
return()
,如 break
, throw
或 return
等
最后,return()
方法中也不是想怎么写就怎么写的,也有自己的要求, return()
方法需要符合以下规范:
-
return(x)
通常应该返回如 { done: true, value: x }
的结果,如果返回的不是个对象则会报错
- 调用
return()
后, next()
返回的对象也应该是 done:true
(这就是为什么有一些迭代器在 for...of
循环中中断后无法再次使用的原因,比如 Generator
)
同时,需要额外注意的是,及时在收到迭代器最后一个值后调用 break
等,也会触发 return()
function createiterable() {
let done = false;
const iterable = {
[Symbol.iterator]() {
return this;
},
next() {
if (!done) {
done = true;
return {
done: false,
value: 'a'
};
} else {
return {
done: true,
value: undefined
};
}
},
return () {
console.log('return() was called!');
return {
done: true,
value: undefined
};
},
};
return iterable;
}
for (const value of createIterable()) {
console.log(value);
break;
}
生成器(Generator)
既是迭代器也是可迭代对象
上文 迭代器协议 中提到的返回的拥有 next()
方法的对象和我们在 Generator
中使用的 next()
方法似乎一模一样。确实, Generator
符合可迭代协议和迭代器协议的。
因为 Generator
既有符合规范的 next()
(迭代器协议)方法,也有 Symbol.iterator
(可迭代协议)方法,因此它 既是迭代器也是可迭代对象 。
可关闭的(closable)
默认情况下,Generator对象是可关闭的。因此在用 for...of
时中断迭代后,无法再次对原有 Generator对象进行迭代。(因为调用return()
后, next()
返回的对象也应该是 done:true
)
当然,既然是默认情况,我们就可以想办法让其无法被关闭:
可以通过包装一下迭代器,将迭代器本身/原型上的 return()
方法被重写掉
class PreventReturn {
constructor(iterator) {
this.iterator = iterator;
}
[Symbol.iterator]() {
return this;
}
next() {
return this.iterator.next();
}
// 重写掉 return 方法
return (value = undefined) {
return {
done: false,
value
};
}
}
更多关于 Generator
的内容就不在本篇进行阐述,有机会将单独作为一篇慢慢讲。
参考
以上是脚本宝典为你收集整理的从forEach到迭代器全部内容,希望文章能够帮你解决从forEach到迭代器所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。