【前端迭代器】一个令人头疼的东西

459 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

for..of循环语句

要理解javaScript的iterator(迭代器),一般会从for..of这个循环语句入手。

for..of循环语句和for..in循环语句长得很像,区别在于for..of每次获取被循环对象的值,而for..in每次获取被循环对象的索引。

我们以数组这种常见的被循环对象为例。

const arr = ['lisa','julia','bob','anderu'];
for (let i of arr){
    console.log(i);
}
for (let i in arr){
    console.log(arr[i]);
}
//都会输出arr数组中的每个值。

但这时,问题出现了,for..of可以遍历数组,字符串,但是不能遍历我们定义的对象。

const obj = {
    name: '克莱恩',
    age: 23,
    profession: 'god',
    sequence: '占卜师',
};
//for..of 会报错
for (let item of obj) {
    console.log(item);
}
//Uncaught TypeError: obj is not iterable

分析报错提示,说obj这个对象不可迭代。

这里就显现出区别了,使用for..of是有条件的,对象这种数据类型不能用它遍历。

那我偏要勉强😙,非要用for..of遍历,还要获取每个属性值呢?

如果不用for..of,可以用forEach()配合Object.keys()方法来遍历对象属性,但我们现在不是正在学迭代器该怎么用嘛

也可以,js给我们提供了一个迭代器的机制(专业术语,看看就行),通过给对象里加一个方法,实现用for..of遍历。

怎么说?

这个方法自带方法名,不能乱起,不然js引擎识别不了,名字叫Symbol.iterator。我们自定义方法的时候,也得用这个方法名。

Symbol.iterator方法名

背景知识,Symbol是js里的一种数据类型,和Array,Boolean类似。Symbol变量的作用在于,每个Symbol变量都是独一无二的,唯一性就有他的好处🧐……

一般的好处在于,对象不是有方法吗,有时候万一给方法名起重名,有覆盖之前代码的风险,一个唯一的方法名就很重要。

let skill = {
    //因为是使用Symbol定义的,所以这里skip,control,abandon这样的命名都是唯一的
    skip: Symbol(),
    control: Symbol(),
    abandon: Symbol(),
}
let kelane = {
    name: '克莱恩',
    //skill.skip方法名唯一存在。不会有重名的隐患。
    [skill.skip]: function () {
        console.log('小丑跳跃');
    },
    [skill.control]: function (){
        console.log('秘偶大师');
    },
    [skill.abandon]: function () {
        console.log('解除秘偶化');
    },
};
kelane[skill.skip]();   //小丑跳跃
kelane[skill.control]();    //秘偶大师

我们可以自定义Symbol类型的值,js中也内置了一些Symbol类型的值,都有特殊用法。

比如:Symbol.iterator,js内置它,是为了用它来辨识一个对象是否可以用for..of遍历。

当一个对象中有以Symbol.iterator为名的方法时,for..of就可以用。

对象默认是没有的。数组默认有(所以数组可以用for..of循环语句)

image.png

没有我们可以自己定义这个方法。

自定义对象的迭代器方法

自己写这个方法得满足一些规范要求(否则for..of还是不能识别,会报错):

  1. 自定义方法(以后就简称这个方法为iterator)的返回值必须是一个对象(简称为iterator object)。
  2. iterator返回的对象(iterator object)中必须有next方法
  3. next方法必须返回 {done:.., value :...} 格式的对象

我们在对象内部定义完这个iterator后,迭代器机制是这样运作的:

当用for..of来遍历对象时,先调用我们自定义的iterator方法。该方法返回迭代器对象(iterator object),里面有next方法。

for..of每次遍历都会调用这个next方法,并接收返回对象里的done和value属性的值,done属性的值必须为布尔值,当true时,说明遍历结束,停止遍历。for..of语句中for(let i of obj)里的i就是value属性值,所以我们自定义方法时可以对这个value进行一些“个性化”处理。

想用for..of遍历对象属性的代码框架就是这个样子:

const obj = {
    name: '克莱恩',
    age: 23,
    profession: 'god',
    sequence: '占卜家',

    //自定义迭代器(实际操作就是自定义一个方法)
    [Symbol.iterator]: function () {
        let index=0;
        let keys = Object.keys(this);//['name', 'age', 'profession', 'sequence']

        //1.自定义方法(以后就简称这个方法为iterator)的返回值必须是一个对象。
        return {

            //2.iterator返回的对象(iterator object)中必须有next方法
            next: () => {

                //3.next方法必须返回 {done:.., value :...} 格式的对象
                return {
                    done: index===keys.length,
                    value: this[keys[index++]],
                }
            }//next方法
        }//iterator object
    }//自定义方法(iterator)
};//obj

for (let item of obj) {
    console.log(item);
}
//应当依次输出:克莱恩,23,god,占卜家

总结:这个自定义迭代器一般用在哪?

  1. 上文提到,next方法的value值我们可以自定义,所以当自定义遍历数据时,考虑迭代器

  2. 有没有发现,for..of每次遍历都调用next方法获取对象,在考虑块级作用域存在的情况下,这其实是个闭包啊!完全满足闭包的三个特点:

    • 存在函数嵌套 —— iterator方法嵌套next方法
    • 内部函数对外部函数的变量存在引用 —— index和keys定义在iterator方法的作用域下,而next方法对其进行调用。
    • 外界获取到内部函数 —— for..of只执行一次自定义方法,获取返回对象(iterator object)的内部方法next,后面每次遍历都是在调next方法,却实现了对iterator object中变量的调用。
  3. 当我们给一个对象设置了Symbol.iterator 方法后,这个对象变成了可迭代对象,而大多数内建方法都假设它们需要处理的是可迭代对象。所以可迭代对象好处有很多,以后慢慢看~