원시 타입 (Primitive Type)

33-js-concepts를 스터디하며 정리한 포스트 입니다.

원시 타입(Primitive Type)이란?

자바스크립트에서는 원시 타입 참조 타입이라는 두 가지 자료형을 제공한다.
자바스크립트에서 원시 타입으로는 아래의 7개가 존재한다.

  1. Boolean (true, false)
  2. null
  3. undefinded
  4. number (1, 1000, 3.14)
  5. string ("Primitive", 'Type')
  6. symbol (Symbol(), Symbol(42), Symbol('foo'))
  7. BigInt (9007199254740991n, BigInt(9007199254740991))

symbolES6에 새로 추가된 원시 타입이고 BigIntChrome 67 부터 추가되었다.
위의 7가지 원시 타입을 제외한 모든 것들은 참조 타입이다.
자바스크립트에서의 참조 타입Object다.
자바스크립트에서 Object함수, 배열또한 포함한다.

console.log(true instanceof Object) // false
console.log(false instanceof Object) // false
console.log(null instanceof Object) // false
console.log(undefined instanceof Object) // false
console.log(1 instanceof Object) // false
console.log(100 instanceof Object) // false
console.log(3.14 instanceof Object) // false
console.log('Primitive' instanceof Object) // false
console.log(Symbol() instanceof Object) // false
console.log(9007199254740991n instanceof Object) // false

위에서 확인할 수 있듯이 모든 값들이 Objectinstance가 아닌 것을 확인할 수 있다.

console.log({ foo: 'bar' } instanceof Object) // true
console.log([1, 'foo'] instanceof Object) // true
function func() {
  console.log('function')
}
console.log(func instanceof Object) // true

원시 타입이 아닌 객체, 배열, 함수는 Objectinstance인 것을 확인할 수 있다.

원시 타입

기본적으로 원시 타입 내부에는 어떠한 메소드도 없다.
toString과 같은 내장 메소드가 존재하지 않다.
undefined.toString(), 111.toString()처럼 사용하면 에러가 발생하는 것으로 알 수 있다.
내장 메소드가 없는 속성때문에 원시 타입은 불변한(immutable)속성을 갖는다.
어떠한 내장 메소드가 없기 때문에 자기 자신을 수정할 수 없기 때문이다.
물론 아래와 같이 하나의 변수에 다른 값들을 계속 저장할 수 있다.

let variable = '100'
variable = 1
variable = 3.14
variable = null

또한 원시 타입Object와 다르게 값 자체가 메모리에 저장된다.

'foo' === 'foo' // true
1 === 1 // true

하지만 Object는 값 자체가 아닌 객체 레퍼런스를 저장한다.

{} === {}; // false
[] === [] // false
(function () {}) === (function () {}); //false

위의 두 예제로 알 수 있는 것은 원시 타입이 저장되고 객체참조가 저장이 된다는 것이다.

예제로 알아보는 자바스크립트 원시 타입

아래와 같이 변수에 원시 타입 값들을 할당할 수 있다.

let firstName = 'minsu'
let secondName = firstName

console.log('firstName :', firstName) // firstName : minsu
console.log('secondName :', secondName) // secondName : minsu

firstNameminsu라는 원시 타입string값을 할당 해 주고
secondNamefirstName의 값을 대입했다.
아래와 같이 firstName"hun"이라는 새로운 값을 할당 해 주었다.

firstName = 'hun'

console.log('secondName :', secondName) // secondName : minsu

firstName에 새로운 값을 대입한 후 secondName을 출력해도 기존의 minsu가 출력이 된다.
이 처럼 원시 타입 자체로 저장되어 secondName에 저장된 값은 변하지 않는다.

let firstPerson = {
  name: 'minsu',
  age: 22,
}
let secondPerson = firstPerson

console.log('firstPerson :', firstPerson)
// firstPerson : { name: 'minsu', age: 22 }
console.log('secondPerson :', secondPerson)
// firstPerson : { name: 'minsu', age: 22 }

원시 타입이 아닌 Object타입을 선언해 firstPerson을 작성하고 secondPerson에 대입했다.
원시 타입을 수정했을 경우 첫 번째 값을 대입한 두 번째 값은 변하지 않았었다.

