都2022年了你不会还没搞懂对象数组的遍历吧

493 阅读8分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

简介

对象、数组的遍历在我们日常开发中基本上天天能碰到,但是对象、数组都有哪些遍历方法,各方法之间又有什么区别你们真的清楚了吗?今天笔者就来总结下。

image.png

对象的遍历

遍历对象的方法有Object.keys()Object.values()Object.entries()Object.getOwnPropertyNames()Object.getOwnPropertySymbols()for inReflect.ownKeys(),但是这些方法又都有各自的特点,我们来总结下

// 定义对象
const obj1 = Object.create(
  { msg: "原型属性值" },
  {
    name: {
      value: "randy",
      writable: true,
      configurable: true,
      enumerable: true,
    },
    age: {
      value: 25,
      writable: true,
      configurable: true,
      enumerable: false,
    },
    [Symbol("test")]: {
      value: "symboltest",
      writable: true,
      configurable: true,
      enumerable: true,
    },
  }
);

Object.entries()Object.keys()Object.values()

Object.entries()Object.keys()Object.values()不能获取Symbol属性、不能获取不可枚举属性、不能获取原型链属性。

for (const entry of Object.entries(obj1)) {
  console.log("entries: ", entry); // ['name', 'randy']
}
for (const key of Object.keys(obj1)) {
  console.log("keys: ", key); // name
}
for (const value of Object.values(obj1)) {
  console.log("values: ", value); // randy
}

Object.getOwnPropertyNames()

Object.getOwnPropertyNames()能获取不可枚举属性、不能获取Symbol属性、不能获取原型链属性。

for (const name of Object.getOwnPropertyNames(obj1)) {
  console.log("getOwnPropertyNames: ", name); // name age
}

Object.getOwnPropertySymbols()

Object.getOwnPropertySymbols()只能获取Symbol属性。并且不管该Symbol属性是否是可枚举,都能遍历出来。

for (const symbol of Object.getOwnPropertySymbols(obj1)) {
  console.log("getOwnPropertySymbols: ", symbol); // Symbol(test)
}

for in

for in获取的是键,不能获取Symbol属性、不能获取不可枚举属性、能获取原型链上的属性。

function test() {
  for (const key in obj1) {
    if (key == "msg") {
      // continue; // 跳过当次
      // break; // 跳出循环
      // return; // 跳出循环
    }
    console.log("for in: ", key); // name msg
  }
}

test()

Reflect.ownKeys()

Reflect.ownKeys()不但能获取自身不可枚举属性,还能获取Symbol类型的属性,但不能获取原型链上的属性。 是Object.getOwnPropertyNames()Object.getOwnPropertySymbols()的组合。

for (const key of Reflect.ownKeys(obj1)) {
  console.log("Reflect.ownKeys: ", key); // name age Symbol(test)
}

不能遍历对象的方法

对象不能用for循环进行遍历

// for (let i = 0; i < obj1.length; i++) {
//   console.log(obj1[i]);
// }

对象不能用for of循环进行遍历

// for (const property of obj1) {
//   console.log(property);
// }

对象不能用forEach循环进行遍历

// obj1.forEach((key) => {
//   console.log(key);
// });

对象不能用map循环进行遍历

// obj1.map((key) => {
//   console.log(key);
// });

数组的遍历

遍历数组的方法有forfor infor offorEachmap,但是这些方法又都有各自的特点,我们来总结下

// 定义数组
const arr1 = ["a", "b", "c", "d"];

for

for 循环获取的是下标

function arrFor() {
  for (let i = 0; i < arr1.length; i++) {
    if (i == 2) {
      // continue; // 跳过当次
      // break; // 跳出循环
      // return; // 跳出循环并跳出方法
    }
    console.log("arr for: ", arr1[i]); // a b c d
  }
}
arrFor();

for in

for in 循环获取的是下标,需要特别注意,获取的下标是string类型,并不是number类型

function arrForIn() {
  for (const index in arr1) {
    if (index == 2) {
      // continue; // 跳过当次
      // break; // 跳出循环
      // return; // 跳出循环并跳出方法
    }
    console.log("arr for in: ", index); // 0 1 2 3
  }
}

arrForIn();

for of

for of 循环获取的是值

function arrForOf() {
  for (const value of arr1) {
    if (value == "c") {
      // continue; // 跳过当次
      // break; // 跳出循环
      // return; // 跳出循环并跳出方法
    }
    console.log("arr for of: ", value); // a b c d
  }
}

arrForOf();

keys() values() entries()

跟对象类似,数组也有keys() values() entries()方法,因为这些方法返回的是迭代器,所以只能通过forof来遍历

for (const iterator of arr1.keys()) {
  console.log("keys: ", iterator); // 0 1 2 3
}

for (const iterator of arr1.values()) {
  console.log("values: ", iterator); // a b c d
}

for (const iterator of arr1.entries()) {
  console.log("entries: ", iterator); // [0, 'a'] [1, 'b'] [2, 'c'] [3, 'd']
}

forEach

forEach 不能使用continue、break并且return的效果和前面的continue是一样的

arr1.forEach((item, index) => {
  if (item == "c") {
    // continue; // 不支持会报错
    // break; // 不支持会报错
    // return; // 跳过当次循环 类似前面的continue
  }
  console.log("forEach: ", item); // a b c d
});

