for..in 和 for..of 的区别,什么是迭代器?

for..infor..of 的区别,什么是迭代器?

for..infor..of 的区别

得出结论的过程

我们常常使用 for..in 来遍历对象,使用 for..of 来遍历数组,如下:

let obj = {
    name: 'weilangpu',
    age: 19,
    hight: 183
}
for(const i in obj) {
    console.log(obj[i]);
}
// weilangpu
// 19
// 183
let arr = [1, 2, 3];
for(const i in arr) {
    console.log(arr[i]);
}
// 1
// 2
// 3
for(const i of arr) {
    console.log(i);
}
// 1
// 2
// 3

我们从上面这个例子可以看到使用 for..in 遍历 对象数组 时,实际上遍历的是对象中可枚举的 属性名 或 数组的下标 ,仍然需要手动获取 值。

与之相反,使用 for..of 遍历 数组 时,实际上就是在遍历数组的 值,而非 下标

那么可不可以用 for..of 遍历对象呢?这样是不是就可以免于在循环体中手动获取 值 ?

答案是否定的。不可以!!! 编译器会报错!!! 如下:

for(const i of obj) {
    console.log(i);
}
// TypeError: obj is not iterable

在报错信息中我们看到一个单词 iterable(即:可迭代对象,稍后会做介绍),编译器说 obj 不是一个 iterable 。那么反过来想,for..of 能够遍历的数组就是一个 iterable 了。

铺垫了这么久终于引出来了 iterable 这个词,那么现在可以说两者的本质区别了。

两者本质区别

for..infor..of 的 本质区别 其实是:

  • for..in 用于枚举对象中的可枚举属性名。

  • for..of 用于遍历 可迭代对象 (即前文的:iterable)的元素。

iterable(可迭代对象)

在介绍这个名词前先理解一下 迭代 这个名词:

  • 迭代:按照顺序反复多次执行一段程序,通常会有明确的终止条件。

简单的说就是 循环 的意思。

那么 iterable 从字面意思理解就是 可以被循环执行的对象

有人要问了,那为什么上个例子中的 obj 不是 iterable ,它不是也可以被循环执行吗?

  • 注意:不应该通过一个对象是否能被遍历而确定它是否是 iterable,即使它能够被 forfor..in 遍历,那在程序内部中的其他方法也未必就会把它当作一个 iterable

那么该怎么区分 iterable非iterbale 两者呢?

  • 区别:是否实现了 iterable 接口

iterable接口

实现 iterable接口 的对象,就 一定 会暴露了一个属性作为 默认的 iterator(即:迭代器,稍后会做介绍),且这个属性必须使用特殊的 Symbol.iterator 作为 键。

这个属性的值必须是一个 迭代器工厂函数 ,调用这个工厂函数必须返回一个新的 迭代器

可以输出这个工厂函数,看看检查对象是否实现了 iterable接口 如下:

console.log(obj[Symbol.iterator]); // undefine
console.log(arr[Symbol.iterator]); // ƒ values() { [native code] }

现在可以看到前面的 obj 并没有实现 iterable接口,与之相对的 arr 实现了 iterable接口,在结合 for..of 的特性,所以 for..of 才无法遍历 obj 而可以遍历 arr

接下来我们介绍一下调用工厂函数后返回的 迭代器 是什么。

iterator(迭代器)

我们先来输出一下上文的 迭代器工厂函数 调用后返回的 迭代器

console.log(arr[Symbol.iterator]()); // Array Iterator {}

我们可以看到他本身是一个对象。这个对象用于迭代于其关联的可迭代对象,在这个例子中就是 arr

怎么调用呢?

迭代器使用 next() 方法在 可迭代对象 中遍历数据。每次调用 next() 都会返回一个 iteratorResult对象 ,这个对象中包含两个属性:

  1. done 属性:是一个 布尔值,表示本次 迭代 是否到达了 可迭代对象 的 底端。

  2. value 属性:包含本次 迭代 在 可迭代对象 中获取的 值(此时 done 应为 false)或者 undefined (此时 done 应为 true 表示已经迭代完成)。

