자바스크립트의 데이터 타입 - 원시타입과 참조타입

ain·2024년 12월 5일
0

JavaScript

목록 보기
5/6
post-thumbnail

우리가 다루는 데이터들을 메모리에 저장하기 위해서는 변수에 할당을 하고, 필요할 경우 가공을 하고, 또 재사용을 합니다. 에러 없이 데이터를 다루기 위해서는 데이터의 타입에 대해서도 알아야 할 필요가 있는데요, 특히 참조 타입의 경우, 잘 모르고 데이터를 가공할 경우 뜻하지 않은 에러를 발생시킬 수 있습니다.

자바스크립트에서 데이터의 타입을 어떻게 분류하는지 알아보고자 예시를 들며 정리하였습니다.

자바스크립트에서의 데이터 타입

다른 언어에서는 int, byte와 같은 특정 타입의 변수가 있는 반면, 자바스크립트에서는 변수에 타입이 없어 모든 타입을 할당 할 수 있습니다. 그리고 할당 할 수 있는 값에는 원시 타입과 참조 타입 두개의 타입으로 구분되며, 이는 참조하는 방식 기준으로 구분됩니다.

원시 타입

원시 값은 불변값입니다. (불변값이란 값 자체를 바꿀수 없다는 것을 뜻합니다.) 값은 메모리 스택 영역에 저장됩니다.

원시타입에는 숫자, 문자열, 논리값, 특수한 값(undefined, null), 그리고 ECMAScrip6에 추가된 심벌 타입이 포함됩니다.

메소드와 속성

이러한 원시 타입들은 메소드나 내장 속성이 없지만, 값에서 속성에 접근하게 되면 자바스크립트는 그 값을 해당 타입의 래퍼 객체(Object wrapper)로 자동으로 감싸서 객체의 속성에 접근할 수 있게 됩니다.

const name = 'rachel';
console.log(name.length) // 6

위의 코드 예제처럼 원시값인 name의 length 속성에 접근 할 때, 자바스크립트는 name에 String() 레퍼 객체로 감싸줌으로써 객체로 만들어 메소드와 속성에 접근 할 수 있게 됩니다.

// same
const name = 'rachel';
console.log(String(name).length) // 6

이는 숫자, 논리값 또한 마찬가지입니다.

Number(2)
Boolean(true)
...

참조 타입

참조 타입은 객체 타입으로도 불리며 원시타입을 제외한 모든 객체가 참조타입으로 분류됩니다. 객체, 배열, 함수, 생성자 등이 참조 타입에 포함되며, 해당 값들을 변수에 할당할 시, 변수 메모리에는 객체가 저장되어있는 메모리의 주소값(위치 정보)을 참조하게 됩니다.

const photo = { createdAt: "2024-12-05", name: "my-photo" };
const file = photo;

객체가 참조되는 과정 시각화

여러개의 변수가 같은 객체 값을 참조하고 있다면, 어느 하나라도 변경이 일어나면 다른 변수에서도 변경이 발생합니다. 값의 주소를 참조하고 있어 한개의 값만을 읽고 사용하기 때문입니다.

file.name = "modified-photo";
console.log(photo) // { createdAt: "2024-12-05", name: "modified-photo" }
console.log(photo.name) // "modified-photo"

함수의 예시

함수에 전달하는 인수에 객체를 전달할때에도 참조값이 되는 것은 마찬가지입니다.
parameter(매개변수)는 변수이고, 인자를 전달할때 매개변수에도 할당(참조)이 됩니다.

const initialObj = {x: 1, y: 1}
const result = add(initialObj);

function add (obj) {
  obj.sum = obj.x + obj.y
  return obj;
};

console.log(initialObj) // {x: 1, y: 1, sum: 2}
console.log(result) // {x: 1, y: 1, sum: 2}

위 코드에서는, 객체를 전달하여 객체 안의 x값과 y값을 더하고 합을 계산하는 함수 add가 있습니다.

