자바스크립트에서 객체란

Jin·2022년 3월 1일
0

Javascript

목록 보기
11/22

객체는 선언적 형식과 생성적 형식, 두 가지로 정의합니다.

var obj = {
  key: value,
} // 리터럴 형식

var obj2 = new Object(); // 생성자 형식
obj2.key = value;

두 형식 모두 결과적으로 생성되는 객체는 같습니다. 유일한 차이점은 리터럴 형식은 한 번의 선언으로 다수의 key - value 쌍을 프로퍼티로 추가할 수 있지만, 생성자 형식은 한 번에 한 프로퍼티만 추가할 수 있다는 것입니다.

이전에 자바스크립트 (이하 JS)의 타입의 종류를 말씀드린 적이 있습니다.

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol

여기서 object와 symbol을 뺀 나머지 5가지 타입은 단순 원시 타입입니다.

여기서 명확히 짚고 넘어가야 할 것이 단순 원시 타입은 객체가 아니라는 것입니다.

"자바스크립트는 모든 것이 객체다"라는 사실이 아닙니다.

반면, object와 symbol이 속해있는 복합 원시 타입은 객체의 하위 타입입니다.

JS 함수는 기본적으로는 일급 객체이므로 다른 함수에 인자로 전달할 수 있고 다른 함수로부터 함수를 반환받을 수 있으며, 함수 자체를 변수에 할당하거나 자료 구조에 저장할 수도 있습니다.

그리고 이 일급 객체는 일반 객체와 똑같은 객체로 취급됩니다.

내장 객체라고 부르는 객체 하위 타입도 존재합니다.

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

내장 객체는 거의 타입처럼 보이는데다 겉모습 때문에 클래스처럼 보이기 쉽습니다. 하지만 이들은 단지 JS의 내장 함수일 뿐으로 생성자로 사용되어 주어진 하위 타입의 새 객체를 생성합니다.

var str = "문자열";
console.log(typeof str); // string
console.log(str instanceof String); // false

var str2 = new String("문자열");
console.log(typeof str2); // object
console.log(str2 instanceof String); // true

원시 값은 객체가 아닌 원시 리터럴이며 불변값입니다. 문자 개수를 세는 등 메서드를 호출하고 싶을 때는 객체가 필요합니다.

다행히도 JS 엔진은 상황에 맞게 원시 값을 객체로 자동 강제변환하므로 명시적으로 객체를 생성할 일은 거의 없다고 보면 됩니다. 오히려 생성자 형식은 지양하고 리터럴 형식을 적극 권장합니다. (성능상의 이유)

여기서 주의해야 할 부분은 null과 undefined는 객체 래퍼 (Wrapper) 형식이 없는 그 자체로 유일 값이며 Date는 리터럴 형식이 없기 때문에 반드시 생성자 형식으로 생성해야 한다는 점입니다. 또한, Error 객체는 예외가 던져지면 알아서 생성되니 명시적으로 생성할 일은 거의 없다고 봐도 무방합니다.

생성자 형식이 리터럴 형식보다 옵션이 더 많으므로 추가 옵션이 필요한 경우에만 생성자 형식을 사용하면 되겠습니다.


객체의 내용

객체는 특정한 위치에 저장된 모든 타입의 값, 즉 프로퍼티로 내용이 채워집니다.

객체 컨테이너에 값을 저장하지는 않고 컨테이너에는 실제로 프로퍼티 값이 있는 곳을 가리키는 포인터 역할을 담당하는 프로퍼티명이 담겨져 있습니다.

프로퍼티 접근 vs. 키 접근

var obj = {
  a: 2
};

obj.a; // 2
obj['a']; // 2

일반적으로 .a 구문을 프로퍼티 접근, ['a'] 구문을 키 접근이라고 합니다. 어느 방법을 사용하여도 상관없습니다.

하지만, 키 접근에서는 ?, !와 같은 기호가 포함된 키에 접근할 수 있다는 차이가 있습니다.

객체 프로퍼티명은 언제나 문자열입니다. 문자열 이외의 다른 원시 값을 쓰면 우선 문자열로 변환됩니다. 배열 인덱스로 사용하는 숫자도 마찬가지이므로 객체와 배열 사이에 숫자를 써서 헷갈리는 코드를 만들지 않는 습관을 들이는 것도 중요합니다.

계산된 프로퍼티명

