资讯专栏INFORMATION COLUMN

ES6 的 for..of 和 Generator,从伪数组 jQuery 对象说起

Harriet666 / 1992人阅读

摘要:引用自可迭代对象和迭代器不以规矩,不成方圆为了使某个对象成为可迭代对象象,它必须实现方法,也就是说,它得有一个是的属性。的遍历,绝对应该用。

pseudo

英 ["sju:dəʊ] 美 ["su:doʊ]
adj.假的,虚伪的
n.[口]假冒的人,伪君子

pseudo-array

英 [s"ju:dəʊər"eɪ] 美 [s"ju:dəʊər"eɪ]
[计] 伪数组

jQuery 对象是伪数组 两个事实

不管以前知道不知道,至少马上会知道

jQuery 对象是一个伪数组。

var $obj = jQuery();
Array.isArray($obj);    // false

jQuery.fnjQuery.prototype 的简写

jQuery.fn === jQuery.prototype;    // true   

提出问题

既然 jQuery 对象是伪数组,那 ES6 的 for...of 想用在 jQuery 对象上就不会那么顺利。毕竟 jQuery 还没有按 ES6 重写。

那么,想用 for..of 遍历 jQuery 对象中的 DOM 引用,就自己实现吧——这得从 iterable 和 iterator 开始。

The for...of statement creates a loop Iterating over iterable objects (including Array, Map, Set, arguments object and so on), invoking a custom iteration hook with statements to be executed for the value of each distinct property.

引用自:for...of statement | MDN

iterable(可迭代对象) 和 iterator(迭代器) 不以规矩,不成方圆

为了使某个对象成为可迭代对象象,它必须实现 @@iterator 方法,也就是说,它得有一个 key 是 Symbol.iterator 的属性。说人话,就是必须得实现这么个东东:

jQuery.fn[Symbol.iterator] = ....

而这个所谓的 @@iterator 方法,返回的是一个迭代器。迭代器这活也不是随便谁都能干的,它必须得有一个 next() 方法,而这个 next() 方法每次调用,都返回下一个迭代对象。

当然迭代对象也是有标准的,它必须是这么个结构:

{
    done: "(boolean),true 表示迭代完成,false 表示还有下一个",
    value: "这个才是正主,for...of 迭代出来的值"
}

注意 done 这个小坑,其它语言中通常是用 hasNext() 或者 hasMore() 之类的来判断是否有下一个值,而 javascript 是用 done 来判断,它们的逻辑意思正好相反,所以千万注意不要给错了值。

注:Symbol 是 ES6 中引入的新的键类型。之前的键类型只能是字符串,而在 ES6 中,有两种了。关于 Symbol,可以参阅 【探秘ES6】系列专栏(八):JS的第七种基本类型Symbols 和 Symbol - JavaScript | MDN

实现

知道了规矩,实现起来就好办了

jQuery.fn[Symbol.iterator] = function() {
    return (function(_this) {
        var index = 0;
        return {
            next: function() {
                return {
                    done: index >= _this.length,
                    value: _this[index++]
                };
            }
        };
    })(this);
};
测试
for (var dom in $("div")) {
    console.log(dom);
}

正确执行,通过……话虽如此,代码写起来好累。所以,其实应该用 Generator……

Generator

ES6 的又一新特性,Generator 对象(生成器对象),简直就是为迭代而生的。每个生成器对象都符合上面提到的 iterable 和 iterator 两个规矩。换句话说,生成器对象既是一个可迭代对象,又是一个迭代器,而它作为可迭代对象的时候,返回的迭代器就是它自己。

然而生成器对象并不是 new 出来的,而是通过 generator function(生成器函数)生成的;生成器函数得自己写,又不能 new Generator(),那么这个生成器对象从哪里来呢?当然是生成器函数生成的,而且这会用到新语法,以及新的关键字 yield

generator function(生成器函数)和 yield

生成器函数的定义与普通函数略有不同,形式上的区别是,它在 function 关键字后加了一个 * 号,就像这样:

function *aGenerator() { ... }

生成器函数在内容上的区别就是,它的内容其实并不是它自己的内容,而是描述了它产生生成器对象的行为。

有点乱,来捋一捋:

生成器函数返回一个生成器对象

生成器对象是一个迭代器

生成器对象也是一个可迭代对象,每次迭代返回自己(这句暂时忽略)

迭代器有一个 next() 方法用来返回迭代值(以及判断是否完成迭代)

捋清楚了,来说生成器函数的内容——其实就是干上面最后一条描述的事情:描述每次迭代返回的值,以及是否完成迭代。这是与普通 function 完全不同的语法,它是怎么做到的呢?凭空说起来太吃力,先上代码

function *aGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

每次 yield,就相当于一次通过 next() 返回值,也就上面提到的迭代对象的 value 属性。那么 done 属性如何确定呢?如果从生成器函数返回,就 done 了。这有两种情况,一种是自然执行完所有语法,函数结束返回;另一种是在函数体中调用 return 返回。

新问题:眼看例中 3 个 yield 语句排在一起,不是“啪啪啪”一下子就搞完了?最后就 next() 出来一个 3 了事?

