자바스크립트 원시 타입과 객체를 사용할 때 종종 실수하는 것
Tags: javascript
Published: 2021-08-14 12:17:47

자바스크립트는 값을 두 가지로 분류한다. 원시 값<sup>primitive value</sup>과 객체<sup>object</sup>가 있다.

함수의 매개변수에서 값을 사용할 때 종종 내가 원하는 값이 나오지 않을 때가 있었는데, 이러한 특성을 몰랐었기 때문이다.

그리고 한창 리액트<sup>react</sup>가 유행하던 2017년 즈음엔 상태<sup>state</sup> 값으로 객체를 사용할 때 값을 변경해도 업데이트가 되지않는게 한창 이슈였는데 같은 맥락이다.
⠀
⠀
⠀
## 값을 변경할 수 없는 원시 값
변경할 수 없는걸 흔히 불변<sup>immutable</sup>이라 부른다. 원시 값은 이 불변이라는 조건을 가지게 된다.

하지만 다음 소스를 살펴보자
```javascript
let fruitToBuy = "graph"
fruitToBuy = "apple"
console.log(fruitToBuy) // apple
```
불변이라 말했지만 <var>fruitToBuy</var> 변수는 <code>graph</code>에서 <code>apple</code>로 변했다. 

이는 apple 값을 가진새로운 string 타입의 값을 생성한 후 해당 변수에 할당한 것이다.
⠀
⠀
⠀
## 원시 값의 데이터 타입
원시 값에는 다음의 데이터 타입이 존재한다.
<code>undefined</code>, <code>null</code>, <code>boolean</code>, <code>string</code>, <code>number</code>, <code>symbol</code>(ECMAScript 2015), <code>BigInt</code>(ECMAScript 2020)

그러나 찝찝한 부분이 있다. 분명 숫자나 문자도 객체처럼 프로퍼티와 메소드를 가진다. 또한 문자열의 경우 배열이 아닌가? 구현이 어떻게 되었든 스펙에서는 원시 값으로 분류하고 있으니 더이상 객체로 생각하지 말자.
⠀
⠀
⠀
## 그렇다면 객체는 어떻게 변한다는 말일까?
객체 사용은 다음과 같다.
```javascript
const obj = { jobTitle: "개발자" }
obj.greeting = "안녕"

console.log(obj) // { jobTitle: "개발자", greeting: "안녕"}
```
흔히 사용하는 방식이다.

문제로 인식하기 시작하는 시점은 비교할 때이다.
```javascript
obj === { jobTitle: "개발자", greeting: "안녕" } // false

{} === {} // false
```
직관적으로 내용이 같으니 true가 될 것 같지만 아니다. 뭐라고 표현해야 좋을지 모르겠지만 객체는 (우리는 볼 수 없지만)자신이 가진 고유 식별자(C언어의 주소값 같은)를 비교한다.

다음의 예를 보자.
```javascript
const objCopied = obj

console.log(obj === obj) // true
console.log(obj === objCopied) // true
```
두 변수는 이름은 다르지만 같은 객체를 참조하고 있기 때문에 같다. 

그래서 둘 중 하나만 변경해도 둘다 바뀌는 모습을 볼 수 있다.
```javascript
objCopied.greeting = "안녕하세요"

console.log(objCopied) // { jobTitle: "개발자", greeting: "안녕하세요" }
console.log(obj) // { jobTitle: "개발자", greeting: "안녕하세요" }
```
둘 다 바뀌는게 아니라 같은 객체를 참조한다는 표현이 맞겠다.
⠀
⠀
⠀
## 함수 매개변수에서 사용하는 값
함수를 사용하면 딱봐도 복잡해진다.
```javascript
let myVarType = "let"

function myFunction (myVarType) {
  console.log(myVarType) // let
  myVarType = "const"
  
  return myVarType
}

let result = myFunction(myVarType)
console.log(myVarType) // let
console.log(result) // const
```
매개변수로 원시 값인 문자열을 전달했는데, 뭐.. 문법을 배웠다면 이렇게 쓰기 시작한다.

