/**
1.起源
一段标准的for循环
var color = ['red', 'yellow', 'green']
for (let i = 0
console.log(color[i])
}
看着简单,其实这段代码里,我们仅仅需要数组中元素的值,但是却需要提前获取数组长度,声明索引变量,尤其是多个循环嵌套的时候,更需要使用多个变量,代码的复杂度会增加 比如我们使用双重循环进行去重时候
function unique (array) {
let res = []
for (let i = 0
for (let j = 0
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
initialize test increment 三个表达式之间用分号分割,他们分别负责 初始化操作 循环条件判断 计数器变量的更新
for 语句其实就是相当于
initialize
while (test) {
statement
increment
}
代码的逻辑为 新进行初始化,然后每次循环执行之前会执行test表达式,并判断表达式的结果来决定是否执行循环体,如果test计算结果为真值,则执行循环体中的statement,最后,执行increment表达式
而且需要注意的是,其实for 循环中三个表达式中任意一个都可以被忽略,不过分号还要写
比如 for(
比如
var i = 0,
len = colors.length
for (
console.log(colors[i])
}
又比如
var i = 0,
len = colors.length
for (
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 这句没有必要
*/