ES6特性:迭代器、生成器

1、迭代器Iterator

1.1 简介

迭代器/遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。

1.2 作用

Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费

1.3 遍历过程

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的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() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

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;
// 步骤二:使用next遍历迭代器对象
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'],
    // 如果想要将obj对象中的data数据遍历出来,那么就需要自定义一个迭代器,如下:
    // 步骤一:创建自定义迭代器对象
    [Symbol.iterator]() {
    	// 引入this
        const self = this;
        // 索引变量,控制迭代次数
        let index = 0;
        //返回迭代结果
        return {
        	//next()方法
            next() {
                if (index < self.data.length) {
                    return {
                        value: self.data[index++],
                        done: false
                    };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
}

// 步骤二:获取迭代器对象
let iterator = obj.data[Symbol.iterator]()

// 步骤三:遍历
while (!(result = iterator.next()).done) {
    console.log(result);
}

/*
{ value: 'hello', done: false }
{ value: 'world', done: false }
{ value: 'javaScript', done: false }
{ value: 'ES6', done: false }
*/

或者通过generator函数简写:

var yieldIterator = {};
yieldIterator[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...yieldIterator] // [1, 2, 3]

2、生成器Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

2.1 调用

(1)Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。
(2)不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个迭代器对象,调用遍历器对象的next()方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

let hw = helloWorldGenerator();
hw.next() // { value: 'hello', done: false }
hw.next() // { value: 'world', done: false }
hw.next() // { value: 'ending', done: true } 
hw.next() // { value: undefined, done: true }

2.2 主要特征

Generator函数有两个特征:

  1. function关键字与函数名之间有个星号;
  2. 函数内部使用yield表达式

例如:

function * tiger(){
    console.log(111);
    yield '一只没有眼睛';
    console.log(222);
    yield '一直没有尾巴';
    console.log(333);
    yield '真奇怪';
    console.log(444);

}

//yield表达式通过for...of输出结果
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 }
在这里插入图片描述

2.3 生成器函数的参数传递

//给生成器函数添加一个形参:arg
function * tiger(arg){
    console.log(arg); //输出传过来的值
    let first = yield '一只没有眼睛';
    //输出第一个yield接收到的值
    console.log(first); //第二个next()传过来的值给第一个yield接收
    console.log(222);
    let second = yield '一直没有尾巴';
    console.log(333);
    console.log(second);
    let third = yield '真奇怪';
    console.log(333);
    console.log(third);
}

//yield表达式通过for...of输出结果
// for (const v of tiger()) {
//     console.log(v);
// }

//接收生成器函数
let it = tiger("参数值");
console.log(it.next());
//通过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 }

2.4 应用

例题一: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();//tick滴
clock();//tock答
var cg=clock_generator();
cg.next();//tick {value:undefined,done:false}
cg.next();//tock {value:undefined,done:false}

例题二:长轮询

// 定义一个generator函数,其返回值是一个promise
let ajax = function* () {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({
                code: 0
            })
        }, 1000);
    })
}

// 定义一个轮询函数,当轮询结果的code!=0时,1秒之后开始下一次轮询
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);
    }
}

// 初始化抽奖次数为5次
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) {  
	// response参数会当做上一个yield表达式的返回值
	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);
        // 添加next()方法,使其指针往下走
        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;
      // 状态机不通用res
      let res = yield getData();
      console.log(res);
      handle();
      console.log(a);
      yield "结束了";
    }
    let geo = test(function () {
      console.log(111);
    });
    geo.next();
    function getData() {
      // return 'hello'
      return $.get('http://47.106.244.1:8099/manager/category/findAllCategory', {}, (response) => {
        //发起状态机内第二段程序的执行
        geo.next(response);
      });
    }

  </script>
</head>
<body>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值