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 举例


他们都有一个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]

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

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

你可能感兴趣的:(for...of “拉皮条”)