ES6 的 for..of 和 Generator,從偽數(shù)組 jQuery 對象說起

2018-06-09 10:44 更新

pseudo

英 ['sju:d??] 美 ['su:do?]
adj.假的,虛偽的
n.[口]假冒的人,偽君子

pseudo-array

英 [s'ju:d???r'e?] 美 [s'ju:d???r'e?]
[計] 偽數(shù)組

jQuery 對象是偽數(shù)組

兩個事實

不管以前知道不知道,至少馬上會知道

  1. jQuery 對象是一個偽數(shù)組。

    var $obj = jQuery();
    Array.isArray($obj);    // false
  2. jQuery.fnjQuery.prototype 的簡寫

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

提出問題

既然 jQuery 對象是偽數(shù)組,那 ES6 的 for...of 想用在 jQuery 對象上就不會那么順利。畢竟 jQuery 還沒有按 ES6 重寫。

那么,想用 for..of 遍歷 jQuery 對象中的 DOM 引用,就自己實現(xiàn)吧——這得從 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(迭代器)

不以規(guī)矩,不成方圓

為了使某個對象成為可迭代對象象,它必須實現(xiàn) @@iterator 方法,也就是說,它得有一個 key 是 Symbol.iterator 的屬性。說人話,就是必須得實現(xiàn)這么個東東:

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

而這個所謂的 @@iterator 方法,返回的是一個迭代器。迭代器這活也不是隨便誰都能干的,它必須得有一個 next() 方法,而這個 next() 方法每次調(diào)用,都返回下一個迭代對象。

當然迭代對象也是有標準的,它必須是這么個結(jié)構(gòu):

{
    done: "(boolean),true 表示迭代完成,false 表示還有下一個",
    value: "這個才是正主,for...of 迭代出來的值"
}

注意 done 這個小坑,其它語言中通常是用 hasNext() 或者 hasMore() 之類的來判斷是否有下一個值,而 javascript 是用 done 來判斷,它們的邏輯意思正好相反,所以千萬注意不要給錯了值。

注:Symbol 是 ES6 中引入的新的鍵類型。之前的鍵類型只能是字符串,而在 ES6 中,有兩種了。關(guān)于 Symbol,可以參閱 【探秘ES6】系列專欄(八):JS的第七種基本類型SymbolsSymbol - JavaScript | MDN

實現(xiàn)

知道了規(guī)矩,實現(xiàn)起來就好辦了

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);
}

正確執(zhí)行,通過……話雖如此,代碼寫起來好累。所以,其實應該用 Generator……

Generator

ES6 的又一新特性,Generator 對象(生成器對象),簡直就是為迭代而生的。每個生成器對象都符合上面提到的 iterable 和 iterator 兩個規(guī)矩。換句話說,生成器對象既是一個可迭代對象,又是一個迭代器,而它作為可迭代對象的時候,返回的迭代器就是它自己。

然而生成器對象并不是 new 出來的,而是通過 generator function(生成器函數(shù))生成的;生成器函數(shù)得自己寫,又不能 new Generator(),那么這個生成器對象從哪里來呢?當然是生成器函數(shù)生成的,而且這會用到新語法,以及新的關(guān)鍵字 yield。

generator function(生成器函數(shù))和 yield

生成器函數(shù)的定義與普通函數(shù)略有不同,形式上的區(qū)別是,它在 function 關(guān)鍵字后加了一個 * 號,就像這樣:

function *aGenerator() { ... }

生成器函數(shù)在內(nèi)容上的區(qū)別就是,它的內(nèi)容其實并不是它自己的內(nèi)容,而是描述了它產(chǎn)生生成器對象的行為。

有點亂,來捋一捋:

  • 生成器函數(shù)返回一個生成器對象

  • 生成器對象是一個迭代器

  • 生成器對象也是一個可迭代對象,每次迭代返回自己(這句暫時忽略)

  • 迭代器有一個 next() 方法用來返回迭代值(以及判斷是否完成迭代)

捋清楚了,來說生成器函數(shù)的內(nèi)容——其實就是干上面最后一條描述的事情:描述每次迭代返回的值,以及是否完成迭代。這是與普通 function 完全不同的語法,它是怎么做到的呢?憑空說起來太吃力,先上代碼

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

每次 yield,就相當于一次通過 next() 返回值,也就上面提到的迭代對象的 value 屬性。那么 done 屬性如何確定呢?如果從生成器函數(shù)返回,就 done 了。這有兩種情況,一種是自然執(zhí)行完所有語法,函數(shù)結(jié)束返回;另一種是在函數(shù)體中調(diào)用 return 返回。

新問題:眼看例中 3 個 yield 語句排在一起,不是“啪啪啪”一下子就搞完了?最后就 next() 出來一個 3 了事?

非也,yield 返回值與 return 不同。yield 反回值(也就是 next())之后會將代碼暫停在那個位置,等下一次 next() 的時候,繼續(xù)執(zhí)行,到下一個 yield 再暫?!绱酥钡斤@示或隱匿的 return。

改進的 jQuery.fn[Symbol.iterator]

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

比上一個實現(xiàn)簡單了不少吧,但是你以為這是最簡單的么……

巧妙的實現(xiàn)

更簡單的實現(xiàn)

除了可以用 yield 返回值之外,還可以用 yield *返回可迭代對象。這時,控制權(quán)會暫時交給這個可迭代對象,由它接替實現(xiàn) next(),直到 done,再由當前生成器函數(shù)中的下一個 yield 接手繼續(xù)。形象一點的理解——這個過程有點像樹型結(jié)構(gòu)的深度遍歷。

因為原生數(shù)組也是可迭代對象,所以可以取個巧

jQuery.fn[Symbol.iterator] = function*() {
    yield *[].slice.call(this);
};

最簡單的實現(xiàn)

上面說了一通,用了 N 種方法,無非是講解 ES6 的新特性而已。要為 jQuery 實現(xiàn) for...of 遍歷,最簡單的方法其實是拿來主義:

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

最后的提醒

jQuery 對象是一個偽數(shù)組,它的每一個元素都是一個 DOM(或原對象)而不是被封裝的 jQuery 對象,所以,用 for..of 遍歷的時候,和用 jQuery.fn.each() 遍歷一樣,如果想繼續(xù)在每個元素上使用 jQuery 的特性,就要記得用 jQuery() 包裝。

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

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

鄭重致歉

其實,我不小心又騙了大家。jQuery 壓根沒有實現(xiàn) for...of 的必要,即使沒有 ES6,用 for...in 遍歷 jQuery 對象也是一件很悲催的事情,不信你試試。

jQuery 的遍歷,絕對應該用 jQuery.fn.each()。

但是,看在我要以此為例來說 ES6 的幾樣新特性的份上,原諒我吧!^_^!

參考

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號