자바스크립트의 원시 타입(Primitive Type)

EJ·2020년 11월 24일
0

JavaScript 개념

목록 보기
2/13

자바스크립트 언어의 정체는 무엇인가?

자바스크립트는 객체지향 프로그래밍 언어(OOP)이면서 함수형 언어이다.
그렇다면 자바스크립트에 있는 모든 것들은 객체이며, 함수들도 객체로 봐야 하는 걸까? 이에 대해 알아보자.

기초잡기

자바스크립트에는 6가지 종류의 원시 데이터 타입이 있다. 원시 데이터 타입이란 자바스크립트에서 객체가 아닌 것들이며 값 그 자체로 저장된 것이다.

  • Booleans true or false
  • null
  • undefined
  • number (두 배의 정밀함을 가진 64-bit float이다. 자바스크립트에는 정수 타입은 존재하지 않는다.)
  • string
  • symbol (ES6에서 처음 생긴 원시타입이다.)

이 6가지 원시 타입들에 더해 ECMAScript표준은 Object를 정의했다. Object는 간단히 말하자면 키-값 저장소이다.

const object = {
  	key: "value"
}

요약하자면, Primitive Type(6가지 기본 자료형 타입)이 아닌 것은 Object이다. 또한 Object라는 개념은 함수들과 배열들도 포함한다.

또한, 모든 함수들도 Object이다.

// Primitive Types
true instanceof Object; // false -> boolean
null instanceof Object; // false -> null
undefined instanceof Object; // flase -> undefined
0 instanceof Object; // flase -> number
'bar' instanceof Object // false -> string

// Non-primitive types
const foo = function () {}
foo instanceof Obejct; // true -> function

원시 타입

원시 타입에는 어떠한 메소드도 붙어있지 않다. 일르테면 undefined.toString()과 같은 형태의 메소드는 본 적이 없을 것이다. 이러한 특성 때문에, 원시 타입은 변하지 않는(immutable) 속성을 갖는다. 원시 타입은 자신을 변경할 수 있는 메소드를 갖지 않기 때문이다.

물론 변수에 원시 타입을 얼마든지 재할당 할 수 있다. 하지만 이것은 실제로 변수에 할당되었던 원시 타입의 값이 바뀌는 것이 아니라 새로운 원시 타입의 값이 들어가는 개념이다. 원시타입의 값 자체는 절대 바뀔 수 없다.

원시 타입은 불변적이다. (Primitive types are immutable)

게다가, 원시 타입은 참고(reference)로 저장되는 object와 다르게 값 그 자체로 저장되어 있다. 따라서 값이 동일한지 체크할 때 앞의 문장이 정확히 무슨 의미인지 알 수 있다. 아래 배열과 객체는 내용은 같지만 다른 곳을 참조하고 있기 때문에 false를 리턴한다.

"dog" === "dog"; // true
14 === 14; // true

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

원시 타입은 값(value)으로 저장되고, 객체들은 참조(reference)로 저장된다.

함수

함수는 특별한 프로퍼티(객체의 속성)들을 가진 새로운 형태의 객체이다. 생성자와 콜처럼.

const foo = function (baz) {};
f00.name; // "foo"
foo.length; // 1

일반적인 객체와 같이 함수에서 새로운 프로퍼티를 추가하는 것도 가능하다.

foo.bar = "baz";
foo.bar // "baz"

함수의 이러한 특성은 함수를 1급 객체로 만든다. 다음과 같은 조건을 만족하기 때문이다.

  1. 다른 함수의 인자값으로 넘겨질 수 있다.
  2. 변수나 데이터에 할당이 가능하다.
  3. 객체의 리턴값으로 리턴 가능하다.

이러한 특성은 자바스크립트의 객체들이 갖는 특성과 같다.

메소드

메소드는 함수와 같이 객체의 프로퍼티이다.

const foo = {};
foo.bar = function () { console.log("baz"); };
foo.bar(); // "baz"

생성자 함수

자바스크립트에서 생성자 함수란 리턴 값으로 생성하는 함수를 객체 그 자체로서 반환하는 함수이다. 같은 코드를 공유하는 여러가지 객체들을 갖고 싶다면, 생성자 함수를 삽입하는 것은 매우 좋은 선택이다.

생성자 함수는 new라는 키워드가 붙은 이후에 생성자 함수로서 사용된다는 것과 객체 자체를 리턴한다는 것 외에는 다른 함수와 별반 다를 것이 없다.

어떤 함수든 생성자 함수가 될 수 있다.

const Foo = function () {};
const bar = new Foo();
bar; // {}
bar instanceof Foo; // true
bar instanceof Object; // true

생성자 함수는 object를 리턴하게 된다. object에 새로운 프로퍼티들을 할당하기 위해 this를 함수의 몸통 안에서 사용할 수 있다. "baz"값으로 초기화된 bar라는 프로퍼티를 가진 object를 많이 만들고 싶다면 그 로직을 캡슐화하는 Foo라는 생성자 함수를 만들 수 있겠다.

const Foo = function () {
	this.bar = "baz";
};
const qux = new Foo();
qux; // { bar: "baz" }
qux instanceof Foo; // true
qux instanceof Object; // true

새로운 오브젝트를 만들기 위해서 생성자 함수를 사용할 수 있다.

