for in、for of、forEach、Object.keys(obj)、Object.getOwnPropertyNames(obj)的区别、优缺点和使用场景

  • Post author:
  • Post category:其他

先做一个测试题:

const arr = [1, 2, 3]
arr.name = 'name'
// 不要这么做,只是为了测试
Array.prototype.test = function () {}
Object.defineProperty(arr, 'newPro1', { value: '1' })
Object.defineProperty(arr, 'newPro2', { value: '1', enumerable: true })

for (const i in arr) {
  console.log(typeof i, i)
  // 依次打印出 ...
}
for (const v of arr) {
  console.log(v) // 依次打印出 ...
}
arr.forEach((v, i, array) => {
  console.log(typeof i, i, v)
  // 依次打印出 ...
})
console.log(Object.keys(arr)) // 结果
console.log(Object.getOwnPropertyNames(arr))// 结果

使用不同的方法打印的结果是什么
答案:

for (const i in arr) {
  console.log(typeof i, i)
  // 依次打印出 string '0'、string '1'、string '2'、string 'name'、string 'newPro2'、string 'test'
}
for (const v of arr) {
  console.log(v) // 1、2、3
}
arr.forEach((v, i, array) => {
  console.log(typeof i, i, v)
  //  依次打印出number 0 1 、number 1 2、number 2 3
})
console.log(Object.keys(arr)) // ['0', '1', '2', 'name', 'newPro2']
console.log(Object.getOwnPropertyNames(arr)) // ['0', '1', '2', 'length', 'name', 'newPro1', 'newPro2']

由此可以得到for in 、for of 、forEach的区别

for in

循环遍历对象(数组也属于对象)的除Symbol外的可枚举属性,包括从其父类原型上的继承到的可枚举的属性,(js中基本包装类型的原型属性是不可枚举的),也可以遍历字符串,返回每个字符对应的索引

(补充)可枚举属性:

对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。
在使用Object.defineProperty()为对象添加属性是会设置enumerable值
for in 特点:

  1. for in 在遍历数组arr [1,2,3]时会把其索引作为属性,值得注意的是for in会把遍历出来的key值都转换为string形式,所以即使是索引也不是number形式
  2. 直接在arr上添加赋值的属性为可枚举属性
  3. for in中可以使用break、continue、return 退出循环(return语句只能在function中,如果要使用return for in 必选得在一个function中)
  4. 通过Object.defineProperty添加的newPro1,newPro2只有newPro2被枚举到,是因为定义newPro1时未设置enumerable: true ,其默认值为false表示不可枚举
  5. 基本包装类型的原型属性是指js对象原本的原型属性如:Object.toString、Object.hasOwnProperty、Array.isArray 这些属性是不可以枚举的
  6. 通过Array.prototype.test = function () {}为Array添加的原型属性不属于3中提到的基本包装类型的原型属性,arr 的父类为Array,Array上新增的原型属性被arr继承,for in 可以枚举到。由于Array比较特殊,下面这个列子可能会更明白
function Person (name) {
  this.name = name
}
Person.prototype.pro1 = 'pro1'
const p = new Person('test')
for (const i in p) {
  console.log(i) // 依次输出"name" "pro1"
}

缺点:

  1. 遍历出来的key值都将被转换为字符串形式,遍历数组时存在转换成本,某些场景下可能会遇到问题,不建议与数组一起使用
  2. 会枚举出继承的prototype属性。(除js中基本包装类型的原型属性)

for of

for of用于循环遍历具有iterable接口的数据的值,不能用于普通对象
原生具备Iterator接口的数据结构有:

Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象

forEach

forEach只能用于Array、Set、Map的遍历

arr.forEach((value,index,arr)=>{})

forEach和for of功能相似:
区别:for of可以使用continue、break、return退出循环,forEach不可以

Object.keys(obj)

返回对象自身的除Symbol外的可枚举属性,和for in不同的是,Object.keys不会遍历继承的属性

Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回自身的除Symbol外的所有属性名称的组成的数组,其和Object.keys不同的是,Object.keys只能遍历可枚举的属性,getOwnPropertyNames可遍历所有的不管是否可枚举的属性

const obj = { pro1: 'pro1' }
Object.defineProperty(obj, 'newPro1', { value: 'newPro1', enumerable: true })
Object.defineProperty(obj, 'newPro2', { value: 'newPro2', enumerable: false })
console.log(Object.keys(obj))
console.log(Object.getOwnPropertyNames(obj))

在这里插入图片描述

总结(区别)

for in和for of:

for in可以用于对象、数组、字符串的遍历不能用于Map、Set。
for of可以用于具备Iterator接口的数据结构的遍历,不能用于原生对象。

for of 和forEach:

for of中可以使用break、return退出循环,也可以使用continue跳过本次循环后续操作
forEach中只能通过try catch等方式退出循环,通过return跳过本次循环后续操作

for in 和Object.keys()、Object.getOwnPropertyNames

  1. for in 会返回继承到的原型属性,Object.keys和Object.getOwnPropertyNames只返回自身的属性
  2. for in 和Object.keys只能遍历到可枚举的属性,getOwnPropertyNames可以同时返回可枚举和不可枚举的属性

想要获取到Symbol属性可以使用Object.getOwnPropertySymbols(obj)


版权声明:本文为lingliu0824原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。