for of是ES6新增的循环方法,因为for in在遍历数组时,有很多不足之处,现在通常都使用for of来遍历数组。遍历总是看上去很简单,但深究下来,又会牵扯到键值对,原型链,可枚举属性,迭代器接口,Symbol一系列内容,所以本文将一次弄清楚这里面的关系。
当然,在写普通业务时,记得取key用for in,取value用for of,一般是不会出岔子的(吧)?!
一、可迭代对象
迭代器是很基础的数据结构,一般来说,一个标准的迭代器解构会提供一个next()方法,返回值结构如下:
并不是说定义了一个Object,就能对它进行遍历,从Object到Array之间,封装了一个迭代器接口。
let arr = [1, 2, 3, 4]
let str = "1234"
let set = new Set([1, 2, 3, 4])
console.log(arr)
console.log(new String(str))
console.log(set)
显而易见,Symbol(Symbol.iterator): ? values() 是一个迭代器接口,打印观察之。
let arr = [1, 2, 3, 4]
let iter = arr[Symbol.iterator]()
细究来,无非就是数据结构的基础知识迭代器嘛,这里就不展开了,至于可枚举属性,你只要知道,true 是能被遍历到,false 是不能,其他的就暂时不管了。
二、for in的原理
简单来说,for in的作用就是:遍历自身和继承的可枚举属性。
2.1 注意点1
使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问
let a = [1, 2, 3, 4]
for(let i in a){
console.log(a);
}
打印结果:0 1 2 3
Object.prototype.myFunc01 = function () {
console.log('1');
}
Array.prototype.myFunc02 = function (value) {
console.log('2');
}
let b = [1, 2, 3, 4]
for (let i in b) {
console.log(i);
}
打印结果:0 1 2 3 myFunc02 myFunc01
2.2 注意点2
只遍历对象自身的属性,而不遍历继承于原型链上的属性,应使用hasOwnProperty 方法过滤。
Object.prototype.myFunc01 = function () {
console.log('1');
}
Array.prototype.myFunc02 = function (value) {
console.log('2');
}
let b = [1, 2, 3, 4]
for (let key in b) {
if(b.hasOwnProperty(key)){
console.log(key);
}
}
打印结果:0 1 2 3
对于使用for in可能导出的bug,有两种方式避免
1.在循环数组集合时,不使用for-in,统一使用for(let i=0;i<length;i++) 这种形式;
2.在for in循环中增加一个hasOwnProperty 的判断。
2.3 对比Object.keys()
for in的作用和Object.keys()方法特别像,Object.keys() 方法会返回一个由给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for in 循环遍历该对象时返回的顺序一致。
两者的主要区别是for in 循环还会枚举其原型链上的属性,返回值是这个对象的所有可枚举属性组成的字符串数组。
Object.prototype.say=function(){};
let person ={
age: 18,
sleep: function(){}
};
console.log(Object.keys(person));
小技巧:
-
object对象没有length属性,可以通过Object.keys(person).length,来获取person的长度。 -
index索引为字符串型数字,不能直接进行几何运算 -
遍历顺序有可能不是按照实际数组的内部顺序 -
使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性
三、for of的原理
- for of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合。但是不能遍历对象,因为没有迭代器对象,与forEach()不同的是,它可以正确响应break、continue和return语句。
- for of循环不支持普通对象,但如果想迭代一个对象的属性,可以用for in循环(这也是它的本职工作)或内置的Object.keys()方法。
那么如果自己定义一个对象,将其仿写成一个数组,用for of遍历它会怎么样呢?
let obj = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4
}
for(let i in obj){
console.log(i)
}
for(let i of obj){
console.log(i)
}
显然,for of对迭代器接口有所要求,普通对象是无法使用for of遍历的。
3.1 考点1
如果说,要实现过滤非数字键值呢,怎么做呢?思路很简单,JSON.stringify 利用一下就可。
为什么会提这个问题,是为了下一个考察点做铺垫。
let obj = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4,
myFunc02() {
console.log('2');
}
}
let obj1 = JSON.stringify(obj)
console.log(obj1)
let replacer = function (key, value) {
if (isNaN(+key)) {
return undefined
}
return value
}
let obj2 = JSON.stringify(obj, replacer)
console.log(obj2);
3.2 考点2
如果面试官问你,怎么实现让一个普通obj能够让for of遍历,你该怎么做?这里就不细说了,相信懂的人看一遍代码也能马上明白。
四、for in和for of的区别
小结一下。
五、补充:拓展运算符的遍历原理
…(拓展运算符 | 展开运算符) 也是es6的语法,能用for of遍历的,用…也能进行拓展运算。
5.1 例子1
let arr = [1, 2, 3, 4]
function test(...args) {
console.log(args)
}
test(1, 2, 3, 4)
test(...arr)
for(let i of arr){
console.log(i)
}
5.2 例子2
let replacer = function (key, value) {
if (isNaN(+key)) {
return undefined
}
return value
}
Object.prototype.myFunc01 = function () {
console.log('1');
}
let obj = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4,
[Symbol.iterator]() {
let arr = Object.values(JSON.parse(JSON.stringify(obj, replacer))),
index = 0,
len = arr.length
return {
next: function () {
if (index < len) {
return {
value: arr[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
},
myFunc02() {
console.log('2');
}
}
for (let i of obj) {
console.log(i)
}
console.log(...obj)
|