1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 8559字,需瀏覽 18分鐘

         ·

        2020-12-18 00:16


        or…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語句在可迭代對(duì)象上創(chuàng)建一個(gè)迭代循環(huán),調(diào)用自定義迭代鉤子,并為每個(gè)不同屬性的值執(zhí)行語句。

        for...of的語法:

        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ì)象

        通過前面的基本用法,我們知道,for...of可以迭代數(shù)組、Map等數(shù)據(jù)結(jié)構(gòu),順著這個(gè)思路,我們可以結(jié)合對(duì)象的Object.values()、Object.keys()、Object.entries()方法以及解構(gòu)賦值的知識(shí)來用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ì)象無效】 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ù)原型上的不可枚舉屬性(或者說for...of語句遍歷可迭代對(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ì)象沒有這個(gè)方法。
        簡(jiǎn)單來說,for of 語句創(chuàng)建一個(gè)循環(huán)來迭代可迭代的對(duì)象,可迭代的對(duì)象內(nèi)部實(shí)現(xiàn)了Symbol.iterator方法,而普通對(duì)象沒有實(shí)現(xiàn)這一方法,所以普通對(duì)象是不可迭代的。


        Iterator(遍歷器)

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


        簡(jiǎn)單來說,ES6 為了統(tǒng)一集合類型數(shù)據(jù)結(jié)構(gòu)的處理,增加了 iterator 接口,供 for...of 使用,簡(jiǎn)化了不同結(jié)構(gòu)數(shù)據(jù)的處理。
        而 iterator 的遍歷過程,則是類似 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屬性,原因見下文    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)需要說明的是,不用擔(dān)心[Symbol.iterator]屬性會(huì)被Object.keys()獲取到導(dǎo)致遍歷結(jié)果出錯(cuò),因?yàn)镾ymbol.iterator這樣的Symbol屬性,需要通過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ù)組。也就是說,只要某個(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提供的語法糖,其實(shí)內(nèi)在是針對(duì)可迭代對(duì)象的Iterator接口,通過遍歷器按順序獲取對(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迭代。

        迭代器模式

        迭代器模式提供了一種方法順序訪問一個(gè)聚合對(duì)象中的各個(gè)元素,而又無需暴露該對(duì)象的內(nèi)部實(shí)現(xiàn),這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪問集合內(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)用,而我們無需過多的關(guān)注集合對(duì)象內(nèi)部的結(jié)構(gòu),需要處理集合對(duì)象內(nèi)部的數(shù)據(jù)時(shí),我們通過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提出來進(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è)問題給我?guī)砹死_:原生object對(duì)象默認(rèn)沒有部署Iterator接口,即object不是一個(gè)可迭代對(duì)象。
        對(duì)象的擴(kuò)展運(yùn)算符...等同于使用Object.assign()方法,這個(gè)比較好理解。那么,原生object對(duì)象的解構(gòu)賦值又是怎樣一種機(jī)制呢?
        let aClone = { ...a };// 等同于let aClone = Object.assign({}, a);
        有一種說法是: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 語句,該語句創(chuàng)建一個(gè)循環(huán),該循環(huán)遍歷異步可迭代對(duì)象以及同步可迭代對(duì)象,詳情可查看MDN:for-await...of。

        本文完~

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            葡萄AV| 艳妇臀荡乳欲伦交换日本 | 青青草成人片 | 日韩一级片免费看 | 久久夜色精品国产 | 在线观看视频中文字幕 | 四虎永久免费在线 | 欧美男女做爱视频 | 日韩乱伦AV | 黄色一级直播 |