
자바스크립트는 객체(object) 기반의 프로그래밍 언어이며 자바스크립트를 구성하는 거의 모든것이 객체다. 다시 말해, 원시 값을 제외한 나머지 값(함수, 배열, 정규 표현식 등)은 모두 객체다.
원시 타입은 단 하나의 값만 나타내지만, 객체 타입(object 또는 reference type)은 다양한 타입의 값(원시 값 또는 다른 객체)을 하나의 단위로 구성한 복합적인 자료구조다.
또한 원시 값은 변경 불가능한 값이지만 객체 타입의 값, 즉 객체는 변경 가능한 값이다.
객체는 0개 이상의 프로퍼티로 구성된 집합이며, 프로퍼티는 키(key)와 값(value)으로 구성된다.
var person = { // 객체 선언
name: 'Park', // name, age: 프로퍼티 키
age: 20 // 'Park', 20: 프로퍼티 값
};
var counter = {
num: 0, // 프로퍼티
increase: function() { // 메서드
this.num++;
}
};
위 코드처럼, 객체는 프로퍼티와 메서드로 구성된 집합체라고 보면 된다.
프로퍼티는 객체의 상태를 나타내는 값 즉, 데이터를 의미하며
메서드는 프로퍼티(상태 데이터)를 참조하고 조작할 수 있는 동작이다.
자바스크립트의 객체는 함수와 밀접한 관계를 가지는데, 함수로 객체를 생성하기도 하며 사실 함수 자체가 객체이기도 하다!
자바스크립트에서 함수와 객체는 분리해서 생각하면 안 되는 개념이며, 객체를 이해해야 함수를 이해할 수 있고 함수를 이해해야 객체를 이해할 수 있다.
객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 객체지향 프로그래밍이라 한다.
이 내용에 대한 자세한 건 나중에 더 알아보겠다.
C++나 Java 같은 클래스 기반 객체지향 언어는 클래스를 사전에 정의하고, 필요한 시점에 new 연산자와 함께 생성자(constructor)를 호출하여 인스턴스를 생성하는 방식으로 객체를 생성한다.
인스턴스(instance)
클래스에 의해 생성되어 메모리에 저장된 실체. 객체지향 프로그래밍에서 객체는 클래스와 인스턴스를 포함한 개념이다. 클래스는 인스턴스를 생성하기 위한 템플릿의 역할을 하고, 인스턴스는 객체가 메모리에 저장되어 실제로 존재하는 것에 초점을 맞춘 용어이다.
말이 굉장히 어렵다. 비유를 들어 쉽게 이해해보자.
클래스는 공장의 설계도와 같다. 예를 들어 자동차 공장의 설계도라고 가정해본다면, 자동차를 만드는 데에 어떤 부품들이 필요하고, 그 부품들을 어떻게 조립해야 하는지에 대한 정보가 담겨 있다. 즉, 클래스는 객체(만들어진 물건 즉, 자동차)를 만들기 위한 템플릿(즉, 설계도)으로 생각할 수 있다.
이것을 코드로 표현한다면 다음과 같다. :
class Car {
constructor(make, model, year) { // constructor 생성자, 객체의 초기 상태 설정
this.make = make; // this는 Car 클래스를 가리킨다.
this.model = model;
this.year = year;
}
start() {
console.log('Engine started');
}
}
const myCar = new Car('Hyundai', 'Sante Fe', 2023);
console.log(myCar.make); // "Hyundai"
myCar.start(); // "Engine started"
이처럼, 클래스는 객체를 만들기 위한 템플릿 역할을 하는 것이다. 즉, 클래스의 생성자를 통해서 객체를 만들 수 있다!
새로운 객체를 생성할 때, 클래스를 생성하는 방법 외에도 '생성자 함수(Constructor function)'을 사용할 수 있다. 생성자 함수의 예시는 다음과 같다. :
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.start = function() {
console.log('Engine started');
}
}
// new 연산자를 사용하여 객체 생성
const myCar = new Car('Hyundai', 'Santa Fe', 2023);
console.log(myCar.make) // 'Hyundai'
myCar.start(); // 'Engine started'
이처럼, 자바스크립트는 프로토타입 기반 객체지향 언어로써 클래스 기반 객체지향 언어와는 달리 다양한 객체 생성 방법을 지원한다.
이러한 객체 생성 방법 중에 가장 일반적이고 간단한 방법은 객체 리터럴을 사용하는 방법이다.
객체 리터럴은 중괄호({...}) 내에 0개 이상의 프로퍼티를 정의하는데, 변수에 할당되는 시점에 자바스크립트 엔진은 객체 리터럴을 해석해 객체를 생성한다.
const car = {
make: 'hyundai',
start: function() {
console.log('start');
}
};
console.log(typeof car); // object
console.log(car); // {make: 'hyundai', start: f}
중괄호 내 프로퍼티를 정의하지 않고 빈 객체를 생성할 수도 있다.
const empty = {}; // 비어있는 객체 생성
그럼 도대체 왜!? 편리한 객체 리터럴을 놔두고 클래스나 생성자 함수를 사용하는가?
단순한 데이터 객체를 만들거나, 몇 가지 로직 없이 간단한 객체를 만들어야 하는 경우에는 객체 리터럴을 사용하는 것이 더 간단하고 효율적일 수 있다. 객체 리터럴을 사용하면 객체를 직접 정의하고 초기화할 수 있어서 코드가 간결해진다.
하지만 객체에 메서드나 복잡한 동작을 추가해야할 경우 클래스 또는 생성자 함수를 사용하면 객체의 구조를 정의해서 해당 객체에 공통된 동작을 추가할 수 있다. 이렇게 하면 코드 재사용성이 높아지며, 복잡한 객체를 만들 때 유리하다.
따라서 용례에 따라 객체 리터럴, 클래스, 생성자 함수 등을 선택하면 된다. 간단한 데이터 저장용으로는 객체 리터럴이 편리하며, 객체에 메서드나 복잡한 동작이 필요한 경우 클래스 또는 생성자 함수를 사용하는 것이 좋다.
단순하게 지금은, 다른 객체지향 언어에 비하여 자바스크립트는 단순 객체 선언만으로도 객체를 생성할 수 있다는 편리함을 알고 가면 될 듯 하다!
const circle = {
radios : 5,
getDiameter: function() {
return 2 * this.radius; // this는 circle을 가리킴
}
};
메서드 안에서 radius 곱하기 2를 해주고 있는데, 여기서 특이한 것은 'this'이다.
즉, circle 객체에 선언된 프로퍼티인 'radius'를 가리키는 것이며, 객체 안의 값을 바꾸고 싶을 땐 this 자기 참조 변수를 사용하면 되는 것! this에 대해서는 나중에 더 자세히 작성해보겠다.
프로퍼티에 접근하는 방법은 다음과 같은 두 가지이다.
- 마침표 프로퍼티 접근 연산자(.)를 사용하는 점 표기법(dot notation)
- 대괄호 프로퍼티 접근 연산자([...])를 사용하는 대괄호 표기법(bracket notation)
const person = {
name: 'lee'
};
// 점 표기법에 의한 프로퍼티 접근
console.log(person.name); // lee
// 대괄호 표기법에 의한 프로퍼티 접근
console.log(person['name']); // lee
여기서 주의할 점은, 대괄호 프로퍼티 접근 연산자 내부에 지정하는 프로퍼티 키는 반드시 따옴표('')로 감싼 문자열이어야 한다. 따옴표로 감싸지 않은 이름을 프로퍼티 키로 사용하면 자바스크립트 엔진은 이를 식별자로 해석한다.
const person = {
name: 'lee'
};
console.log(person[name]); // ReferenceError: name is not defined
const person = {
name: 'lee'
};
console.log(person.age); // undefined
식별자로 인식하여 선언되지 않은 'name'을 참조하려 했기 때문에 ReferenceError가 발생했다. 또, 객체에 존재하지 않는 프로퍼티에 접근하면 undefined를 반환하므로 주의하자.
프로퍼티 키가 식별자 네이밍 규칙(소문자 알파벳, 하이픈 사용 불가, 첫 글자 숫자 사용 금지 등)을 준수하지 않는 이름, 즉 자바스크립트에서 사용 가능한 유효한 이름이 아니면 반드시 대괄호 표기법을 사용해야 한다. 단, 프로퍼티 키가 숫자로 이뤄진 문자열일 경우 따옴표를 생략할 수 있다. 그 외의 경우에는 대괄호 내에 들어가는 프로퍼티 키는 반드시 따옴표로 감싼 문자열이어야 한다.
const person = {
'last-name': 'lee',
1: 10
};
person.'last-name'; // SyntaxError: Unexpected string
person.last-name; // 브라우저 환경: NaN
person[last-name]; // ReferenceError: name is not defined
person['last-name']; // lee
// 프로퍼티 키가 숫자로 이루어진 문자열인 경우 따옴표를 생략할 수 있다.
// 하지만 반드시 대괄호 표기법을 사용해주어야 한다.
person.1; // SyntaxError: Unexpected number
person.'1';
person[1];
person['1'];
위 코드에서 person.last-name 의 실행결과는 Node.js에서 'ReferenceError: name is not defined' 이고, 브라우저 환경에서는 NaN 이 발생한다. 그 이유는 person.last-name 를 실행할 때 자바스크립트 엔진은 먼저 person.last 를 평가하는데, person 객체에 프로퍼티 키가 last 인 프로퍼티가 없기 때문에 person.last 는 먼저 undefined로 평가된다. 따라서 person.last-name 은 undefined-name 과 같다.
이 다음으로 엔진은 name 이라는 식별자를 찾는데 이때 name 은 프로퍼티 키가 아니라 식별자(변수, 함수 등의 이름)로 해석된다. Node.js 환경에서는 현재 어디에도 name 라는 식별자 선언이 없으므로 'ReferenceError: name is not defined' 라는 에러가 발생한다.
그런데 브라우저 환경에서는 name 이라는 전역 변수(전역 객체 window의 프로퍼티)가 암묵적으로 존재한다. 전역 변수 name 은 브라우저 창의 이름을 가리키며, 기본값은 빈 문자열이다.
따라서 person.last-name 은 undefined-'' 와 같으므로 NaN 이 된다.
이미 존재하는 프로퍼티에 값을 할당하면 프로퍼티 값이 갱신된다.