资讯专栏INFORMATION COLUMN

[前端漫谈_1] 从 for of 聊到 Generator

miqt / 1450人阅读

摘要:数据的层级意味着迭代数据结构并提取它的数据。对于技术人而言技是单兵作战能力,术则是运用能力的方法。在前端娱乐圈,我想成为一名出色的人民艺术家。

聊聊 for of

说起 for of 相信每个写过 JavaScript 的人都用过 for of ,平时我们用它做什么呢?大多数情况应该就是遍历数组了,当然,更多时候,我们也会用 map() 或者 filer() 来遍历一个数组。 但是就像我们标题里面说的,它跟 Generator 能扯上什么关系呢?

首先我们想一个问题,为什么使用 for of 或者 map()/filer() 方法就可以遍历一个数组 (或者类数组对象: Strings , Maps , Sets , arguments ) 呢? 为什么不能用他们来遍历一个对象呢?

你能学到什么

for of 更深入的理解

iterator 到底是何方神圣?

数组也是对象,为什么不能用 for of 来遍历对象呢?

如何实现对象的 for of?

Generator 又是何方神圣?他有什么用呢?

类数组对象的玄机

在真正揭开谜底之前,站在 for of 的角度想一下,现在让你去遍历一个数组,你需要知道什么信息呢?

对应下标的值

是否遍历结束的标志

带着这样的思考,我们打印一个数组来看看这里面的玄机:

const numbersArray = [1, 2, 3];

console.dir(numbersArray);

数组 (或者类数组对象: Strings , Maps , Sets , arguments ) 的原型中都实现了一个方法 Symbol.iterator,问题来了,那么这个 Symbol.iterator 又有什么用呢? 拿出来试一下就知道了:

let iterator = numbersArray[Symbol.iterator]();
// 我们把这个 Symbol.iterator 打印一下看看里面到底有些什么
console.dir(iterator);

这里有一个 next() 方法对吗?执行这个 next() 方法:

iterator.next(); // 输出 {value: 1, done: false}
iterator.next(); // 输出 {value: 2, done: false}
iterator.next(); // 输出 {value: 3, done: false}
iterator.next(); // 输出 {value: undefined, done: true}
请注意,当下标超出时,value: undefined

我们发现这个 iterator.next() 每次都返回了一个对象。这对象包含两个信息:当前下标的值,以及遍历是否结束的标志。这印证了我们之前思考,有了这两个信息,你作为 for of 函数,也能打印出数组的每一项了不是吗?

新的问题来了,iterator 到底是何方神圣呢?

iterator(迭代器) & The iterator protocol(迭代协议)

聊到了 iterator 我们不得不先说一下 The iterator protocol(迭代协议)

" The iterable protocol allows JavaScript objects to define or customize their iteration behavior " - MDN

MDN 上是这么说的:The iterator protocol 允许 JavaScript 对象去定义或定制它们的迭代行为 ,所以上面出现的 Symbol.iterator 这个方法,就是数组对于这个协议的实现。那么按照这个协议,数组是怎么实现了一个 iterator 呢?

"In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination. More specifically an iterator is any object which implements the Iterator protocol by having a next() method which returns an object with two properties: value, the next value in the sequence; and done, which is true if the last value in the sequence has already been consumed. If value is present alongside done, it is the iterator"s return value." - MDN

这一大段看起来比较费劲,简单来说就像我们上一章节所印证的,它实现的方式是定义了一个 next() 方法,而这个 next() 方法每次被执行都会返回一个对象: {value:xxx/undefined , done: true/false } 其中 value 代表的是当前遍历到的值,done 代表是否遍历结束。

本小节回答了我们之前的提问: 为什么不能用 for of 来遍历一个对象呢? 原因很简单:JavaScript 的对象中没有实现一个这样的 iterator 。你可以打印一个对象来看看结果如何:

console.dir({ a: 1, b: 2 });

okay, 到这里如果就结束的话,那我们了解得还不够深入,于是再问一个问题:

Why is there no built-in object iteration ? (为什么在 object 中没有内置迭代器呢? )

为什么在 object 中没有内置迭代器呢?

对啊,为什么呢? 我们在各样的场景中也需要来遍历一个对象啊?为什么没有内置一个迭代器呢?要回答这个问题,我们得从另外一个角度出发,了解一些基本的概念:

我们常常说遍历对象,但是简单来说,只会在两种层级上来对一个 JavaScript 对象进行遍历:

程序的层级 - 什么意思呢?在程序层级上,我们对一个对象进行迭代,是在迭代展示其结构的对象属性。 可能还不是很好理解,举个栗子:Array.prototype.length 这个属性与对象的结构相关,但却不是它的数据。

数据的层级 - 意味着迭代数据结构并提取它的数据。举个栗子:我们在迭代一个数组的时候,迭代器是对于它的 每一个数据进行迭代,如果 array = [a, b, c, d] 那么迭代器访问到的是 1, 2, 3, 4

