깊은복사와 얕은복사의 개념

김명성·2022년 1월 27일
1

자바스크립트

목록 보기
16/26

깊은 복사, 얕은 복사

자바스크립트에서 값은 원시값과 참조 값으로 나뉜다.


원시형


  • Number
  • String
  • Boolean
  • null
  • undefined
원시값은 값을 복사 할 때 복사된 값을 다른 메모리에 할당 하기 때문에 원래의 값과 복사된 값이 서로에게 영향을 미치지 않는다.

참조형

  • Object
  • Symbol
  • Array
참조형은 식별자의 메모리 값에 객체의 주소가 할당되어 있는 값이기 때문에 서로 같은 값을 공유한다
const a = {number : 1};
let b = a;

b.number = 2;

console.log(a); // {number : 2}
console.log(b); // {number : 2}

이런 객체의 특징 때문에 불변성을 지키기 위해서는 복사본을 만들어 사용하는데, 객체를 복사하는 방법은 크게 두가지로 나뉜다.


1.얕은 복사 (Shallow Copy)

앝은 복사는 참조(주소)값의 복사를 나타낸다.
하나의 참조형 자료가 변경된다면 변경되는 값을 공유한다.


const obj = { vaule: 1 }
const newObj = obj;

뉴오브젝트.vaule = 2;

console.log(오브젝트.vaule); // 2
console.log(오브젝트 === 뉴오브젝트); // true

불변성을 지키기에는 어려운 방법이다.

두 객체를 비교하면 true로 나온다.

얕은 복사의 종류


Object.assign()

let 오브젝트 = Object.assign(obj1,obj2)

obj1에 obj2를 삽입한다.

Object.assign(타겟,출처)


객체의 속성이 복사 불가능하면 오류가 발생되는데, 오류가 발생하기 전에 이미 추가,변경된 값은 대상 객체에 유지된다.
같은 키를 가진 속성은 뒤에 위치한 객체의 프로퍼티값으로 덮어 쓴다.

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };
const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

전개연산자(Spread Operator)

새로운 변수에 전개연산자를 사용하여 객체나 배열의 값을 넘기는 용도로 사용할 수 있다.
전개 연산자는 (...변수이름)으로 사용한다.

    const o1 = {
        a: 'A',
        b: 'B'
    };
    const o2 = {
        c: 'C',
        d: 'D'
    };
    const o3 = {...o1, ...o2};
    console.log(o3);
    {
        a: 'A',
        b: 'B',
        c: 'C',
        d: 'D'
    }

전개 연산자를 사용하면 객체 자체가 할당되는 것이 아닌, 각각의 값이 할당 된다.
전개 연산자를 이용한 복사는 1차원에서만 유효하다.
전개 연산자로 할당하면 2차원 배열로 변환되지는 않는다.
하지만 2차원 이상의 배열을 할당할 땐 1차원 요소만 깊은 복사가 되고,
2차원 이상의 배열은 참조값으로 값을 공유하게 된다.

    let 배열1 = [4, 5, [6, 7]];
    let 배열2 = [1, 2, 3, ...배열1];
    console.log(배열2); // [1, 2, 3, 4, 5, [6, 7]]
	배열1 = [4,5,[10,100]];
	console.log(배열2); // [1, 2, 3, 4, 5, [6, 7]]

전개 연산자는 원본 배열을 그대로 유지하면서 새로운 배열을 만들기에 기존 배열을 유지해야 하는 상황에서 유용하다.
const arr1 = [1, 2, 3];
const arr2 = [...arr1].reverse();
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [3, 2, 1]

아래와 같은 방법으로 전개연산자를 사용할 수 있다.
1.
let arr1 = [1, -2, 3, 4, 5];
let arr2 = [9, 5, 3, -8, 1];

Math.max(...arr1, ...arr2) // 9
const maxValue = Math.max(1, ...arr1, 2, ...arr2, 30)// 30

2.
let arr1 = [3, 5, 1];
let arr2 = [8, 9, 15];

let arr3 = [0, ...배열1, 2, ...배열2];

console.log(배열3) // 입력된 순서대로 0,3,5,1,2,8,9,15 출력됨.

배열이 아니더라도 이터러블이라면 전개 연산자를 사용할 수 있다.

    객체에서의 spread
    const slime = {
    name: '슬라임'
    };

    const cuteSlime = {
    name: '슬라임',
    attribute: 'cute'
    };

    const purpleCuteSlime = {
    name: '슬라임',
    attribute: 'cute',
    color: 'purple'
    };

