前言
迭代器iterator
当我们遍历数组的时候,用for 循环的时候,如果多个循环嵌套,则需要有多个循环变量,每次需要追踪多个变量,所以,利用迭代器可以消除这种问题。本质上其实迭代器也是一个对象,而且Es6也提出了for...of 循环,来配合遍历迭代器.与for循环相比,for...of不需要追踪值在集合中的位置,也不需要控制循环结束的时机。
迭代器
迭代器是一种对象,是一种专有接口,所有的迭代器对象都有一个next() 方法,调用next() 方法后会返回一个结果对象,这个对象包含两个值,一个是value,值是要返回的当前值,一个是done,done 是一个布尔值,如果还有可返回的value,则done返回false,如果没有,则done返回false, 当done 返回true的时候,value 是函数调用过程中最后一次给调用者传递的参数,如果没有,则返回undefined.
我们来自己创建一个迭代器,其实十分简单,返回一个迭代器对象。
const arr = [1, 2, 3];
const myIterator = (item) => {
let i = 0;
return {
next: () => {
let done = i >= item.length;
let value = done ? undefined : item[i++];
return {
value,
done,
};
},
};
};
let iter = myIterator(arr);
console.log(iter.next(), "--->1"); // { value: 1, done: false } --->1
console.log(iter.next(), "---->2"); // { value: 2, done: false } ---->2
console.log(iter.next(), "---->3"); // { value: 3, done: false } ---->3
console.log(iter.next(), "---->3"); // { value: undefined, done: true } ---->3
迭代器对象除了具有next() 方法外,还具有return() 方法和throw()方法,不过return()方法和throw()方法不是必须的
return() 方法
如果for...of 循环出错或者有break语句导致提前退出,就会调用return()方法。return()方法必须返回一个对象。但是return() 的返回值是不会生效的。
const myIterator = (item) => {
let i = 0;
return {
next: () => {
let done = i >= item.length;
let value = done ? undefined : item[i++];
return {
value,
done,
};
},
return: () => {
console.log('提前退出')
return {done: true}
}
};
};
let obj = {age: '1', name: 'ss'};
obj[Symbol.iterator] = () => {
return myIterator([1,2,3,4])
}
// 返回1, 调用return 方法,打印提前退出
for (let item of obj) {
console.log(item) // 1
break;
}
throw() 方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
Symbol.iterator
Iterator 为所有的数据结构提供了一种统一的访问机制,我们可以利用 for...of 来进行循环遍历。
当我们用for...of 遍历对象的时候,就会提示报错,但是遍历数组或者map, set, 字符串等就可以,是因为数组,map, set,字符串部署了Iterator 接口,简单来讲,就是他们都有一个内置的Symbol.iterator属性。
let arr = {age: '1', name: 'ss'}
for (let item of arr) {
console.log(i) // TypeError: arr is not iterable
}
let array = [1,2,3];
// 和arr对象不同的是,
// 我们可以在array.__proto__下看到,有一个Symbol(Symbol.iterator): values() 属性
原生默认具备Symbol.iterator属性的数据结构有
- Array
- Map
- Set
- String
- 函数的arguments对象
- NodeList对象
一个数据结构只要有Symbol.iterator属性,就可以认为是可遍历的,就可以使用for...of 来进行遍历。Symbol.iterator 的值就是一个返回迭代器对象的函数
// 我们把上面刚刚写的myIterator函数赋值给obj的Symbol.iterator
let obj = {age: '1', name: 'ss'};
obj[Symbol.iterator] = () => {
return myIterator([1,2,3,4])
}
// 然后用for...of 遍历 没有报错
for (let item of obj) {
console.log(item) // 1,2,3,4
}
我们也可以改变他的Symbol.iterator属性。
let str = '你捉不到的this';
str[Symbol.iterator] = () => {
return myIterator([1,2,3,4])
}
for (let item of str) {
console.log(item) // 1,2,3,4
}
模拟for of
实现for...of 也很简单,获取到Symbol.iterator属性的迭代器对象然后遍历
const myForOf = (item, callback) => {
if (typeof callback !== "function") {
return new TypeError("callback is not function");
}
if (typeof item[Symbol.iterator] !== "function") {
return new TypeError(`${item} is not iterator`);
}
let getIteratorObj;
let result;
getIteratorObj = item[Symbol.iterator]();
result = getIteratorObj.next();
while (!result.done) {
callback(result.value);
result = getIteratorObj.next();
}
};
myForOf([1, 2, 3, 4], (i) => {
console.log(i, "--->myForOF");
});
生成器
上面我们自己手动编写了一个简单的迭代器对象,但是实际上的迭代器编写规则是十分复杂的,所以,ES6 还提供了一个生成器函数,用来创建返回迭代器对象。
生成器默认会给Symbol.Iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象
生成器是一个返回迭代器的函数
他有几个特征。一个是 function关键字后与函数名之间有一个* 号,调用方式和普通函数方式调用相同,函数体内部使用yield 表达式,来定义不同的内部状态,相当于是一个状态机
function * createInterator() {
console.log(1);
yield 1;
console.log(2);
yield 2;
console.log(3);
yield 3;
// return 4 则next() 返回{value: 4, done: true}
}
let r = createInterator();
r.next() // 1, {value: 1, done: false}
r.next() // 2, {value: 2, done: false}
r.next() // 3, {value: 3, done: false}
r.next() // {value: undefined, done: true}
// 注意: 每次调用next()。都会执行到yield 语句,然后不在执行,直到调用下一个next 方法,并且返回一个对象,value值为yield的返回值。如果函数最后return 的话,调用next 返回的value是函数最后return 的值
// 当执行next 的时候,会执行到遇到yield表达式的那行,后面的代码暂停,然后yield 后面表达式的值作为返回对象的value, 下一次调用next,继续往下执行,知道遇到下一个yield。 如果没有新的yield,直到遇到return,将return 值当成value值,并且done 为true, 如果继续next(),则返回{value: undefined, done: true}
let obj = {
create: function *(){
console.log('--->生成器')
}
}
let obj1 = {
*create(){
console.log('--->生成器')
}
}
不可以使用箭头函数来创建生成器函数
yield
Yield 只能在生成器内部使用,在其他地方使用会抛出错误,即使在生成器函数的内部函数中使用也不可以
next()
next()方法可以传入一个参数,参数表示的是上一个yield表达式的值(也就相当于上一个yield表达式返回的值)。如果给第一次调用next()方法传入参数,是无效的,会被忽略。因为第一次调用next() 方法前不会执行任务yield语句
function * create(x){
yield x + 1;
yield x + 2;
let z = yield 3;
yield z + 5;
}
let c = create(1);
c.next() // {value: 2, done: false}
c.next() // {value: 3, done: false}
c.next() // {value: 3, done: false}
c.next() // 没有传入参数,相当于undefined,所以上一次yield的返回值z = undefined, undefined + 5 = NAN, 所以返回 {value: NaN, done: false}
throw()
生成器函数返回的迭代器对象含有一个throw方法,可以将它抛出的错误传递给生成器函数内部,在内部进行捕获。
调用throw()方法后,会抛出一个错误,后面的代码是否执行,取决于生成器函数内部的代码,如果将错误捕获,就会接着执行,不捕获就不执行
function * create(){
yield 1;
yield 2;
try {
yield 3;
} catch(err) {
yield 4;
}
yield 5;
}
let r = create()
r.next() // {value: 1, done: false}
r.throw(new Error('xxx')) // Error
r.next() // {value: undefined, done: true} 如果内部没有捕获错误,下次nextdone直接会变为 true
return
如果生成器函数中return, 则return后面的语句不再执行,如果return了值,则值是value的值
function * create() {
yield 1;
yield 2;
console.log('2')
return 3;
}
let r = create();
r.next() // {value: 1, done: false}
r.next() // {value: 2, done: false}
r.next() // 2 {value: 3, done: true}
看到这里啦,点个赞吧~~
如有错误,还请指出~~