Logo

자바스크립트 객체에 특정 속성이 있는지 확인하는 방법

객체 지향 프로그래밍 언어인 자바스크립트에서는 객체에 어떤 속성이 들어 있는지 알아내야 할 때가 많은데요. 이번 포스팅에서는 자바스크립트 객체에 특정 속성을 있는지 확인하는 다양한 방법에 대해서 실습을 통해 정리해보겠습니다.

in 연산자

자바스크립트에서 객체에 어떤 속성이 있는지 확인하는 가장 널리 알려진 방법은 in 연산자를 사용하는 것입니다. 속성명 in 객체의 형태로 사용하며, 해당 속성이 객체 안에 있으면 결과가 참(true)이고 없으면 결과가 거짓(false)입니다.

const obj = { a: 1 };
"a" in obj; // true
"b" in obj; // false

객체를 생성한 후에도 얼마든지 속성을 추가할 수 있기 때문에 in 연산자의 결과는 객체의 현재 상태에 따라 얼마든지 달라질 수 있습니다.

obj.b = 2;
"b" in obj; // true

한 가지 주의할 부분은 in 연산자는 속성의 존재 여부만 따지지 속성에 할당된 값이 무엇인지는 중요하지 않다는 것입니다. 그러므로 null이나 undefined가 할당된 속성을 상대로도 속성 자체가 존재한다면 결과는 참이 됩니다.

obj.a = null;
"a" in obj; // true
obj.a = undefined;
"a" in obj; // true

하지만 delete 연산자를 이용하여 객체로 부터 속성 자체를 제거해주면 in 연산자의 결과로 거짓을 얻을 수 있습니다.

delete obj.a;
"a" in obj; // false

자신의 속성과 상속된 속성

자바스크립트에서 대부분의 객체는 Object 클래스를 상속하고 있는데요. 그럼 in 연산자는 Object 클래스로 부터 상속받은 속성들을 상대로는 어떤 결과를 낼까요?

예를 들어, 객체 리터럴(literal), 즉 중괄호({}) 기호를 통해 만든 모든 자바스크립트 객체는 Object 클래스로 부터 toString() 함수와 valueOf() 함수를 상속받습니다.

const obj = { a: 1 };
obj.toString(); // '[object Object]'
obj.valueOf(); // {a: 1}

이 두 개의 속성을 상대로 in 연산자를 사용해보면 참이 결과로 나오는 것을 볼 수 있습니다.

"valueOf" in obj; // true
"toString" in obj; // true

이러한 현상은 우리가 정의한 클래스로 만든 인스턴스에서도 확인할 수 있는데요.

예를 들어, Person 클래스를 정의하고, Student 클래스가 상속받게 해보겠습니다.

class Person {
  constructor(name) {
    this.name = name;
  }

  getInfo() {
    return `Name: ${this.name}`;
  }
}

class Student extends Person {
  constructor(name, grade) {
    super(name);
    this.grade = grade;
  }

  getGrade() {
    return `Name: ${this.name}, Grade: ${this.grade}`;
  }
}

이제 Student 클래스로 생성한 인스턴스를 상대로 in 연산자를 테스트해보겠습니다.

const student = new Student("Dale", 1);
"getGrade" in student; // true (Student의 속성)
"getInfo" in student; // true (Person의 속성)
"valueOf" in student; // true (Object의 속성)

Student 클래스에서 정의한 getGrade 속성 뿐만 아니라 Student 클래스의 부모인 Person 클래스의 getInfo 속성이 있다고 나옵니다. 마찬가지로 Person 클래스의 부모인 Object 클래스의 valueOf 속성도 있다고 나오죠. 클래스를 정의할 때 extends 뒤에 부모 클래스를 명시하지 않으면 기본적으로 Object 클래스가 부모가 되기 때문입니다.

Object.hasOwn()

