本文从使用 forEach
对数组进行遍历开始提及,粗略对比使用 forEach
, for...in
, for...of
进行遍历的差别,并由此引入 ES6 中 *可迭代对象/迭代器 *的概念,并对其进行粗略介绍。javascript
forEach
方法按升序为数组中的有效值的每一项执行一次callback
函数html
empty
的内容。(undefined
不会被跳过哦)。forEach
的第二个参数 thisArg
传入后能够改变 callback
函数的 this
值,若是不传则为 undefined
。固然, callback
所拿到的 this
值也受广泛规律的支配:这意味着若是 callback
是个箭头函数,则 thisArg
会被忽略。(由于箭头函数已经在词法上绑定了this值,不能再改了)forEach
遍历的范围在第一次调用 callback
前就会肯定,这意味着调用forEach
后添加到数组中的项不会被 callback
访问到。同时,因为 forEach
的遍历是基于下标的(能够这么理解,并能从 Polyfill 中看到这一实现),那么在循环中删了数组几项内容则会有奇怪的事情发生:好比下图中,在下标为 1 的时候执行了 shift()
,那么原来的第 3 项变为了第 2 项,原来的第 2 项则被跳过了。for
/ for..of
Array.prototype.every()
/ Array.prototype.some()
并返回 false
/ true
来进行中断/跳出undefined
)for...in
循环实际是为循环”enumerable“对象而设计的:java
for...in
语句以任意顺序遍历一个对象的 可枚举属性 。对于每一个不一样的属性,语句都会被执行。es6
for...in
循环将遍历对象自己的全部可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)。for...in
的同时还须要配合 getOwnPropertyNames()
或 hasOwnProperty()
for ... in
将以任何特定的顺序返回索引,好比在 IE 下可能会乱来。Array
:
for...of
的本质是在 可迭代对象(iterable objects) 上调用其 迭代方法 建立一个迭代循环,并执行对应语句。能够迭代 数组/字符串/类型化数组(TypedArray)/Map/Set/generators/类数组对象(NodeList/arguments) 等。须要注意的是, Object
并非一个可迭代对象。数组
for...of
是 ES6 中新出炉的,其弥补了 forEach
和 for...in
的诸多缺点:浏览器
break
, throw
或return
等终止那么咱们简单的几句话说明一下 for...of
和 for...in
的区别:数据结构
for...of
语句遍历 可迭代对象 定义要迭代的数据。for...of
获得的是 值(value), 而 for...in
获得的是 键(key)。那么扯到了 可迭代对象 ,就不得不说说 ES6 中新增的与 可迭代对象/迭代器 有关东西了。函数
*iteration *是 ES6 中新引入的遍历数据的机制,其核心概念是:iterable(可迭代对象) 和 iterator(迭代器):ui
Symbol.iterator
方法首先,可迭代协议容许 JavaScript 对象去定义或定制它们的迭代行为。而如 Array
或 Map
等内置可迭代对象有默认的迭代行为,而如 Object
则没有。(因此为何不能对 Object
用 for...of
)再具体一点,即对象或其原型上有 [Symbol.iterator]
的方法,用于返回一个对象的无参函数,被返回对象符合迭代器协议。而后在该对象被迭代的时候,调用其 [Symbol.iterator]
方法得到一个在迭代中使用的迭代器。this
首先能够看见的是 Array 和 Map 在原型链上有该方法,而 Object 则没有。这印证了上面对于哪些能够用于 for...of
的说法。
若是咱们非要想用 for...of
对 Object
所拥有的属性进行遍历,则可以使用内置的 Object.keys()
方法:
for (const key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
复制代码
或者若是你想要更简单得同时获得键和值,能够考虑使用 Object.entries()
:
for (const [key, value] of Object.entries(someObject)) {
console.log(key + ": " + value);
}
复制代码
其次,有以下状况会使用可迭代对象的迭代行为:
for...of
上文说到返回了一个迭代器用于迭代,那咱们就来看看符合什么样规范的才算一个 迭代器 。 只须要实现一个符合以下要求的 next
方法的对象便可:
属性
|
值
|
next
|
返回一个对象的无参函数,被返回对象拥有两个属性:
|
本质上,在使用一个迭代器的时候,会不断调用其 next()
方法直到返回 done: true
。
既然符合可迭代协议的均为可迭代对象,那接下来就简单自定义一下迭代行为:
// 让咱们的数组倒序输出 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
中中断迭代,其本质是中断了迭代器,迭代器在中断后会被关闭。说到这里,就继续说一下迭代器关闭的状况了。
首先,迭代器的关闭分为两种状况:
next()
方法直到返回 done: true
,也就是迭代器正常执行完后关闭return()
方法来告诉迭代器不打算再调用 next()
方法那么何时会调用迭代器的 return
方法呢:
return()
是个可选方法,只有具备该方法的迭代器才是 可关闭的(closable)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;
}
复制代码
上文 迭代器协议 中提到的返回的拥有 next()
方法的对象和咱们在 Generator
中使用的 next()
方法彷佛如出一辙。确实, Generator
符合可迭代协议和迭代器协议的。
由于 Generator
既有符合规范的 next()
(迭代器协议)方法,也有 Symbol.iterator
(可迭代协议)方法,所以它 既是迭代器也是可迭代对象 。
默认状况下,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
的内容就不在本篇进行阐述,有机会将单独做为一篇慢慢讲。