원시, 참조 타입

담당
김민재
완료
완료
유형
JavaScript
비고
*은 작성자의 주관적인 설명입니다.
 

요약

  • 원시 타입의 값은 변경 불가능한 값, 참조(객체) 타입의 값은 변경 가능한 값이다.
  • 원시 값을 변수에 할당하면 변수에는 실제 값이 저장, 객체를 변수에 할당하면 변수에는 참조 값이 저장된다.
  • 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달, 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달 (* 모자딥다에서는 이를 각각 값에 의한 전달, 참조에 의한 전달 이라고 지칭하는데 ECMAScript 사양에는 등장하지 않는 용어이며 모던자바스크립트 서적에서 타 언어에서 자주 사용하는 용어를 가져와서 사용한다고 하므로 참고할것) ++ 추가설명 : 자바 스크립트에는 포인터가 존재하지 않기때문에 포인터가 존재하는 타 언어의 참조에 의한 전달과 의미가 정확히 일치하지 않는다는점도 주의.
  • 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할수 없다.
  • 참조 값은 저장된 메모리 주소는 다르지만 동일한 참조 값을 갖는다. 여러 식별자가 하나의 객체를 공유한다는것을 의미하며 어느 한쪽에서 객체를 변경하면 서로 영향을 주고받는다.
  • 객체를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계까지만 복사하는것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는것을 말한다.
 

심화 내용

원시값

notion image
  • 원시 타입의 값은 변경 불가능한 값 → 원시 값 자체를 변경할수 없다는 뜻, 재할당을 통해 변수 값을 변경(엄밀히 말하면 교체)할수 있다. 이러한 특성을 불변성이라 한다.
    • 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할수 있는 방법이 없다.
 

문자열과 불변성

notion image
문자열은 유사 배열 객체이면서 이터러블 이므로 배열과 유사하게 각 문자에 접근할수 있다.
*이터러블 : 반복 가능한 이라는 뜻을 가지고 있습니다. 대표적으로 배열과 문자열이 있습니다.
for..of와 같은 반복문을 사용할수있습니다.
  • 변수에 새로운 문자열을 재할당은 가능, 일부 문자를 변경하는것은 불가능
    • 문자열은 변경 불가능한 값이기 때문.
문자열에 관한 유사 배열 객체 추가 설명
notion image
유사 배열 객체란 마치 배열처럼 인덱스로 프로퍼티 값에 접근 가능하며 length 프로퍼티를 갖는 객체, 이를 통해 문자열은 배열처럼 인덱스를 통해 각 문자에 접근 할수 있으며, length 프로퍼티를 갖기 때문에 유사 배열 객체이며 for문으로 순회 할수 있습니다.
 
원시값과 래퍼 객체 설명

원시값과 래퍼 객체

문자열이나 숫자, 불리언 등의 원시값이 있는데도 문자열, 숫자, 불리언 객체를 생성하는 String, Number, Boolean 등의 표준 빌트인 생성자 함수가 존재하는 이유
 
원시값은 객체가 아니므로 프로퍼티나 메서드를 가질수 없는데도 원시값인 문자열이 객체처럼 동작한다.
const str = 'hello'; // 원시 타입인 문자열이 프로퍼티와 메서드를 갖고 있는 객체처럼 동작한다. console.log(str.length); // 5 console.log(str.toUpperCase()); // HELLO
 
이는 원시값이 문자열, 숫자, 불리언 값의 경우 이들 원시값에 대해 마치 객체처럼 마침표 표기법(또는 대괄호 표기법)으로 접근하면 자바스크립트 엔진이 일시적으로 원시값을 연관된 객체로 변환해 주기 때문이다.
즉, 원시값을 객체로 사용하면 자바스크립트 엔진은 암묵적으로 연관된 객체를 생성하여 생성된 객체로 프로퍼티에 접근하거나 메서드를 호출하고 다시 원시값으로 되돌린다.
이처럼 문자열, 숫자, 불리언 값에 대해 객체처럼 접근하면 생성되는 임시 객체를 래퍼 객체라고 한다.
 
예를 들어 문자열에 대해 마침표 표기법으로 접근하면 그 순간 래퍼 객체인 String 생성자 함수의 인스턴스가 생성되고 문자열은 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된다.
const str = 'hi'; // 원시 타입인 문자열이 래퍼 객체인 String 인스턴스로 변환된다. console.log(str.length); // 2 console.log(str.toUpperCase()); // HI // 래퍼 객체로 프로퍼티에 접근하거나 메서드를 호출한 후, 다시 원시값으로 되돌린다. console.log(typeof str); // string
문자열 래퍼 객체인 String 생성자 함수의 인스턴스는 String.prototype의 메서드를 상속받아 사용할수 있다.
notion image
래퍼 객체의 처리가 종료되면 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된 원시값으로 원래의 상태, 즉 식별자가 원시값을 갖도록 되돌리고 래퍼 객체는 가비지 컬렉션의 대상이 된다.
 
