변경 가능한 값-객체(참조 값)

1Hoit·2022년 9월 21일
0

자바스크립트

목록 보기
3/25

지난 시간에 이어서...

지난 번에 원시 값에 대해 공부해 보았다. 이와 밀접한 관련이 있는 객체에 대해 알아보자!

객체 - 변경 가능한 값

원시 값 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 문법)

  1. 객체 리터럴로 객체 생성
    가장 일반적이고 간단한 방법.
    나머지 방법은 뒤에서 다루겠다.
const person = {
  //프로퍼티
  name:"Joe", //프로퍼티 키는 name, 값은 "Joe"
  // 메서드  
  sayHi : function(){console.log(`Hi I'm ${name}`);} 
}

객체는 프로퍼티의 집합이며, 이는 키와 값으로 구성된다.

프로퍼티 키는 문자열 or 심벌 값
프로퍼티 값은 모든 값이 될 수 있다.

프로퍼티 키는 프로퍼티 값에 접근 할 수있는 이름이다.

  • 식별자 네이밍 규칙에 따라 작성하는 것이 좋다.
  • 규칙에 따른다면 키는 " " 생략이 가능하다.

프로퍼티 접근 방법

  1. 마침표 표기법 (Dot Notation): . 을 이용
  2. 대괄호 표기법 (Bracket Notation): [...] 을 이용

  1. 마침표 표기법 (Dot Notation)
    1) 프로퍼티 식별자는 오로지 알파벳만 가능하다(_, &, $포함)
    2) 숫자로 시작할 수 없고 변수를 포함할 수 없다.
    3) key는 객체의 프로퍼티만 허용되기 때문에, 다른 변수를 통해 key값을 참조할 수 없다.
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으로만 접근 가능
// 만약 대괄호 표기법이라면 접근가능
  1. 대괄호 표기법 (Bracket Notation)
    1) 프로퍼티 식별자는 문자열 혹은 문자열을 참조하는 변수
    2) 숫자로 시작할 수 있고 변수,공백 사용이 가능하다.
    3) 변수가 문자열로 해석되면 변수 또한 쓸 수 있다.
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)의 경우는 원본 객체 타입 테이터 저장된 주소를 공유하지 않는 복사본을 만드는 것.
    원본이나 복사본을 변경할 때 다른 객체가 변경되지 않는다.
    따라서 원본이나 복사본을 의도하지 않게 변경되는 것을 방지할 수 있다.

이게 중요한 이유는?

웹 애플리케이션 구축 시 리액트를 활용하는데 리액트 환경에서 데이터를 변경할 때는 원본 데이터를 유지한 채 원본 데이터를 복사해서 변경 사항을 복사본에 반영하여 작업해야 한다.
왜냐하면 원본 데이터와 변경된 데이터를 비교하여 차이가 감지 되었을 때 리렌더링을 진행하기 때문이다.

  1. 얕은 복사
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
  1. 깊은 복사
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
  1. 완전한 깊은 복사 - lodash사용
  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 (이응모)

profile
프론트엔드 개발자를 꿈꾸는 원호잇!

0개의 댓글