map

map 一般我们很少使用map单独做循环,一般是利用map返回新数组的特性对原数组进行一些批量处理。 不能使用continue、break并且return的效果和前面的continue是一样的

arr1.map((item, index) => {
  if (item == "c") {
    // continue; // 不支持会报错
    // break; // 不支持会报错
    // return; // 跳过当次循环 类似前面的continue
  }
  console.log("map: ", item); // a b c d
});

总结

  1. 对象不能使用forfor offorEachmap方法进行遍历。
  2. Object.entries()Object.keys()Object.values()不能获取Symbol属性、不能获取不可枚举属性、不能获取原型链属性。
  3. Object.getOwnPropertyNames()能获取不可枚举属性、不能获取Symbol属性、不能获取原型链属性。
  4. Object.getOwnPropertySymbols()只能获取Symbol属性。并且不管该Symbol属性是否是可枚举,都能遍历出来。
  5. for in获取的是键,不能获取Symbol属性、不能获取不可枚举属性、能获取原型链上的属性。
  6. Reflect.ownKeys()不但能获取自身不可枚举属性,还能获取Symbol类型的属性,但不能获取原型链上的属性。 是Object.getOwnPropertyNames()Object.getOwnPropertySymbols()的组合。
  7. 在遍历数组的时候 forEachmap 只能使用return操作循环(效果和continue一样),不能使用continuebreakfor forin forof能使用continuebreak、return
  8. 数组keys() values() entries()方法,因为这些方法返回的是迭代器,所以只能通过forof来遍历。

扩展

使用 for of 遍历对象

前面我们说到,对象是不能使用for of来进行遍历的,那是怎么又可以呢?for ofES6新增的,这个方法是基于迭代器Iterator来实现遍历的,意思就是只要你有了迭代器就能使用for of进行遍历,在js中,Array/Set/Map/String都默认支持迭代器,但是对象是没有实现该迭代器的,所以我们想要对象也能使用for of来进行遍历的话就需要实现该对象的迭代器了。

实现迭代器很简单,就是实现[Symbol.iterator]方法。

const obj = {
    [Symbol.iterator]:function(){}
}

[Symbol.iterator] 属性名是固定的写法,只要拥有了该属性的对象,就能够用迭代器的方式进行遍历。

迭代器的遍历方法是首先获得一个迭代器的指针,初始时该指针指向第一条数据之前

接着通过调用 next 方法,改变指针的指向,让其指向下一条数据

每一次的 next 都会返回一个对象,该对象有两个属性

  • value 代表想要获取的数据
  • done 布尔值,false表示当前指针指向的数据有值,true表示遍历已经结束

下面我们来看个例子

因为数组是默认实现了迭代器的,所以它肯定是有[Symbol.iterator]属性的,所以我们来看看

const arr = [1, 2, 3];
const it = arr[Symbol.iterator](); // 获取数组中的迭代器
// 执行迭代器的next()方法
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }

// for of其实输出的是next()对象里的value
for (const iterator of arr) {
  console.log(iterator); // 相继输出 1 2 3
}

所以接下来我们用对象作为例子,自己实现[Symbol.iterator]属性然后使用for of来进行遍历。

const user = { name: "randy", age: 24, sex: "male" };

user[Symbol.iterator] = function () {
  // 获取自身所有能遍历的key组成的数组
  const keys = Object.keys(user);
  let i = 0;
  return {
    next() {
      return {
        // 外部每次执行next都能得到数组中的第i个元素
        value: keys[i++],
        // 如果数组的数据已经遍历完则返回true
        done: i > keys.length,
      };
    },
  };
};

for (const iterator of user) {
  console.log(iterator); // 依次输出 name age sex
}

我们知道迭代器输出的是next方法返回对象的value,所以如果我们想遍历对象的时候返回的不是key而是value我们只需要稍微改下就可以了。

const user = { name: "randy", age: 24, sex: "male" };

user[Symbol.iterator] = function () {
  // 获取自身所有能遍历的value组成的数组
  const values = Object.values(user);
  let i = 0;
  return {
    next() {
      return {
        // 外部每次执行next都能得到数组中的第i个元素
        value: values[i++],
        // 如果数组的数据已经遍历完则返回true
        done: i > values.length,
      };
    },
  };
};

for (const iterator of user) {
  console.log(iterator); // 依次输出 randy 24 male
}

看到这是不是懂了for of的原理呢?其实for of就是万能遍历方法,只要你实现了迭代器。

系列文章

都2022年了你不会还没搞懂JS数据类型吧

都2022年了你不会还没搞懂JS原型和继承吧

都2022年了你不会还没搞懂JS赋值拷贝、浅拷贝、深拷贝吧

都2022年了你不会还没搞懂对象数组的遍历吧

都2022年了你不会还没搞懂this吧

都2022年了你不会还没搞懂JS Object API吧

都2022年了你不会还没搞懂js垃圾回收和内存泄露吧

都2022年你不会还没搞懂js执行上下文和事件循环机制吧

都2022年了你不会还没搞懂js中的事件吧

都2020年了你不会还没搞懂js异步编程吧

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!