[Javascript] 객체(Object)
자바스크립트는 객체(Object) 기반의 스크립트 언어로 자바스크립트를 이루고 있는 거의 모든 것이 객체이다.
자바스크립트에는 총 여덟 가지 자료형이 있다. 이들 중 일곱 개(Number, BigInt, String, Boolean, undefined, null, Symbol)는 하나의 데이터(문자열, 숫자 등)만 담을 수 있어 원시형(primitive type)이라 부른다. 반면 원시형을 제외한 나머지 값들(함수, 배열, 정규표현식 등)은 모두 객체이다.
자바스크립트의 객체는 key와 value로 구성된 property의 집합이다. 프로퍼티의 key로는 문자열과 symobl이 쓰일 수 있는 반면, value로는 자바스크립트에서 사용할 수 있는 모든 값이 활용될 수 있다. 함수 또한 자바스크립트에서는 일급 객체이므로 value로 사용될 수 있는데, 프로퍼티의 값이 함수일 경우 일반 함수와 구분하기 위해 메서드(method)라고 부른다.
이처럼 객체는 데이터를 의미하는 property와 데이터를 참조하고 조작할 수 있는 동작을 의미하는 메서드(method)로 구성된 집합이다. 그러니까 객체는 데이터(프로퍼티)와 그 데이터에 관련된 동작(메서드)를 모두 포함할 수 있기 때문에 데이터와 동작을 하나의 단위로 구조화할 수 있다는 점에서 유용하다.
자바스크립트의 객체는 객체 지향의 상속을 구현하기 위해 prototype이라고 불리는 객체의 프로퍼티와 메서드를 상속받을 수 있다. 이러한 prototype은 타 언어와 구별되는 중요한 개념이다.
객체 Property
property는 key와 value로 구성되는데 이중 key는 프로퍼티를 유일하게 식별하기 위한 식별자의 역할을 한다. 앞서도 언급했지만 key의 명명 규칙과 value로 사용할 수 있는 값은 아래와 같다.
- key: 빈 문자열을 포함하는 모든 문자열 또는 symbol 값
- value: 모든 값
key에 문자열이나 symbol 이외의 값을 지정하면 암묵적으로 타입이 변환되어 문자열이 된다. 또한 이미 존재하는 key를 중복 선언하면 나중에 선언한 프로퍼티가 먼저 선언한 프로퍼티를 덮어쓴다.
또한 배열과 달리 객체는 프로퍼티를 열거할 때 순서를 보장하지 않는다는 특징이 있다.
객체 생성 방법
자바와 같은 클래스 기반의 객체 지향 언어는 클래스를 사전에 정의하고 필요한 시점에 new 연산자를 사용하여 인스턴스를 생성하는 방식으로 객체를 생성한다. 하지만 자바스크립트는 프로토타입 기반의 객체 지향 언어로 클래스라는 개념이 없고 별도의 객체 생성 방법이 존재한다.
물론 ES6에서 새롭게 Class가 도입되었다. 프로토타입 기반 프로그래밍은 클래스가 존재하지 않는 객체 지향 프로그래밍 스타일이다. 클래스 없이 프로토타입 체인과 클로저 등으로 객체 지향 언어의 상속, 캡슐화 등의 개념을 구현한다. 하지만 클래스 기반 언어에 익숙한 프로그래머들에게는 혼란을 야기하여 자바스크립트를 어렵게 하는 장벽처럼 인식되었다. ES6의 클래스는 기존 프로토타입 기반 객체 지향 프로그래밍보다 클래스 기반 언어에 익숙한 프로그래머가 보다 빠르게 학습할 수 있는 새로운 문법을 제시하는 효과를 자겨왔다. ES6의 클래스는 새로운 객체 지향 모델을 제공하는 것이 아니라 클래스도 사실은 함수이고 기존 프로토타입 기반 패턴의 Syntactic sugar라고 할 수 있다.
1. 객체 리터럴
가장 일반적인 자바스크립트의 객체 생성 방식이다. 객체 리터럴 방식을 활용하면 클래스 기반 객체 지향 언어들에 비해 간편하게 객체를 생성할 수 있다는 장점이 있다. 중괄호 {...}를 이용해 객체를 생성하는데, 중괄호 내에 하나 이상의 프로퍼티를 기술하면 해당 프로퍼티가 추가된 객체를 생성할 수 있으며 중괄호 내에 아무것도 기술하지 않으면 빈 객체가 생성된다.
var emptyObject = {};
console.log(typeof emptyObject); // object
var person = {
name: 'Lee',
gender: 'male',
sayHello: function () {
console.log('Hi! My name is ' + this.name);
}
};
console.log(typeof person); // object
console.log(person); // {name: "Lee", gender: "male", sayHello: ƒ}
person.sayHello(); // Hi! My name is Lee
2. Object 생성자 함수
new 연산자와 Object 생성자 함수를 통해 빈 객체를 생성할 수도 있다. 그렇게 빈 객체를 생성한 다음 프로퍼티나 메서드를 추가하여 객체를 완성시키는 방법.
여기서 생성자 함수 (constructor)란 new 키워드와 함께 객체를 생성하고 초기화시키는 함수를 말한다. 생성자 함수를 통해 생성된 객체를 instance라 한다. 자바스크립트는 Object 생성자 함수 이외에도 String, Number, Boolean, Array, Date, RegExp 같은 빌트인(Built-in) 생성자 함수를 제공한다. 이때 일반 함수와 생성자 함수를 구분하기 위해 생성자 함수의 이름은 파스칼 케이스(Pascal Case)를 사용하는 것이 일반적이다.
객체 내에 존재하지 않는 key에 값을 할당하면 해당 객체에 프로퍼티를 추가하고 값을 할당시킬 수 있다. 이미 존재하는 key에 새로운 값을 할당하면 프로퍼티 값은 할당한 값으로 변경된다.
// 빈 객체의 생성
var person = new Object();
// 프로퍼티 추가
person.name = 'Lee';
person.gender = 'male';
person.sayHello = function () {
console.log('Hi! My name is ' + this.name);
};
console.log(typeof person); // object
console.log(person); // {name: "Lee", gender: "male", sayHello: ƒ}
person.sayHello(); // Hi! My name is Lee
하지만 반드시 Object 생성자 함수를 사용해 빈 객체를 생성해야 하는 것은 아니다. 객체를 생성하는 방법은 위에서 언급한 객체 리터럴을 사용하는 것이 더 간편하다. Object 생성자 함수를 이용한 객체 생성 방식은 특별한 이유가 없다면 그다지 유용해 보이지 않는다.
사실 객체 리터럴 방식의 객체 생성은 Object 생성자 함수로 객체를 생성하는 것을 단순화시킨 일종의 축약 표현이다. 다시 말해서 자바스크립트 엔진은 객체 리터럴로 객체를 생성하는 코드를 만나면 내부적으로 Object 생성자 함수를 사용하여 객체를 생성한다는 것이다. 따라서 개발자가 일부러 Object 생성자 함수를 사용해 객체를 생성해야 할 일은 거의 없다고 할 수 있다.
3. 생성자 함수
사실 객체 리터럴 방식과 Object 생성자 함수 방식은 프로퍼티의 value만 다른 여러 객체를 생성할 때 불편하다는 단점이 있다. 아래와 같이 동일한 프로퍼티를 갖는 객체임에도 매번 같은 프로퍼티를 기술해 줘야 하기 때문이다.
var person1 = {
name: 'Lee',
gender: 'male',
sayHello: function () {
console.log('Hi! My name is ' + this.name);
}
};
var person2 = {
name: 'Kim',
gender: 'female',
sayHello: function () {
console.log('Hi! My name is ' + this.name);
}
};
하지만 다음과 같이 생성자 함수를 사용하면 마치 객체를 생성하기 위한 템플릿(클래스)처럼 사용하여 프로퍼티가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
// 생성자 함수
function Person(name, gender) {
this.name = name;
this.gender = gender;
this.sayHello = function(){
console.log('Hi! My name is ' + this.name);
};
}
// 인스턴스의 생성
var person1 = new Person('Lee', 'male');
var person2 = new Person('Kim', 'female');
console.log('person1: ', typeof person1);
console.log('person2: ', typeof person2);
console.log('person1: ', person1);
console.log('person2: ', person2);
person1.sayHello();
person2.sayHello();
생성자 함수의 특징은 아래와 같다.
- 생성자 함수의 이름은 일반적으로 대문자로 시작한다. 이는 생성자 함수임을 인식하는 데 도움을 준다.
- 프로퍼티 또는 메서드 이름 앞에 기술한 this는 생성자 함수가 생성할 인스턴스(instance)를 가리킨다.
- this에 연결(바인딩)되어 있는 프로퍼티와 메서드는 public(외부에서 참조 가능)하다.
- 생성자 함수 내에서 선언된 일반 변수는 private(외부에서 참조 불가능)하다. 즉 생성자 함수 내부에서는 자유롭게 접근이 가능하나 외부에서 접근할 수 없다.
function Person(name, gender) {
var married = true; // private
this.name = name; // public
this.gender = gender; // public
this.sayHello = function(){ // public
console.log('Hi! My name is ' + this.name);
};
}
var person = new Person('Lee', 'male');
console.log(typeof person); // object
console.log(person); // Person { name: 'Lee', gender: 'male', sayHello: [Function] }
console.log(person.gender); // 'male'
console.log(person.married); // undefined
자바스크립트의 생성자 함수는 이름 그대로 객체를 생성하는 함수이다. 하지만 Java와 같은 클래스 기반의 객체 지향 언어의 생성자(constructor)와는 달리 그 형식이 정해져 있는 것이 아니라 기존 함수와 동일한 방법으로 생성자 함수를 선언하고, new 연산자를 통해 호출하면 해당 함수가 생성자 함수로 동작하는 방식이다.
이는 생성자 함수가 아닌 일반 함수에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수도 있다는 것을 의미한다. 따라서 일반적으로 생성자 함수의 이름은 첫 글자를 대문자로 기술하여 혼동을 방지하는 것이 필요하다.
new 연산자와 함께 함수를 호출하면 this 바인딩이 다르게 동작한다는 특징이 있는데 이에 대한 자세한 내용은 추후 별도의 포스팅을 통해 설명하려 한다.
객체 프로퍼티 활용
1. Property key
프로퍼티의 key로는 일반적으로 문자열(빈 문자열 포함)을 사용한다. key에 문자열이나 symbol 이외의 값을 지정하면 암묵적으로 문자열로의 형변환이 이루어진다. 문자열 타입의 값으로 수렴될 수 있는 표현식도 key로 사용될 수 있다.
결국 key는 문자열이므로 원칙적으로는 quotation('' 또는 "")을 사용해 묶어 줘야 하는데 자바스크립트에서 사용 가능한 유효한 이름인 경우 이 따옴표를 생략할 수 있다. 반대로 말하면 자바스크립트에서 사용 가능한 유효한 이름이 아닌 경우 반드시 따옴표를 사용해야 한다는 것이다.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male',
1: 10,
function: 1 // OK. 하지만 예약어는 사용하지 말아야 한다.
};
console.log(person);
위 예시에서 first-name의 경우 반드시 따옴표를 사용해야 key로 활용될 수 있지만 first_name과 같이 프로퍼티 이름을 지정했다면 따옴표를 생략할 수 있다. first-name은 자바스크립트 내에서 '-' 연산자가 있는 표현식으로 인식되기 때문이다.
var person = {
first-name: 'kkk', // SyntaxError: Unexpected token -
};
표현식을 key로 사용하려면 key로 사용할 표현식을 대괄호로 감싸줘야 한다. 그러면 자바스크립트 엔진은 표현식을 평가하기 위해 식별자 first를 먼저 찾을 것이고 여기서는 first가 별도로 선언되어 있지 않으니 Refference Error가 발생하게 된다.
var person = {
[first-name]: 'kkk', // ReferenceError: first is not defined
};
var last = '5', name = '2'
var person2 = {
[last-name]: 'hahaha'
} // {3: 'hahaha'}
일반적으로 예약어를 객체의 key로 사용해도 에러가 발생하지는 않지만 예상치 못한 문제가 발생할 수 있으므로 사용하지 않는 것을 권장한다. 자바스크립트의 예약어 목록은 아래와 같다.
abstract arguments boolean break byte
case catch char class* const
continue debugger default delete do
double else enum* eval export*
extends* false final finally float
for function goto if implements
import* in instanceof int interface
let long native new null
package private protected public return
short static super* switch synchronized
this throw throws transient true
try typeof var void volatile
while with yield
// *는 ES6에서 추가된 예약어
2. Property value 읽기
객체의 value에 접근하는 방법은 점 표기법(dot notation)과 대괄호 표기법(square bracket notation)이 있다. 아래의 예시를 통해 두 차이를 살펴 보자.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male',
1: 10,
'is student' : true,
};
console.log(person);
console.log(person.first-name); // NaN: undefined-undefined
console.log(person[first-name]); // ReferenceError: first is not defined
console.log(person['first-name']); // 'kkk'
console.log(person.gender); // 'male'
console.log(person[gender]); // ReferenceError: gender is not defined
console.log(person['gender']); // 'male'
console.log(person['1']); // 10
console.log(person[1]); // 10 : person[1] -> person['1']
console.log(person.1); // SyntaxError
console.log(person['is student']) // true
key가 유효한 자바스크립트 이름이고 예약어가 아니라면 value는 점 표기법, 대괄호 표기법 모두 사용해 접근할 수 있다. 그러나 반대의 경우에는 대괄호 표기법을 통해서만 접근할 수 있다. 대괄호 표기법을 사용하는 경우 대괄호 내에 들어가는 프로퍼티 이름은 반드시 문자열이어야 한다.
또한 'is student'와 같이 여러 단어들을 조합해 key를 만든 경우에는 이를 따옴표로 감싸줘야 하며 읽을 때에는 대괄호 표기법을 이용해 접근해야 한다. 대괄호 표기법은 key에 어떤 문자열이 있든 상관없이 동작한다.
마지막 프로퍼티의 끝은 'is student': true,에서와 같이 쉼표로 끝날 수 있다. 이러한 쉼표를 trailing 혹은 hanging 쉼표라고 부르는데 이와 같이 끝에 쉼표를 붙이면 모든 프로퍼티들이 유사한 형식을 이루기 때문에 프로퍼티를 추가, 삭제, 이동하는 게 편리해진다.
객체에 존재하지 않는 프로퍼티를 참조하면 undefined를 반환한다.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male',
};
console.log(person.age); // undefined
3. 프로퍼티 값 갱신
객체가 이미 소유하고 있는 프로퍼티에 새로운 값을 할당하면 프로퍼티의 값은 갱신된다.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male',
};
person['first-name'] = 'Kim';
console.log(person['first-name'] ); // 'Kim'
또한 객체가 const로 선언되어 있는 경우 const는 상수이므로 프로퍼티의 값 갱신이 안 될 것처럼 보이지만 수정할 수 있다는 점에 유의해야 한다. 아래 예시의 (*)로 표시한 줄에서 오류를 일으킬 것처럼 보일 수 있겠지만 실제로는 그렇지 않다. const는 user만 고정시킬 뿐 그 안의 내용은 고정시키지 않기 때문이다. const는 user = ...와 같이 해당 변수를 전체적으로 설정하려고 할 때에만 오류를 발생시킨다.
const user = {
name: "John"
};
user.name = "Pete"; // (*)
alert(user.name); // Pete
4. 프로퍼티 동적 생성
객체가 소유하고 있지 않은 key에 값을 할당하면 해당 key와 value로 구성된 프로퍼티를 생성하여 객체에 추가한다.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male',
};
person.age = 20;
console.log(person.age); // 20
5. 프로퍼티 삭제
delete 연산자를 사용하면 객체의 프로퍼티를 삭제할 수 있다. 이때 피연산자는 property key여야 한다.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male',
};
delete person.gender;
console.log(person.gender); // undefined
delete person;
console.log(person); // Object {first-name: 'kkk', last-name: 'Lee'}
6. 대괄호 표기법
위에서 언급했듯 여러 단어를 조합해 프로퍼티 key를 만든 경우에는 점 표기법을 사용해 프로퍼티 값을 읽을 수 없다.
let user = {
name: "John",
age: 30,
"likes birds": true,
};
user.likes birds = true // Uncaught SyntaxError: Unexpected identifier 'birds'
자바스크립트는 위와 같은 코드를 이해하지 못한다. user.likes까지는 이해하다가 예상치 못한 birds를 만나면 문법 에러를 뱉어내게 되는 것이다.
점 표기법은 key가 유효한 변수 식별자인 경우에만 사용할 수 있다. 유효한 변수 식별자에는 공백이 없어야 하며 숫자로 시작하지 않아야 하고 $와 _를 제외한 특수문자가 없어야 한다.
대괄호 표기법 안에서 문자열을 사용할 때에는 문자열을 따옴표로 묶어줘야 한다(따옴표의 종류는 상관이 없다). 대괄호 표기법을 사용하면 아래 예시에서 변수를 key로 사용한 것과 같이 문자열뿐만 아니라 모든 표현식의 평가 결과를 프로퍼티로 사용할 수 있다.
let key = "likes birds";
// user["likes birds"] = true와 같다
user[key] = true;
위 예시에서 변수 key는 런타임에 평가되기 때문에 사용자 입력 값의 변경 등에 따라 값이 변경될 수 있다. 어떤 경우든 평가가 끝난 이후의 결과가 프로퍼티 key로 사용된다. 이를 응용하면 코드를 유연하게 작성할 수 있다.
let user = {
name: "John",
age: 30
};
let key = prompt("사용자의 어떤 정보를 얻고 싶으신가요?", "name");
// 변수로 접근
alert( user[key] ); // John (프롬프트 창에 "name"을 입력한 경우)
반면 점 표기법의 경우 이와 같은 방식이 불가능하다.
let user = {
name: "John",
age: 30
};
let key = "name";
alert( user.key ) // undefined
객체를 만들 때 객체 리터럴 안의 프로퍼티 키가 대괄호로 둘러싸여 있는 경우 이를 computed property라고 부른다. 이에 대한 자세한 내용은 아래 글 참조.
[Javascript] computed property 문법(객체의 key를 변수로 접근하기)
과거, 자바스크립트에서는 객체를 만들 때 정해진 문자열 이름의 속성명을 사용해 왔다. 하지만 ES6 이후로는 computed property를 사용하여 객체를 선언하는 순간 변수 또는 함수를 활용하여 동적인
developing-move.tistory.com
7. 프로퍼티 값 단축 구문
실무에서는 property의 value를 기존 변수에서 받아와 사용하는 경우가 종종 있다.
function makeUser(name, age) {
return {
name: name,
age: age,
// ...등등
};
}
let user = makeUser("John", 30);
alert(user.name); // John
위 예시의 property들은 이름과 값이 변수의 이름과 동일하다. 이렇게 변수를 사용해 프로퍼티를 만드는 경우는 아주 흔한데 프로퍼티 값 단축 구문(property value shorthand)을 사용하면 코드를 짧게 줄일 수 있다. name: name 대신 name만 적어줘도 프로퍼티를 설정할 수 있는 것이다.
function makeUser(name, age) {
return {
name, // name: name 과 같음
age, // age: age 와 같음
// ...
};
}
8. 프로퍼티 존재 여부 확인하기
자바스크립트 객체의 중요한 특징 중 하나는 다른 언어들과는 달리 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined를 반환한다는 것이다.
이러한 특징을 응용하면 프로퍼티 존재 여부를 쉽게 확인할 수 있다.
let user = {};
console.log( user.noSuchProperty === undefined ); // true는 '프로퍼티가 존재하지 않음'을 의미.
이렇게 undefined와 비교하는 것 이외에도 연산자 in을 사용하면 프로퍼티 존재 여부를 확인할 수 있다. 문법과 예시는 아래와 같다.
"key" in object
let user = { name: "John", age: 30 };
console.log( "age" in user ); // user.age가 존재하므로 true가 출력됨.
console.log( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false가 출력됨.
in 연산자를 사용할 때 in의 왼쪽에는 프로퍼티 이름이 와야 하는데 보통 따옴표로 감싼 문자열이 와야 한다. 이때 따옴표를 생략하면 아래와 같이 엉뚱한 변수가 조사 대상이 되기도 한다.
let user = { age: 30 };
let key = "age";
console.log( key in user ); // true, 변수 key에 저장된 값("age")을 사용해 프로퍼티 존재 여부를 확인.
이쯤 되면 undefined와 비교해도 충분한데 왜 in 연산자가 있는 건지 의문이 들 수 있다. 대부분의 경우 일치 연산자를 사용해 프로퍼티 존재 여부를 알아내는 방법(=== undefined)은 꽤 잘 동작한다. 그러나 가끔 이 방법이 효과가 없는 경우가 있다. 이럴 때 in을 사용하면 프로퍼티 존재 여부를 제대로 판별할 수 있다.
프로퍼티는 존재하는데 값에 undefined를 할당한 예시를 보자.
let obj = {
test: undefined
};
console.log( obj.test ); // 값이 `undefined`이므로 undefined가 출력되지만 프로퍼티 test는 존재함.
console.log( "test" in obj ); // `in`을 사용하면 프로퍼티 유무를 제대로 확인할 수 있음(true가 출력됨).
위 예에서 obj.test는 실재하는 프로퍼티이므로 in 연산자를 사용하면 정상적으로 true를 반환한다. 물론 undefined는 변수는 정의되어 있으나 값이 할당되어 있지 않은 경우이기 때문에 value가 undefined인 경우는 흔치 않다. 값을 알 수 없거나 비어 있다는 것을 명시할 때에는 주로 null을 사용하기 때문.
9. for-in문
for-in문을 사용하면 객체(배열 포함)에 포함된 모든 프로퍼티에 대해 loop를 수행할 수 있다.
var person = {
'first-name': 'kkk',
'last-name': 'Lee',
gender: 'male'
};
// prop에 객체의 프로퍼티 이름이 반환된다. 단, 순서는 보장되지 않는다.
for (var prop in person) {
console.log(prop + ': ' + person[prop]);
}
/*
first-name: kkk
last-name: Lee
gender: male
*/
var array = ['one', 'two'];
// index에 배열의 경우 인덱스가 반환된다
for (var index in array) {
console.log(index + ': ' + array[index]);
}
/*
0: one
1: two
*/
for-in문은 객체의 문자열 key를 순회하기 위한 문법으로 배열에는 사용하지 않는 것이 좋다. 그 이유는 아래와 같다.
- 객체의 경우 프로퍼티의 순서가 보장되지 않는다. 원래 객체의 프로퍼티에는 순서가 없기 때문이다.
- 배열 요소들만을 순회하지 않는다.
// 배열 요소들만을 순회하지 않음
var array = ['one', 'two'];
array.name = 'my array';
for (var index in array) {
console.log(index + ': ' + array[index]);
}
/*
0: one
1: two
name: my array
*/
이와 같은 for-in문이 단점을 극복하기 위해 ES6에서는 for-of문이 추가되었다.
const array = [1, 2, 3];
array.name = 'my array';
for (const value of array) {
console.log(value);
}
/*
1
2
3
*/
for (const [index, value] of array.entries()) {
console.log(index, value);
}
/*
0 1
1 2
2 3
*/
for-in문은 객체의 프로퍼티를 순회하기 위해 사용하고 for-of문은 배열의 요소를 순회하기 위해 사용한다.
[Javascript] for ... in
자바스크립트에서 객체는 데이터를 구조화하고 저장하는 데 사용되는 중요한 요소다. 객체는 여러 가지 프로퍼티를 포함하며 각 프로퍼티는 특정한 정보나 기능을 나타낸다. 그러므로 객체의
developing-move.tistory.com
[Javascript] for ... of
for ... of문은 iterable 객체를 순회하는 반복문이다. ECMAScript 2015(ES6)부터 도입된 자바스크립트의 반복문 중 하나로, DOM 컬렉션 객체의 각 요소를 반복하여 작업하는 데 용이한 방법을 제공한다. cf)
developing-move.tistory.com