本想就針對for..of看下焕窝,結(jié)果拔出蘿卜帶出泥,ES6相關(guān)扯出一大堆,感覺有必要記錄澈圈。
1彬檀、for...of只會遍歷value
2、for...of比單純的for循環(huán)小巧瞬女,不需要寫一堆變量和++窍帝,比forEach靈活,因為它可以使用break等關(guān)鍵字
這是我從網(wǎng)上搜集來的對for..of的說法
第一個诽偷,如果是只真對于數(shù)組來說坤学,就算對吧,(因為我們一般也不會在原型方法上做手腳)如果是其他的可迭代對象报慕,比如 Map深浮,循環(huán)出來的是這樣的[key,value],得清楚眠冈,for...of不是專為數(shù)組預(yù)備的飞苇,以下會說明。
第二個蜗顽,嗯布卡,還算靠譜。不過貌似還是針對數(shù)組來說的雇盖,一定要脫離數(shù)組看for...of
開始羽利,看個例子
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
let iterable = [3, 5, 7];
iterable.foo = "hello";
for (let i in iterable) {
console.log(i);
// 0, 1, 2, "foo", "arrCustom", "objCustom"
}
console.log(Object.keys(iterable))
//[ '0', '1', '2', 'foo' ]
for (let i of iterable) {
console.log(i);
// 3, 5, 7
}
這個例子除了展示for...of與for...in的區(qū)別,也很好的說明刊懈,千萬不要項目中去給內(nèi)置構(gòu)造函數(shù)的原型對象添加方法和屬性,產(chǎn)生的副作用非常嚴重娃闲,不要給你的隊友制造麻煩虚汛。
如果你非要添加,參考以下幾點
1.和你的隊友協(xié)商好皇帮。
2.把你添加的屬性方法設(shè)置成不可枚舉卷哩。
3.你能保證你定義的屬性名日后不會被ECMA定義。
4.你很清楚在指定工程中這樣做會產(chǎn)生的可預(yù)見的副作用(很難)属拾。
for...of可迭代對象(包括 Array将谊,Map,Set渐白,String尊浓,TypedArray[描述了一個底層的二進制數(shù)據(jù)緩沖區(qū)],arguments 對象等等)
for...of可以迭代偽數(shù)組纯衍,為什么呢栋齿?用nodeList 和 arguments 舉例
<div></div>
<div></div>
<script>
console.log(document.getElementsByTagName('div'))
!function(){
console.log(arguments)
}(1, 2, 3)
</script>
他們都有一個Stmbol.iterator的屬性,但是ES6之前這些偽數(shù)組可是沒有這個玩意的,添加Stmbol.iterator屬性就是為了讓偽數(shù)組支持for...of遍歷瓦堵,不只是偽數(shù)組基协,Array,Map菇用,Set澜驮,String,TypedArray惋鸥,這些同樣具備Stmbol.iterator屬性杂穷。
對象具備Stmbol.iterator屬性被稱為遵守可迭代協(xié)議,for...of語句只會遍歷遵守可迭代協(xié)議的對象揩慕。
Stmbol.iterator這個值有點奇葩
查看MDN解釋
Symbol.iterator 屬性的屬性特性:
writable false //不可修改值
enumerable false //不可枚舉
configurable false //不可以使用delete刪除
先測試內(nèi)置對象中自帶Stmbol.iterator的對象是否具備以上的特性:
var pro = Array.prototype
Object.getOwnPropertyDescriptor(pro, Symbol.iterator)
{writable: true, enumerable: false, configurable: true, value: ?}
只有一個不可枚舉特性與上相符亭畜,其他都不相符。
自定義添加的Stmbol.iterator問題更大點
var a = {[Symbol.iterator]:1}
console.log(Object.getOwnPropertyDescriptor(a, Symbol.iterator))
{value: 1, writable: true, enumerable: true, configurable: true}
很明顯迎卤,自定義添加的Symbol.iterator三個修飾符都是true
但是經(jīng)過實測拴鸵,結(jié)論如下
1、自定義添加的Stmbol.iterator屬性值可以修改
2蜗搔、自定義添加的Stmbol.iterator屬性可以刪除
3劲藐、重點:自定義添加的Stmbol.iterator屬性雖然enumerable描述符為true,代表可以枚舉樟凄,但是聘芜,我使用Object.keys和for...in都無法遍歷出來,即使使用Object.getOwnPropertyNames 也沒個卵用【Chrome 和 Firefox測試效果一致】
所以缝龄,如果你想添加自定義Stmbol.iterator汰现,最好手動添加三個描述符的值為false。
無法使用for...of直接遍歷對象
var myIterator = {}
for(let i of myIterator){ console.log(i)}
//Uncaught TypeError: myIterator is not iterable
添加Symbol.iterator后叔壤,使用for...of直接遍歷對象瞎饲,依然報錯
var myIterator = {
[Symbol.iterator]: function() { return 1 }
}
for(let i of myIterator){ console.log(i)}
//Result of the Symbol.iterator method is not an object
根據(jù)上圖可以發(fā)現(xiàn),Stmbol.iterator這個屬性的值為一個函數(shù)炼绘,這個函數(shù)同樣要遵守一個協(xié)議嗅战,叫做迭代器協(xié)議。否則就會出現(xiàn)報錯俺亮,迭代器的兩種寫法:
1驮捍、可以是自定義函數(shù),他的寫法如下:
(下面會具體實現(xiàn)脚曾,先過一遍)
var myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() { return this }
}
2东且、也可以是生成器函數(shù),他的寫法如下:
(下面會具體實現(xiàn)斟珊,先過一遍)
var myIterator = {
[Symbol.iterator]: function*() {
yield 1;
yield 2;
yield 3;
}
}
**其他問題
問:生成器對象到底是一個迭代器苇倡,還是一個可迭代對象富纸?
答:生成器對象既是迭代器,也是可迭代對象
var aGeneratorObject = function*(){
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
Ⅰ. "function"旨椒,因為它有next方法晓褪,所以它是一個迭代器
typeof aGeneratorObject[Symbol.iterator];
Ⅱ. "function",因為它有@@iterator方法综慎,所以它是可迭代的
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
Ⅲ. true涣仿,因為它的@@iterator方法返回自己(一個迭代器),所以它是一個格式良好的迭代器
Ⅰ. 和 Ⅱ.是(可用的)可迭代對象的充要條件示惊;
Ⅲ. 是判斷一個(可用的)可迭代對象的迭代器格式是良好的好港,還是不良好的方法;
可以通過給自定義對象添加迭代器米罚,使它能夠使用for...of遍歷钧汹。
需求:
var a = {a:1, b:2}
使
for(let i of a){
console.log(i)
}
輸出
//['a', 1]
//['b', 2]
方法一:自定義函數(shù)方式添加
const setIterator = function (obj = {}){
// 這樣添加保證writable,enumerable录择,configurable都為false
let pro = Object.defineProperty({}, Symbol.iterator, {
value: function(){
let index = 0;
let propKeys = Object.keys(this);
return {
next: function (){
if (index < propKeys.length) {
let key = propKeys[index];
index++;
return {
value: [key, this[key]],
done: false
}
} else {
return { done: true }
}
}.bind(this)
}
}
});
console.log(pro[Symbol.iterator]() === pro) //false
//因為值為false拔莱,所以這不是一個良好的迭代器格式
//但是不影響它使用
return Object.assign(Object.create(pro), obj)
}
var myIterable = setIterator({a:1, b:2});
for(let i of myIterable){
console.log(i)
}
// ['a', 1]
// ['b', 2]
將方法一改成良好的迭代器寫法
const setIterator = function (obj = {}){
let index = 0;
let pro = Object.defineProperties({}, {
next: {
value(){
let propKeys = Object.keys(this);
if (index < propKeys.length) {
let key = propKeys[index];
index ++;
return {
value: [key, this[key]],
done: false
}
} else {
index = 0;
//使用完記得把變量歸0,以便下次遍歷
return { done: true }
}
}
},
[Symbol.iterator]: {
value(){ return this }
}
});
console.log(Object.getOwnPropertyDescriptors(pro))
console.log(pro[Symbol.iterator]() === pro) // true
//值為true隘竭,所以這是一個良好的迭代器格式
return Object.assign(Object.create(pro), obj)
}
var myIterable = setIterator({a:1, b:2});
for(let i of myIterable){
console.log(i)
}
// ['a', 1]
// ['b', 2]
**備注:改成良好的迭代器格式?jīng)]有什么實際意義塘秦,只為展示。
方法二:函數(shù)生成器方式添加
const setIterator = function (obj = {}){
let pro = Object.defineProperty({}, Symbol.iterator, {
value: function * (){
let index = 0;
let propKeys = Object.keys(this);
while (index < propKeys.length){
let key = propKeys[index];
index ++;
yield [key, this[key]]
}
}
});
console.log(pro[Symbol.iterator]() === pro) // false
return Object.assign(Object.create(pro), obj)
}
var myIterable = setIterator({a:1, b:2, c:2});
for(let i of myIterable){
console.log(i)
}
// ['a', 1]
// ['b', 2]
**關(guān)于函數(shù)生成器
這個例子很能說明問題动看,具體原文 http://www.reibang.com/p/36c74e4ca9eb
這個例子很能說明問題尊剔,具體參見 http://www.reibang.com/p/36c74e4ca9eb
function* test(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
console.log(`x=${x}, y=${y}, z=${z}`)
return (x + y + z)
}
var a = test(5);
console.log(a)
console.log(a.next())
console.log(a.next())
console.log(a.next())
//Object [Generator] {}
//{ value: 6, done: false }
//{ value: NaN, done: false }
//x=5, y=NaN, z=undefined
//{ value: NaN, done: true }
var b = test(5);
console.log(b)
console.log(b.next())
console.log(b.next(12))
console.log(b.next(13))
//Object [Generator] {}
//{ value: 6, done: false }
//{ value: 8, done: false }
//x=5, y=24, z=13
//{ value: 42, done: true }
能夠接受可迭代對象作為參數(shù)的API
new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])
Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)
能夠處理 可迭代對象 的語句和表達式有:
for...of 循環(huán)、展開語法菱皆、yield*须误,和結(jié)構(gòu)賦值。
yield* 參照https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*
for(let value of ["a", "b", "c"]){
console.log(value);}// "a"http:// "b"http:// "c"
[..."abc"]; // ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];}
gen().next(); // { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
a // "a"
...展開語法 仇轻, new Set 霹期, new Map
展開語法:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Spread_syntax
var a1 = [1, 2, 3, 4]
var o1 = {a: 1, b: 2}
console.log([...a1]) // [ 1, 2, 3, 4 ]
//在Areay內(nèi)擴展Array,正常
console.log({...a1}) // { '0': 1, '1': 2, '2': 3, '3': 4 }
//在Object內(nèi)擴展Array拯田,正常
console.log({...o1}) // { a: 1, b: 2 }
//在Object內(nèi)擴展Object,正常
console.log([...o1]) // o1 is not iterable
//在Array內(nèi)擴展Object甩十,報錯
如果要在Array內(nèi)使用擴展運算符船庇,被擴展的對象必須是可迭代對象。
初始化自定義對象{}不具備迭代器侣监,所以不能在Array內(nèi)...展開鸭轮,
除非是手動定義迭代器,
//這個是上面我定義的函數(shù)橄霉,再此就直接使用了窃爷。
var myIterable = setIterator({a:1, b:2})
[...myIterable ] = [['a', 1], ['b', 2]]
備注:即使你為一個object包裝成一個可迭代對象(如上),
在 {...myIterable} 時,得到的也是 {a:1, b:2},自定義的迭代器
不會影響object在object內(nèi)的...默認展開按厘。
可以把偽數(shù)組医吊,轉(zhuǎn)化成數(shù)組,如下
var div = document.getElementsByTagName('div')
[...div] // [div, div, div.....]
!function (){
console.log([...arguments]) //[1, 2, 3]
}(1,2,3)
作為函數(shù)形參和實參時的區(qū)別逮京,如下
function test(a, b, c){
console.log(a) //1
console.log(b) //2
console.log(c) //3
}
!function (...arg){
console.log(arg) //[1, 2, 3]
test(...arg)
}(1,2,3)
new Set:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set
Set翻譯為集合或集卿堂。
對象允許你存儲任何類型的唯一值,
無論是原始值或者是對象引用懒棉。
目前我知道的作用就是去重草描。
測試前須知:
let n1 = NaN, n2 = n1
console.log(n1 == n2) // false
console.log(-0 === 0) // true
測試:
let a = {},
b = a,
c = {},
d = function(){},
e = d,
f = NaN,
g = f,
h = -0,
i = 0;
let mySet = new Set([a, b, c, d, e, f, g, h, i])
console.log(mySet) // Set { {}, {}, [Function: d], NaN, 0 }
console.log([...mySet]) // [ {}, {}, [Function: d], NaN, 0 ]
結(jié)論:他能去重NaN,但不能區(qū)分0和-0
new Map:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map
Map翻譯為映射策严。
測試前須知:
數(shù)組與對象也都可以看成映射穗慕,只不過對象的key只能是字符串和Symbol
數(shù)組的key只能是正整數(shù)的字符串
【數(shù)組的下標也是字符串,使用Array[0]妻导,是先把0轉(zhuǎn)化成字符串】
但是Map就沒這個限制了逛绵,他的key可以是任何數(shù)據(jù)類型
測試:
let arr1 = [a1 = new Function, a2 = {}, a3 = [], a4 = NaN, a5 = new Map]
let arr2 = [b1 = 1, b2 = [], b3 = new Function, b4 = 3, b5 = 'abc']
let myMap = new Map;
arr1.forEach((item, i) => {
myMap.set(item, arr2[i])
})
console.log(myMap.get(a1) === b1) //true
console.log(myMap.get(a2) === b2) //true
console.log(myMap.get(a3) === b3) //true
console.log(myMap.get(a4) === b4) //true
console.log(myMap.get(a5) === b5) //true