객체 자신의(direct) 속성 뿐만 아니라 상위 클래스로부터 상속된(inherited) 속성까지 참으로 판단하는 in 연산자의 특징이 자바스크립트로 프로그래밍을 할 때 예상치 못한 문제로 이어질 수 있는데요. in 연산자를 사용하면 속성이 객체 자신에 속해있는지, 부모 클래스에 정의되어 있는지 알 수 없기 때문에 혼란을 일으킬 수 있습니다.

예를 들어서, toString 속성은 Object 클래스로 부터 상속받기 때문에 in 연산자의 결과로 참이 나오는데요.

const obj = { a: 1 };
obj.toString(); // '[object Object]'
"toString" in obj; // true

일반 객체에 toString 속성을 추가해도 in 연산자의 결과는 참이 나오고,

obj.toString = function () {
  return this.a;
};
obj.toString(); // 1
"toString" in obj; // true

toString 속성을 제거해도 in 연산자의 결과는 참이 나옵니다.

delete obj.toString;
obj.toString(); // '[object Object]'
"toString" in obj; // true

만약에 상속받은 속성은 제외하고, 객체 자신에 어떤 속성이 존재하는 알고 싶다면 Object.hasOwn() 메서드를 사용하면 되는데요. Object.hasOwn(객체, 속성명)의 형태로 이 메서드를 호출하면 됩니다.

const obj = { a: 1 };
obj.toString(); // '[object Object]'
Object.hasOwn(obj, "toString"); // false
obj.toString = function () {
  return this.a;
};
obj.toString(); // 1
Object.hasOwn(obj, "toString"); // true
delete obj.toString;
obj.toString(); // '[object Object]'
Object.hasOwn(obj, "toString"); // false

이제 우리는 toString 속성이 객체 자신에 들어 있는지, 상위 클래스로부터 왔는지 명확하게 구분할 수 있게 됩니다.

Object.prototype.hasOwnProperty()

Object.prototype.hasOwnProperty() 메서드는 Object.hasOwn() 메서드가 등장하기 전에 Object.hasOwn() 메서드와 동일한 목적으로 많이 사용되었습니다. Object 클래스의 정적(static) 메서드인 hasOwn()과 달리 Object 클래스로 생성한 인스턴스의 메서드이기 때문에 객체.hasOwnProperty(속성명) 형태로 호출합니다.

그런데 Object 객체의 hasOwnProperty() 메서드를 사용할 때는 몇 가지 조심해야 할 부분이 있습니다.

우선 프로토타입(prototype)이 없는 객체는 애초에 hasOwnProperty 속성이 존재하지 않아서 hasOwnProperty() 메서드를 호출하는 것이 불가능합니다.

예를 들어서, 다음과 같이 Object.setPrototypeOf() 메서드를 사용하여 객체의 프로토타입을 null로 설정해주면 hasOwnProperty() 메서드를 호출할 수 없게 됩니다.

const obj = { a: 1 };
obj.hasOwnProperty("a"); // true

Object.setPrototypeOf(obj, null);
obj.hasOwnProperty("a"); // TypeError: obj.hasOwnProperty is not a function

다음과 같이 프로토타입이 없는 객체, null-prototype 객체는 생성할 수도 있는데요. 역시 어떤 방식을 사용하든 hasOwnProperty() 속성이 없어서 타입 오류가 발생합니다.

const obj = Object.create(null, { a: { value: 1 } });

obj.hasOwnProperty("a"); // TypeError: obj.hasOwnProperty is not a function
obj.hasOwnProperty; // undefined
const obj = { a: 1, __proto__: null };

obj.hasOwnProperty("a"); // TypeError: obj.hasOwnProperty is not a function
obj.hasOwnProperty; // undefined

null을 상속하는 클래스를 정의해도 동일한 현상을 확인할 수 있습니다.

class NullClass extends null {
  constructor() {
    return Object.create(new.target.prototype);
  }
}

new NullClass().hasOwnProperty; // undefined