非也,yield 返回值与 return 不同。yield 反回值(也就是 next())之后会将代码暂停在那个位置,等下一次 next() 的时候,继续执行,到下一个 yield 再暂停……如此直到显示或隐匿的 return

改进的 jQuery.fn[Symbol.iterator]
jQuery.fn[Symbol.iterator] = function*() {
    for (var i = 0; i < this.length; i++) {
        yield this[i];
    }
};

比上一个实现简单了不少吧,但是你以为这是最简单的么……

巧妙的实现 更简单的实现

除了可以用 yield 返回值之外,还可以用 yield *返回可迭代对象。这时,控制权会暂时交给这个可迭代对象,由它接替实现 next(),直到 done,再由当前生成器函数中的下一个 yield 接手继续。形象一点的理解——这个过程有点像树型结构的深度遍历。

因为原生数组也是可迭代对象,所以可以取个巧

jQuery.fn[Symbol.iterator] = function*() {
    yield *[].slice.call(this);
};
最简单的实现

上面说了一通,用了 N 种方法,无非是讲解 ES6 的新特性而已。要为 jQuery 实现 for...of 遍历,最简单的方法其实是拿来主义:

jQuery.fn[Symbol.iterator] = [][Symbol.iterator];
最后的提醒

jQuery 对象是一个伪数组,它的每一个元素都是一个 DOM(或原对象)而不是被封装的 jQuery 对象,所以,用 for..of 遍历的时候,和用 jQuery.fn.each() 遍历一样,如果想继续在每个元素上使用 jQuery 的特性,就要记得用 jQuery() 包装。

// for...of
for (var dom in $("span")) {
   var $span = $(dom);
}

// jQuey.fn.each
$("span").each(function() {
    var $span = $(this);
});
郑重致歉

其实,我不小心又骗了大家。jQuery 压根没有实现 for...of 的必要,即使没有 ES6,用 for...in 遍历 jQuery 对象也是一件很悲催的事情,不信你试试。

jQuery 的遍历,绝对应该用 jQuery.fn.each()

但是,看在我要以此为例来说 ES6 的几样新特性的份上,原谅我吧!^_^!

参考

ECMAScript 6 Features

Iteration protocls - JavaScript | MDN

Generator - JavaScript | MDN

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

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

相关文章

  • 集合数据处理(C#、JavaScript Java)

    摘要:系列公用委托都用于委托带有返回值的的方法,所有都是最后一个参数代表返回值类型。的字面对象据称也是哈希实现。 Java 丢了好多年,最近在拣起来,首先当然是了解这么多年来它的变化,于是发现了 Java 8 的java.util.stream。在学习和试验的过程中,相比较于 C# 和 javascript,有那么些心得,作文以记之。 早些时间写过一篇《ES6 的 for..of 和 Ge...

    zacklee 评论0 收藏0
  • 集合数据处理(C#、JavaScript Java)

    摘要:系列公用委托都用于委托带有返回值的的方法,所有都是最后一个参数代表返回值类型。的字面对象据称也是哈希实现。 Java 丢了好多年,最近在拣起来,首先当然是了解这么多年来它的变化,于是发现了 Java 8 的java.util.stream。在学习和试验的过程中,相比较于 C# 和 javascript,有那么些心得,作文以记之。 早些时间写过一篇《ES6 的 for..of 和 Ge...

    sherlock221 评论0 收藏0
  • forEach到迭代器

    摘要:本文从使用对数组进行遍历开始说起,粗略对比使用进行遍历的差异,并由此引入中可迭代对象迭代器的概念,并对其进行粗略介绍。说到这里,就继续说一下迭代器关闭的情况了。确实,符合可迭代协议和迭代器协议的。 本文从使用 forEach 对数组进行遍历开始说起,粗略对比使用 forEach , for...in , for...of 进行遍历的差异,并由此引入 ES6 中 可迭代对象/迭代器 的概...

    rockswang 评论0 收藏0
  • JavaScript 设计模式(五):迭代器模式

    摘要:文章内容分两部分前半部分为迭代器模式概念后半部分为中迭代器上半部分开始迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。下半部分开始的迭代器迭代器等同于遍历器。执行该函数,会返回一个遍历器对象。 showImg(https://segmentfault.com/img/bVbuyaZ?w=800&h=600); 文章内容分两部分: 前半部分为 迭...

    zhou_you 评论0 收藏0
  • 【JS基础】从JavaScript中for...of说起(上) - iterator gene

    摘要:当这个迭代器的方法被首次后续调用时,其内的语句会执行到第一个后续出现的位置为止,后紧跟迭代器要返回的值。在这个回调函数里,我们使用第一个请求返回的,再次发起一个请求。 写在前面 本文首发于公众号:符合预期的CoyPan 后续文章:【JS基础】从JavaScript中的for...of说起(下) - async和await 先来看一段很常见的代码: const arr = [1, 2, ...

    wslongchen 评论0 收藏0

发表评论

0条评论

Harriet666

|高级讲师

TA的文章

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