firstPerson.age = 23
console.log('secondPerson :', secondPerson)
// firstPerson : { name: 'minsu', age: 23 }

firstPersonage값을 변경했지만 secondPerson의 값도 변경된 것을 확인할 수 있다.
따라서 Object는 값(Value)가 아닌 참조(Reference)로 값을 저장하는 것을 확인할 수 있다.

원시 타입처럼 값 복사하기

그렇다면 Object에서 원시 타입처럼 값을 복사하려면 어떻게 해야할까?
Object.assign매서드를 사용해 복사하면 된다.

let assignLikePrimitiveOne = {
  name: 'minsu',
  age: 22,
}
let assignLikePrimitiveTwo = Object.assign({}, assignLikePrimitiveOne)

console.log('assignLikePrimitiveOne :', assignLikePrimitiveOne)
// assignLikePrimitiveOne : { name: 'minsu', age: 22 }
console.log('assignLikePrimitiveTwo :', assignLikePrimitiveTwo)
// assignLikePrimitiveTwo : { name: 'minsu', age: 22 }

Object.assign을 이용해 Obejct.assing(<원본 객체>, <복사할 객체>)로 값을 대입한다.
따로 유지해야할 객체 값이 없으므로 빈 객체({})에 assignLikePrimitiveOne을 할당했다.
이전과 같이 assignLikePrimitiveOne.age = 23과 같이 값을 변경해 보자.

assignLikePrimitiveOne.age = 23
console.log('assignLikePrimitiveTwo :', assignLikePrimitiveTwo)
// assignLikePrimitiveTwo : { name: 'minsu', age: 22 }

assignLikePrimitiveOne의 값을 바꿨지만 assignLikePrimitiveTwo의 값은 변경되지 않았다.

그림으로 Reference 이해하기

secondPerson = firstPerson처럼 값을 복사했을 경우 아래와 같이 복사된다.

1

첫 번째 예시는 위와 같이 firstPerson, secondPerson 모두 Heap의 하나의 값을 가리킨다.
따라서 firstPerson의 값을 변경할 경우 아래 처럼 두 개의 참조가 가르키는 값이 변경된다.

2

Object.assign을 사용한 두 번째 예제는 아래와 같은 모습으로 값이 저장된다.

3

assignLikePrimitiveOne의 값을 변경하면 아래 처럼 변경된다.

4

두 개의 객체 모두 같은 값을 갖는 다른 레퍼런스를 갖기 때문에 값을 바꿔도 변경되지 않는다.

Reference 깊게 들어가기

이전의 예제와 다르게 Object안에 Array속성을 추가해보았다.

let firstInfo = {
  name: 'minsu',
  age: 22,
  hobbies: ['Baseball', 'Basketball'],
}

let secondInfo = Object.assign({}, firstInfo)

console.log('firstInfo :', firstInfo)
//firstInfo : { name: 'minsu', age: 22, hobbies: [ 'Baseball', 'Basketball' ] }
console.log('secondInfo :', secondInfo)
//secondInfo : { name: 'minsu', age: 22, hobbies: [ 'Baseball', 'Basketball' ] }

Object.assign을 사용해서 객체를 복사한 후 hobbies에 값을 추가해보자.

firstInfo.hobbies.push('Games')
console.log('secondInfo :', secondInfo)
// secondInfo : {
//   name: 'minsu',
//   age: 22,
//   hobbies: [ 'Baseball', 'Basketball', 'Games' ]
// }

Object.assign을 사용했음에도 secondInfohobbies배열에도 값이 추가되었다.
아래 그림과같이 Object자체는 새로운 값으로 할당이 되었지만
hobbies참조가 저장되어 있어서 hobbies는 참조하는 배열을 가르키고 있다.

5

따라서 push함수로 배열에 값을 추가하면 아래와 같이 값이 저장된다.

6

이는 Object.assign함수가 내부의 모든 객체를 복사하지 않기 때문이다.
깊은 복사를하기 위해서는 재귀 함수를 이용하거나 JSON객체의 함수를 이용하면 된다.


Written by@Minsu Kim
Software Engineer at KakaoPay Corp.