유명한 JS 문법서들의 서문에서는 항상 JS의 언어적 특징으로 느슨한 타입의 동적 언어
를 언급한다. JS가 느슨한 타입
의 언어라는 점과 동적 타입
의 언어라는 사실은 아무리 강조해도 지나치지 않다. 실무에서 자신이 코드를 작성해 놓고도 JS 코드가 왜 이렇게 돌아가지? 하는 대부분의 순간의 해답이 느슨한 타입
언어와 동적 타입
언어라는 속성에 기인하기 때문이다.
하지만 프론트엔드 개발자를 꿈꾸며 JS 문법서의 첫 장을 열었을 병아리 개발자들이 JS가 느슨한 타입
의 동적 타입
언어임을 이해하는 것은 결코 쉽지 않다.
이러한 맥락에서 이번 포스트에서는 느슨한 타입
의 동적 타입
언어라는 특성에 대해 상세히 설명하여 개발자 꿈나무들의 이해를 돕고자 한다. 우선, 느슨한 타입
은 대체 무엇이고, 동적 타입
언어란 무엇인지 살펴 본 뒤, 해당 특성으로 인한 JS의 한계에 대해 논의하며 이러한 맥락에서 나온 TypeScript
, Flow
에 대해 소개하려한다.
느슨한 타입(loosely typed)
: 자료형 없이 변수를 선언하는 것강력한 타입(strong typed)
: 자료형과 함께 변수를 선언하는 것
강력한 타입
의 언어는 자료형과 함께 변수를 선언해야한다. 자료형이란 데이터의 타입을 의미한다. 대표적인 강력한 타입
의 언어로는 C
, Java
가 있다. 자료형과 함께 변수를 선언한 경우, 해당 자료형에 부합하지 않는 값이 변수에 할당되는 경우, 컴파일 단계
에서 TypeError
가 발생한다.
반면 느슨한 타입
의 자료형 없이 변수를 선언한다. 느슨한 타입
의 대표 언어로는 JavaScript
, Python
을 꼽을 수 있다. 자료형 없이 변수를 선언하고 특정 값으로 초기화한 경우, 런타임
시 변수 내부에 있는 값에 기반하여 해당 변수의 자료형이 내부적으로 지정된다.
/* Java Example (strong typing) */
int a = 1031; // int 선언되어 컴파일 타임시 체크됨
String b = "erica"; // String 선언되어 컴파일 타임시 체크됨
/* JavaScript Example (loose typing) */
let a = 1031; // 런타임 시 Number로 처리됨
let b = "erica"; // 런타임 시 String으로 처리됨
JS의 경우, 런타임
에 JS 엔진
이 변수에 할당된 값을 바탕으로 해당 변수의 자료형을 지정한다. 다들 알고 있듯 JS의 자료형은 크게 원시값(Primitive value)
과 객체
로 나눌 수 있으며, ES6에는 총 7개의 원시값이 존재(Number
, String
, Boolean
, Null
, Undefined
, BigInt
, Symbol
)한다.
정리하면, JS가 느슨한 타입
의 언어라는 것은, JS에서 변수를 선언할 때 자료형을 명시하지 않으며, 실제로 해당 변수가 어떤 자료형인지는 런타임
시에 JS 엔진
이 해당 변수에 할당된 값에 따라 내부적으로 지정한다는 의미이다.
이 밈은 동적 타입
언어와 정적 타입 언어를 단적으로 묘사하고 있다. 단순한 네모 퍼즐을 맞추는 동적 타입
언어와는 달리 정적 타입
언어는 퍼즐의 나오고 들어간 방향, 즉, 자료형
을 따져가며 맞춰야 한다는 점에서 살짝 복잡 할 수 있다. 하지만 동적 타입
언어가 퍼즐을 다 맞춰도 삐뚤빼뚤 한 것처럼 동적 타입
언어는 런타임 오류
라는 치명적인 맹점이 있다.
- 정적 타입(staic type) 언어: 컴파일 시 변수의 자료형이 결정되는 언어.
- 동적 타입(dynamic type) 언어: 런타임 시 변수의 자료형이 결정되는 언어.
- 만약
런타임
과컴파일 타임
에 대해 헷갈린다면, 해당 링크를 참고하자!
정적 타입
언어를 사용할 때 개발자는 변수에 들어갈 값의 형태에 따라 변수를 선언할 때 미리 변수의 자료형을 지정한다. 정적 타입
언어는 소스 코드를 컴파일
할 때, 만약 선언된 자료형과 변수 내부의 값의 유형이 일치하지 않는다면 TypeError
를 발생시킨다. 정적 타입
언어는 컴파일
시에 타입을 지정하기 때문에 실행 속도가 동적 타입
언어에 비해 빠르고, 컴파일 타임
에 TypeError
를 통해 문제가 발생한 지점을 바로 확인할 수 있다는 장점이 있다. 다만 처음 코드를 쓸 때 자료형을 적어주어야 해서 다소 번거로울 수 있다... (개인적으로는 오히려 자료형을 명시하지않는 느슨한 언어가 더 불편하다...)
동적 타입
언어는 컴파일 시 변수의 자료형이 결정되는 정적 타입 언어와 달리 런타임
에 변수의 자료형이 결정된다. 코드를 작성할 때 변수가 특정 자료형과 연결되지 않기 때문에 모든 자료형의 값으로 재할당 될 수 있다는 유연성
이 있다.
// JS는 쉽게 재할당 가능
let a = 28 // a가 Number
a = 'erica' // a가 이제 String
a = true // a가 이제 Boolean
변수 자료형을 명시하지 않으므로 코딩 속도가 살짝 빠를 수 있다. 하지만 이런 사소한 장점에 비해 치명적인 단점이 있다. 런타임
즉, 코드를 실제로 실행하며 점검하기 때문에 변수에 예상치 못한 타입이 들어왔을 때 코드 실행 중 에러
가 발생한다는 점이다. 코드가 딱 멈췄을 때, 소스 코드에서 에러 발생 지점을 확인, 이를 수정하는 작업은 당연히 컴파일 타임에 비해 번거로울 수 밖에 없다. (이러한 맥락에서 많은 개발자들이 JS에서 )TypeScript
, Flow
로 이주했다...
앞서 살펴 본 바와 같이 느슨한 타입
의 언어는 변수 선언 시 자료형을 지정하지 않는 것, 동적 타입
언어는 런타임 시 변수의 자료형을 결정하는 것을 지칭한다. JS는 느슨한 타입
이면서 동적 타입
의 언어이다.
런타임
에 변수의 자료형이 JS 엔진
에 의해 내부적으로 결정되기 때문에 실행 중에 예상치 못한 자료형이 해당 변수에 들어왔을 때, TypeError
가 발생할 수 있다. 심지어 런타임 에러
의 경우 컴파일 타임 에러
에 비해 수정이 어렵다. (더 나아가서는 FE 코드의 에러가 서버에 영향을 끼칠 수도 있다...)
이러한 불편함에서 개발자들은 과감히 다른 언어로의 이주를 택했다. JS의 문법에 자료형 선언을 덧붙인 TypeScript
, Flow
등으로 개발 언어를 전환한 것이다.
TypeScript
는 JS를 따르되, 변수 선언 시 자료형을 꼭 함께 쓰도록 수정한 정적 타입
언어이다. 정적 타입
이기 때문에 TypeScript
는 컴파일 타임
에 해당 변수의 자료형이 올바른지 여부를 검사할 수 있다는 지점에서 JS 대비 특장점을 가지고 있다. (나도 얼른 )TypeScript
로 이주해야겠다.. 물론 단점이 없지는 않지만, 그래도 컴파일 타임
타입 체크는 못참지!
스크랩해도 될까요? 도움 많이 받았습니다.