기존의 객체는 수정하지 않고 새로운 객체를 만드려고 할때,
전개연산자 문법을 사용하면 다음과 같이 작성 할 수 있다.


    const slime = {
    name: '슬라임'
    };

    const cuteSlime = {
    ...slime,
    attribute: 'cute'
    };

    const purpleCuteSlime = {
    ...cuteSlime,
    color: 'purple'
    };

아래 예시에선 모든 인수가 배열 args에 모입니다.

    function sumAll(...args) {
    let sum = 0;
    for (let arg of args) sum += arg;
    return sum;
    }

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6

깊은 복사 Deep Copy
깊은 복사된 객체는 객체안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 말한다.

1. 재귀함수를 이용한 복사

    const obj = {
        a: 1,
        b: {
        c: 2,
        },
    };
    
    function copyObj(obj) {
        const result = {};
    
        for (let key in obj) {
        if (typeof obj[key] === 'object') {
            result[key] = copyObj(obj[key]);
        } else {
            result[key] = obj[key];
        }
        }
    
        return result;
    }
    const copiedObj = copyObj(obj);
    copiedObj.b.c = 3
    obj.b.c === copiedObj.b.c //false 
  1. JSON.stringify()
    JSON.stringify()는 객체를 json 문자열로 변환하는데 이과정에서 원본 객체와의 참조가 모두 끊어진다. 객체를 json 문자열로 변환후 JSON.parse()를 이용해 다시 자바스크립트 객체로 만들어주면 깊은 복사가 된다.

하지만 이방법은 사용하기는 쉽지만 다른 방법에비해 아주 느리다고 알려져있다.

const obj = {
        a: 1,
        b: {
        c: 2,
        },
    };
    
    const copiedObj = JSON.parse(JSON.stringify(obj));
    
    copiedObj.b.c = 3
    
    obj.b.c === copiedObj.b.c //false

JSON.parse()
문자열을 JSON으로서 구문 분석하고, 선택적으로 분석 결과의 값과 속성을 변환해 반환합니다.
JSON.stringify()
주어진 값에 해당하는 JSON 문자열을 반환합니다. 선택 사항으로 특정 속성만 포함하거나 사용자 정의 방식으로 속성을 대체합니다.

출처

https://velog.io/@th0566/Javascript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC
https://ui.toast.com/weekly-pick/ko_20160826
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
https://velog.io/@recordboy/%EC%A0%84%EA%B0%9C-%EC%97%B0%EC%82%B0%EC%9E%90Spread-Operator
https://velog.io/@recordboy/JavaScript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%ACShallow-Copy%EC%99%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%ACDeep-Copy
https://velog.io/@bigbrothershin/JavaScript-%ED%95%A8%EC%88%98-%EC%8B%AC%ED%99%94-%EB%82%98%EB%A8%B8%EC%A7%80-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EC%99%80-%EC%A0%84%EA%B0%9C-%EC%97%B0%EC%82%B0%EC%9E%90

이벤트 위임(Event Delegation)
사용자의 액션에 의해 이벤트 발생 시 이벤트는 document 레벨까지 버블링 되어 올라간다. 이 때문에 자식 엘리먼트에서 발생하는 이벤트를 부모 엘리먼트에서도 감지할 수 있다. 이러한 동작을 이용해 사용할 수 있는 방법이 이벤트 위임이다. 특정 엘리먼트에 하나하나 이벤트를 등록하지 않고 하나의 부모에 등록하여 부모에게 이벤트를 위임하는 방법이 바로 그것이다.

코드를 통해 살펴보자. 예를 들어, 다음과 같은 마크업을 가진 메뉴가 있다고 하자.

<ul id="menu">
<li><button id="file">파일</button></li>
<li><button id="edit">편집</button></li>
<li><button id="view">보기</button></li>
</ul>

코드를 통해 살펴보자. 예를 들어, 다음과 같은 마크업을 가진 메뉴가 있다고 하자.

<ul id="menu">
<li><button id="file">파일</button></li>
<li><button id="edit">편집</button></li>
<li><button id="view">보기</button></li>
</ul>

각각의 기능 클릭 시 특정 동작을 하게 하려면 보통은 다음과 같이 이벤트를 등록한다.

document.getElementById("file").addEventListener("click", function(e) {
// 파일 메뉴 동작
});
document.getElementById("edit").addEventListener("click", function(e) {
// 편집 메뉴 동작
});
document.getElementById("view").addEventListener("click", function(e) {
// 보기 메뉴 동작
});
메뉴가 추가될 때마다 이벤트 핸들러가 하나씩 늘어날 것이다.

하지만 이벤트 위임을 사용하면 상위 엘리먼트인

0개의 댓글