es6 之 for of

108 阅读5分钟
/**
1.起源
一段标准的for循环
var color = ['red', 'yellow', 'green']
for (let i = 0; i < color.length; i++) {
  console.log(color[i])
}
看着简单,其实这段代码里,我们仅仅需要数组中元素的值,但是却需要提前获取数组长度,声明索引变量,尤其是多个循环嵌套的时候,更需要使用多个变量,代码的复杂度会增加 比如我们使用双重循环进行去重时候
function unique (array) {
  let res = []
  for (let i = 0; i < array.length; i++) {
    for (let j = 0; j < res.length; i++) {
      if(array[i] === res[j])
      break
    }
    if (j === res.length) {
      res.push(array[i])
    }
  }
  return res
}

2.迭代器
所谓迭代器,其实就是一个具有next() 方法的对象,每次调用next()都返回一个结果对象,该对象会有两个属性 ,value表示当前的值,done表示遍历是否结束

function createIterator (item) {
  let i = 0
  return {
    next: function() {
      let done = i >= item.length
      let value = !done ? item[i++] : undefined
      return {
        done: done,
        value: value
      }
    }
  }
}
// iterator 就是一个迭代器对象
let iterator = createIterator([1,2,3])

console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

3.for of
除了迭代器之外,我们还需要一个可以遍历迭代器对象的方法,ES6提供了for of语句,我们直接用for of 遍历一下我们上节生成的遍历器对象试试
let iterator = createIterator([1,2,3])
for (let value of iterator) {
  console.log(value)
}
结果报错 TypeError: iterator is not iterable,表明我们生成的 iterator 对象并不是 iterable(可遍历的)。
那么什么是可遍历的呢?? 其实一种数据节后只要部署了Iterator接口,我们就称这种数据结构时可以遍历的
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就认为是可以遍历的
列子
const obj = {
  value: 1
}
for (value of obj) {
  console.log(value)
}
我们直接for of遍历一个对象,会报错, 然而我们给对象添加Symbol.iterator 属性
const obj = {
  value: 1
}
obj[Symbol.iterator] = function() {
  return createIterator([1,2,3])
}

for (value of obj) {
  console.log(value)
}
由此,我们也可以发现 for of 遍历的其实是对象的 Symbol.iterator 属性。

4. 默认可遍历对象
然而如果我们直接遍历一个数组对象
const colors = ['red', 'green', 'yellow']
for (let color of colors) {
  console.log(color)
}
尽管我们没有手动添加 Symbol.iterator属性但是还是可以遍历成功,这是因为ES6默认部署了Symbol.iterator属性,当然我们也可以手动修改这个属性
let colors = ['red', 'green', 'blue']
colors[Symbol.iterator] = function() {
  return createIterator([1,2,3])
}
除了数组之外,还有一些数据结构默认部署了Symbol.iterator属性
所以 for of 循环使用范围包括 数组 Set Map 类数组对象arguments DOM NodeList
Generator 字符串


5.模拟实现for of
其实模拟实现for of也比较简单,基本就是通过Symbol.iterator 属性获取迭代器对象,然后使用while遍历下
function forOf(obj, cb) {
  let iterator, result
  if (typeof obj[Symbol.iterator] !== "function") {
    throw new TypeError(result + 'is not itertable')
  }
  if (typeof cb !== "function") throw new TypeError("cb must be callable")
  iterable = obj[Symbol.iterator]()
  result = iterable.next()
  while(!result.done) {
    cb(result.value)
    result = iterable.next()
  }
}

6.内建迭代器
为了更好地访问对象内容,我们不仅需要数组中的值还有需要索引,ES6为数组 Map Set结合内建了一下三种迭代器
1.entries()返回一个遍历对象,用来遍历键名 键值 组成的数组,对于数组,键名就是索引值
2.keys() 返回 一个遍历对象,用来遍历所有的键名
3.value() 范湖一个遍历对象 返回所有的键值
以数组为例子
let colors = ['red', 'green', 'yellow']
for (let index of colors.keys()) {
  console.log(index)
}
// 0
// 1
// 2
for (let color of colors.values()) {
  console.log(color)
}

// red
// green
// yellow
for (let item of colors.entires()) {
  console.log(items)
}
// [ 0, "red" ]
// [ 1, "green" ]
// [ 2, "yellow" ]

Map 类型与数组类似,但是对于Set类型需要注意以下
let colors = new Set(['red', 'green', 'yellow'])
for (let index of colors.keys()) {
  console.log(index)
}
// red
// green
// yellow

for (let color of colors.values()) {
  console.log(color)
}
// red
// green
// yellow

for (let item of colors.entries()) {
  console.log(item)
}
// [ "red", "red" ]
// [ "green", "green" ]
// [ "yellow", "yellow" ]

Set 类型的keys()和values() 返回的是相同的迭代器,这也意味着在Set这种数据结构中键名与键值相同

而且每个集合类型都有一个默认的迭代器,在for of 循环中,如果没有显示指定则使用默认的迭代器,数组和Set集合的默认迭代器是values()方法,Map 集合默认的迭代器是entries方法
也就是为什么直接for of 遍历和Set Map数据结构, 会有不同的数据结构返回

const values = new Set([1,2,3])
for (let value in values) {
  console.log(value)
}
// 1
// 2
// 3

const values = new Map([["key1", "value1"], ["key2", "value2"]])
for (let value of values) {
  console.log(value)
}
["key1", "value1"]
["key2", "value2"]

遍历Map 数据结构的时候可以顺便结合解构赋值
const values = new Map([["key1", "value1"], ["key2", "value2"]])
for (let [key , value] of values) {
  console.log(key + ':' + value)
}
key1:value1
key2:value2

7.Babel是如何编译for of的
const colors = new Set(["red", "green", "blue"]);
for (let color of colors) {
    console.log(color);
}
代码编译之后结果
var colors = new Set(["red", "green", "blue"])

var _iteratorNormalCompletion = true
var _didIteratorError = false
var _iteratorError = undefined

try {
  for (
    var _iterator = colors[Symbol.iterator](), _step;
    !(_iteratorNoramlCompletion = (_step = iterator.next()).done);
    _iteratorNormalCompletion = true
  ) {
    var color = step.value
    console.log(color)
  }
} catch (err) {
  _didIteratorError = true
  _iteratorError = err
} finally {
  try {
    if (!_iteratorNormalCompletionm && _iterator.return) {
      _iterator.return()
    }
  } finally {
    if (_didIteratorError) {
      throw _iteratorError
    }
  }
}
至少由编译的结果可以看出,使用 for of 循环的背后,还是会使用 Symbol.iterator 接口。
这段代码 编译的代码稍微复杂的有两段,一个是for循环
for (
  var _iterator = colors[Symbol._iterator](), _step;
  !(_iteratorNormalCompletion = (_step = iterator.next()).done);
  _iteratorNormalCompl etion = true
) {
  var color = _step.value
  console.log(color)
}
跟标准的 for 循环写法有些差别,我们看下 for 语句的语法:
for (initialize; test; increment) statement
initialize test increment 三个表达式之间用分号分割,他们分别负责 初始化操作 循环条件判断 计数器变量的更新
for 语句其实就是相当于
initialize
while (test) {
  statement
  increment
}
代码的逻辑为 新进行初始化,然后每次循环执行之前会执行test表达式,并判断表达式的结果来决定是否执行循环体,如果test计算结果为真值,则执行循环体中的statement,最后,执行increment表达式

而且需要注意的是,其实for 循环中三个表达式中任意一个都可以被忽略,不过分号还要写
比如 for(;;)不过就是一个死循环
比如
var i = 0,
    len = colors.length;
for (; i < len; i++) {
    console.log(colors[i]);
}
又比如
var i = 0,
    len = colors.length;
for (; i < len; ) {
    i++;
}
然后我们再来看 Babel 编译的这个 for 循环表达式:
for(
  var _iterator = colors[Symbol.iterator](),_step;
  !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
  _iteratorNormalCompletion = true
) {
  var color =  _step.value
  console.log(color)
}
用while写法相当于
var _iterator = colors[Symbol.iterator](),
_step;
while(!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) {
  var color = _step.value
  console.log(color)
  _iteratorNoramlCompletion = true
}
是不是就好懂了很多,其实_iteratorNormalCompletion = true 这句没有必要

*/