1、迭代器Iterator
1.1 简介
迭代器/遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator 接口,就可以完成遍历操作。
1.2 作用
Iterator 的作用有三个: 一是为各种数据结构,提供一个统一的、简便的访问接口; 二是使得数据结构的成员能够按某种次序排列; 三是ES6创造了一种新的遍历命令for...of 循环,Iterator 接口主要供for...of 消费。
1.3 遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
1.4 迭代器实现
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of 循环。原生具备 Iterator 接口的数据结构如下:Array、Map、Set、String、TypedArray、arguments、NodeList 等。 例如:下面是数组的Symbol.iterator 属性。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next()
iter.next()
iter.next()
iter.next()
1.4.1 迭代器协议
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。
1.4.2 关于next()函数
是一个无参数函数,返回一个应当拥有以下两个属性的对象: done 和 value,done(boolean)如果迭代器可以产生序列中的下一个值,则为 false(这等价于没有指定 done 这个属性)。如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。如果返回了一个非对象值(比如 false 或 undefined),则会抛出一个 TypeError 异常(“iterator.next() returned a non-object value”)
1.4.3 遍历
方式一:使用迭代器的next 方法遍历
let someString = "hi";
let iterator = someString[Symbol.iterator]();
let result;
while (!(result = iterator.next()).done) {
console.log(result);
}
方式二:使用for-of 遍历
let someString = "hi";
for (let key of someString) {
console.log(key);
}
1.5 自定义迭代器
数组、Map等结构中的成员都是有顺序的,即都是线性的结构,而对象,各成员并没有一个确定的顺序,所以遍历时先遍历谁后遍历谁并不确定。所以,给一个对象部署iterator接口,其实就是对该对象做一种线性转换。如果你有这种需要,就需要手动给你的对象部署iterator接口。
const obj = {
name:"字符串",
data: [ 'hello', 'world','javaScript','ES6'],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
};
或者通过generator函数简写:
var yieldIterator = {};
yieldIterator[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...yieldIterator]
生成器Generator
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
调用
(1)Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。 (2)不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个迭代器对象,调用遍历器对象的next()方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
let hw = helloWorldGenerator();
hw.next()
hw.next()
hw.next()
hw.next()
主要特征
Generator函数有两个特征:
- function关键字与函数名之间有个星号;
- 函数内部使用yield表达式
例如:
function * tiger(){
console.log(111);
yield '一只没有眼睛';
console.log(222);
yield '一直没有尾巴';
console.log(333);
yield '真奇怪';
console.log(444);
}
for (const v of tiger()) {
console.log(v);
}
let it = tiger();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
for…of遍历的结果: 111 一只没有眼睛 222 一直没有尾巴 333 真奇怪 444
next()方法遍历结果 111 { value: ‘一只没有眼睛’, done: false } 222 { value: ‘一直没有尾巴’, done: false } 333 { value: ‘真奇怪’, done: false } 444 { value: undefined, done: true }
可以发现,每次next()方法指针往下移动的方式是:输出从方法第一个花括号{到第一个yield表达式之间的内容,接下来是移动到第二个yield表达式那,输出第一个和第二个表达式之间的内容…(左闭右开)最后一个yield表达式到方法的结尾括号处的内容(因为这里无数据,所以输出{ value: undefined, done: true }
生成器函数的参数传递
function * tiger(arg){
console.log(arg);
let first = yield '一只没有眼睛';
console.log(first);
console.log(222);
let second = yield '一直没有尾巴';
console.log(333);
console.log(second);
let third = yield '真奇怪';
console.log(333);
console.log(third);
}
let it = tiger("参数值");
console.log(it.next());
console.log(it.next("第二个next()传过来的值给第一个yield接收"));
console.log(it.next("第三个next()传过来的值给第二个yield接收"));
console.log(it.next("第四个next()传过来的值给第三个yield接收"));
结果:
参数值 { value: ‘一只没有眼睛’, done: false } 第二个next()传过来的值给第一个yield接收 222 { value: ‘一直没有尾巴’, done: false } 333 第三个next()传过来的值给第二个yield接收 { value: ‘真奇怪’, done: false } 333 第四个next()传过来的值给第三个yield接收 { value: undefined, done: true }
应用
例题一:generator是实现状态机的最佳结构。
let flag = true;
function clock(){
if(flag){ console.log("tick");} else { console.log("tock"); }
flag = !flag;
}
function * clock_generator(){
while(true){
console.log("tick"); yield;
console.log("tock"); yield;
}
}
clock();
clock();
var cg=clock_generator();
cg.next();
cg.next();
例题二:长轮询
let ajax = function* () {
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
code: 0
})
}, 1000);
})
}
let pull = function () {
let generator = ajax();
let step = generator.next();
step.value.then((v) => {
if (v.code != 0) {
setTimeout(() => {
console.log('wait');
pull();
}, 1000);
} else {
console.log(v);
}
})
}
pull();
例题三:抽奖
let draw = function (count) {
console.log(`剩余${count}次`);
}
let residue = function * (count) {
while (count > 0) {
count--;
yield draw(count);
}
}
let star = residue(5);
let btn = document.createElement('button');
btn.id = 'start';
btn.textContent = '抽奖';
document.body.appendChild(btn);
document.getElementById('start').addEventListener('click', () => {
star.next();
}, false)
异步操作的同步化。可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
function* main() {
let result = yield request("http://some.url");
let resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
axios.get(url).then(function(response){it.next(response);});
}
it = main();
it.next();
例题四:
setTimeout(() => {
console.log(111);
setTimeout(() => {
console.log(222);
setTimeout(() => {
console.log(333);
}, 3000);
}, 2000);
}, 1000);
通过生成器函数可以解决上述代码无限缩进的问题
function one(){
setTimeout(() => {
console.log(111);
it.next();
}, 1000);
}
function two(){
setTimeout(() => {
console.log(222);
it.next();
}, 2000);
}
function three(){
setTimeout(() => {
console.log(333);
it.next();
}, 3000);
}
function * start(){
yield one();
yield two();
yield three();
}
let it = start();
it.next();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generator函数</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function* test(handle) {
handle();
let a = 1;
let res = yield getData();
console.log(res);
handle();
console.log(a);
yield "结束了";
}
let geo = test(function () {
console.log(111);
});
geo.next();
function getData() {
return $.get('http://47.106.244.1:8099/manager/category/findAllCategory', {}, (response) => {
geo.next(response);
});
}
</script>
</head>
<body>
</body>
</html>
|