弄懂for in、for of、可迭代对象
for of是ES6新增的循环方法,因为for in在遍历数组时,有很多不足之处,现在通常都使用for of来遍历数组。遍历总是看上去很简单,但深究下来,又会牵扯到键值对,原型链,可枚举属性,迭代器接口,Symbol一系列内容,所以本文将一次弄清楚这里面的关系。
当然,在写普通业务时,记得取key用for in,取value用for of,一般是不会出岔子的(吧)?!
一、可迭代对象
迭代器是很基础的数据结构,一般来说,一个标准的迭代器解构会提供一个next()方法,返回值结构如下:
-
value:本次迭代拿到的值 -
done:一个标识符
并不是说定义了一个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上自定义一个myFunc方法
Object.prototype.myFunc01 = function () {
console.log('1');
}
// 在原型链上的Array上自定义一个myFunc方法
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上自定义一个myFunc方法
Object.prototype.myFunc01 = function () {
console.log('1');
}
// 在原型链上的Array上自定义一个myFunc方法
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)); //结果 ["age", "sleep"]
// for in 结果:["age", "sleep", "say"]
小技巧:
-
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)
}
//打印:0 1 2 3 length
for(let i of obj){
console.log(i)
}
// 报错 Uncaught TypeError: obj is not iterable
显然,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)
// {"0":1,"1":2,"2":3,"3":4,"length":4}
let replacer = function (key, value) {
// 如果key不是数字,则过滤掉这个值
if (isNaN(+key)) {
return undefined
}
return value
}
let obj2 = JSON.stringify(obj, replacer)
console.log(obj2);
// {"0":1,"1":2,"2":3,"3":4}
3.2 考点2
如果面试官问你,怎么实现让一个普通obj能够让for of遍历,你该怎么做?这里就不细说了,相信懂的人看一遍代码也能马上明白。
// stringify过滤函数
let replacer = function (key, value) {
// 如果key不是数字,则过滤掉这个值
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]() {
// 过滤掉非number索引的item
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 = 0; i < obj.length; i++) {
console.log(obj[i])
}
// 1 2 3 4
for (let i of obj) {
console.log(i)
}
// 1 2 3 4
四、for in和for of的区别
小结一下。
-
for of无法循环遍历对象
-
遍历输出结果不同
for in循环遍历的是数组的键值(索引),而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) // [1, 2, 3, 4]
test(...arr) // [1, 2, 3, 4]
// 即...[1, 2, 3, 4] = 1,2,3,4
for(let i of arr){
console.log(i) // 1 2 3 4
}
5.2 例子2
// stringify过滤函数
let replacer = function (key, value) {
// 如果key不是数字,则过滤掉这个值
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]() {
// 过滤掉非number索引的item
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)
}
// 1 2 3 4
console.log(...obj) // 1 2 3 4
原文始发于微信公众号(豆子前端):[js基础]for in、for of、拓展运算符
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56667.html