지난 번에 원시 값에 대해 공부해 보았다. 이와 밀접한 관련이 있는 객체에 대해 알아보자!
원시 값 vs 객체(참조 값)
원시 값: 원시 값을 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 원시 값에 접근할 수 있다. 즉, 원시 값을 할당한 변수는 원시 값 자체를 값으로 가진다.
객체: 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값에 접근할 수 있다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소 그 자체이다.
잘 이해가 안간다면 그림을 살펴보자~
원시 값
객체
위에서 설명했던 것이 그림을 통해 보면 좀 더 쉽게 이해가 갈 것이다.
즉, 원시 값과 객체의 차이점은 객체의 메모리 주소는 객체의 내용을 가지는 다른 메모리 공간을 참조한다는 것이다.
따라서 아래와 같이 얕은 복사를 하게 된다면
결과는 주석의 내용과 같다
var obj1 = { name: 'lee' };
var obj2 = obj1;
console.log(obj1 === obj2); // true
obj1.name = 'Kim';
obj2.add = 'Seoul';
console.log(obj1, obj2); // obj1 =obj2 = { name: 'Kim', add: 'Seoul' }
console.log(obj1 === obj2); // true
이것은 두 개의 식별자가 하나의 객체를 공유한다는 것을 의미한다. 그러므로 원본과 사본 중 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다.
자바스크립트에서는 다양한 객체 생성 방법이 있다.
1. 객체 리터럴
2. Object 생성자 함수
3. 생성자 함수
4. Object.create 메서드
5. 클래스 (ES6 문법)
- 객체 리터럴로 객체 생성
가장 일반적이고 간단한 방법.
나머지 방법은 뒤에서 다루겠다.const person = { //프로퍼티 name:"Joe", //프로퍼티 키는 name, 값은 "Joe" // 메서드 sayHi : function(){console.log(`Hi I'm ${name}`);} }
프로퍼티 키는 문자열 or 심벌 값
프로퍼티 값은 모든 값이 될 수 있다.
const myself={
name : 'Code Kim',
age : 30,
location : {
country : 'South Korea',
city : 'Seoul'
}
}
console.log(myself.name);
console.log(myself.age);
console.log(myself.location);
let myName = "Joe";
console.log(myself.myName); //undefined
// myName은 myself에 정의된 프로퍼티가 아님
// myname.name으로만 접근 가능
// 만약 대괄호 표기법이라면 접근가능
let myself={
name : 'Code Kim',
age : 30,
location : {
country : 'South Korea',
city : 'Seoul'
}
}
// 객체 안의 키에 접근 할 때는 반드시 따옴표로 감싼 문자열이어야 한다.
console.log(myself['name']);
console.log(myself['age']);
console.log(myself['location']);
let obj = {
cat: '고양이',
dog: '개',
};
let dog = 'cat';
//대괄호 표기법은 obj안에 dog프로퍼티를 찾지 않고,
//변수dog에 cat을 대입하여 문자열값이 패스되고 cat프로퍼티를 찾는다
let sound1 = obj[dog];
console.log(sound1); // 고양이
//점 표기법은 변수에 접근할 수 없어 dog변수의 값 대신 dog문자열의 값을 찾는다
let sound2 = obj.dog;
console.log(sound2); // 개
다만,키 값이 동적으로 변하는 변수를 통해 obj 프로퍼티를 참조할 때는 부조건 대괄호 표기법을 사용해야한다.
상황에 맞게 잘 활용하자.
- 얕은 복사(shallow copy)는 원본 객체 타입 테이터가 저장된 주소를 공유하는 복사본을 만드는 것.
원본이나 복사본 중 하나를 변경하면 다른 객체가 변경될 수 있다.
따라서 원본이나 복사본에 예상치 못한 변경이 발생할 수 있다.- 깊은 복사(deep copy)의 경우는 원본 객체 타입 테이터 저장된 주소를 공유하지 않는 복사본을 만드는 것.
원본이나 복사본을 변경할 때 다른 객체가 변경되지 않는다.
따라서 원본이나 복사본을 의도하지 않게 변경되는 것을 방지할 수 있다.
웹 애플리케이션 구축 시 리액트를 활용하는데 리액트 환경에서 데이터를 변경할 때는 원본 데이터를 유지한 채 원본 데이터를 복사해서 변경 사항을 복사본에 반영하여 작업해야 한다.
왜냐하면 원본 데이터와 변경된 데이터를 비교하여 차이가 감지 되었을 때 리렌더링을 진행하기 때문이다.
const arr = [1, 2, [3, 4]];
const obj = { a: 1, b: 4, c: { d: 3 } };
console.log('[Shallow Copy]');
// 배열
const shallowArr = arr;
console.log(`shallowArr `, shallowArr);
console.log(arr === shallowArr); //true
console.log(arr[2] === shallowArr[2]); //true
// 객체
const shallowObj = obj;
console.log(`shallowObj `, shallowObj);
console.log(obj === shallowObj); //true
console.log(obj.c === shallowObj.c); //true
const arr = [1, 2, [3, 4]];
const obj = { a: 1, b: 4, c: { d: 3 } };
console.log('[Deep Copy]');
// 배열
console.log('배열');
// 1. Spread syntax
const spreadArr = [...arr];
console.log('1. Spread syntax ->', spreadArr);
console.log(arr === spreadArr); //false
console.log(arr[2] === spreadArr[2]); //true
// 2. Array.concat()
const concatArr = [].concat(arr);
console.log('2. Array.concat() ->', concatArr);
console.log(arr === concatArr); //false
console.log(arr[2] === concatArr[2]); //true
// 3. Array.slice()
const sliceArr = arr.slice(0);
console.log('3. Array.slice() ->', sliceArr);
console.log(arr === sliceArr); //false
console.log(arr[2] === sliceArr[2]); //true
// 객체
console.log('객체');
// 1. Spread syntax
const spreadObj = { ...obj };
console.log('1. Spread syntax ->', spreadObj);
console.log(obj === spreadObj); //false
console.log(obj.c === spreadObj.c); //true
// 2. Object.assign()
const assignObj = Object.assign({}, obj);
console.log('2. Object.assign() ->', assignObj);
console.log(obj === assignObj); //false
console.log(obj.c === assignObj.c); //true
const arr = [1, 2, [3, 4]];
const obj = { a: 1, b: 4, c: { d: 3 } };
console.log('[완전한 Deep Copy]');
// 1. lodash 라이브러리를 활용
// "npm install lodash"를 통해 설치
// lodash의 cloneDeep을 사용한 깊은 복사
console.log('1. lodash library');
const _ = require('lodash');
const lodashArr = _.cloneDeep(arr);
console.log('a. lodashArr', lodashArr);
console.log(arr === lodashArr); //false
console.log(arr[2] === lodashArr[2]); //false
const lodashObj = _.cloneDeep(obj);
console.log('b. lodashObj ->', lodashObj);
console.log(obj === lodashObj); //false
console.log(obj.c === lodashObj.c); //false
// 2. JSON.stringify(), JSON.parse()
console.log('2. JSON.stringify(), JSON.parse()');
const jsonArr = JSON.parse(JSON.stringify(arr));
console.log('a. jsonArr', jsonArr);
console.log(arr === jsonArr); //false
console.log(arr[2] === jsonArr[2]); //false
const jsonObj = JSON.parse(JSON.stringify(obj));
console.log('a. jsonObj', jsonObj);
console.log(obj === jsonObj); //false
console.log(obj.c === jsonObj.c); //false
추가적으로 대부분 2번의 깊은 복사 즉, spred syntax를 사용해 해결되는 것이 많다. 다른 방법도 알아두고 2번의 방법을 잘 사용할 줄 알아야 할 것 같다.
리액트 실전 사용 예제)
const [cartList, setCartList] = useState(
// 원본 데이터
[
{
id: 1,
title: IPhone 14 Pro,
quantity : 1
},
{
id: 2,
title: IPhone 13 Pro Max,
quantity : 1
}
]
);
// 상품 추가(Create)
const addItem = () => {
setCartList([
...cartList, //2번의 깊은 복사 방법 사용.
{
id: 3,
title: IPhone 11 Pro,
quantity : 2
}
])
}
객체에 대해 알아 보았다.
출처 및 참고 - 모던 자바스크립트 Deep Dive (이응모)