done 的状态为 true 时表示已经迭代完成,此时 value 的值应该为 undefined,此后再去调用 next() 函数会一直返回同样的值。如下:

let iter = arr[Symbol.iterator]();
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: undefined, done: true}
console.log(iter.next()); // {value: undefined, done: true}

不同 迭代器的实例 之间没有联系,不会互相影响,都能够独立地遍历可迭代对象。如下:

let iter1 = arr[Symbol.iterator]();
let iter2 = arr[Symbol.iterator]();
console.log(iter1.next()); // {value: 1, done: false}
console.log(iter1.next()); // {value: 2, done: false}
console.log(iter2.next()); // {value: 1, done: false}
console.log(iter1.next()); // {value: 3, done: false}

关于迭代器的补充

这里可能会稍微有点绕,结合上文多读几遍就好。

任何一个内置 可迭代接口 返回的 迭代器 本身也是一个 可迭代对象

既然 迭代器 本身也是一个 可迭代对象,那就意味着这个 迭代器 也实现了 可迭代接口 那么根据前文所说,我们可以在通过 迭代器 中的 可迭代接口,在创建一个 迭代器 ,这里 Symbol.iterator 属性引用的工厂函数会返回相同的迭代器。例子如下:

let iter1 = arr[Symbol.iterator]();

// 这个输出可以看出 iter1 这个 迭代器 也实现了可迭代接口。
console.log(iter1[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }

// 调用 iter1 中可迭代接口的工厂函数并赋值给 iter2
let iter2 = iter1[Symbol.iterator]();
// 比较两者
console.log(iter1 === iter2); // true

既然每个 迭代器 也实现了 可迭代接口 ,其本身也是一个 可迭代对象,那么就意味着 它可以用在任何需要 可迭代对象 的地方。例子如下:

let iter = arr[Symbol.iterator]();

// 利用 for..of 的机制内部使用 数组本身 的可迭代接口
for(const i of arr) {
    console.log(i);
}
// 1
// 2
// 3

// 利用 for..of 的机制内部使用 迭代器本身 的可迭代接口
for(const i of iter) {
    console.log(i);
}
// 1
// 2
// 3

效果是一样的。

这就是为什么我们可以利用 for..of 遍历 Map 实例 或 set 实例 中 entries()keys()values() 三个方法所返回的 迭代器,因为这些迭代器本身也是可迭代对象,且调用其 可迭代接口 的工厂函数后返回的 迭代器 与本身相等。例子如下:

let mp = new Map();

// 获取 mp 的 迭代器 赋值给iter1
let iter1 = mp.entries();

// 调用 iter1 中的 可迭代接口的工厂函数 返回一个迭代器赋值给iter2
let iter2 = iter1[Symbol.iterator]();

console.log(iter1 === iter2); // true

自定义迭代器

我们知道了 迭代器 的原理后,就可以自定义迭代器,把第一个例子中的 非可迭代对象 obj

变为可迭代对象。如下:

let obj = {
    name: 'weilangpu',
    age: 19,
    hight: 183,
    // 可迭代接口 迭代器的工厂函数
    [Symbol.iterator]() {
        let idx = 0;
        let o = this;
        let ks = Object.keys(o);
        // 返回一个 迭代器对象
        return {
            // 迭代器对象中包含 next() 方法
            next() {
                return {
                    value: o[ks[idx ++]],
                    done: (idx > ks.length)
                };
            }
        };
    }
}
let iter = obj[Symbol.iterator]();
console.log(iter.next()); // {done: false, value: 'weilangpu'}
console.log(iter.next()); // {done: false, value: 19}
console.log(iter.next()); // {done: false, value: 183}
console.log(iter.next()); // {done: true, value: undefined}
console.log(iter.next()); // {done: true, value: undefined}

但是需要注意,这个例子返回的 自定义的迭代器 本身并 不是 一个 可迭代对象,并没有实现可迭代接口,如下:

console.log(iter[Symbol.iterator]); // undefined

当然也可以通过一些办法实现这个效果,这里就不做叙述啦,有兴趣可以翻阅 资料 或 红宝书的第七章 看看。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值