자바스크립트

자바스크립트 - 기본자료형, 참조자료형, 깊은 복사, 얕은 복사, Object.assign()

judyshin 2024. 4. 16. 17:35

기본자료형 & 참조자료형

기본 자료형
(Primitive Value)


문자열(String) immutable
숫자형(Number)
논리형(Boolean)
undefined
null
BigInt
Symbol
참조 자료형
(Referenct Value)
객체 mutable

 

기본자료형과 참조자료형이 어떻게 복사되는 지 알아봅시다

기본자료형의 복사

기본 자료형(문자열 ,숫자, 불린 값 등)은 변수에 데이터를 할당할 때 데이터 값이 할당이 됩니다

따라서 다른 변수에 이 변수를 할당하면 새 변수에 데이터 값을 복사해서 할당합니다

let flower = 'Rose';
let copy = flower;    // 변수 flower의 데이터 "Rose"를 할당

> 이 상태에서 원래 변수의 값을 바꾸면 어떻게 될까요?

flower = 'Lily';

console.log(flower);  // Lily 출력
console.log(copy);   // Rose 출력

copy = 'Tulip';

console.log(flower);  // Lily 출력
console.log(copy);   // Tulip 출력
  • 위 코드를 보면 원래 변수의 값만 변경이 되는 것을 볼 수 있습니다
    또한 값을 복사한 변수의 값을 변경해도 원래의 변수에 영향을 미치지 않습니다

같은 값을 가진 두 개의 변수가 각각의 메모리 공간을 가지게 됩니다.

이렇게 복사한 다른 변수에 영향을 미치지 않는 것을 깊은 복사라고 합니다

 

*원시값을 immutable(변하지 않는)의 성질이 있다고 합니다

참조자료형의 복사

기본 자료형과 다르게 참조 자료형(객체 등)은

변수에 데이터 자체가 아닌, 데이터가 저장된 메모리의 주소값을 할당합니다

const person = {
    name: 'Judy',
    age: 15,
}

const copyPerson = person; 
person.name = 'Budy';

console.log(person.name);	//출력 Budy
console.log(copyPerson.name);    //출력 Budy
  • 위 코드에서 person이라는 객체를 선언하고, 새로운 변수 copyPerson에 person을 할당했습니다
    그리고 person.name의 값을 변경했습니다
    그러자 직접적으로 바꾸지 않은 copyPerson.name의 값까지 변경돼 버렸습니다
copyPerson.name = "Gudy"

console.log(person.name);	// 출력 Gudy
console.log(copyPerson.name);  // 출력 Gudy
  • copyPerson.name의 값을 변경해도 person.name까지 변경되는 것을 확인할 수 있습니다

이는 두 객체가 같은 메모리의 주소값을 참조하고 있기 때문입니다

두 객체가 하나의 저장공간을 공유한다고 볼 수 있습니다

같은 주소값을 참조하여 같은 메모리 공간을 공유하고 있다

이렇게 데이터를 복사했을 때 한쪽 데이터가 변경되면

다른 쪽 데이터도 변경되어 서로 영향을 받는 것을 얕은 복사라고 합니다

얕은 복사: 복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조(메모리 내의 같은 값을 가리킴)를 공유하는 복사
깊은 복사: 복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조(메모리 내의 같은 값을 가리킴)를 공유하지 않는 복사
-MDN Web Docs-

객체의 깊은 복사

1. Object.assign({}, object) - 얕은 복사

const person = {
	name: 'Judy',
    location: {
    	country: 'Korea',
        city: 'Seoul'
    }
}

var copied = Object.assign({}, person)
copied.name = "Budy";
copied.location.city = "Busan";

console.log(person.name);    	// Judy 출력 - 영향을 받지 않음
console.log(person.location.city);	// Busan 출력 - 영향을 받음
  • copied의 location의 프로퍼티를 변경하자 손대지 않은 person의 location의 프로퍼티가 변경된 것을 볼 수 있습니다

객체를 새로운 저장공간에 복사하고 싶을 땐 Object.assign({}, object)을 사용할 수 있습니다

중첩되지 않은 계층의(1계층) 값은 변경되도 서로 간에 영향을 끼치지 않습니다

그러나 위 코드에서와 같이 객체 내 중첩된 객체가 변경되면 영향을 끼칩니다

이유는 객체 내에 객체가 중첩될 때, 여기서 얕은 복사가 일어나기 때문입니다

