1. 徹底理解 for of 和 Iterator

        共 9835字,需瀏覽 20分鐘

         ·

        2021-02-02 23:40

        本文主要來說下ES6Iterator,目的在于理解它的概念、作用、以及現(xiàn)有的應(yīng)用,最后學(xué)以致用。

        Iterator可以說是ES6內(nèi)相當(dāng)重大的一個特性,也是很多其他特性運(yùn)行的基石。

        為什么Iterator地位如此之高呢?

        從一個變量說起

        var?arr?=?['紅','綠','藍(lán)'];

        上面是一個普通的數(shù)組,如果我要獲取他的每一項(xiàng)數(shù)據(jù),應(yīng)該怎么做?

        這個還不簡單,直接來個 for循環(huán),如果你覺得循環(huán) low,那就來個 forEach 唄。

        ok,立刻擼串代碼

        //for?循環(huán)
        for(var?i=0;i????console.log(arr[i]);
        }

        //forEacth
        arr.forEach(item=>{
        ????console.log(item);
        });

        //?結(jié)果?略

        ok,沒毛病。

        那咱們繼續(xù),請看下面代碼。給定一個字符串,如果我想輸出每一個字符怎么做?

        var?str='1234567890';

        有么有搞錯,這簡單的讓人發(fā)笑。

        可以用 for in,也可以用 for 循環(huán),按照類數(shù)組方式來處理。

        立刻擼串代碼

        //for?in
        for(var?s?in?str){
        ????console.log(str[s]);//s?是?屬性名稱【key】
        }

        //轉(zhuǎn)為數(shù)組
        for(var?i?=0;iconsole.log(str[i]);
        }

        //或者轉(zhuǎn)換為數(shù)組
        Array.prototype.slice.call(str);

        不過 for in 不是用來獲取數(shù)據(jù)的,他會遍歷對象上所有可枚舉的屬性,包括自身的和原型鏈上的,所以這個不保險。

        emmm....沒啥問題,那咱們繼續(xù)。

        請看下面代碼,給定一個map對象,然后輸出它每一項(xiàng)數(shù)據(jù)。

        var?map?=?new?Map();
        map.set('zhang','1000w');
        map.set('liu','200w');
        map.set('sun','100w');

        forEach 就妥妥的了。

        map.forEach((val,key)=>{
        ????console.log(val,key);
        })

        到這里看了這么多如此簡單到令人發(fā)指的提問,估計(jì)都有些坐不住了,要掀桌子走人了。請稍后,慢慢往下看。

        發(fā)現(xiàn)問題

        好了,在上一步幾個簡單的問題中,我們的操作都是獲得他們的每一項(xiàng)數(shù)據(jù)。

        當(dāng)然方法有很多種,實(shí)現(xiàn)方式也有很多,for 循環(huán),forEachfor in 啦。

        但是有沒有發(fā)現(xiàn)一個問題,或者我們站在一個更高的維度去看待,其實(shí)這些方法都不能通用,也就是說上面的這幾種集合數(shù)據(jù)不能使用統(tǒng)一的遍歷方法來進(jìn)行數(shù)據(jù)獲取。

        誰說的,能統(tǒng)一呀,都可以用 forEach來遍歷,數(shù)組和map 本身就支持,字符串我直接轉(zhuǎn)為數(shù)組后可以了。

        ok,這沒什么毛病。

        但是每次都要轉(zhuǎn)換,還要封裝,還有可能要侵入原型。

        那有沒有一種更好的,通用方法,讓開發(fā)者用的更舒服,更爽呢?

        答案是肯定的,es5的時候還沒出現(xiàn),升級到 es6就有了。

        NB的 for of,扒一扒

        這個可以對不同數(shù)據(jù)結(jié)構(gòu)進(jìn)行統(tǒng)一遍歷的方式就是 es6for of 循環(huán)。

        for of 循環(huán) 和 古老的for 循環(huán)很像呀。不就是個新增語法么。

        引用阮大佬書中一句話,相信一看便知。

        ES6 借鑒 C++、Java、C# 和 Python 語言,引入了for...of循環(huán)。作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一的方法。

        關(guān)鍵在于統(tǒng)一,另一個就是直接取值,簡化操作,不需要在聲明和維護(hù)什么變量、對數(shù)據(jù)做轉(zhuǎn)換。

        原來for of 這么牛,for 循環(huán)搞不定的你可以搞定。

        為什么 for of 能具備這個能力,可以為不同的數(shù)據(jù)結(jié)構(gòu)提供一種統(tǒng)一的數(shù)據(jù)獲取方式。

        for of 真的這么強(qiáng)大嗎,他背后的機(jī)制是什么?

        其實(shí)for of 并不是真的強(qiáng)大,他只是一種ES6新的語法而已。

        并不是所有的對象都能使用 for of,只有實(shí)現(xiàn)了Iterator接口的對象才能夠使用 for of 來進(jìn)行遍歷取值。

        所以說 for of 只是語法糖,真正的主角是Iterator。

        What ? Iterator.....

        主角登場- Iterator 迭代器

        Iterator 是一種接口,目的是為不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的數(shù)據(jù)訪問機(jī)制。也可以理解為 Iterator 接口主要為 for of 服務(wù)的,供for...of進(jìn)行消費(fèi)。

        其實(shí)在很多后端語言多年前早已存在 Iterator 這個特性,如 java、C++、C#等。

        既然他是一種接口,那我們應(yīng)該怎樣實(shí)現(xiàn)這個接口呢?實(shí)現(xiàn)規(guī)則是什么樣的呢?

        因?yàn)?javascript 語言里沒有接口的概念,這里我們可以理解成它是一種特殊的對象 - 迭代器對象,返回此對象的方法叫做迭代器方法。

        首先他作為一個對象,此對象具有一個next方法,每次調(diào)用 next 方法都會返回一個結(jié)果值。

        這個結(jié)果值是一個 object,包含兩個屬性,valuedone。

        value表示具體的返回值,done 是布爾類型的,表示集合是否遍歷完成或者是否后續(xù)還有可用數(shù)據(jù),沒有可用數(shù)據(jù)則返回 true,否則返回 false。

        另外內(nèi)部會維護(hù)一個指針,用來指向當(dāng)前集合的位置,每調(diào)用一次 next 方法,指針都會向后移動一個位置(可以想象成數(shù)組的索引)。

        看下代碼實(shí)現(xiàn)

        function?getIterator(list)?{
        ????var?i?=?0;
        ????return?{
        ????????next:?function()?{
        ????????????var?done?=?(i?>=?list.length);
        ????????????var?value?=?!done???list[i++]?:?undefined;
        ????????????return?{
        ????????????????done:?done,
        ????????????????value:?value
        ????????????};
        ????????}
        ????};
        }
        var?it?=?getIterator(['a',?'b',?'c']);
        console.log(it.next());?//?{value:?"a",?done:?false}
        console.log(it.next());?//?{value:?"b",?done:?false}
        console.log(it.next());?//?{value:?"c",?done:?false}
        console.log(it.next());?//?"{?value:?undefined,?done:?true?}"
        console.log(it.next());?//?"{?value:?undefined,?done:?true?}"
        console.log(it.next());?//?"{?value:?undefined,?done:?true?}"

        上面代碼便是根據(jù)迭代器的基本概念衍生出來的一個模擬實(shí)現(xiàn)。

        • getIterator方法返回一個對象 - 可迭代對象
        • 對象具有一個next 方法,next 方法內(nèi)部通過閉包來保存指針 i 的值,每次調(diào)用 next 方法 i 的值都會+1.
        • 然后根據(jù) i 的值從數(shù)組內(nèi)取出數(shù)據(jù)作為 value,然后通過索引判斷得到 done的值。
        • 當(dāng) i=3的時候,超過數(shù)組的最大索引,無可用數(shù)據(jù)返回,此時done 為true,遍歷完成。

        可迭代對象

        到這里我們已經(jīng)大概了解了 Iterator, 以及如何創(chuàng)建一個迭代器對象。但是他和 for of 有什么關(guān)系呢?

        for of 運(yùn)行機(jī)制

        當(dāng) for of執(zhí)行的時候,循環(huán)過程中引擎就會自動調(diào)用這個對象上的迭代器方法, 依次執(zhí)行迭代器對象的 next 方法,將 next 返回值賦值給 for of 內(nèi)的變量,從而得到具體的值。

        我覺得上面一句話包含了一個重要的信息- “對象上的迭代器方法”。

        實(shí)現(xiàn)可迭代對象

        對象上怎么會有迭代器方法呢?

        ES6里規(guī)定,只要在對象的屬性上部署了Iterator接口,具體形式為給對象添加Symbol.iterator屬性,此屬性指向一個迭代器方法,這個迭代器會返回一個特殊的對象 - 迭代器對象。

        而部署這個屬性并且實(shí)現(xiàn)了迭代器方法后的對象叫做可迭代對象。

        此時,這個對象就是可迭代的,也就是可以被 for of 遍歷。

        Symbol.iterator,它是一個表達(dá)式,返回Symbol對象的iterator屬性,這是一個預(yù)定義好的、類型為 Symbol 的特殊值。

        舉個例子

        普通的對象是不能被 for of 遍歷的,直接食用會報(bào)錯。

        var?obj?=?{};

        for(var?k?of?obj){
        ????
        }

        obj 不是可迭代的對象。

        那么我們來,讓一個對象變成可迭代對象,按照協(xié)議也就是規(guī)定來實(shí)現(xiàn)即可。

        iterableObj 對象上部署 Symbol.iterator屬性,然后為其創(chuàng)建一個迭代器方法,迭代器的規(guī)則上面我們已經(jīng)說過啦。

        var?iterableObj?=?{
        ????items:[100,200,300],
        ????[Symbol.iterator]:function(){
        ????var?self=this;
        ????var?i?=?0;
        ????return?{
        ????????next:?function()?{
        ????????????var?done?=?(i?>=?self.items.length);
        ????????????var?value?=?!done???self.items[i++]?:?undefined;
        ????????????return?{
        ????????????????done:?done,
        ????????????????value:?value
        ????????????};
        ????????}
        ????};
        ????}
        }

        //遍歷它
        for(var?item?of?iterableObj){
        ????console.log(item);?//100,200,300
        }

        就這么簡單,上面這個對象就是可迭代對象了,可以被 for of 消費(fèi)了。

        Iterator 原生應(yīng)用場景

        我們再回到最開始使用 for of 來進(jìn)行遍歷字符串、數(shù)組、map,我們并沒有為他們部署Iterator接口,仍然可以使用 for of 遍歷。

        這是因?yàn)樵?ES6中有些對象已經(jīng)默認(rèn)部署了此接口,不需要做任何處理,就可以使用 for of 來進(jìn)行遍歷取值。

        不信?咿,你好難搞,我不要你說 - 信,我要我說 - 信。

        看看能不能拿到它們的迭代器。

        數(shù)組

        //數(shù)組
        var?arr=[100,200,300];

        var?iteratorObj=??arr[Symbol.iterator]();//得到迭代器方法,返回迭代器對象

        console.log(iteratorObj.next());
        console.log(iteratorObj.next());
        console.log(iteratorObj.next());
        console.log(iteratorObj.next());


        字符串

        因?yàn)樽址旧淼闹凳怯行虻模⑶揖哂蓄悢?shù)組的特性,支持通過索引訪問,所以也默認(rèn)部署了iterator接口。

        var?str='abc';

        var?strIteratorObj?=?str[Symbol.iterator]();//得到迭代器

        console.log(strIteratorObj.next());
        console.log(strIteratorObj.next());
        console.log(strIteratorObj.next());
        console.log(strIteratorObj.next());

        或者直接看原型上是否部署了這個屬性即可。

        arguments 類數(shù)組

        函數(shù)內(nèi)的arguments 是一個類數(shù)組,他也支持 for of,因?yàn)樗麅?nèi)部也部署了Iterator 接口。

        我們都知道對象是默認(rèn)沒有部署這個接口的,所以arguments這個屬性沒有在原型上,而在在對象自身的屬性上。

        function?test(){
        ????var?obj?=?arguments[Symbol.iterator]();
        ???console.log(?arguments.hasOwnProperty(Symbol.iterator));
        ???console.log(?arguments);
        ???console.log(obj.next());
        }

        test(1,2,3);

        總結(jié)來說,已默認(rèn)部署 Iterator 接口的對象主要包括數(shù)組、字符串、Set、Map 、類似數(shù)組的對象(比如arguments對象、DOM NodeList 對象)。

        代碼驗(yàn)證略,都是一個套路,不多說。

        Iterator 另外一個作用

        Iterator除了可以為不同的數(shù)據(jù)結(jié)構(gòu)提供一種統(tǒng)一的數(shù)據(jù)訪問方式,還有沒有發(fā)現(xiàn)其他的作用?

        那就是數(shù)據(jù)可定制性,因?yàn)槲覀兛梢噪S意的控制迭代器的 value 值。

        比如:數(shù)組本身就是一個可迭代的,我們可以覆蓋他的默認(rèn)迭代器。

        var?arr=[100,200,300];

        for(var?o?of?arr){
        ????console.log(o);
        }

        for of 數(shù)組默認(rèn)輸出如下

        經(jīng)過我們的改造

        var?arr=[100,200,300];
        arr[Symbol.iterator]=function(){

        ????var?self=this;
        ????var?i?=?0;
        ????return?{
        ????????next:?function()?{
        ????????????var?done?=?(i?>=?self.length);
        ????????????var?value?=?!done???self[i++]?:?undefined;
        ????????????return?{
        ????????????????done:?done,
        ????????????????value:?value
        ????????????};
        ????????}
        ????};
        }

        for(var?o?of?arr){
        ????console.log(o);
        }

        對象為什么沒有默認(rèn)部署

        對象可能有各種屬性,不像數(shù)組的值是有序的。

        所以遍歷的時候根本不知道如何確定他們的先后順序,所以需要我們根據(jù)情況手動實(shí)現(xiàn)。

        擴(kuò)展

        for of 中斷

        我們都知道普通的 for 循環(huán)是可以隨時中斷的,那 for of 是否可以呢?

        答案是肯定的,for of機(jī)制兼顧了forforEach。

        迭代器除了必須next 方法外,還有兩個可選的方法 returnthrow方法。

        如果 for of 循環(huán)提前退出,則會自動調(diào)用 return 方法,需要注意的是 return 方法必須有返回值,且返回值必須是 一個object。

        ps:以拋出異常的方式退出,會先執(zhí)行 return 方法再拋出異常。

        var?iterableObj?=?{
        ????items:[100,200,300],
        ????[Symbol.iterator]:function(){
        ????var?self=this;
        ????var?i?=?0;
        ????return?{
        ????????next:?function()?{
        ????????????var?done?=?(i?>=?self.items.length);
        ????????????var?value?=?!done???self.items[i++]?:?undefined;
        ????????????return?{
        ????????????????done:?done,
        ????????????????value:?value
        ????????????};
        ????????},
        ????????return(){
        ????????????console.log('提前退出');
        ????????????return?{//必須返回一個對象
        ????????????????done:true
        ????????????}
        ????????}
        ????};
        ????}

        }

        for(var?item?of?iterableObj){
        ????console.log(item);
        ????if(item===200){
        ????????break;
        ????}
        }

        for(var?item?of?iterableObj){
        ????console.log(item);
        ????throw?new?Error();
        }

        ps:throw 方法這里先不說,這里不是他的用武之地,后續(xù)文章見。

        不止 for of

        除了 for of 執(zhí)行的時候會自動調(diào)用對象的Iterator方法,那么ES6里還有沒有其他的語法形式?

        解構(gòu)賦值

        對可迭代對象進(jìn)行解構(gòu)賦值的時候,會默認(rèn)調(diào)用Symbol.iterator方法。

        //字符串
        var?str='12345';
        var?[a,b]=str;
        console.log(a,b);?//?1?2

        //map
        var?map?=?new?Map();
        map.set('我','前端');
        map.set('是','技術(shù)');
        map.set('誰','江湖');
        map.set('作者','zz_jesse');

        var?[d,e]=map;
        console.log(d,e);
        //["我",?"前端"]?["是",?"技術(shù)"]
        ....

        同樣如果對一個普通對象進(jìn)行解構(gòu),則會報(bào)錯。

        因?yàn)槠胀▽ο蟛皇强傻鷮ο蟆?/p>

        var?[d,e]={name:'zhang'};

        從一個自定義的可迭代對象進(jìn)行解構(gòu)賦值。

        var?iterableObj?=?{
        ????items:['紅','綠','藍(lán)'],
        ????[Symbol.iterator]:function(){
        ????var?self=this;
        ????var?i?=?0;
        ????return?{
        ????????next:?function()?{
        ????????????var?done?=?(i?>=?self.items.length);
        ????????????var?value?=?!done???self.items[i++]?:?undefined;
        ????????????return?{
        ????????????????done:?done,
        ????????????????value:?value
        ????????????};
        ????????}
        ?????};
        ???}
        }

        var?[d,e]=iterableObj;
        console.log(d,e);//紅?綠?

        解構(gòu)賦值的變量的值就是迭代器對象的 next 方法的返回值,且是按順序返回。

        擴(kuò)展運(yùn)算符

        擴(kuò)展運(yùn)算符的執(zhí)行(...)也會默認(rèn)調(diào)用它的Symbol.iterator方法,可以將當(dāng)前迭代對象轉(zhuǎn)換為數(shù)組。

        //字符串
        var?str='1234';
        console.log([...str]);//[1,2,3,4]??轉(zhuǎn)換為數(shù)組


        //map?對象
        var?map=new?Map([[1,2],[3,4]]);
        [...map]?//[[1,2],[3,4]

        //set?對象
        var?set=new?Set([1,2,3]);
        [...set]?//[1,2,3]

        通用普通對象是不可以轉(zhuǎn)為數(shù)組的。

        var?obj={name:'zhang'};
        [..obj]//報(bào)錯

        作為數(shù)據(jù)源

        作為一些數(shù)據(jù)的數(shù)據(jù)源,比如某些api 方法的參數(shù)是接收一個數(shù)組,都會默認(rèn)的調(diào)用自身迭代器。

        例如:Set、Map、Array.from 等

        //為了證明,先把一個數(shù)組的默認(rèn)迭代器給覆蓋掉

        var?arr=[100,200,300];

        arr[Symbol.iterator]=function(){

        ????var?self=this;
        ????var?i?=?0;
        ????return?{
        ????????next:?function()?{
        ????????????var?done?=?(i?>=?self.length);
        ????????????var?value?=?!done???self[i++]?:?undefined;
        ????????????return?{
        ????????????????done:?done,
        ????????????????value:?value+'前端技術(shù)江湖'?//注意這里
        ????????????};
        ????????}
        ????};

        }

        for(var?o?of?arr){
        ????console.log(o);
        }

        //?100前端技術(shù)江湖
        //?200前端技術(shù)江湖
        //?300前端技術(shù)江湖

        已覆蓋完成

        //生成?set??對象
        var?set??=?new?Set(arr);
        //調(diào)用?Array.from方法
        Array.from(arr);

        yield* ?關(guān)鍵字

        yield*后面跟的是一個可遍歷的結(jié)構(gòu),執(zhí)行時也會調(diào)用迭代器函數(shù)。

        let?foo?=?function*?()?{
        ??yield?1;
        ??yield*?[2,3,4];
        ??yield?5;
        };

        yield 需要著重說明, 下一節(jié)再詳細(xì)介紹。

        判斷對象是否可迭代

        既然可迭代對象的規(guī)則必須在對象上部署Symbol.iterator屬性,那么我們基本上就可以通過此屬來判斷對象是否為可迭代對象,然后就可以知道是否能使用 for of 取值了。

        function?isIterable(object)?{
        ????return?typeof?object[Symbol.iterator]?===?"function";
        }
        console.log(isIterable('abcdefg'));?//?true
        console.log(isIterable([1,?2,?3]));?//?true
        console.log(isIterable("Hello"));?//?true
        console.log(isIterable(new?Map()));?//?true
        console.log(isIterable(new?Set()));?//?true
        console.log(isIterable(new?WeakMap()));?//?false
        console.log(isIterable(new?WeakSet()));?//?false

        總結(jié)

        ES6的出現(xiàn)帶來了很多新的數(shù)據(jù)結(jié)構(gòu),比如 Map ,Set ,所以為了數(shù)據(jù)獲取的方便,增加了一種統(tǒng)一獲取數(shù)據(jù)的方式 for of 。而 for of 執(zhí)行的時候引擎會自動調(diào)用對象的迭代器來取值。

        不是所有的對象都支持這種方式,必須是實(shí)現(xiàn)了Iterator接口的才可以,這樣的對象我們稱他們?yōu)榭傻鷮ο蟆?/p>

        迭代器實(shí)現(xiàn)方式根據(jù)可迭代協(xié)議,迭代器協(xié)議實(shí)現(xiàn)即可。

        除了統(tǒng)一數(shù)據(jù)訪問方式,還可以自定義得到的數(shù)據(jù)內(nèi)容,隨便怎樣,只要是你需要的。

        迭代器是一個方法, 用來返回迭代器對象。

        可迭代對象是部署了 Iterator 接口的對象,同時擁有正確的迭代器方法。

        ES6內(nèi)很多地方都應(yīng)用了Iterator,平時可以多留意觀察,多想一步。

        是結(jié)束也是開始

        到這里我們已經(jīng)可以根據(jù)迭代器的規(guī)則自定義迭代器了,但實(shí)現(xiàn)的過程有些復(fù)雜,畢竟需要自己來維護(hù)內(nèi)部指針,有不少的邏輯處理,難免會出錯。

        那有沒有更優(yōu)雅的實(shí)現(xiàn)方式呢?

        有,那就是-Generator -生成器 。

        let?obj?=?{
        ??*?[Symbol.iterator]()?{
        ????yield?'hello';
        ????yield?'world';
        ??}
        };

        for?(let?x?of?obj)?{
        ??console.log(x);
        }
        //?"hello"
        //?"world"

        可以看出它非常簡潔,無需過多代碼就可以生成一個迭代器。

        它除了可以作為生成迭代器的語法糖,他還有更多神奇的能力。

        這次就先搞定Iterator,下次搞 Generator 。

        練習(xí)

        如果覺得本文有收獲的話,可以試著做做下面的練習(xí)題,加深下理解,然后在評論內(nèi)寫上你的答案。

        1. 寫一個迭代器(Iterator)對象 。
        2. 自定義一個可迭代對象。
        3. 說說你對Iterator的理解,總結(jié)性輸出下。

        參考

        https://es6.ruanyifeng.com/#docs/iterator

        https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols

        https://www.cnblogs.com/xiaohuochai/p/7253466.html

        月初福利

        每月1號組織小伙伴們來抽獎。

        給本文發(fā)評論或者點(diǎn)贊就可以參與抽獎啦




        僅限于關(guān)注本號小伙伴參與哦


        點(diǎn)個『在看』支持下?


        瀏覽 99
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 疯狂刺激3p人妻 操片在线观看 | 人妻无码аⅴ天堂中文在线 | 黄色网址免费在线观看 | 国产a级免费观看 | 久久婷婷婷精品秘国产生583 |