for...of “拉皮条”

本想就针对for..of看下,结果拔出萝卜带出泥,ES6相关扯出一大堆,感觉有必要记录。

1、for...of只会遍历value
2、for...of比单纯的for循环小巧,不需要写一堆变量和++,比forEach灵活,因为它可以使用break等关键字

这是我从网上搜集来的对for..of的说法

第一个,如果是只真对于数组来说,就算对吧,(因为我们一般也不会在原型方法上做手脚)如果是其他的可迭代对象,比如 Map,循环出来的是这样的[key,value],得清楚,for...of不是专为数组预备的,以下会说明。

第二个,嗯,还算靠谱。不过貌似还是针对数组来说的,一定要脱离数组看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的区别,也很好的说明,千万不要项目中去给内置构造函数的原型对象添加方法和属性,产生的副作用非常严重,不要给你的队友制造麻烦。

如果你非要添加,参考以下几点
1.和你的队友协商好。
2.把你添加的属性方法设置成不可枚举。
3.你能保证你定义的属性名日后不会被ECMA定义。
4.你很清楚在指定工程中这样做会产生的可预见的副作用(很难)。

for...of可迭代对象(包括 Array,Map,Set,String,TypedArray[描述了一个底层的二进制数据缓冲区],arguments 对象等等)

for...of可以迭代伪数组,为什么呢?用nodeList 和 arguments 举例

<div></div>
<div></div>
<script>
  console.log(document.getElementsByTagName('div'))
  !function(){
    console.log(arguments)
  }(1, 2, 3)
</script>

他们都有一个Stmbol.iterator的属性,但是ES6之前这些伪数组可是没有这个玩意的,添加Stmbol.iterator属性就是为了让伪数组支持for...of遍历,不只是伪数组,Array,Map,Set,String,TypedArray,这些同样具备Stmbol.iterator属性。

对象具备Stmbol.iterator属性被称为遵守可迭代协议,for...of语句只会遍历遵守可迭代协议的对象。

Stmbol.iterator这个值有点奇葩

查看MDN解释

Symbol.iterator 属性的属性特性:
writable false //不可修改值
enumerable false //不可枚举
configurable false //不可以使用delete删除

先测试内置对象中自带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
但是经过实测,结论如下

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

根据上图可以发现,Stmbol.iterator这个属性的值为一个函数,这个函数同样要遵守一个协议,叫做迭代器协议。否则就会出现报错,迭代器的两种写法:

1、可以是自定义函数,他的写法如下:
(下面会具体实现,先过一遍)

var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
}

2、也可以是生成器函数,他的写法如下:
(下面会具体实现,先过一遍)

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]

方法一:自定义函数方式添加

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]

**备注:改成良好的迭代器格式没有什么实际意义,只为展示。

方法二:函数生成器方式添加

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]

**关于函数生成器
这个例子很能说明问题,具体原文 //www.greatytc.com/p/36c74e4ca9eb

这个例子很能说明问题,具体参见 //www.greatytc.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 }

能够接受可迭代对象作为参数的API

new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])
Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)

能够处理 可迭代对象 的语句和表达式有:
for...of 循环、展开语法、yield*,和结构赋值。

yield* 参照https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*

for(let value of ["a", "b", "c"]){
    console.log(value);}// "a"// "b"// "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内扩展Array,正常
console.log({...a1}) // { '0': 1, '1': 2, '2': 3, '3': 4 }
//在Object内扩展Array,正常

console.log({...o1}) // { a: 1, b: 2 }
//在Object内扩展Object,正常
console.log([...o1]) // o1 is not iterable
//在Array内扩展Object,报错

如果要在Array内使用扩展运算符,被扩展的对象必须是可迭代对象。
初始化自定义对象{}不具备迭代器,所以不能在Array内...展开,
除非是手动定义迭代器,
//这个是上面我定义的函数,再此就直接使用了。
var myIterable = setIterator({a:1, b:2})
[...myIterable ] = [['a', 1], ['b', 2]]

备注:即使你为一个object包装成一个可迭代对象(如上),
在 {...myIterable} 时,得到的也是 {a:1, b:2},自定义的迭代器
不会影响object在object内的...默认展开。

可以把伪数组,转化成数组,如下
var div = document.getElementsByTagName('div')
[...div]  // [div, div, div.....]

!function (){
  console.log([...arguments]) //[1, 2, 3]
}(1,2,3) 

作为函数形参和实参时的区别,如下
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 ]
结论:他能去重NaN,但不能区分0和-0

new Map:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map

Map翻译为映射。
测试前须知:
数组与对象也都可以看成映射,只不过对象的key只能是字符串和Symbol
数组的key只能是正整数的字符串
【数组的下标也是字符串,使用Array[0],是先把0转化成字符串】

但是Map就没这个限制了,他的key可以是任何数据类型
测试:
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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354