-
jQuery 对象是伪数组
- jQuery 对象是一个伪数组。
var $obj = jQuery();Array.isArray($obj); // false
- jQuery.fn 是 jQuery.prototype 的简写
jQuery.fn === jQuery.prototype; // true
提出问题
既然 jQuery 对象是伪数组,那 ES6 的 for...of 想用在 jQuery 对象上就不会那么顺利。毕竟 jQuery 还没有按 ES6 重写。
那么,想用 for..of 遍历 jQuery 对象中的 DOM 引用,就自己实现吧——这得从 iterable 和 iterator 开始。
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];
}
};
最简单的实现
上面说了一通,用了 N 种方法,无非是讲解 ES6 的新特性而已。要为 jQuery 实现 for...of 遍历,最简单的方法其实是拿来主义:
jQuery.fn[Symbol.iterator] = [][Symbol.iterator];
最后的提醒
jQuery 对象是一个伪数组,它的每一个元素都是一个 DOM(或原对象)而不是被封装的 jQuery 对象,所以,用 for..of 遍历的时候,和用 jQuery.fn.each() 遍历一样,如果想继续在每个元素上使用 jQuery 的特性,就要记得用 jQuery() 包装。
// for...offor (var dom in $("span")) {
var $span = $(dom);}// jQuey.fn.each$("span").each(function() { var $span = $(this);});
后记
其实,jQuery 压根没有必要为 for...of 实现 iterator。虽然用 for...in 遍历 jQuery 对象也是一件很悲催的事情(不信你试试),但有 Array.from(),还有 jQuery.fn.toArray() 啊。
但话说回来,jQuery 的遍历,多数情况下还是应该用 jQuery.fn.each()。