문제는 자바스크립트에 슬슬 익숙해져 객체를 활용하기 시작하는데 아래와 같이 매개변수로 객체를 전달할 때 발생한다.
```javascript
let me = { jobTitle = "개발자" }

function turnOver (me) {
  me.jobTitle = "디자이너"
  return me
}

let result = turnOver(me)
console.log(me) // { jobTitle: "디자이너" }
console.log(result)  // { jobTitle: "디자이너" }
console.log(me === result) // true
```
직관적으론 아까처럼 달라야 하는데, 같은 객체다. 매개변수의 모양때문에 해깔린다면 다음처럼 바꿀 수 있다.
```javascript
//function turnOver() {
  let me2 = me
  me2.jobTitle = "디자이너"
//  return me2
//}
// function을 주석처리한건 혹시나 스코프를 설명해야할지도 몰라서...
console.log(me === me2) // true
```
그럼 이전 챕터에서 다루었던 객체의 예제와 모양이 같아졌다. 매개변수도 결국 일반적인 변수선언과 똑같기 때문에 다르게 생각할 필요 없다.
⠀
★ 누군가는 이 현상을 콜 바이 레퍼런스call by reference 혹은 주소 값을 전달한다고 표현하는데 자바스크립트는 포인터 기능이 없다.
⠀
만약 함수에서 다른 객체를 반환하고 싶다면 새로운 객체를 만들면 된다.
```javascript
function turnOver(me) {
  const newProfile = { ...me }
  console.log(newProfile) // { jobTitle: "개발자" }
  console.log(newProfile === me) // false
  newProfile.jobTitle = "디자이너"

  console.log(me) // { jobTitle: "개발자" }
  console.log(newProfile) // { jobTitle: "디자이너" }
  return newProfile
}
```
⠀
⠀
⠀
## 객체의 프로퍼티에서도 똑같이 적용된다
객체 사용도 익숙해지면 객체 프로퍼티에 객체를 활용한다.
```javascript
const company = {
  name: "폭스바겐",
  // 자바스크립트에선 배열도 객체이기 때문에 이 예제에선 객체라고 칭하겠다.
  subsidiary: [
    "벤틀리",
    "부가티",
    "아우디"
  ]
}
```
위와 같은 객체가 있을 때 프로퍼티를 변수에 담아 활용해보았다.
```javascript
let name = company.name
name = "폭스바겐 그룹"
console.log(name) // 폭스바겐 그룹
console.log(company) // {name: "폭스바겐", subsidiary: ["벤틀리", "부가티", "아우디"]

const subsidiary = company.subsidiary
subsidiary.push('람보르기니')
console.log(subsidiary === company.subsidiary) // true
console.log(company) // {name: "폭스바겐", subsidiary: ["벤틀리", "부가티", "아우디", "람보르기니"]}

company.subsidiary.push("포르쉐");
console.log(subsidiary) // ["벤틀리", "부가티", "아우디", "람보르기니", "포르쉐"]
```
변수에서 객체의 프로퍼티로 모양이 바뀌었지만 다르게 생각할 필요가 없다.
⠀
⠀
⠀
## 리액트에서 상태를 객체로 사용할 때
리액트가 등장하면서 UI를 구현하기 얼마나 편해졌는지 모른다. 그러다보니 이런 저런 데이터를 활용하기 시작하는데 객체를 사용할 때 문제가 발생한다.
```javascript
import React from 'react'

export default function () {
  const [me, setMe] = React.useState({ jobTitle: "개발자" })
  return (
    <button
      onClick={function () {
        me.jobTitle = "디자이너"
        console.log(me) // { jobTitle: "디자이너" }
        setMe(setMe)
      }}
    >
      직종변경하기(현재 {me})
    </button>
  )
}
```
버튼에는 <code>직종변경하기 (현재 개발자)</code>가 표시되어있는데 클릭하면 <code>직종변경하기 (현재 디자이너)</code>가 될거라 예상하지만 바뀌지 않는다. 분명 객체의 내용은 바뀌었는데 state의 기준에선 이전 값과 현재 값을 비교했을 때 같기 때문이다.

계속 봐왔듯 객체의 프로퍼티를 바꾼다고 다르게 인식하지 않는다. 새로운 객체를 만들어야 다르다고 인식한다.
```javascript
setMe({ ...me })
```

사실 이 예제는 적절하진 않다. 다만 redux나 context를 활용할 때 똑같은 원리가 적용되므로 상대적으로 표기하기 간편한 state를 사용했다.
⠀
⠀
⠀
## 그런데 null은 object라는데요?
값이 없다는 의미로 null을 사용한다. 유효성 검사를 위해 타입을 확인하는데,
```javascript
console.log(typeof null) // object
```
타입이 객체로 나온다. 정말 혼란스럽다.

왜 이렇게 된건지는 [해당 포스팅](https://2ality.com/2013/10/typeof-null.html)을 참조하면 될 것 같다.
⠀
⠀
⠀
## 계기
주니어 시절 정말 답답했다. 객체가 내 생각대로 반환되지 않았기 때문이다. 지금도 종종 실수가 있긴하다. 함수 서너개만 거치면 어디서 문제가 생겼는지, 어떻게 바꿀까 고민하는데 생각보다 많은 시간이 든다. 그래서 왠만하면 원시 값을 사용하곤한다.

제이쿼리를 이용해 간단한 돔<sup>DOM</sup> 조작할 땐 몰라도 문제 없었는데, 앱의 모양에 가까워 질수록 이런 기본이 정말 중요했다.