자바스크립트엔 여덟 가지 자료형이 있습니다. 이 중 일곱 개는 오직 하나의 데이터만 담을 수 있어 원시형(primitive type)
이라 부릅니다.
객체는 중괄호 {...}
를 이용해 만들 수 있습니다. 중괄호 안에는 키: 값 쌍으로 구성된 프로퍼티
를 여러 개 넣을 수 있습니다. 키
엔 문자형, 값
엔 자료형이 허용됩니다.
객체는 몇 가지 특수한 기능을 가진 연관 배열입니다.
let user = new Object();
let user = {};
let user = {
name: "John",
age: 30,
};
점 표기법을 이용하면 프로퍼티 값을 읽는 것도 가능합니다.
alert(user.name);
alert(user.age);
delete
연산자를 사용하면 프로퍼티를 삭제할 수 있습니다.
delete user.age;
자바스크립트 객체의 중요한 특징 중 하나는 다른 언어와는 달리, 존재하지 않는 프로퍼티에 접근하려 해도 에러가 발생하지 않고 undefined
를 반환한다는 것입니다.
let user = {};
alert(user.noSuchProperty === undefined); // true는 프로퍼티가 존재하지 않음을 의미합니다.
이렇게 undefined
와 비교하는 것 이외에도 연산자 in
을 사용하면 프로퍼티 존재 여부를 확인할 수 있습니다.
"key" in object;
for…in
반복문을 사용하면 객체의 모든 키를 순회할 수 있습니다.
객체와 원시 타입의 근본적인 차이 중 하나는 객체는 참조에 의해
저장되고 복사된다는 것입니다.
원시값(문자열, 숫자, 불린 값)은 값 그대로
저장 할당되고 복사되는 반면에 말이죠.
변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어 있는 메모리 주소
인 객체에 대한 참조 값
이 저장됩니다.
객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않습니다.
객체 비교 시 동등 연산자 ==
와 일치 연산자 ===
는 동일하게 동작합니다.
자바스크립트는 객체 복제 내장 메서드를 지원하지 않기 때문에 조금 어렵습니다. 사실 객체를 복제해야 할 일 은 거의 없습니다. 참조에 의한 복사로 해결 가능한 일이 대다수이죠.
정말 복제가 필요한 상황이라면 새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순회해 원시 수준까지 프로퍼티를 복사하면 됩니다.
let user = {
name: "John",
age: 30,
};
let clone = {};
for (let key in user) {
clone[key] = user[key];
}
clone.name = "Pete";
alert(user.name); // 기존 객체에는 여전히 John이 있습니다.
Object.assign
을 사용하는 방법도 있습니다.
Object.assign(dest, [src1, src2, src3 ...]);
프로퍼티는 다른 객체에 대한 참조값일 수도 있습니다.
let user = {
name: "John",
sizes: {
height: 182,
width: 50,
},
};
alert(user.size.height);
이 객체를 복사하기 위해서는 user[key]
의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야 합니다. 이런 방식을 깊은 복사
라고 합니다.
자바스크립트 라이브러리인 lodash의 메서드인 _.cloneDeep(obj)를 사용하면 쉽게 깊은 복사를 할 수 있습니다.
원시값, 객체, 함수 등 우리가 만드는 모든 것은 메모리가 차지합니다. 그렇다면 더는 쓸모 없어지게 된 것들은 어떻게 처리될까요? 지금부턴 자바스크립트 엔진이 어떻게 필요 없는 것을 찾아내 삭제하는지 알아보겠습니다.
도달 가능성(reachability)
이라는 개념을 사용해 메모리 관리를 수행합니다.
태생부터 도달 가능한 것들은 명백한 이유 없이는 삭제되지 않습니다.
- 현재 함수의 지역 변수와 매개변수
- 중첩 함수의 체인에 있는 함수에서 사용되는 변수와 매개변수
- 전역 변수
이런 값들을 루트(root) 라고 합니다.
루트가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값이 됩니다.
let user = {
name: "John",
};
user = null;
let user = {
name: "John",
age: 30,
sayHi: function () {
alert("Hello");
},
};
객체 프로퍼티에 할당된 함수를 메서드 라고 부릅니다.
위 예시에선 user에 할당된 sayHi가 메서드입니다.
🤔 **객체 지향 프로그래밍** 객체를 사용하여 개체를 표현하는 방식을 객체 지향 프로그래밍(object-oriented programming, OOP)이라 부릅니다.메서드는 객체에 저장된 정보에 접근할 수 있어야 제 역할을 할 수 있습니다. 모든 메서드가 그런 건 아니지만, 대부분의 메서드가 객체 프로퍼티의 값을 활용합니다.
user.sayHi()
의 내부 코드에서 객체 user
에 저장된 이름을 이용해 인사물을 만드는 경우가 그런 경우에 속합니다.
메서드 내부에서 this
키워드를 사용하면 객체에 접근할 수 있습니다.
자바스크립트의 this
는 다름 프로그래밍 언어의 this
와 동작 방식이 다릅니다. 자바스크립트에선 모든 함수에 this
를 사용할 수 있습니다.
this
값은 런타임에 결정됩니다. 컨텍스트에 따라 달라집니다.
동일한 함수라도 다른 객체에서 호출했다면 this
가 참조하는 값이 달라집니다.
옵셔널 체이닝 ?.
을 사용하면 프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있습니다.
사용자가 여러 명 있는데 그 중 몇 명은 주소 정보를 가지고 있지 않다고 가정해봅시다. 이럴 때 user.address.street
를 사용해 주소 정보에 접근하면 에러가 발생할 수 있습니다.
이런 문제를 해결하기 위해 &&
연산자를 사용하곤 했습니다.
let user = {};
alert(user && user.address && user.address.street); // undefined
AND를 연결해서 사용하면 코드가 아주 길어진다는 단점이 있습니다.
생성자 함수(constructor function)와 일반 함수에 기술적인 차이는 없습니다. 다만 생성자 함수는 아래 두 관례를 따릅니다.
- 함수 이름의 첫 글자는 대문자로 시작합니다.
- 반드시
'new'
연산자를 붙여 실행합니다.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("보라");
alert(user.name); // 보라
alert(user.isAdmin); // false
생성자의 의의는 재사용할 수 있는 객체 생성 코드를 구현하는 것에 있습니다.
모든 함수는 생성자 함수가 될 수 있습니다. new
를 붙여 실행한다면 어떤 함수라도 객체 생성 코드를 구현할 수 있습니다.
return문이 있다면 다음과 같은 일이 벌어집니다.
- 객체를
return
한다면this
대신 객체가 반환됩니다. - 원시형을
return
한다면return
문이 무시됩니다.
Symbol은 원시형 데이터로, 유일무이한 식별자를 만드는 데 사용됩니다. 이름이 같더라더 값이 항상 다릅니다. 이름이 같을 때 값도 같길 원한다면 전역 레지스트리를 사용해야 합니다. Symbol.for(key)는 key 라는 이름을 가진 전역 심볼을 반환합니다.
심볼을 이용하면 숨김(hidden)
프로퍼티를 만들 수 있습니다. 숨김 프로퍼티는 외부 코드에서 접근이 불가능하고 값도 덮어쓸 수 없는 프로퍼티입니다.
let user = {
name: "John",
};
let id = Symbol("id");
user[id] = 1;
alert(user[id]);
그런데 문자열 "id"
를 키로 사용해도 되는데 Symbol("id")
을 사용한 이유가 무엇일까요?
user
는 서드파티 코드에서 가지고 온 객체이므로 함부로 새로운 프로퍼티를 추가할 수 없습니다. 그런데 심볼은 서드파티 코드에서 접근할 수 없기 때문에, 심볼을 사용하면 서드파티 코드가 모르게 user
에 식별자를 부여할 수 있습니다.