내일배움캠프 9일차 TIL - 깊은 복사

Sunny·2024년 1월 2일
0

오늘은 숙제 코드를 구현하다가 생긴 의문점과 해결법에 대해 작성하고자 한다.

목표

다음과 같은 객체가 있다.

var user = {
    name: "john",
    age: 20,
}

이를 다음의 함수를 사용하여 별도의 객체로 복사하면서 passedTime만큼 age를 수정하도록 할 수 있었다.

var getAged = function (user, passedTime) {
    var agedUser = {};
    if (typeof user === 'object' && user !== null){
        for (var prop in user){
            if (prop == 'age'){
                agedUser[prop] = user[prop] + passedTime;
            }
            else {agedUser[prop] = getAged(user[prop], passedTime);}
        }
    }
    else {agedUser = user};
    return agedUser
}

굳이 이런 구조를 따온 것에는 이유가 있었다.
이후 user 내부에 객체가 들어가더라도 멋지게 복사해내고 싶었던 것이다.

그렇게 테스트를 위해 늘어난 John의 신상정보는 다음과 같다.

var user = {
    name: "John",
    age: 20,
    brother: { name: "Chalie", age: 15 },
    likes: ['dog', 'pizza', 'hokey'],
}

하지만 이 상태로 getAged를 사용해보면 문제가 발생한다.

1. 내부 객체 안에도 age 키가 있으면 그 값도 수정한다.

원본 객체에 John의 동생을 포함시키면

	brother: { name: "Chalie", age: 15 },

John의 동생도 복사될 때 나이를 먹는다.

var agedUser = getAged(user, 6);
...
	brother: { name: "Chalie", age: 21 },

이 경우에는 맞는 말이긴 하지만, 만약 John의 나이만 수정해야 한다면 어떻게 해야 할까?

2. typeof 함수로 object값이 나오는 것은 다 객체라고 판단한다.

null은 &&으로 걸러내고 있긴 하지만 예를 들면, 배열도 객체라고 판단한다.

    likes: ['dog', 'pizza', 'hokey']

이렇게 John이 좋아하는 것들을 배열 형태로 넣어 보면,

	likes: { '0': 'dog', '1': 'pizza', '2': 'hokey' }

복사된 객체는 배열도 객체로 만들어버린다.

시도

이 문제를 어떻게 해결할 수 있을까?

Try 1: 배열

var getAged = function (user, passedTime) {
    var agedUser = {};

    if (typeof user === 'object' && user !== null) {

        for (var prop in user) {

            if (prop === 'age') {
                agedUser['age'] = user.age + passedTime;
                console.log(agedUser)
            }
            else if (prop == 0) {
                agedUser = [];
                for (prop in user) {
                    agedUser[prop] = user[prop];
                    console.log(agedUser);

                }
            }
            else { agedUser[prop] = getAged(user[prop], passedTime); }
        }
    }
    else { agedUser = user };
    return agedUser;
}

이 코드는 배열 인식 부분을 개선하기 위한 코드이다.
배열의 index 부분을 인식하게 하기 위해 prop이 0이면 배열로 생각해달라고 쓰고,
비슷한 방식을 age에도 적용해서 효과가 있는지 콘솔 로그로 확인한다.

age는 문제 없이 John과 동생의 나이를 더해주고,
likes에도 요소가 배열 형태로 착착 들어간다.

하지만 이 방법에는 허점이 있다.
만약 객체의 키가 0이면 어떡할 것인가?
그래서 user에 키가 0인 정보를 추가했다.

var user = {
    name: "John",
    age: 20,
    brother: { name: "Chalie", age: 15 },
    likes: ['dog', 'pizza', 'hokey'],
    0: 'happy',
}

그러면 정말 실행 시 문제가 생긴다.

[
  'happy',
  name: 'John',
  age: 26,
  brother: { name: 'Chalie', age: 21 },
  likes: [ 'dog', 'pizza', 'hokey' ]
]

이를 해결하기 위해 첫 번째로는, 'prop == 0'에 && 조건을 넣는 것을 생각해보았다.
Object.entries()를 사용하면 배열은 key가 아니고 index니까 그걸 활용하면 될 줄 알았는데...
안 먹히기에 다른 배열로 테스트해보았더니 멀쩡하게 자기가 객체인 양 index를 key 삼아 출력하는 것이 아닌가.
그래서 코드로 객체와 배열을 구분하는 방법을 검색해보았더니,

Array.isArray(obj)

라는 방법이 이미 있었다.

Solution 1: 배열을 알아보다

var getAged = function (user, passedTime) {
    var agedUser = {};

    if (typeof user === 'object' && user !== null) {

        for (var prop in user) {

            if (prop === 'age') {
                agedUser['age'] = user.age + passedTime;
            }
            else if (Array.isArray(user)) {
                agedUser = user.map(function (item) {
                    return getAged(item, passedTime);
                });
            }
            else { agedUser[prop] = getAged(user[prop], passedTime); }
        }
    }
    else { agedUser = user };
    return agedUser;
}

당연하겠지만 배열일 경우 깔끔하게 인식한다.
for문도 .map으로 깔끔하게 바꿔주었다.

만약 typeof가 object로 처리하는 다른 자료형으로 인해 문제가 생겨도 else if를 추가하여 실제로는 어떤 자료형을 가지고 있는지 알아보도록 하면 된다.

Try 2: 떡국 금지

우선, 내가 시도한 방법은 나이를 못 먹게 하는 방법이었다.
예를 들면 '나이를 몇 번 먹었는지 변수로 세기'나 '나이 먹는 코드를 복사 끝난 후 적용하기' 같은 방법이다.
하지만 함수가 내부로 계속 들어가면서 겹겹이 실행되기 때문에 이것저것 생각해본 방법들이 잘 동작하지 않았다.

그리고 조언을 구해서 마침내 동작한 방법이 이것이다.

var getAged = function (user, passedTime) {
    var agedUser = {};

    if (typeof user === 'object' && user !== null) {

        for (var prop in user) {

            if (prop === 'age') {
                agedUser['age'] = user.age + passedTime;
            }
            else if (Array.isArray(user)) {
                agedUser = user.map(function (item) {
                    return getAged(item, passedTime);
                });
            }
            else { agedUser[prop] = getAged(user[prop], passedTime); }
        }
    }
    else { agedUser = user };
    if (agedUser['brother']){
        agedUser.brother.age -= passedTime;
    }
    return agedUser;
}

하지만 이 방법의 문제는 나이를 못 먹게 하는 게 아니라 나이를 추가로 먹었을 때 뺀다는 데 있다.
이 점을 개선해보았다.

Solution 2: 나이는 한 명만

var getAged = function (user, passedTime) {
    var agedUser = {};

    if (typeof user === 'object' && user !== null) {

        for (var prop in user) {
            if (Array.isArray(user)) {
                agedUser = user.map(function (item) {
                    return getAged(item, passedTime);
                });
            }
            else { agedUser[prop] = getAged(user[prop], passedTime); }
        }
    }
    else { agedUser = user };
    if (agedUser['brother']) {
        agedUser.age += passedTime;
    }
    return agedUser;
}

이 코드는 내부에 'brother'라는 키가 있다는 걸 알아채면 John에게만 나이를 먹게 한다.
사실 그렇기 때문에 내가 나이를 먹일 agedUser가 John이냐, 아니냐만 중요하다.

그렇다면, 이 부분을 'brother' 같은 구체적인 이름 없이도 해보고 싶긴 한데 이 점은 좀 더 고민해보도록 하겠다.

0개의 댓글