明白了这个缘由,JavaScript 虽然不支持用 for of 来遍历对象,但是提供了一个 for in 方法来遍历所有非 Symbol 类型并且是可枚举的属性。

标准不支持,如果我们就是要用 for-of 来遍历对象呢?那我们可以任性的实现一个啊:

Object.prototype[Symbol.iterator] = function*() {
  for (const [key, value] of Object.entries(this)) {
    yield { key, value };
  }
};
for (const { key, value } of { a: 1, b: 2, c: 3 }) {
  console.log(key, value);
}

不知道你有没有注意一个细节,在我们任性的实现一个 iterator 的代码中,我们用到了一个很奇怪的结构 function*() {} ,这个就是我们接下来要介绍的 Generator

Generators

看到这个名字觉得很厉害哈,但其实很简单,写一个 Generator 你只需要在函数名和 function 关键字中间加一个 * 号就可以了。至于里面的 yield 是什么,后面会说的。

talk is cheap , show me the code ,用一个例子,简单说一下概念。

我们现在定义了一个这样的 Generator 叫做 gen

function* gen() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
}

我们只能看到,这里面有 4 个语句,那打印一下看看呗:

这里发现了一个熟悉的函数,next() 方法,我们把 gen 实例化一下,执行一下它的 next() 来看看结果:

还是熟悉的味道,那么到这里,我们已经知道,Generator 可以实例化出一个 iterator ,并且这个 yield 语句就是用来中断代码的执行的,也就是说,配合 next() 方法,每次只会执行一个 yield 语句。

多说一句,针对 Generator 本身,还有一个有意思的特性,yield 后面可以跟上另一个 Generator 并且他们会按照次序执行:

function* gen() {
  yield 1;
  yield* gen2();
  return;
}

function* gen2() {
  yield 4;
  yield 5;
}

let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

结果很有意思不是吗?而且 return 会终结整个 Generator ,换句话说:写在 return 后面的 yield 不会执行。

Generator 有什么用?

Generator 有什么用? 聪明的同学可能已经猜到了,是的,它能够中断执行代码的特性,可以帮助我们来控制异步代码的执行顺序:

例如有两个异步的函数 AB, 并且 B 的参数是 A 的返回值,也就是说,如果 A 没有执行结束,我们不能执行 B

那这时候我们写一段伪代码:

function* effect() {
  const { param } = yield A();
  const { result } = yield B(param);
  console.table(result);
}

这时候我们如果需要得到 result 那么我们就需要:

const iterator = effect()
iterator.next()
iterator.next()

执行两次 next() 得到结果,看起来很傻不是吗?有没有好的办法呢?(废话,肯定有啊)
假设你在每次执行 A() / B() 的请求结束之后,都会自动执行 next() 方法呢?这不就解决了吗?

这样的库早就存在了,建议大家参考 co 的源码,当然你也可以通过阅读 这篇文章 来看看,到底 Generator 是怎么玩的。

最后

这里是 Dendoink ,奇舞周刊原创作者,掘金 [联合编辑 / 小册作者] 。
对于技术人而言:技 是单兵作战能力,术 则是运用能力的方法。得心应手,出神入化就是 艺 。在前端娱乐圈,我想成为一名出色的人民艺术家。扫码关注公众号 前端恶霸 我在这里等你:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104323.html

相关文章

  • [前端漫谈_3] filter 聊到 Promise

    摘要:前言在学习前端的时候,我总是能听到很多高级词汇,比如今天会聊到的函数式编程高阶函数。接下来我们看看,高阶函数有可能会遇到的问题,又如何去解决。 前言 在学习前端的时候,我总是能听到很多高级词汇,比如今天会聊到的 函数式编程(Functional Programming) & 高阶函数 (Higher-order function) 。但是当你真正的理解什么是 函数式编程 & 高阶函数 ...

    crossoverJie 评论0 收藏0
  • [前端漫谈_2] Dva 的 Effect 到 Generator + Promise 实现异步

    摘要:你能学到什么如何使用实现异步编程异步编程的原理解析前言结合上一篇文章,我们来聊聊基础原理说到异步编程,你想到的是和,但那也只是的语法糖而已。表示一个异步操作的最终状态完成或失败,以及其返回的值。至此实现异步操作的控制。 你能学到什么 如何使用 Generator + Promise 实现异步编程 异步编程的原理解析 前言 结合 上一篇文章 ,我们来聊聊 Generator 基础原理...

    pekonchan 评论0 收藏0
  • IIFE 聊到 Babel 带你深入了解前端模块化发展体系

    摘要:我觉得那时他可能并没有料到,这一规则的制定会让整个前端发生翻天覆地的变化。前言 作为一名前端工程师,每天的清晨,你走进公司的大门,回味着前台妹子的笑容,摘下耳机,泡上一杯茶,打开 Terminal 进入对应的项目目录下,然后 npm run start / dev 或者 yarn start / dev 就开始了一天的工作。 当你需要进行时间的转换只需要使用 dayjs 或者 momentj...

    tinylcy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<