객체는 프로퍼티(키-값 쌍)을 저장한다.
프로퍼티의 키는 문자형 이어야만 하고, 값은 모든 자료형 이 허용된다.
// 객체 생성자
const user = new object();
user.['name'] = '존';
user.age = 30;
user['phone number'] = '010-1111-1111';
// 띄어쓰기가 있는 문자열(복수의 단어)는 때괄호 표기법으로만 정의가능하다.
// 점표기법에서 식별자는 숫자로 시작, 공백, $, _를 사용할 수 없다.
// 객체 리터럴
const user = {
name: '존',
age: 30,
'phone number': '010-1111-1111',
};
// 점 표기법
console.log(user.name);
console.log(user['phone number']);
// 띄어쓰기가 있는 문자열(복수의 단어)는 때괄호 표기법으로만 읽어올 수 있다.
// 점표기법에서 식별자는 숫자로 시작, 공백, $, _를 사용할 수 없다.
// 대괄호 표기법
const key = 'name';
console.log(user['name']);
console.log(user[key]); // 대괄호 표기법 사용시 변수를 키로 사용 가능
delete user.age;
delete user['age'];
let dynamicKey1 = 'banana';
let dynamicKey2 = 'apple';
// 일반적인 방법
const bag = {};
bag[dynamicKey1] = 2;
bag[dynamicKey2 + ' computer'] = 4; // 복잡한 표현식도 가능
// 계산된 프로퍼티
const bag = {
[dynamicKey1]: 2,
[dynamicKey2 + ' computer']: 4, // 복잡한 표현식도 가능
};
const age = 20;
const user = {
age,
};
변수를 사용해서 프로퍼티를 만드는 경우 위와 같이 프로퍼티의 키와 같을 단축해서 사용할 수 있다.
프로퍼티 이름 제약사항
변수로
for
,let
,return
와 같은 예약어를 사용할 수 없다.하지만 프로퍼티에 키로
for
,let
,return
와 같은 예약어를 사용할 수 있다.정수 프로퍼티
프로퍼티 키로 숫자를 사용할 수 있다.
const obj = { 0: 'hello', }; obj[1] = 'world'; console.log(obj); console.log(obj[0], obj['1']);
키에 숫자를 사용하면 문자열로 자동 형 변환되어 사용된다. 예를들어 키를 숫자형
0
으로 하면 문자형'0'
으로 형변환 되어 사용된다.또한 프로퍼티에 접근할 때도 똑같이 숫자형
0
으로 하면 문자형'0'
형 변환되어 사용된다.
객체의 프로토타입
객체에는
__proto__
라는 프로퍼티가 존재한다. 이 프로퍼티는 프토타입의 상속과 관련된 내용들이 정의되어있다.따라서
__proto__
라는 예약어는 사용하면 안된다.
in
자바스크립트에서는 존재하지 않는 프로퍼티에 접근하면 에러가 발생하지 않고
undefined
를 반환한다.따라서 의도치 않은 에러를 만날수 있기 때문에 프로퍼티가 존재하는지 여부를 확인할 필요가 있을것이다.
const obj = {
name: '존',
};
console.log('name' in obj); // true
console.log(obj['name']); // 존
console.log('age' in obj); // false
console.log(obj['age']); // undefined
for in
문 사용하면 객체의 모든 프로퍼티 키 를 순회할 수 있다.
let user = {
name: 'John',
age: 30,
isAdmin: true,
};
for (let key in user) {
console.log(key, user[key]);
}
// name John
// age 30
// isAdmin true
정수 프로퍼티는 자동정렬.
그 외의 프로퍼티는 객체에 추가한 순서대로 정렬.
let codes = { 49: '독일', 41: '스위스', 44: '영국', // .., 1: '미국',};for (let code in codes) { console.log(code, codes[code]); // 1, 41, 44, 49}// 1 미국// 41 스위스// 44 영국
원시형, 참조형 모두 기본적으로 복사하면 같은 참조값을 가리킨다.
허나 복사한 곳에서 값을 수정했을 때 차이가 두드러진다.
복사한 곳에서 값을 수정했을 때
이런 차이점은 객체는 변수가 객체를 곧바로 가리키는 것이아닌 객체를 참조하고있는 곳을 가리키기 때문이다.
객체 비교시 동등 연산자 ==
와 일치 연산자 ===
는 동일하게 동작한다.
const obj1 = {};const obj2 = obj1;console.log(obj1 == obj2); // trueconsole.log(obj1 === obj2); // true
const obj1 = {};const obj2 = {};console.log(obj1 == obj2); // falseconsole.log(obj1 === obj2); // false
const a = 1;const b = a;console.log(a == b); // trueconsole.log(a === b); // true
const a = 1;const b = 1;console.log(a == b); // trueconsole.log(a === b); // true
값은 같지만 독립된 객체를 만드는 방법
const user = { name: 'John', age: 30,};// 객체 복사const clone1 = user;// 객체 복제 (깊은복사)const clone2 = {}; // 새로운 빈 객체// 빈 객체에 user 프로퍼티 전부를 복사for (let key in user) { clone2[key] = user[key]; // 모든 프로퍼티를 순회하면서 복사}// 결과확인console.log(user); // { name: 'John', age: 30 }console.log(clone1); // { name: 'John', age: 30 }console.log(clone2); // { name: 'John', age: 30 }console.log(user == clone1); // trueconsole.log(user === clone1); // trueconsole.log(user == clone2); // falseconsole.log(user === clone2); // false
Object.assign
을 활용한 복사Object.assign(dest, [src1, src2, src3...])
dest
: 복사를 할 목표 변수
src
: 복사를 하고자 하는 객체
반환값 : dest
를 반환
// 예제1let user = { name: "John" };let permissions1 = { canView: true };let permissions2 = { canEdit: true };// permissions1과 permissions2의 프로퍼티를 user로 복사한다.Object.assign(user, permissions1, permissions2);// now user = { name: "John", canView: true, canEdit: true }// 예제2// 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값을 덮어씌운다.let user = { name: "John" };Object.assign(user, { name: "Pete" });console.log(user.name); // user = { name: "Pete" }// 예제3let user = { name: "John", age: 30};let clone = Object.assign({}, user);
const user = { name: 'John', size: { height: 182, width: 50, },};const clone = Object.assign({}, user);console.log(user === clone); // falseconsole.log(user.size === clone.size); // trueclone.size.height = 200; // 내부의 중첩된 객체의 값 수정console.log(user); // { name: 'John', size: { height: 200, width: 50 } }console.log(clone);// { name: 'John', size: { height: 200, width: 50 } }
객체 안에 또 다른 객체가 중첩되어 있는 형태의 경우 위에서 언급한 깊은 복사 방식을 사용해도 내부의 중첩된 객체는 동일한 객체를 참조하게 된다.
이런 문제를 해결하기 위해서는 모든 프로퍼티를 순회하면서 해당 프로퍼티의 값이 객체인 경우 객체의 구조를 복사하는 방식으로 해결해야한다.
이렇게 객체안에 중첩된 객체까지 복사를 하는 것을 깊은 복사(deep copy)라고 한다.
// lodash 메서드_.cloneDeep(obj)
반환값 : 깊은 복사된 객체
위 메서드를 사용하면 깊은 복사를 할 수 있다.
const user = { name: 'John', size: { height: 182, width: 50, },};const clone = _.cloneDeep(user);console.log(user === clone); // falseconsole.log(user.size === clone.size); // falseclone.size.height = 200; // 내부의 중첩된 객체의 값 수정console.log(user);// { name: 'John', size: { height: 180, width: 50 } }console.log(clone);// { name: 'John', size: { height: 200, width: 50 } }
자바스크립트는 메모리 관리를 위해 엔진 내에서 가비지 컬렉터가 끈임없이 동작하며,
원시값, 객체, 함수등 메모리에 차지하고 있는 요소들을 모니터링하고 삭제한다.
도달 가능성(reachability) 이라는 개념을 통해 메모리 관리를 수행.
도달 가능한(reachable) 값은 쉽게 말해 어떻게든 접근하거나 사용할 수 있는 값을 의미.
도달 가능한(reachable) 값은 메모리에서 삭제되지 않는다.
루트(root) : 태새부터 도달 가능하기 때문에 명백한 이유 없이는 삭제되지 않는다.
루트(root)가 참조하는 값이나 체이닝으로 루트에서 참조할 수 있는 값은 도달 가능한 값이 된다.
루트 or 전역(global)에서 도달할 수 없는 값은 가비지 컬렉팅 대상이 된다.
this
객체의 프로퍼티에 저장된 함수를 메서드라고한다.
// 함수 표현식, const user = { name: '존', sayHi: function() { console.log('hello'); }}// 화살표 함수const user = { name: '존', sayHi: () => { console.log('hello'); }}// 메서드 단축구문const user = { name: '존', sayHi() { console.log('hello'); }}
this
메서드는 객체에 저장된 정보에 접근할 수 있어야 비로속 제 역할을 할 수 있다.
일반 적으로 메서드가 객체의 프로퍼티의 값을 활용한다.
메서드 내부에서 this
키워드를 사용하면 현재 객체에 접근할 수 있다.
const user = { name: 'john', sayHi: function sayHi() { console.log(`hello! my name is ${this.name}`); },};user.sayHi();
this
자바스크립트에선 모든 함수는 this
키워드를 사용할 수 있다.
함수의 실행 컨텍스트에 따라 this
가 참조하는 값이 달라진다.
function showName() { console.log(this.name);}const user = { name: '존', showThis,};const admin = { name: '피터', showThis,};user.showThis(); // 존, this는 useradmin.showThis(); // 피터, this는 admin
궁금증
function showName() {console.log(this);}showName(); // node라면 global, 브라우저라면 window
위와 같은 함수를 이용하면 전역객체를 확인할 수 있다.
const name = '마이크';function showName() {console.log(this.name);}const user = {name: '존',showThis,};const admin = {name: '피터',showThis,};showName(); // undefined, 마이크를 예상했는데...user.showThis(); // 존admin.showThis(); // 피터
위와 같이
showName()
을 하면this
가 전역객체이고 전역에 선언되어있는name
을 출력할 줄 알았는데 undefined가 출력되었다.
왜 전역에 정의되어있는name
을 참조하지 못할까..?
화살표 함수에는 고유한 this
를 가지고 있지 않다.
화살표 함수의 바로 한 단계 외부함수의 this
를 참조한다.
아래 코드를 보면 많이 헷갈린다. 계속 고민해보자.
const user = { firstName: '보라', sayHi() { const name = '철수'; const arrow = () => { const name = '훈이'; console.log(this.firstName); }; arrow(); },};user.sayHi(); // 보라
arrow()
의 this
는 외부 함수 user.sayHi()
의 this
가 된다.
this
를 이용하고 싶은 경우 화살표 함수를 사용하자.const showName = () => { console.log(this.name);};const user = { name: '존', showName,};const admin = { name: '피터', showName,};user.showName(); // undefinedadmin.showName(); // undefined
const showName1 = () => { console.log(this.name);};const user = { name: '존', show: showName1,};const guest = { name: '잭', show() { showName1(); },};const admin = { name: '피터', show() { const showName2 = () => { //정의부, 외부함수 admin.show()의 this를 참조 console.log(this.name); }; showName2(); },};user.show(); // undefinedguest.show(); // undefinedadmin.show(); // 피터
위 코드를 보아 함수의 정의부의 바로 윗 단계의 외부의 this
를 참조하는 것같다.