JavaScript에서, 모든 표준 내장 객체의 복사 작업(
전개 구문, Array.prototype.concat(), Array.prototype.slice(), Array.from(), Object.assign(), Object.create()
)은 깊은 복사가 아닌 얕은 복사본을 생성합니다.

-MDN Web Docs의 얕은 복사 중-

2. JSON.stringify() & JSOM.parse() 사용하기 - 깊은 복사

const person = {
	name: 'Judy',
    location: {
    	country: 'Korea',
        city: 'Seoul'
    }
}

const copied = JSON.parse(JSON.stringify(person));

copied.name = "Budy";
copied.location.city = "Busan";

console.log(person.name);    	// Judy 출력 - 영향을 받지 않음
console.log(person.location.city);	// Seoul 출력 - 영향을 받지 않음
  • JSON.stringify()의 결과: '{"name":"Judy","location":{"country":"Korea","city":"Seoul"}}'
  • JSON.parse()는 JSON을 다시 객체로 만들어줌

객체를 직렬화하여 JSON으로 만든 후, 다시 객체로 만들어서 새로운 변수에 할당하면

원래 객체와 복사된 객체는 서로 영향을 끼치지 않게 됩니다

 

그러나 이것도 완전한 해결책은 아닙니다

  • 함수, Date, undefined, Infinity, -Infinity, NaN, RegExp 등 일부 데이터 타입은 올바르게 복사되지 않을 수 있습니다.
  • 순환 참조가 있는 객체는 이 방법으로 복사할 수 없습니다.

3. deepCopy() 함수 선언 및 사용 - 깊은 복사

function deepCopy(obj) {
	// obj가 null 이나 객체 타입이 아니라면 반환
    if (obj === null || typeof obj !== "object") {
    return obj;
  }
  // obj가 배열인지 객체인지에 따라 초기화
  const copy = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
  		// 객체 내의 객체나 배열이면 재귀적으로 함수 호출
        copy[key] = deepCopy(obj[key]);
    }
  return copy;
}
  • 배열, 함수까지 가능하나 Date 등의 객체 타입은 불가하고, 순환 참조 처리도 하지 않았습니다

+ChatGPT4가 짜준 궁극의 deepCopy() - 써보진 않음

더보기
function deepcopy(object, cache = new Map()) {
    if (object === null || typeof object !== 'object') {
        return object;
    }

    // 순환 참조 처리
    if (cache.has(object)) {
        return cache.get(object);
    }

    // 배열 처리
    var newObject = Array.isArray(object) ? [] : {};

    // 캐시에 객체 추가
    cache.set(object, newObject);

    // Symbol 포함한 모든 프로퍼티 처리
    Reflect.ownKeys(object).forEach(key => {
        newObject[key] = deepcopy(object[key], cache);
    });

    // 특수 객체 처리 (예: Date, RegExp)
    if (object.constructor && object.constructor !== Object) {
        if (object.constructor === Date) {
            newObject = new Date(object.valueOf());
        } else if (object.constructor === RegExp) {
            newObject = new RegExp(object.source, object.flags);
        }
    }

    return newObject;
}

4. lodash.clonedeep 함수 사용 - 깊은 복사

> 결과를 보장받고 싶다면 lodash npm 패키지를 다운받아 사용하도록 합시다!

 

++궁금했던 것

속성이 모두 원시 값인 객체의 복사는 깊은 복사 와 얕은 복사의 정의에 모두 부합합니다.
그렇지만 중첩된 속성이 없기 때문에 이런 복사의 깊이에 대해 이야기하는 것은 다소 쓸모가 없습니다.
보통 중첩된 속성을 변경하는 맥락에서 깊은 복사하는 것에 대해 이야기합니다.

얕은 복사의 경우, 중첩된 객체의 값이 아닌 최상위 속성만 복사합니다.
-MDN Web Docs의 얕은 복사 중-

 

참고: 
https://hwani.dev/js-primitive-reference-types/

https://junvelee.tistory.com/62

 

2024.04.04 - [자바스크립트] - 자바스크립트 - 객체, 프로퍼티, key, value, 메서드

 

자바스크립트 - 객체, 프로퍼티, key, value, 메서드

객체(Object)란? 자바스크립트에서는 원시 타입을 제외한 나머지 모두가 객체입니다(함수나 배열도 객체입니다) JavaScript 내장 객체: 자바스크립트 엔진이 구동되는 시점에 바로 제공되고 어디에

judyshin.tistory.com