new키워드 없이 단순히 Foo()라는 함수를 실행한다면 Foo는 일반적인 함수처럼 동작할 것이다. 그리고 위의 코드에서 함수 내부에 들어있는 this라는 키워드는 '실행 컨텍스트(Execution Context)'와 응답을 주고 받는다. 그래서 만일 Foo()라는 함수를 전역 컨텍스트에서 실행시키에 되면, 전역 컨텍스트 시점의 thiswindow객체에 bar라는 프로퍼티가 추가된다.

Foo(); // undefined
window.bar; // "baz"

반대로 말하면, 일반 함수를 생성자 함수로 실행한다면 함수의 역할을 한다기보다 그저 새로운 함수 오브젝트를 반환할 뿐이라는 것이다.

const pet = new String("dog");
// pet은 원시 타입의 "dog" 값을 갖는 것이 아니라 생성자 함수로 생성된 String 객체를 갖게 된다.

래퍼 오브젝트(Wrapper Object, 포장 오브젝트)

String, Number, Boolean, Function와 같은 원시 타입을 new키워드로 생성하면 원시 타입에 대한 래퍼 오브젝트(Wrapper Object)가 생성된다.

String은 문자열이 인자로 들어왔을 때, 원시문자열(Primitive String)을 생성하는 전역 함쉬앋. String은 인자로 들어온 값을 문자열로 바꾸려 한다.

String(1337); // "1337"
String(true); // "true"
String(null); // "null"
String(undefined); // "undefined"
String(); // ""
String("dog") === "dog"; // "true"
typeof String("dog"); // "string"

하지만 new키워드를 붙인다면 String은 여전히 생성자 함수로도 쓰일 수 있다.

const pet = new String("dog");
typeof pet; // "object"
pet === "dog"; // false

그리고 위의 생성자는 래퍼 오브젝트(Wrapper Object)라고 불리는 새로운 Object를 만들 것이다. 위의 코드에섯 새로운 Object는 "dog"라는 문자열을 다음과 같은 프로퍼티로 나타낸다.

{
  	0: "d",
    1: "o",
    2: "g",
    length: 3
}

래퍼 오프젝트(Wrapper Objects)는 오브젝트 래퍼(Object Wrappers)라는 이름으로도 자주 불린다.

오토박싱(Auto-Boxing)

흥미로운 것은 원시 타입 문자열 생성자와 일반 오브젝트 생성자 둘 다 String함수를 이용한다. 더욱 흥미로운 것은 원시 타입 문자열에서 .constructor를 이용하여 생성자 프로퍼티를 확인할 수 있다는 것이다. 원시 타입은 메소드를 가질 수 없다고 했는데 어떻게 된 일일까?

const pet = new String("dog");
pet.constructor === String; // true
String("dog").constructor === String; // true

여기서 오토박싱이라 불리는 일이 벌어진다. 특정한 원시 타입에서 프로퍼티나 메소드를 호출하려 할 때, 자바스크립트는 처음으로 이것을 임시 래퍼 오브젝트로 바꾼 뒤에 프로퍼티나 메소드에 접근하려 한다. 중요한 것은 이 과정에서 원본에는 아무런 영향을 미치지 않는다.

const foo = "bar";
foo.length; // 3
foo === "bar"; // true

위의 예에서 length라는 프로퍼티에 접근하기 위해 자바스크립트는 foo오토박싱하고 이것을 래퍼 오브젝트에 넣는다. 그리고 래퍼 오브젝트의 length프로퍼티에 접근하고 값을 이용한 뒤에는 지워버린다. 이 모든 과정은 foo라는 원시 타입 변수에 전혀 영향을 미치지 않는다. foo는 여전히 그저 원시 타입 문자열일 뿐이다.

이러한 일련의 과정은 원시 타입에 프로퍼티를 할당하려고 할 때 자바스크립트가 왜 아무런 경고나 에러메시지를 출력하지 않는지를 알려준다. 원시 타입은 프로퍼티를 가질 수 없는데도 말이다. 왜냐하면 프로퍼티를 할당할 때 잠시 원시 타입을 이용한 Wrapper Object(래퍼 오브젝트)를 만드록 그곳에 할당하기 때문이다.

const foo = 42;
foo.bar = "baz"; // Assignment done on temporary wrapper object
foo.bar; // undefined

만일 undefinednull과 같이 래퍼 오브젝트가 없는 원시 타입에 대해서 프로퍼티를 할당하려고 하면 자바스크립트는 에러메시지를 나타낼 것이다.

const foo = null;
foo.bar = "baz"; // Uncaught TypeError: Cannot set property 'bar' of null

요약

  1. 자바스크립트의 모든 것이 Object(객체)인 것은 아니다.
  2. 자바스크립트에는 6개의 원시 타입이 존재한다.
  3. 원시 타입이 아닌 것들은 모두 Object(객체)이다.
  4. 함수는 단순히 특별한 타입의 Object(객체)일 뿐이다.
  5. 함수는 새로운 Object(객체)를 만들기 위해 사용될 수 있다.(생성자 함수)
  6. Strings, Numbers, Booleans는 원시 타입이면서 오브젝트이다.(래퍼 오브젝트를 갖는다.)
  7. 자바스크립트 내부에 존재하는 오토박싱(Autoboxing)이라는 기능 때문에 몇몇 원시 타입들(Strings, Numbers, Booleans)는 Object(객체)처럼 동작한다.

profile
주니어 프론트엔드 개발자 👼🏻

0개의 댓글