ES6부터는 객체 리터럴 선언 구문의 키 이름 부분에 변수명을 넣고 []로 감싸면 변수의 값이 키 이름으로 적용됩니다.

var prefix = "foo";
var obj = {

}

console.log(obj.foo); // "hello"
console.log(obj.foobar); // "world"

프로퍼티 vs. 메서드

여타 언어에서 객체에 부속된 함수를 주로 '메서드'라 부르고 JS 함수 역시 객체의 부속물이라 생각하기 쉽습니다. 하지만, 엄밀히 말해 함수는 결코 객체에 속하는 것이 아니며, 객체 레퍼런스로 접근한 함수를 그냥 메서드라 칭하는 건 그 의미를 지나치게 확대해석한 것입니다.

여기서 메서드와 함수의 차이를 깊게 들어가는 것은 주객이 전도될 수 있으므로 JS에서는 함수와 메서드란 말이 서로 바꿔 사용할 수 있다 정도로 결론을 짓겠습니다.

하나 기억해야 할 것은 함수 표현식을 객체 리터럴의 한 부분으로 선언하였더라도 이 함수가 저절로 객체에 달라붙는 것이 아니고 해당 함수 객체를 참조하는 레퍼런스가 하나 더 생기는 것 뿐이라는 것입니다.

var obj = {
  foo: function() {
    console.log("foo");
  }
}

var someFoo = obj.foo;
someFoo; // foo 함수 코드 그 자체
obj.foo; // foo 함수 코드 그 자체

배열

var arr = ["foo", 42, true];
arr.bar = "bar";
arr.length; // 3
arr.bar; // "bar"

인덱스를 쓰지 않고 일반적인 키/값 객체로 배열을 사용할 수도 있지만 부작용과 최적화 측면에서 그다지 좋은 생각은 아닙니다. 분명 bar라는 키 값을 삽입하였고 결과값도 잘 출력되지만 배열의 길이에는 변함이 없기 때문입니다.

키/값 저장소 용도로는 객체를, 숫자 인덱스를 가진 저장소로는 배열을 사용하는 것이 좋습니다.

참고로 배열에 프로퍼티를 추가할 때 프로퍼티명이 숫자와 유사하면 숫자 인덱스로 잘못 해석되어 배열 내용이 달라질 수 있으니 주의하여야 합니다.

var arr = ["foo", 42, true];
arr["3"] = "bar";
arr.length; // 3
arr[3]; // "bar"

Getter와 Setter

ES5부터는 게터/세터를 통해 기본 로직을 오버라이딩할 수 있습니다.

var obj = {
  get a() {
    return this._a;
  },

  set a(val) {
    this._a = val * 2;
  }
}


obj. a = 2;
console.log(obj.a); // 4

프로퍼티에 접근하면 자동으로 게터 함수가 은밀하게 호출되어 어떤 값이라도 게터 함수가 반환한 값이 결괏값이 됩니다.

만약에 세터없이 게터만 설정되어 있고 할당문으로 값을 세팅하려고 하면 에러 없이 조용히 무시됩니다. 그러므로 게터와 세터는 항상 둘 다 선언하는 것이 좋습니다.

존재 확인

var obj = {
  a: 2
}

("a" in obj); // true
("b" in obj); // false

obj.hasOwnProperty("a"); // true
obj.hasOwnProperty("b"); // false

in 연산자는 어떤 프로퍼티가 해당 객체에 존재하는지 아니면 이 객체의 상위에도 존재하는지 확인합니다. 이와 달리 hasOwnProperty()는 단지 프로퍼티가 해당 객체에 있는지만 확인하고 타고 올라가지는 않습니다.

순회

forEach()는 배열 전체 값을 순회하지만 콜백 함수의 반환 값은 무시하므로 중간에 그만 둘 수 없습니다.

every()는 배열 끝까지 또는 콜백 함수가 false를 반환할 때까지 순회하며 some()은 이와 정반대로 배열 끝까지 또는 콜백 함수가 true를 반환할 때까지 순회합니다. 따라서, 이러한 특별한 반환 값은 일반적인 for 루프의 break 문처럼 끝까지 순회하기 전에 일찌감치 순회를 끝내는 데 사용됩니다.

ES6에서는 for ... of 루프를 통해 내부적으로 순회당 한 번씩 객체의 next() 메서드를 호출하여 연속적으로 반환 값을 순회합니다.

profile
배워서 공유하기

0개의 댓글