目录
以往ES6文章:
ES6标准---【一】【学习ES6看这一篇就够了!!】_es6学习-CSDN博客
ES6标准---【二】【学习ES6看这一篇就够了!!】_es6中的includes-CSDN博客
ES6标准---【三】【学习ES6看这一篇就够了!!!】-CSDN博客
ES6标准---【四】【学习ES6标准看这一篇就够了!!!】_es6 有arguments 吗-CSDN博客
ES6标准---【五】【看这一篇就够了!!!】-CSDN博客
ES6标准---【六】【学习ES6标准看这一篇就够了!!!】-CSDN博客
iterator(遍历器)概念
JavaScript原有的表示“集合”的数据结构,主要是数组(array)和对象(object)
ES6又添加了“Map”、“Set”
用户还可以组合使用它们,定义自己的数据结构
如:“数组的成员是Map,Map的成员是对象”
为此我们需要一种统一的接口机制,来处理所有不同的数据结构(包括用户自定义的数据结构),这个机制就是“遍历器”(Iterator)”
任何数据结构只要部署Iterator接口,就可以完成遍历操作
Iterator的作用
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- 创造了一种“for...of”循环,Iterator主要依靠它来实现遍历
Iterator遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
- 第二次调用指针对象的next方法,可以将指针指向数据结构的第二个成员
- 不断调用指针对象的next方法,直到指向结束位置
每一次调用next方法,都会返回数据结构当前遍历成员的信息
返回内容:一个包含“value”和“done”两个属性的对象
- value:当前成员的值
- done:布尔值,表示遍历是否结束
一个模拟next方法返回值的例子
注意:
只是模拟!!!!
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
默认Iterator接口
ES6规定,默认的Iterator接口部署在数据结构的“Symbol.iterator”属性(一个数据结构只要有Symbol.iterator属性,就可以遍历)
ES6的有些数据结构原生具有Iterator接口,而有的数据结构需要部署“Symbol.iterator属性”才能够遍历,称为部署了遍历器接口
原生具有Iterator接口的数据结构
- array:数组
- Map:映射
- Set:集合
- String:字符串
- TypedArray
- 函数的arguments对象
- NodeList对象
对于原生部署Iterator接口的数据结构,不用自己写遍历器生成函数,“for...of”循环会自动遍历它们
调用Iterator的场合
解构赋值
对数组和set结构进行解构赋值时,会默认调用Symbol.iterator方法
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
扩展运算符
扩展运算符(...)也会调用默认的Iterator接口
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
实际上,这提供了一种简便机制,可以将任何部署了Iterator接口的数据结构转为数组
let arr = [...iterable];
yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
其他场合
- for...of
- Array.from()
- Map()、Set()、
- Promise.all()
- Promis.race()
字符串的Iterator接口
字符串是一个类似数组的对象,也原生具有Iterator接口
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
修改原生的Symbol.iterator方法
我们可以覆盖原生的Symbol.iterator方法,达到修改遍历器的目的
var str = new String("hi");
[...str] // ["h", "i"]
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
[...str] // ["bye"]
str // "hi"
for...of循环【重要】
for...of循环可以使用的范围包括“数组”、“Set”、“Map”、“字符串”等等
for...of循环内部调用的是数据结构的Symbol.iterator方法
语法:
for(变量 of Iterator)
{
....
}
遍历数组
<body>
<script>
const arr = ['red', 'green', 'blue'];
console.log("遍历arr:");
for(let v of arr) {
console.log(v); // red green blue
}
const obj = {};
console.log("遍历obj:");
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
for(let v of obj) {
console.log(v); // red green blue
}
</script>
</body>
效果:
因为对象obj部署了arr的Symbol.iterator属性,结果obj的for...of循环产生了与arr完全一样的结果(因为for...of遍历的是Iterator的Symbol.iterator属性)
- JavaScript原有的“for...in”循环,只能获得对象的键名,不能直接获取键值。ES6提供“for...of”循环,允许遍历获得键值
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
- for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性,而for...in会全部返回对象的所有属性
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
Set和Map结构
Set和Map的遍历顺序都是按照“各个成员被添加进数据结构的顺序”
- Set结构遍历:返回一个值
- Map结构遍历:返回一个数组(键:键名)
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
计算生成的数据结构
ES6的数组、Set、Map都部署了下面三个方法,调用后都返回遍历器对象:
- entries():返回一个遍历器对象,用来遍历[键名,键值],对于数组,键名就是索引,键是索引值,对于Set,键名与键值相同(Map结构的iterator接口默认调用entries方法)
- keys():返回一个遍历器对象,用来遍历所有的键名
- values():返回一个遍历器对象,用来遍历所有的键值
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
类似数组的对象
// 字符串
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}
// DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
- 并不是所有类似数组的对象都具有Iterator结构,一个解决办法,就是用array.from方法将其转为数组(或使用扩展运算符)
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确
for (let x of Array.from(arrayLike)) {
console.log(x);
}
效果
对象
对于普通的对象,for...of结构不能直接使用,会报错,必须部署了iterator接口才能使用
但是for...in仍然可以用来遍历键名
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
// edition
// committee
// standard
for (let e of es6) {
console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function
- 可以使用Object.keys()、Object.values()、Object.entries()方法来遍历普通对象
<body>
<script>
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
}
console.log("遍历键:");
for (let e of Object.keys(es6)) {
console.log(e);
}
console.log("遍历值:");
for (let e of Object.values(es6)) {
console.log(e);
}
console.log("遍历键值对:");
for (let e of Object.entries(es6)) {
console.log(e);
}
</script>
</body>
效果:
与其他遍历语法的比较
以数组为例说明不同遍历语法之间的区别
最原始for循环
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
内置的forEach方法
foeEach的问题在于,break命令、return命令不能中途退出for循环
myArray.forEach(function (value) {
console.log(value);
});
for...in循环
for...in循环有几个缺点:
- 数组的键名全是数字,但是for...in循环是以字符串“0”、“1”、“2”作为键名
- for...in循环不仅遍历数组键名,还会遍历手动添加的其他键
- 在某些情况下,for...in循环会以任意顺序遍历键名
“for...in循环主要是为遍历对象而设计的,不适用于遍历数组”
for (var index in myArray) {
console.log(myArray[index]);
}
for...of循环
for...of循环的优点:
- 有着与for...in一样的语法,但是没有for...in的缺点
- 可以与breck、return、continue配合使用
- 提供了遍历所有数据结构的统一操作接口
for (let value of myArray) {
console.log(value);
}