hasOwnProperty() 인스턴스 메서드를 사용할 때 또 다른 문제는 Object 클래스로 부터 상속받은 hasOwnProperty 속성을 얼마든지 객체에서 덮어쓸 수 있으며, 이 것을 방지할 마땅한 방법이 없다는 것입니다.

const obj = { a: 1 };
obj.hasOwnProperty("b"); // false
obj.hasOwnProperty = () => true;
obj.hasOwnProperty("b"); // true

물론 궁여지책으로 함수 객체의 call() 함수를 사용하여, 강제로 Object 클래스의 hasOwnProperty() 메서드가 호출되도록 해줄 수도 있지만 보시다시피 코드를 이해하기가 어려워집니다.

Object.prototype.hasOwnProperty.call(obj, "b"); // false
({}).hasOwnProperty.call(foo, "b"); // false

바로 이와 같은 Object.prototype.hasOwnProperty() 메서드의 문제를 근본적으로 해결하기 위해서 등장한 것이 Object.hasOwn() 메서드입니다!

Object.hasOwn(obj, "b"); // false

따라서 구형 브라우저를 지원해야하는 웹 애플리케이션이나 Node.js 버전 16.9 미만을 사용하는 서버 애플리케이션에 아니라면 가급적 Object.hasOwn() 메서드를 쓰는 것이 권장됩니다.

실전 예제

마지막으로 자바스크립트 객체에 특정 속성을 있는지 확인는 코드를 짜다가 실전에서 겪을 법한 실수를 보여드릴까요?

간단한 실습 코드를 작성해보겠습니다. abbreviations 객체에는 줄임말에 대한 본래 긴 말이 저장되어 있습니다. standFor() 함수는 줄임말이 인자로 주어졌을 때 본래 긴 말을 반환합니다. 만약에 줄임말이 abbreviations 객체에 저장되어 있지 않다면 오류를 던집니다.

const abbreviations = {
  HTML: "HyperText Markup Language",
  CSS: "Cascading Style Sheets",
  JS: "JavaScript",
};

function standFor(abbr) {
  if (abbr in abbreviations) {
    return abbreviations[abbr];
  }
  throw new Error("Not found");
}

작성한 standFor() 함수는 다음과 같이 예상한대로 작동합니다.

standFor("HTML"); // 'HyperText Markup Language'
standFor("CSS"); // 'Cascading Style Sheets'
standFor("TypeScript"); // Error: Not found

하지만 인자로 toString을 넘겨보면 예상치 않게 오류가 발생하지 않고 함수가 반환이 되는데요.

standFor("toString"); // ƒ toString() { [native code] } 😵

이 것은 if 조건문 안에서 in 연산자를 사용하여 약자가 abbreviations 객체에 존재하는지 확인하기 때문입니다.

이 문제를 해결하기 위해서 if 조건문 안에서 in 연산자 대신에 Object.hasOwn() 메서드를 사용해보겠습니다.

function standFor(abbr) {
  if (Object.hasOwn(abbreviations, abbr)) {    return abbreviations[abbr];
  }
  throw new Error("Not found");
}

다시 인자로 toString을 넘겨보면 이번에는 예상대로 오류가 발생하는 것을 볼 수 있습니다.

standFor("toString"); // Error: Not found

마치면서

지금까지 자바스크립트에서 어떻게 객체에 특정 속성이 존재하는지 확인할 수 있는지 살펴보았습니다. 간단히 정리를 해보면, 객체 자신의 속성 뿐만 아니라 상위 클래스로 부터 상속받은 속성까지 고려하고 싶다면 in 연산자를 사용해야합니다. 상위 클래스로 부터 상속받은 속성을 제외하고 객체 자신이 특정 속성을 가지고 있는지 알고 싶다면 Object.hasOwn() 메서드를 사용하면 됩니다. Object.prototype.hasOwnProperty() 메서드는 몇 가지 문제점이 있어서 사용하는 것을 피해야합니다.