for...of “拉皮條”

本想就針對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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市栗竖,隨后出現(xiàn)的幾起案子暑脆,更是在濱河造成了極大的恐慌,老刑警劉巖狐肢,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件添吗,死亡現(xiàn)場離奇詭異,居然都是意外死亡份名,警方通過查閱死者的電腦和手機碟联,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來僵腺,“玉大人鲤孵,你說我怎么就攤上這事〕饺纾” “怎么了普监?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長琉兜。 經(jīng)常有香客問我凯正,道長,這世上最難降的妖魔是什么豌蟋? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任廊散,我火速辦了婚禮,結(jié)果婚禮上梧疲,老公的妹妹穿的比我還像新娘允睹。我一直安慰自己运准,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布缭受。 她就那樣靜靜地躺著胁澳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贯涎。 梳的紋絲不亂的頭發(fā)上听哭,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音塘雳,去河邊找鬼陆盘。 笑死,一個胖子當著我的面吹牛败明,可吹牛的內(nèi)容都是我干的隘马。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼妻顶,長吁一口氣:“原來是場噩夢啊……” “哼酸员!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起讳嘱,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤幔嗦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沥潭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邀泉,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年钝鸽,在試婚紗的時候發(fā)現(xiàn)自己被綠了汇恤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡拔恰,死狀恐怖因谎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颜懊,我是刑警寧澤财岔,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站河爹,受9級特大地震影響使鹅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昌抠,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲁僚。 院中可真熱鬧炊苫,春花似錦裁厅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至唠梨,卻和暖如春袋励,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背当叭。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工茬故, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚁鳖。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓磺芭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親醉箕。 傳聞我的和親對象是個殘疾皇子钾腺,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351