add 함수 내부를 보면 매개변수의 값들을 더한 후, sum이라는 키를 만들어 합계값을 할당하고 있습니다. 그렇기 때문에, {x: 1, y: 1}를 넣었을때 {x: 1, y: 1, sum:2} 가 반환되는게 당연합니다. 하지만 이를 initialObj라는 변수에 할당하였고 initialObj와 매개변수 obj는 같은 객체 주소를 바라보고 있기 때문에, add 함수 안에서 바뀐 객체가 initialObj에서도 바뀌게 됩니다.

⚡️ 인자가 원시값인 경우는?
인자가 원시값인 경우엔 위에서 원시타입에 대해 설명한 바와 똑같이 작동합니다.
어떠한 함수에서 x와 y를 매개변수로 받고 x나 y의 값을 변경할때, 매개변수는 바뀌겠지만 해당 함수에 전달한 원시값은 바뀌지 않습니다.

function getX (x) {
  x = 10;
  return x;
};
const _x = 1;
const result = getX(_x);
console.log(_x); // 1
console.log(result); // 10

배열의 예시

배열 또한 참조 타입에 해당되기 때문에 원본 객체를 참조 하고 있는 변수를 건들인다면 원본 객체까지 영향이 갑니다.

const students = [
  { name: "chandler" },
  { name: "amy" },
  { name: "zoe" }
]

const sortedStudents = students.sort((a, b) => a.name.localeCompare(b.name));

console.log(students); 
// [
//  { name: "amy" },
//  { name: "chandler" },
//  { name: "zoe" }
// ]
console.log(sortedStudents);
// [
//  { name: "amy" },
//  { name: "chandler" },
//  { name: "zoe" }
// ]

위 코드처럼 students라는 배열을 정렬하고 싶을때, sort 함수를 바로 사용하게 되면 students 객체까지 정렬이 되버려 정렬이 안되어 있는 원본 객체는 사용하지 못하게 됩니다.

⚡️ sort 말고 toSorted
최근에 추가된 sort의 복사 대응 버전 toSorted를 사용하면, 정렬된 새로운 배열을 반환하여 참조없이 사용할 수 있습니다.
MDN - Array.prototype.toSorted()

p.s. 얕은 복사 / 깊은 복사

원본 객체의 참조를 끊고 객체를 복사할 하려면 얕은 복사나 깊은 복사를 해야합니다.

얕은 복사란 객체에서 중첩 첫번째 단계까지만 참조를 끊는 것입니다.

얕은 복사의 예시 (Object.assign())

const initialData = {
  name: "rachel",
  age: 30,
  child: {
    name: "Emma",
    gender: {
      name: 'female'
    }
  }
};

const data = Object.assign({}, initialData);
_data.name = "ross";
_data.child.name = "Ben";
_data.child.gender = "male";

console.log(data);
// {
//  name: "rachel",
//  age: 30,
//  child: {
// **changed
//    name: "Ben",
//    gender: {
//      name: 'male'
//    }
//  }
// };

여기서 첫번째 단계에는 name과 age가 있고, 그 다음 단계에는 child의 name이 있습니다. 이 data를 얕은 복사 하게 된다면 child의 name부터 바뀌게 됩니다.

깊은 복사의 예시 (JSON.parse, JSON.stringyfy)

깊은 복사는 중첩이 몇단계가 되었든 모두 복사하여 새로운 객체를 생성합니다.

const initialData = {
  name: "rachel",
  age: 30,
  child: {
    name: "Emma",
    gender: {
      name: 'female'
    }
  }
};

const data = JSON.parse(JSON.stringify(initialData));
_data.name = "ross";
_data.child.name = "Ben";
_data.child.gender = "male";

console.log(data);
// {
//  name: "rachel",
//  age: 30,
//  child: {
//    name: "Emma",
//    gender: {
//      name: 'female'
//    }
//  }
// };

위 코드처럼 원본 객체에는 아무런 영향이 가지 않는 걸 볼 수 있습니다.


이 글을 본 분들은 자바스크립트 기본기를 탄탄하게 세워 뜻하지 않은 에러로 불필요한 디버깅을 하지 않길 바랍니다.
🙏🏻

profile
프론트엔드 개발 연습장 ✏️ 📗

0개의 댓글