자바스크립트 객체와 증감 연산자

허재원·2022년 10월 27일
0

벤딩머신 프로젝트

해당 프로젝트에서 Vanilla JavaScript로 옵저버 패턴을 활용하여 전역 관리 시스템을 구현하는 중에 문제가 발생했다. 그리고 문제의 시발점은 아래의 코드에서부터 시작했다.

// 객체, 배열 똑같은 상태인 경우 방지
if (JSON.stringify(_value) === JSON.stringify(value)) return;
case UPDATE_CART:
  return {
    ...state,
    cartItems: state.cartItems.map((item) =>
      item.colaId === action.payload ? { ...item, quantity: ++item.quantity } : item
    ),
  };

객체나 배열이 똑같은 상태로 들어올 때는 반응하지 않도록(리렌더링되지 않도록) 방지하기 위해 위 코드를 작성했는데 아래와 같은 예시 코드에서 계속 true 값으로 나와 위 조건에 true로 계속 리렌더링이 되지 않았다. 내가 생각했을때에는 분명히 값이 바뀌었는데,,,

그래서 따로 아래 코드로 따로 왜 true가 나오는지에 대해 공부하기 시작했다.

const all = {
  name: "oneny",
  schools: [{
      name: "Yorktown",
      students: 100
    },
    {
      name: "Stratford",
      students: 120
    },
    {
      name: "Washington & Lee",
      students: 200
    },
    {
      name: "Wakefield",
      students: 300
    },
  ],
};

const updatedAll = {
  ...all,
  schools: all.schools.map((e, i) => {
    return e.name === "Stratford" ? { ...e, students: 121 } : e;
  }),
}

const updatedAll2 = {
  ...all,
  schools: all.schools.map((e, i) => {
    return e.name === "Stratford" ? { ...e, students: --e.students } : e;
  }),
}

console.log(JSON.stringify(all) === JSON.stringify(updatedAll));
console.log(JSON.stringify(all) === JSON.stringify(updatedAll2));

처음 console.log(JSON.stringify(all) === JSON.stringify(updatedAll));은 all.schools배열에서 한 객체의 프로퍼티(students)의 값을 121 원시값으로 바꾸는 경우에는 당연히 false가 나왔다.
하지만 console.log(JSON.stringify(all) === JSON.stringify(updatedAll2));은 all.schools 배열에서 한 객체의 프로퍼티(students)의 값을 --e.students로 바꾸니 계속해서 true가 나왔다.

그래서 왜 그런지 계속 고민하다 배열이나 객체이면 그 안에 들어가서 원시값들을 비교해줘야 하나... 하면서 다음과 같은 코드를 작성했다.

function deepEqual(object1, object2) {
    const keys1 = Object.keys(object1);
    const keys2 = Object.keys(object2);

    if (keys1.length !== keys2.length) {
        return false;
    }

    for (const key of keys1) {
        const val1 = object1[key];
        const val2 = object2[key];
        const areObjects = isObject(val1) && isObject(val2);

        // are objects and do not equal to each other(using same function to compare primitives)
        if (areObjects && !deepEqual(val1, val2) || !areObjects && val1 !== val2) {
            return false;
        }
    }

    return true;
}

function isObject(object) {
    return object !== null && typeof object === "object";
}

이렇게 deepEqual 함수를 만들어서 두 객체를 비교해봤지만 그래도 문제는 해결되지 않았다... 그래서 계속 고민하던 중 stackoverflow에 물어봤다. 그리고 10분 뒤 바로 답글이 달렸다!

When you use Array.prototype.map(), if the element passed to your callback is an object, then mutating it will also mutate the original object in the original array. To see this in action, try printing 'all' after the .map() call. You will see that it has been changed.

console.log(JSON.stringify(all) === JSON.stringify(updatedAll));
console.log(JSON.stringify(all) === JSON.stringify(updatedAll2));
console.log(all);
console.log(all.schools[0] === updatedAll2.schools[0]); // true
console.log(all.schools[1] === updatedAll2.schools[1]); // false

헉...!! 다시 올라가서 추가로 console.log(all)을 작성하고 출력해보니 원본(all)에서 값이 변해있었다. updatedAll2를 분명 생각해서 작성했던 코드가 --e.students로 인해서 e.students = e.student - 1로 원본에 -1를 하고 그 결과를 할당하여 all의 schools 배열의 1번 인덱스 객체의 students도 영향이 갔던 것이다!!!

const all = {
  name: "oneny",
  schools: [{
      name: "Yorktown",
      students: 100
    },
    {
      name: "Stratford",
      students: 120
    },
    {
      name: "Washington & Lee",
      students: 200
    },
    {
      name: "Wakefield",
      students: 300
    },
  ],
};

const updatedAll2 = {
  ...all,
  schools: all.schools.map((e, i) => {
    return e.name === "Stratford" ? { ...e, students: e.students - 1 } : e;
  }),
}

console.log(JSON.stringify(all) === JSON.stringify(updatedAll2)); // false

그래서 빠르게 --e.students에서 e.students - 1로 수정하니 all에 변화시킨 것을 해결할 수 있었다... 어떻게 보면 너무 간단한 문제를 장황하게 설명한 것 같고, --와 같은 증감 연산자를 사용한다고 해서 원본(all)에까지 영향이 갈 것이라는 생각을 왜 못했을까..라는 생각을 해본다..
암튼 다시 프로젝트에 ++, -- 증감연산자 사용했던 것을 + 1, - 1로 바꾸어 문제를 해결해서 프로젝트를 진행했다.

0개의 댓글