<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 為什么不能遍歷普通對(duì)象?

          共 8730字,需瀏覽 18分鐘

           ·

          2020-12-17 12:07

          關(guān)注?程序員成長(zhǎng)指北,回復(fù)“1

          加入我們一起學(xué)習(xí),天天進(jìn)步

          for…of 及其使用

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

          for...of的語(yǔ)法:

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

          常用用法

          {  // 迭代字符串  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ù)對(duì)象) 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

          // 普通對(duì)象const obj = {  foo: 'value1',  bar: 'value2'}for(const item of obj){  console.log(item)}// Uncaught TypeError: obj is not iterable
          可以看出,for of可以迭代大部分對(duì)象甚至字符串,卻不能遍歷普通對(duì)象。

          如何用for...of迭代普通對(duì)象

          通過(guò)前面的基本用法,我們知道,for...of可以迭代數(shù)組、Map等數(shù)據(jù)結(jié)構(gòu),順著這個(gè)思路,我們可以結(jié)合對(duì)象的Object.values()、Object.keys()、Object.entries()方法以及解構(gòu)賦值的知識(shí)來(lái)用for...of遍歷普通對(duì)象。
          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,所以我們得到以下遍歷普通對(duì)象的方法。
          const obj = {  foo: 'value1',  bar: 'value2'}// 方法一:使用for of迭代Object.entries(obj)形成的二維數(shù)組,利用解構(gòu)賦值得到valuefor(const [, value] of Object.entries(obj)){  console.log(value) // value1, value2}
          // 方法二:Map// 普通對(duì)象轉(zhuǎn)Map// Map 可以接受一個(gè)數(shù)組作為參數(shù)。該數(shù)組的成員是一個(gè)個(gè)表示鍵值對(duì)的數(shù)組console.log(new Map(Object.entries(obj)))
          // 遍歷普通對(duì)象生成的Mapfor(const [, value] of new Map(Object.entries(obj))){ console.log(value) // value1, value2}
          // 方法三:繼續(xù)使用for infor(const key in obj){ console.log(obj[key]) // value1, value2}
          { // 方法四:將【類數(shù)組(array-like)對(duì)象】轉(zhuǎn)換為數(shù)組 // 該對(duì)象需具有一個(gè) length 屬性,且其元素必須可以被索引。 const obj = { length: 3, // length是必須的,否則什么也不會(huì)打印 0: 'foo', 1: 'bar', 2: 'baz', a: 12 // 非數(shù)字屬性是不會(huì)打印的 }; const array = Array.from(obj); // ["foo", "bar", "baz"] for (const value of array) { console.log(value); } // Output: foo bar baz}{ // 方法五:給【類數(shù)組】部署數(shù)組的[Symbol.iterator]方法【對(duì)普通字符串屬性對(duì)象無(wú)效】 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 // 不會(huì)再執(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 // 仍然會(huì)繼續(xù)執(zhí)行下一次循環(huán),打印2      console.log(item) // 2  })
          For…of 與 For…in對(duì)比
          for...in 不僅枚舉數(shù)組聲明,它還從構(gòu)造函數(shù)的原型中查找繼承的非枚舉屬性;
          for...of 不考慮構(gòu)造函數(shù)原型上的不可枚舉屬性(或者說(shuō)for...of語(yǔ)句遍歷可迭代對(duì)象定義要迭代的數(shù)據(jù)。);
          for...of 更多用于特定的集合(如數(shù)組等對(duì)象),但不是所有對(duì)象都可被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'  }

          普通對(duì)象為何不能被 for of 迭代

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




          可以看到,這些可被for of迭代的對(duì)象,都實(shí)現(xiàn)了一個(gè)Symbol(Symbol.iterator)方法,而普通對(duì)象沒(méi)有這個(gè)方法。
          簡(jiǎn)單來(lái)說(shuō),for of 語(yǔ)句創(chuàng)建一個(gè)循環(huán)來(lái)迭代可迭代的對(duì)象,可迭代的對(duì)象內(nèi)部實(shí)現(xiàn)了Symbol.iterator方法,而普通對(duì)象沒(méi)有實(shí)現(xiàn)這一方法,所以普通對(duì)象是不可迭代的。


          Iterator(遍歷器)

          關(guān)于Iterator(遍歷器),可以參照阮一峰老師寫的《ECMAScript 6 入門教程—異步遍歷器》教程。


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

          如何實(shí)現(xiàn)Symbol.iterator方法,使普通對(duì)象可被 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}
          我們可以嘗試給普通對(duì)象實(shí)現(xiàn)一個(gè)Symbol.iterator接口:
          // 普通對(duì)象const obj = {  foo: 'value1',  bar: 'value2',  [Symbol.iterator]() {    // 這里Object.keys不會(huì)獲取到Symbol.iterator屬性,原因見(jiàn)下文    const keys = Object.keys(obj);     let index = 0;    return {      next: () => {        if (index < keys.length) {          // 迭代結(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接口后,我們甚至還可以像下面這樣把對(duì)象轉(zhuǎn)換成數(shù)組:
          console.log([...obj]); // ["value1", "value2"]console.log([...{}]); // console.log is not iterable (cannot read property Symbol(Symbol.iterator))
          我們給obj對(duì)象實(shí)現(xiàn)了一個(gè)Symbol.iterator接口,在此,有一點(diǎn)需要說(shuō)明的是,不用擔(dān)心[Symbol.iterator]屬性會(huì)被Object.keys()獲取到導(dǎo)致遍歷結(jié)果出錯(cuò),因?yàn)镾ymbol.iterator這樣的Symbol屬性,需要通過(guò)Object.getOwnPropertySymbols(obj)才能獲取,Object.getOwnPropertySymbols() 方法返回一個(gè)給定對(duì)象自身的所有 Symbol 屬性的數(shù)組。
          有一些場(chǎng)合會(huì)默認(rèn)調(diào)用 Iterator 接口(即Symbol.iterator方法:
          擴(kuò)展運(yùn)算符...:這提供了一種簡(jiǎn)便機(jī)制,可以將任何部署了 Iterator 接口的數(shù)據(jù)結(jié)構(gòu),轉(zhuǎn)為數(shù)組。也就是說(shuō),只要某個(gè)數(shù)據(jù)結(jié)構(gòu)部署了 Iterator 接口,就可以對(duì)它使用擴(kuò)展運(yùn)算符,將其轉(zhuǎn)為數(shù)組(毫不意外的,代碼[...{}]會(huì)報(bào)錯(cuò),而[...'123']會(huì)輸出數(shù)組['1','2','3'])。
          數(shù)組和可迭代對(duì)象的解構(gòu)賦值(解構(gòu)是ES6提供的語(yǔ)法糖,其實(shí)內(nèi)在是針對(duì)可迭代對(duì)象的Iterator接口,通過(guò)遍歷器按順序獲取對(duì)應(yīng)的值進(jìn)行賦值。而普通對(duì)象解構(gòu)賦值的內(nèi)部機(jī)制,是先找到同名屬性,然后再賦給對(duì)應(yīng)的變量。);
          yield*:_yield*后面跟的是一個(gè)可遍歷的結(jié)構(gòu),它會(huì)調(diào)用該結(jié)構(gòu)的遍歷器接口;
          由于數(shù)組的遍歷會(huì)調(diào)用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場(chǎng)合,其實(shí)都調(diào)用;
          字符串是一個(gè)類似數(shù)組的對(duì)象,也原生具有Iterator接口,所以也可被for of迭代。

          迭代器模式

          迭代器模式提供了一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中的各個(gè)元素,而又無(wú)需暴露該對(duì)象的內(nèi)部實(shí)現(xiàn),這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪問(wèn)集合內(nèi)部的數(shù)據(jù)。
          迭代器模式為遍歷不同的集合結(jié)構(gòu)提供了一個(gè)統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作。
          不難發(fā)現(xiàn),Symbol.iterator實(shí)現(xiàn)的就是一種迭代器模式。集合對(duì)象內(nèi)部實(shí)現(xiàn)了Symbol.iterator接口,供外部調(diào)用,而我們無(wú)需過(guò)多的關(guān)注集合對(duì)象內(nèi)部的結(jié)構(gòu),需要處理集合對(duì)象內(nèi)部的數(shù)據(jù)時(shí),我們通過(guò)for of調(diào)用Symbol.iterator接口即可。
          比如針對(duì)前文普通對(duì)象的Symbol.iterator接口實(shí)現(xiàn)一節(jié)的代碼,如果我們對(duì)obj里面的數(shù)據(jù)結(jié)構(gòu)進(jìn)行了如下調(diào)整,那么,我們只需對(duì)應(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 < this.data.length) {          // 迭代結(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í)際使用時(shí),我們可以把上面的Symbol.iterator提出來(lái)進(jìn)行單獨(dú)封裝,這樣就可以對(duì)一類數(shù)據(jù)結(jié)構(gòu)進(jìn)行迭代操作了。
          當(dāng)然,下面的代碼只是最簡(jiǎn)單的示例,你可以在此基礎(chǔ)上探究更多實(shí)用的技巧。
          const obj1 = {  data: ['value1', 'value2']}const obj2 = {  data: [1, 2]}// 遍歷方法consoleEachData = (obj) => {  obj[Symbol.iterator] = () => {    let index = 0;    return {      next: () => {        if (index < obj.data.length) {          return {            value: obj.data[index++],            done: false          };        } else {          return { value: undefined, done: true };        }      }    };  }  for (const value of obj) {    console.log(value);  }}consoleEachData(obj1); // value1 value2consoleEachData(obj2); // 1  2

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

          在寫這篇文章時(shí),有個(gè)問(wèn)題給我?guī)?lái)了困擾:原生object對(duì)象默認(rèn)沒(méi)有部署Iterator接口,即object不是一個(gè)可迭代對(duì)象。
          對(duì)象的擴(kuò)展運(yùn)算符...等同于使用Object.assign()方法,這個(gè)比較好理解。那么,原生object對(duì)象的解構(gòu)賦值又是怎樣一種機(jī)制呢?
          let aClone = { ...a };// 等同于let aClone = Object.assign({}, a);
          有一種說(shuō)法是:ES6提供了Map數(shù)據(jù)結(jié)構(gòu),實(shí)際上原生object對(duì)象被解構(gòu)時(shí),會(huì)被當(dāng)作Map進(jìn)行解構(gòu)。關(guān)于這點(diǎn),大家有什么不同的觀點(diǎn)嗎?歡迎評(píng)論區(qū)一起探討。
          同時(shí),ECMAScript后面又引入了異步迭代器for await...of 語(yǔ)句,該語(yǔ)句創(chuàng)建一個(gè)循環(huán),該循環(huán)遍歷異步可迭代對(duì)象以及同步可迭代對(duì)象,詳情可查看MDN:for-await...of。
          ??愛(ài)心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入Node進(jìn)階交流群!「在這里有好多 Node 開(kāi)發(fā)者,會(huì)討論 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  色播丁香五月天 | 无码免费一区二区 | 久久黄色樱桃视频 | 豆花视频无码 | 国产精品伦理一区 |