// 1. 식별자 str은 문자열을 값으로 가지고 있다. const str = 'hello'; // 2. 식별자 str은 암묵적으로 생성된 래퍼 객체를 가리킨다. // 식별자 str의 값 'hello'는 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된다. // 래퍼 객체에 name 프로퍼티가 동적 추가된다. str.name = 'Lee'; // 3. 식별자 str은 다시 원래의 문자열, 즉 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된 원시값을 갖는다. // 이때 2번에서 생성된 래퍼 객체는 아무도 참조하지 않는 상태이므로 가비지 컬렉션의 대상이 된다. // 4. 식별자 str은 새롭게 암묵적으로 생성된(2번에서 생성된 래퍼 객체와는 다른) 래퍼 객체를 가리킨다. // 새롭게 생성된 래퍼 객체에는 name 프로퍼티가 존재하지 않는다. console.log(str.name); // undefined // 5. 식별자 str은 다시 원래의 문자열, 즉 래퍼 객체의 [[StringData]] 내부 슬롯에 할당된 원시값을 갖는다. // 이때 4번에서 생성된 래퍼 객체는 아무도 참조하지 않는 상태이므로 가비지 컬렉션의 대상이 된다. console.log(typeof str, str); // string hello
 

 

값에 의한 전달

notion image
 
변수에 원시 값을 갖는 변수를 할당하면 할당받는 변수에는 할당되는 변수의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달 이라고 한다.
*위에서도 설명했듯이 이 용어는 ECMAScript 공식 용어가 아니기때문에 오해의 소지가 있을수있다.
 
값에 의한 전달 심화내용
notion image
엄격하게 표현하면 변수에는 값이 전달되는것이 아니라 메모리 주소가 전달된다.
변수와 같은 식별자는 값이 아니라 메모리 주소를 기억한다.
전달된 메모리 주소를 통해 메모리 공간에 접근하면 값을 참조할수있다.
 

참조에 의한 전달

notion image
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달이라고 한다.
*위에서도 설명했듯이 이 용어는 ECMAScript 공식 용어가 아니기때문에 오해의 소지가 있을수있다.
 
값에 의한 전달과 참조에 의한 전달 심화내용
  • 값에 의한 전달과 참조에 의한 전달은 식별자가 기억하는 메모리 공간에 저장되어 있는 값을 복사해서 전달하는 면에서 동일하다.
  • 변수에 저장되어 있는 값이 원시 값이냐 참조 값이냐의 차이가 존재한다.
  • 따라서 자바스크립트에는 참조에 의한 전달은 존재하지 않고 값에 의한 전달만이 존재한다고 말할수있다.
 

얕은 복사와 깊은 복사

얕은 복사와 깊은 복사로 생성된 객체는 원본과는 다른 객체, 원본과 복사본은 참조 값이 다른 별개의 객체.
  • 얕은 복사는 객체에 중첩되어 있는 객체의 경우 참조 값을 복사.
  • 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사해서 원시값처럼 완전한 복사본을 생성
깊은 복사 하는 예시들
  1. JSON.parse() + JSON.stringify()
const obj = { a: 1, b: { c: 2, } } const newObj = JSON.parse(JSON.stringify(obj)) newObj.b.c = 99; console.log(obj === newObj); // false console.log(obj.b.c); // 2 console.log(obj.b.c === newObj.b.c); // false
 
  1. lodash - cloneDeep()
const obj = { a: 1, b: { c: 2, }, }; const newObj = _.cloneDeep(obj); newObj.b.c = 99; console.log(obj === newObj); // false console.log(obj.b.c); // 2 console.log(obj.b.c === newObj.b.c); // false
 
  1. 재귀 함수
const deepCopy = (obj) => { if (obj === null || typeof obj !== "object") { return obj; } let copy = {}; for (let key in obj) { copy[key] = deepCopy(obj[key]); } return copy; } const obj = { a: 1, b: { c: 2, }, func: function () { return this.a; }, }; const newObj = deepCopy(obj); newObj.b.c = 99; console.log(obj === newObj); // false console.log(obj.b.c); // 2 console.log(obj.b.c === newObj.b.c); // false

참고

모던 자바스크립트 딥다이브 11장 원시값과 객체의 비교 P_137 ~ 153