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



Iterator(遍歷器)

如何實(shí)現(xiàn)Symbol.iterator方法,使普通對象可被?for of?迭代
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}
//?普通對象
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
};
console.log([...obj]);?//?["value1",?"value2"]
console.log([...{}]);?//?console.log?is?not?iterable?(cannot?read?property?Symbol(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迭代。
迭代器模式
用的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
}
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ǔ)充
let?aClone?=?{?...a?};
//?等同于
let?aClone?=?Object.assign({},?a);

評論
圖片
表情
