我发现有些初学的小伙伴在判断对象是否是空对象时用if(obj)或者是用if({}==={})。当你给他测试时,他才意识到问题。

判断对象是否为空的问题不仅在面试中经常被问到,在项目开发中也会经常使用到—比如我们在判断后端数据是否为空时所做的相应处理,以避免项目运行时产生问题。下面就说几种判断对象是否为空的方法。

有以下六种方式判断对象是否为空:

  • 使用JSON.striginfy()
  • 使用Object.keys()
  • for…in循环
  • Object.getOwnPropertyNames()
  • Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols
  • Reflect.ownKeys

JSON.striginfy()

这是最常用的一个判断方法,该方法可以序列化对象并将其转换为相应的JSON字符串格式。

let obj = {};
console.log(JSON.stringify(obj) === "{}");//返回true

注意❗

如果存在未定义属性值,或函数,Symbol值,在序列化过程中,它们将被忽略或转换为空。

const obj = {
  a: undefined,
  b: function() {},
  c: Symbol()
}
console.log(JSON.stringify(obj) === '{}')  // true

所以不是很推荐这种方式,但如果能够确认不会出现以上这种情况的话,这种方法当然是最简单的一种方式。

Object.keys() 

这也是会经常用到的方式。Object.keys()返回对象的属性名组成的一个数组,若长度为0 ,则为空对象。

let obj = {}
console.log(Object.keys(obj).length == 0);//返回true

注意❗

只能遍历可枚举的属性,但不能遍历非枚举的属性。

例如,我们先给obj定义一个属性a,将其设置成可枚举类型,结果当然是false:

const obj = {}
Object.defineProperty(obj, 'a', {
  value: 1,
  enumerable: true
})
console.log(obj.a) // 1
console.log(Object.keys(obj).length === 0) // false

接下来我们再将enumerable 设置成false,属性a将不会统计出来,结果显示obj为空对象:

const obj = {}
Object.defineProperty(obj, 'a', {
  value: 1,
  enumerable: false
})
console.log(obj.a) // 1
console.log(Object.keys(obj).length === 0) // true

for in循环 

for in循环需要和hasOwnProperty结合使用来判断对象是否为空。因为在for in循环时也会遍历原型上的属性,为了排除原型上的属性,所以使用hasOwnProperty。

const obj = {}
Object.prototype.a = 1 // 原型上的值
 
function isEmptyObj(obj) {
  let flag = true
  for (let o in obj) {
    if (obj.hasOwnProperty(o)) {
      flag = false
      break
    }
  }
  return flag
}
console.log(isEmptyObj(obj))  // true

注意❗

这个方法同Object.keys一样,不能遍历非枚举的属性**。**演示如上…

Object.getOwnPropertyNames() 

以上两种方法都不能获取非枚举属性,那有没有办法获取到非枚举属性呢?噔噔噔…就是这个Object.getOwnPropertyNames()!Object.getOwnPropertyNames()可以获取对象本身所有属性名称(包括非枚举属性)组成的数组。

const obj = {}
Object.defineProperty(obj, 'a', {
  value: 1,
  enumerable: false
})
console.log(Object.getOwnPropertyNames(obj))  // [ 'a' ]

似乎解决了前面的难题,但另一个难题又出现了----无法获取作为名称属性的Symbol值。上代码:

const a = Symbol()
const obj = {
  [a]: 1
}
 
console.log(obj)  // { [Symbol()]: 1 }
console.log(Object.getOwnPropertyNames(obj).length === 0)  // true
console.log(JSON.stringify(obj) === '{}')  // true
console.log(Object.keys(obj).length === 0)  // true

好吧,是所有的方法都识别不出symbol的值。在找到完美的解决方案之前我们坚持不懈。有没有既能非枚举属性,又能识别symbol的方法嘞?答案是有滴,请看下面的方法

Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols 

Object.getOwnPropertyNames唯一已知的缺点是无法获取以Symbol为名的属性。而Object.getOwnPropertySymbols 只能获取以Symbol为名的属性。所以这两个方法是相互补充,一拍即合。

const a = Symbol()
const obj1 = {
  [a]: 1
}
const obj2 = {b: 2}
const obj3 = {}
Object.defineProperty(obj3, 'a', {
  value: 1,
  enumerable: false
})
const obj4 = {}
 
function getLength(obj) {
  return Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).length
}
console.log(getLength(obj1) === 0)  // false
console.log(getLength(obj2) === 0)  // false
console.log(getLength(obj3) === 0)  // false
console.log(getLength(obj4) === 0)  // true

嗯嗯嗯,经过上面的测试,完全符合我们的预期,perfect!似乎是有点麻烦😡 我们再得寸进尺一下,有没有既能识别非枚举属性,又能识别symbol值,又很简单的方法嘞?有…继续往下看

Reflect.ownKeys

其返回值也是数组。

const a = Symbol()
const obj1 = {
  [a]: 1
}
const obj2 = {b: 2}
const obj3 = {}
Object.defineProperty(obj3, 'a', {
  value: 1,
  enumerable: false
})
const obj4 = {}
 
console.log(Reflect.ownKeys(obj1).length === 0)  // false
console.log(Reflect.ownKeys(obj2).length === 0)  // false
console.log(Reflect.ownKeys(obj3).length === 0)  // false
console.log(Reflect.ownKeys(obj4).length === 0)  // true

总结 

JSON.striginfy(),Object.keys(),for…in循环,Object.getOwnPropertyNames()适合简单的判断,不能识别非枚举属性,不能识别symbol值。

Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols和Reflect.ownKeys能识别非枚举属性,也能识别symbol值。