JavaScript 얕은 복사 깊은 복사

서나무·2022년 12월 2일
0

JavaScript

목록 보기
1/3
post-thumbnail

얕은 복사

얕은 복사는 참조타입의 변수를 직접 대입해서, 같은 메모리 주소를 참조하게 되는 것을 의미한다.

객체의 얕은 복사

// 초기 값을 가지는 init 객체
const init = {
  name: '',
  job: '',
};

// clone에 init 대입
const clone = init;
clone.name = '서나무';
clone.job = 'Frontend developer';

console.log(init);
console.log(clone);

코드를 실행시켜보면 clone 객체뿐 아니라 init 객체의 name job 도 함께 변경되었다.

배열의 얕은 복사

// 초기 값을 가지는 init 배열
const init = ['name', 'job'];

// clone에 init 대입
const clone = init;
clone[1] = 'game';

console.log(init);
console.log(clone);

코드를 실행시켜보면 clone 배열뿐 아니라 init 배열의 index1에 해당하는 요소도 함께 변경되었다.

깊은 복사

깊은 복사란 복사 대상 객체에게 새로운 메모리에 할당해서 복사 소스 객체다른 메모리 주소를 참조하도록 하는 것을 의미한다.

Spread 연산자 (전개 연산자)

JavaScript에서 객체와 배열을 복사할 때, spread 연산자를 사용할 수 있다.

객체

const init = {
  name: '',
  job: '',
};

const clone = { ...init };
clone.name = '서나무';
clone.job = 'Frontend developer';

console.log(init);
console.log(clone);

코드를 실행시켜보면 clone 객체의 name job만 변경된 것을 볼 수 있다.

배열

const init = ['name', 'job'];

const clone = [...init];
clone[1] = 'game';

console.log(init);
console.log(clone);

코드를 실행시켜보면 clone 배열의 index1에 해당하는 요소만 변경된 것을 볼 수 있다.

객체 Object.assign() 함수

assign 함수는 첫번째 인자로 복사 대상 객체를 넣어주고, 두번째 인자로 복사할 소스 객체를 넣어주면 된다.

const init = {
  name: '',
  job: '',
};

const clone = Object.assign({}, init);
clone.name = '서나무';
clone.job = 'Frontend developer';

console.log(init);
console.log(clone);

코드를 실행시켜보면 clone 객체의 name job만 변경된 것을 볼 수 있다.

배열 concat() 함수

concat 함수는 빈 배열을 선언하고 복사 소스 배열을 넣어주면 된다.

const init = ['name', 'job'];

const clone = [].concat(init);
clone[1] = 'game';

console.log(init);
console.log(clone);

코드를 실행시켜보면 clone 배열의 index1에 해당하는 요소만 변경된 것을 볼 수 있다.

JavaScript 깊은 복사 함수의 한계

JavaScript에서 제공하는 전개 연산자, assign함수, concat함수는 치명적인 한계가 있다.

깊이가 1인 경우에만 깊은 복사가 되고, 깊이가 깊어지는 다차원 객체, 배열의 경우에는 얕은 복사가 된다는 것이다.

const init = {
  name: '',
  job: '',
  use: {
    lang: '',
  },
};

const clone = { ...init };
clone.name = '서나무';
clone.job = 'Frontend developer';
clone.use.lang = 'JavaScript';

console.log(init);
console.log(clone);

코드 실행 결과

{ name: '', job: '', use: { lang: 'JavaScript' } } // ! 얕은 복사
{ name: '서나무', job: 'Frontend developer', use: { lang: 'JavaScript' } }

name job은 깊은 복사가 됐지만, use 객체의 lang은 얕은 복사가 됐다.

다차원 객체, 배열 깊은 복사

외부 라이브러리를 사용하지 않고 다차원 객체, 배열을 깊은 복사를 해보자.

재귀 함수 구현하기

검색을 해보니 객체와 배열이 혼합된 형태의 깊은 복사를 할 수 있는 재귀 함수 예제가 없었다.

