一起养成写作习惯!这是我参与「掘金日新计划 · 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循环语句)
没有我们可以自己定义这个方法。
自定义对象的迭代器方法
自己写这个方法得满足一些规范要求(否则for..of还是不能识别,会报错):
- 自定义方法(以后就简称这个方法为iterator)的返回值必须是一个对象(简称为iterator object)。
- iterator返回的对象(iterator object)中必须有next方法
- 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,占卜家
总结:这个自定义迭代器一般用在哪?
-
上文提到,next方法的value值我们可以自定义,所以当自定义遍历数据时,考虑迭代器
-
有没有发现,
for..of每次遍历都调用next方法获取对象,在考虑块级作用域存在的情况下,这其实是个闭包啊!完全满足闭包的三个特点:- 存在函数嵌套 —— iterator方法嵌套next方法
- 内部函数对外部函数的变量存在引用 —— index和keys定义在iterator方法的作用域下,而next方法对其进行调用。
- 外界获取到内部函数 ——
for..of只执行一次自定义方法,获取返回对象(iterator object)的内部方法next,后面每次遍历都是在调next方法,却实现了对iterator object中变量的调用。
-
当我们给一个对象设置了
Symbol.iterator方法后,这个对象变成了可迭代对象,而大多数内建方法都假设它们需要处理的是可迭代对象。所以可迭代对象好处有很多,以后慢慢看~