<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          可以迭代大部分?jǐn)?shù)據(jù)類型的 for…of 為什么不能遍歷普通對象?

          共 5704字,需瀏覽 12分鐘

           ·

          2020-12-13 05:58

          作者:孤篷
          來源:SegmentFault 思否社區(qū)



          for…of?及其使用


          我們知道,ES6 中引入 for...of 循環(huán),很多時候用以替代 for...in 和 forEach()?,并支持新的迭代協(xié)議。for...of 允許你遍歷 Array(數(shù)組), String(字符串), Map(映射), Set(集合),TypedArray(類型化數(shù)組)、arguments、NodeList對象、Generator等可迭代的數(shù)據(jù)結(jié)構(gòu)等。for...of語句在可迭代對象上創(chuàng)建一個迭代循環(huán),調(diào)用自定義迭代鉤子,并為每個不同屬性的值執(zhí)行語句。


          for...of的語法:


          for?(variable?of?iterable)?{
          ????//?statement
          }
          // variable:每個迭代的屬性值被分配給該變量。
          // iterable:一個具有可枚舉屬性并且可以迭代的對象。


          常用用法


          {
          ??//?迭代字符串
          ??const?iterable?=?'ES6';
          ??for?(const?value?of?iterable)?{
          ????console.log(value);
          ??}
          ??//?Output:
          ??//?"E"
          ??//?"S"
          ??//?"6"
          }
          {
          ??//?迭代數(shù)組
          ??const?iterable?=?['a',?'b'];
          ??for?(const?value?of?iterable)?{
          ????console.log(value);
          ??}
          ??//?Output:
          ??//?a
          ??//?b
          }
          {
          ??//?迭代Set(集合)
          ??const?iterable?=?new?Set([1,?2,?2,?1]);
          ??for?(const?value?of?iterable)?{
          ????console.log(value);
          ??}
          ??//?Output:
          ??//?1
          ??//?2
          }
          {
          ??//?迭代Map
          ??const?iterable?=?new?Map([["a",?1],?["b",?2],?["c",?3]]);
          ??for?(const?entry?of?iterable)?{
          ????console.log(entry);
          ??}
          ??//?Output:
          ??//?["a",?1]
          ??//?["b",?2]
          ??//?["c",?3]

          ??for?(const?[key,?value]?of?iterable)?{
          ????console.log(value);
          ??}
          ??//?Output:
          ??//?1
          ??//?2
          ??//?3
          }
          {
          ??//?迭代Arguments?Object(參數(shù)對象)
          ??function?args()?{
          ????for?(const?arg?of?arguments)?{
          ??????console.log(arg);
          ????}
          ??}
          ??args('a',?'b');
          ??//?Output:
          ??//?a
          ??//?b
          }
          {
          ??//?迭代生成器
          ??function*?foo(){?
          ????yield?1;?
          ????yield?2;?
          ????yield?3;?
          ??};?

          ??for?(let?o?of?foo())?{?
          ????console.log(o);?
          ??}
          ??//?Output:
          ??//?1
          ??//?2
          ??//?3
          }


          Uncaught TypeError: obj is not iterable


          //?普通對象
          const?obj?=?{
          ??foo:?'value1',
          ??bar:?'value2'
          }
          for(const?item?of?obj){
          ??console.log(item)
          }
          //?Uncaught?TypeError:?obj?is?not?iterable


          可以看出,for of可以迭代大部分對象甚至字符串,卻不能遍歷普通對象。


          如何用for...of迭代普通對象


          通過前面的基本用法,我們知道,for...of可以迭代數(shù)組、Map等數(shù)據(jù)結(jié)構(gòu),順著這個思路,我們可以結(jié)合對象的Object.values()、Object.keys()、Object.entries()方法以及解構(gòu)賦值的知識來用for...of遍歷普通對象。


          • Object.values()、Object.keys()、Object.entries()用法及返回值

          const?obj?=?{
          ??foo:?'value1',
          ??bar:?'value2'
          }
          //?打印由value組成的數(shù)組
          console.log(Object.values(obj))?//?["value1",?"value2"]

          //?打印由key組成的數(shù)組
          console.log(Object.keys(obj))?//?["foo",?"bar"]

          //?打印由[key,?value]組成的二維數(shù)組
          //?copy(Object.entries(obj))可以把輸出結(jié)果直接拷貝到剪貼板,然后黏貼
          console.log(Object.entries(obj))?//?[["foo","value1"],["bar","value2"]]

          • 因?yàn)閒or...of可以迭代數(shù)組和Map,所以我們得到以下遍歷普通對象的方法

          const?obj?=?{
          ??foo:?'value1',
          ??bar:?'value2'
          }
          //?方法一:使用for?of迭代Object.entries(obj)形成的二維數(shù)組,利用解構(gòu)賦值得到value
          for(const?[,?value]?of?Object.entries(obj)){
          ??console.log(value)?//?value1,?value2
          }

          //?方法二:Map
          //?普通對象轉(zhuǎn)Map
          // Map 可以接受一個數(shù)組作為參數(shù)。該數(shù)組的成員是一個個表示鍵值對的數(shù)組
          console.log(new?Map(Object.entries(obj)))

          //?遍歷普通對象生成的Map
          for(const?[,?value]?of?new?Map(Object.entries(obj))){
          ??console.log(value)?//?value1,?value2
          }

          //?方法三:繼續(xù)使用for?in
          for(const?key?in?obj){
          ??console.log(obj[key])?//?value1,?value2
          }

          {
          ??//?方法四:將【類數(shù)組(array-like)對象】轉(zhuǎn)換為數(shù)組
          ??//?該對象需具有一個 length 屬性,且其元素必須可以被索引。
          ??const?obj?=?{
          ????length:?3,?//?length是必須的,否則什么也不會打印
          ????0:?'foo',
          ????1:?'bar',
          ????2:?'baz',
          ????a:?12??//?非數(shù)字屬性是不會打印的
          ??};
          ??const?array?=?Array.from(obj);?//?["foo",?"bar",?"baz"]
          ??for?(const?value?of?array)?{?
          ??????console.log(value);
          ??}
          ??//?Output:?foo?bar?baz
          }
          {
          ??//?方法五:給【類數(shù)組】部署數(shù)組的[Symbol.iterator]方法【對普通字符串屬性對象無效】
          ??const?iterable?=?{
          ????0:?'a',
          ????1:?'b',
          ????2:?'c',
          ????length:?3,
          ????[Symbol.iterator]:?Array.prototype[Symbol.iterator]
          ??};
          ??for?(let?item?of?iterable)?{
          ????console.log(item);?//?'a',?'b',?'c'
          ??}
          }

          注意事項(xiàng)


          • 有別于不可終止遍歷的forEach,for...of的循環(huán)可由break, throw, continue 或return終止,在這些情況下,迭代器關(guān)閉。

          const?obj?=?{
          ????foo:?'value1',
          ????bar:?'value2',
          ????baz:?'value3'
          ??}
          ??for(const?[,?value]?of?Object.entries(obj)){
          ????if?(value?===?'value2')?break?//?不會再執(zhí)行下次迭代
          ????console.log(value)?//?value1
          ??};
          ??[1,2].forEach(item?=>?{
          ??????if(item?==?1)?break?//?Uncaught?SyntaxError:?Illegal?break?statement
          ??????console.log(item)
          ??});
          ??[1,2].forEach(item?=>?{
          ??????if(item?==?1)?continue?//?Uncaught?SyntaxError:?Illegal?continue?statement:?no?surrounding?iteration?statement
          ??????console.log(item)
          ??});
          ??[1,2].forEach(item?=>?{
          ??????if(item?==?1)?return?//?仍然會繼續(xù)執(zhí)行下一次循環(huán),打印2
          ??????console.log(item)?//?2
          ??})

          • For…of?與?For…in對比


            • for...in 不僅枚舉數(shù)組聲明,它還從構(gòu)造函數(shù)的原型中查找繼承的非枚舉屬性;
            • for...of 不考慮構(gòu)造函數(shù)原型上的不可枚舉屬性(或者說for...of語句遍歷可迭代對象定義要迭代的數(shù)據(jù)。);
            • for...of 更多用于特定的集合(如數(shù)組等對象),但不是所有對象都可被for...of迭代。

          Array.prototype.newArr?=?()?=>?{};
          ??Array.prototype.anotherNewArr?=?()?=>?{};
          ??const?array?=?['foo',?'bar',?'baz'];
          ??for?(const?value?in?array)?{?
          ????console.log(value);?//?0?1?2?newArr?anotherNewArr
          ??}
          ??for?(const?value?of?array)?{?
          ????console.log(value);?//?'foo',?'bar',?'baz'
          ??}



          普通對象為何不能被?for of?迭代


          前面我們有提到一個詞叫“可迭代”數(shù)據(jù)結(jié)構(gòu),當(dāng)用for of迭代普通對象時,也會報(bào)一個“not iterable”的錯誤。實(shí)際上,任何具有 Symbol.iterator 屬性的元素都是可迭代的。我們可以簡單查看幾個可被for of迭代的對象,看看和普通對象有何不同:


          可以看到,這些可被for of迭代的對象,都實(shí)現(xiàn)了一個Symbol(Symbol.iterator)方法,而普通對象沒有這個方法。

          簡單來說,for of 語句創(chuàng)建一個循環(huán)來迭代可迭代的對象,可迭代的對象內(nèi)部實(shí)現(xiàn)了Symbol.iterator方法,而普通對象沒有實(shí)現(xiàn)這一方法,所以普通對象是不可迭代的。



          Iterator(遍歷器)


          關(guān)于Iterator(遍歷器)的概念,可以參照阮一峰大大的《ECMAScript 6 入門》——Iterator(遍歷器)的概念:


          簡單來說,ES6 為了統(tǒng)一集合類型數(shù)據(jù)結(jié)構(gòu)的處理,增加了 iterator 接口,供 for...of 使用,簡化了不同結(jié)構(gòu)數(shù)據(jù)的處理。而 iterator 的遍歷過程,則是類似 Generator 的方式,迭代時不斷調(diào)用next方法,返回一個包含value(值)和done屬性(標(biāo)識是否遍歷結(jié)束)的對象。



          如何實(shí)現(xiàn)Symbol.iterator方法,使普通對象可被?for of?迭代


          依據(jù)上文的指引,我們先看看數(shù)組的Symbol.iterator接口:

          const?arr?=?[1,2,3];
          const?iterator?=?arr[Symbol.iterator]();
          console.log(iterator.next());?//?{value:?1,?done:?false}
          console.log(iterator.next());?//?{value:?2,?done:?false}
          console.log(iterator.next());?//?{value:?3,?done:?false}
          console.log(iterator.next());?//?{value:?undefined,?done:?true}

          我們可以嘗試給普通對象實(shí)現(xiàn)一個Symbol.iterator接口:

          //?普通對象
          const?obj?=?{
          ??foo:?'value1',
          ??bar:?'value2',
          ??[Symbol.iterator]()?{
          ????//?這里Object.keys不會獲取到Symbol.iterator屬性,原因見下文
          ????const?keys?=?Object.keys(obj);?
          ????let?index?=?0;
          ????return?{
          ??????next:?()?=>?{
          ????????if?(index???????????//?迭代結(jié)果?未結(jié)束
          ??????????return?{
          ????????????value:?this[keys[index++]],
          ????????????done:?false
          ??????????};
          ????????}?else?{
          ??????????//?迭代結(jié)果?結(jié)束
          ??????????return?{?value:?undefined,?done:?true?};
          ????????}
          ??????}
          ????};
          ??}
          }
          for?(const?value?of?obj)?{
          ??console.log(value);?//?value1?value2
          };

          上面給obj實(shí)現(xiàn)了Symbol.iterator接口后,我們甚至還可以像下面這樣把對象轉(zhuǎn)換成數(shù)組:

          console.log([...obj]);?//?["value1",?"value2"]
          console.log([...{}]);?//?console.log?is?not?iterable?(cannot?read?property?Symbol(Symbol.iterator))

          我們給obj對象實(shí)現(xiàn)了一個Symbol.iterator接口,在此,有一點(diǎn)需要說明的是,不用擔(dān)心[Symbol.iterator]屬性會被Object.keys()獲取到導(dǎo)致遍歷結(jié)果出錯,因?yàn)镾ymbol.iterator這樣的Symbol屬性,需要通過Object.getOwnPropertySymbols(obj)才能獲取,Object.getOwnPropertySymbols()?方法返回一個給定對象自身的所有 Symbol 屬性的數(shù)組。

          ??有一些場合會默認(rèn)調(diào)用 Iterator 接口(即Symbol.iterator方法:

          • 擴(kuò)展運(yùn)算符...:這提供了一種簡便機(jī)制,可以將任何部署了 Iterator 接口的數(shù)據(jù)結(jié)構(gòu),轉(zhuǎn)為數(shù)組。也就是說,只要某個數(shù)據(jù)結(jié)構(gòu)部署了 Iterator 接口,就可以對它使用擴(kuò)展運(yùn)算符,將其轉(zhuǎn)為數(shù)組(毫不意外的,代碼[...{}]會報(bào)錯,而[...'123']會輸出數(shù)組['1','2','3'])。
          • 數(shù)組和可迭代對象的解構(gòu)賦值(解構(gòu)是ES6提供的語法糖,其實(shí)內(nèi)在是針對可迭代對象的Iterator接口,通過遍歷器按順序獲取對應(yīng)的值進(jìn)行賦值。而普通對象解構(gòu)賦值的內(nèi)部機(jī)制,是先找到同名屬性,然后再賦給對應(yīng)的變量。);
          • yield*:_yield*后面跟的是一個可遍歷的結(jié)構(gòu),它會調(diào)用該結(jié)構(gòu)的遍歷器接口;
          • 由于數(shù)組的遍歷會調(diào)用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場合,其實(shí)都調(diào)用;
          • 字符串是一個類似數(shù)組的對象,也原生具有Iterator接口,所以也可被for of迭代。



          迭代器模式


          迭代器模式提供了一種方法順序訪問一個聚合對象中的各個元素,而又無需暴露該對象的內(nèi)部實(shí)現(xiàn),這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪問集合內(nèi)部的數(shù)據(jù)。迭代器模式為遍歷不同的集合結(jié)構(gòu)提供了一個統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作。

          不難發(fā)現(xiàn),Symbol.iterator實(shí)現(xiàn)的就是一種迭代器模式。集合對象內(nèi)部實(shí)現(xiàn)了Symbol.iterator接口,供外部調(diào)用,而我們無需過多的關(guān)注集合對象內(nèi)部的結(jié)構(gòu),需要處理集合對象內(nèi)部的數(shù)據(jù)時,我們通過for of調(diào)用Symbol.iterator接口即可。

          比如針對前文普通對象的Symbol.iterator接口實(shí)現(xiàn)一節(jié)的代碼,如果我們對obj里面的數(shù)據(jù)結(jié)構(gòu)進(jìn)行了如下調(diào)整,那么,我們只需對應(yīng)的修改供外部迭代使

          用的Symbol.iterator接口,即可不影響外部迭代調(diào)用:
          const?obj?=?{
          ??//?數(shù)據(jù)結(jié)構(gòu)調(diào)整
          ??data:?['value1',?'value2'],
          ??[Symbol.iterator]()?{
          ????let?index?=?0;
          ????return?{
          ??????next:?()?=>?{
          ????????if?(index???????????//?迭代結(jié)果?未結(jié)束
          ??????????return?{
          ????????????value:?this.data[index++],
          ????????????done:?false
          ??????????};
          ????????}?else?{
          ??????????//?迭代結(jié)果?結(jié)束
          ??????????return?{?value:?undefined,?done:?true?};
          ????????}
          ??????}
          ????};
          ??}
          }
          //?外部調(diào)用
          for?(const?value?of?obj)?{
          ??console.log(value);?//?value1?value2
          }

          實(shí)際使用時,我們可以把上面的Symbol.iterator提出來進(jìn)行單獨(dú)封裝,這樣就可以對一類數(shù)據(jù)結(jié)構(gòu)進(jìn)行迭代操作了。當(dāng)然,下面的代碼只是最簡單的示例,你可以在此基礎(chǔ)上探究更多實(shí)用的技巧。

          const?obj1?=?{
          ??data:?['value1',?'value2']
          }
          const?obj2?=?{
          ??data:?[1,?2]
          }
          //?遍歷方法
          consoleEachData?=?(obj)?=>?{
          ??obj[Symbol.iterator]?=?()?=>?{
          ????let?index?=?0;
          ????return?{
          ??????next:?()?=>?{
          ????????if?(index???????????return?{
          ????????????value:?obj.data[index++],
          ????????????done:?false
          ??????????};
          ????????}?else?{
          ??????????return?{?value:?undefined,?done:?true?};
          ????????}
          ??????}
          ????};
          ??}
          ??for?(const?value?of?obj)?{
          ????console.log(value);
          ??}
          }
          consoleEachData(obj1);?//?value1?value2
          consoleEachData(obj2);?//?1??2



          一點(diǎn)補(bǔ)充


          在寫這篇文章時,有個問題給我?guī)砹死_:原生object對象默認(rèn)沒有部署Iterator接口,即object不是一個可迭代對象。對象的擴(kuò)展運(yùn)算符...等同于使用Object.assign()方法,這個比較好理解。那么,原生object對象的解構(gòu)賦值又是怎樣一種機(jī)制呢?

          let?aClone?=?{?...a?};
          //?等同于
          let?aClone?=?Object.assign({},?a);

          有一種說法是:ES6提供了Map數(shù)據(jù)結(jié)構(gòu),實(shí)際上原生object對象被解構(gòu)時,會被當(dāng)作Map進(jìn)行解構(gòu)。關(guān)于這點(diǎn),大家有什么不同的觀點(diǎn)嗎?歡迎評論區(qū)一起探討。



          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。

          -?END -

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜乳一区二区三区精品 | 综合不卡在线 | 国内自拍一区 | 精品久久精品 | 免费视频久久 |