그래서 직접 재귀 함수를 구현해봤다.

const deepCopy = (target) => {
  /**
   * 객체를 받아서 깊은 복사 후 반환하는 재귀함수
   * @param {Object} object 복사할 객체
   * @returns {Object} 복사된 객체
   */
  const deepCopyObject = (object) => {
    if (object === null || typeof object !== 'object') return object;
    const clone = {};
    for (const key in object) {
      if (Array.isArray(object[key])) {
        clone[key] = deepCopyArray(object[key]);
      } else {
        clone[key] = deepCopyObject(object[key]);
      }
    }
    return clone;
  };

  /**
   * 배열을 받아서 깊은 복사 후 반환하는 재귀함수
   * @param {Array} array 복사할 배열
   * @returns {Array} 복사된 배열
   */
  const deepCopyArray = (array) => {
    if (array === null || !Array.isArray(array)) return array;
    const clone = [];
    for (let key = 0; key < array.length; key++) {
      if (Array.isArray(array[key])) {
        clone[key] = deepCopyArray(array[key]);
      } else {
        clone[key] = deepCopyObject(array[key]);
      }
    }
    return clone;
  };

  if (typeof target === 'object' || Array.isArray(target)) {
    return Array.isArray(target)
      ? deepCopyArray(target)
      : deepCopyObject(target);
  }
  return target;
};

재귀 함수를 이용해서 다차원 객체를 깊은 복사 해보자.

const init = {
  name: '',
  job: '',
  use: {
    lang: '',
  },
  hobby: [
    {
      game: '',
    },
  ],
};

const clone = deepCopy(init);
clone.use.lang = 'JavaScript';
clone.hobby[0].game = 'Crazy Arcade';

console.log(init);
console.log(clone);

코드 실행 결과

{ name: '', job: '', use: { lang: '' }, hobby: [ { game: '' } ] }
{
  name: '',
  job: '',
  use: { lang: 'JavaScript' },
  hobby: [ { game: 'Crazy Arcade' } ]
}

깊은 복사가 잘 되었고, 이는 객체 뿐 아니라 다차원 배열도 깊은 복사가 가능하다.

JSON 객체 사용하기

JSON 객체는 두 가지 메서드를 가지고 있다.

  • stringify : javascript 값을 JSON으로 변환 후 반환
  • parse : JSON을 javascript 값으로 변환 후 반환

복사할 소스 객체를 stringify메서드로 JSON으로 만든 후 다시 parse메서드로 javascript 값으로 변환하면 된다.

const init = {
  name: '',
  job: '',
  use: {
    lang: '',
  },
};

const clone = JSON.parse(JSON.stringify(init));
clone.name = '서나무';
clone.job = 'Frontend developer';
clone.use.lang = 'JavaScript';

console.log(init);
console.log(clone);

JSON 메서드는 정말 느릴까?

JSON 객체의 메서드를 사용하는 것은 매우 느리다는 의견이 있어서 정말 그런지 궁금했다.

그래서 콘솔로 JSON 메서드와 내가 구현한 재귀 함수의 실행 시간을 출력해봤다.

const init = {
  'depth1-1': {
    dethp2: [
      'depth3',
      {
        depth4: '',
      },
      [
        {
          'depth5-1': '',
          'depth5-2': '',
          'depth5-3': {
            depth6: '',
          },
        },
      ],
    ],
  },
  'depth1-2': {
    dethp2: [
      'depth3',
      {
        depth4: '',
      },
      [
        {
          'depth5-1': '',
          'depth5-2': '',
          'depth5-3': {
            depth6: '',
          },
        },
      ],
    ],
  },
};

console.time('Custom recursion function');
const clone1 = deepCopy(init);
console.timeEnd('Custom recursion function');

console.time('JSON object');
const clone2 = JSON.parse(JSON.stringify(init));
console.timeEnd('JSON object');

내가 만든 재귀 함수가 훨씬 더 느리다. 🤣🤣

profile
주니어 프론트엔드 개발자

0개의 댓글