Github에 공개되어있는 내용 중 TYPES & GRAMMAR의 챕터 1 : TYPES 를 개인적으로 번역한 것입니다.
원문 : https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch1.md
챕터 1 : TYPES
대부분의 개발자들은 자바스크립트와 같은 동적 언어는 타입을 갖지 않는다고 말할 것이다. 이 주제에 대해 알아보기 위해 ES5.1 명세를 살펴보자.
(http://www.ecma-international.org/ecma-262/5.1/)
이 명세 안의 알고리즘은 타입과 연관된 값을 다룬다. 타입의 값들은 아래 목록에 정확히 정의되어있다. 타입들은 ECMA Script language types and specification types에 더 세밀하게 분류되어있다.
ECMAScript 언어의 타입은 ECMAScript 언어를 사용하는 개발자가 정의한 값에 바로 부합한다. ECMAScript 언어의 타입에는 Undefined, Null, Boolean, String, Number, Object가 있다.
이제, 만약 당신이 정적 타입 언어를 선호한다면 '타입'이라는 말이 이런 식으로 사용되는 것을 반대할 것이다. 이런 언어들에서 '타입'은 자바스크립트에서보다 더 많은 것을 의미한다.
몇몇 사람들은 자바스크립트가 타입을 갖지 않고, 타입 대신 '태그'나 '서브 타입'이라 불려야한다고 주장한다.
하! 우리는 이런 러프한 정의(스펙에 있는 말그대로)를 이용할 것이다. 타입은 고유한 것이며, 값의 동작을 구분하고 엔진과 개발자에게 다른 값과 구별이 되도록 하는 내장된 문자열이다.
다른 말로, 엔진과 개발자 모두 숫자 42를 문자열 "42"와 다르다고 인식한다면, 두 값은 number와 string, 다른 타입을 가지게 된다. 당신이 42란 값을 사용할 때 Math 클래스처럼 숫자로서의 동작을 의도할 것이다. 하지만 당신이 "42"라는 값을 사용할 때는 화면에 값을 출력하는 등 문자열로서의 동작을 의도할 것이다. 이 두 값은 서로 다른 타입을 가진다.
이것은 명확한 정의없이 이루어진다. 하지만 이 문제에 대해선 이것으로 충분하다. 그리고 이것이 자바스크립트가 자신을 기술하는 방식과 일치한다.
다른 명칭의 타입
학문적인 정의의 불일치를 넘어서 자바스크립트가 타입을 가지고 있던 아니던 뭐가 문제인걸까?
각 타입과 그 동작에 대한 적절한 이해는 다른 타입으로의 올바른 형변환(챕터 4)에 있어 필수적이다. 최근 모든 자바스크립트 프로그램에서는 특정 폼에서 값의 형변환이 필요한 경우가 있다. 그러므로 그것은 근거와 책임감있게 해야한다.
만약 당신이 number 타입을 값 42에서 1 포지션의 글자 "2"를 추출하는 것처럼 string 타입으로 사용하고 싶다면 반드시 값의 타입을 number에서 string으로 변환해야할 것이다.
간단해 보이지만, 이런 형변환은 다른 여러 방법으로 이루어질 수 있다. 이 중 몇가지는 명확하고 배우기 쉬우며 안정적이다. 다만 당신이 주의하지 않을 경우 형변환은 예상치못한 방향으로 발생할 수 있다.
어쩌면 형변환의 혼동은 자바스크립트 개발자에게 좌절을 안기는 가장 큰 원인일 것이다. 이것은 언어의 설계에 결함이 있어 피해야한다고 자주 비난받는다.
당신의 인식을 바꾸고 형변환의 유용함과 위력을 확인하기 위해, 자바스크립트 타입의 완전한 이해로 왜 형변환의 나쁜 평판이 과도하게 퍼져있고 어떤 것이 부당한지 알아볼 것이다. 하지만 먼저, 우리는 값과 타입들에 대한 더 나은 이해가 필요하다.
내장 타입들
자바스크립트는 7가지 타입을 내장하고있다.
• null
• undefined
• boolean
• number
• string
• object
• symbol (ES6에서 추가됨)
- 이 모든 타입들은 object를 제외하고 모두 '원시 타입'이라 불린다.
typeof 연산자는 주어진 값의 타입을 유추해 항상 일곱개의 타입 중 하나를 문자열로 반환한다. 놀랍게도 아래 목록에서 7개 타입과 1대1로 정확하게 매치되는 것은 없다.
1 2 3 4 5 6 7 | typeof undefined === "undefined"; // true typeof true === "boolean"; // true typeof 42 === "number"; // true typeof "42" === "string"; // true typeof { life : 42 } === "object"; // true typeof Symbol() === "symbol"; // true | cs |
보이듯이, 이 6개의 타입들은 타입명과 일치하는 값을 가지고 문자열을 반환한다. Symbol은 ES6에 추가된 새로운 데이터 타입이며 챕터3에서 다루도록 한다.
눈치챘듯이 null이 위 목록에서 제외되있다. null은 특수한 경우이다 -- typeof 연산자와 결합되면 버그가 발생할 수 있다.
1 | typeof null === "object"; // true | cs |
만약 이 코드가 "null"을 반환한다면 좋을 것이다. 하지만 이 고질적인 자바스크립트의 버그는 20여년 가까이 유지되고 있고 영영 고쳐지지 않을 것으로 보인다. 왜냐하면 이미 너무 많은 웹 컨텐츠에서 이 buggy한 동작에 의존하고 있고 이 버그를 "고치게"되면 더 많은 "버그들"을 만들고 많은 웹사이트를 망칠것이기 때문이다.
만약 null 값을 타입을 이용해 검사하고 싶다면 복합적인 조건이 필요하다.
1 2 3 | var a = null; (!a && typeof a === "object"); // true | cs |
null은 유일하게 false값을 가지지만 typeof 연산자 사용시 "object"를 반환하는 유일한 원시적인 값이다.
그러면 typeof 연산자가 리턴할 수 있는 일곱번째 문자열 값은 무엇일까?
1 | typeof function a(){ ... } === "function"; // true | cs |
이 typeof 연산자의 결과를 보면 function이 자바스크립트에 내장된 원시 타입이라고 생각하기 쉽다. 그러나, 당신이 스펙을 읽고나면 object의 '서브 타입'으로 보게 될 것이다. 특이하게, 함수는 '호출 가능한 객체'로 취급된다. 이 호출 가능한 객체는 [[Call]] 이라는 불러올 수 있는 내부 속성을 가진다.
사실은 함수는 정말로 유용한 객체라는 것이다. 더 중요한 것은, 함수는 속성을 가질 수 있다. 예를 들어,
1 2 3 | function a(b, c) { ... } | cs |
이 함수 객체는 length라는 속성을 가지는데, 함께 선언된 매개변수의 개수를 의미한다.
1 | a.length; // 2 | cs |
당신이 b와 c라는 두 개의 매개변수를 가지는 함수를 선언했을 때부터 그 함수의 length는 2가 된다.
배열의 경우에는 어떨까? 배열 또한 별개의 타입일까?
1 | typeof [1, 2, 3] === "object"; // true | cs |
놉! object이다. 배열은 object의 '서브 타입'으로 생각하는게 가장 적절하다.
타입들의 값
자바 스크립트에서 변수들은 타입을 가지지 않고 값이 타입을 가진다. 언제나 변수들은 값을 가질 수 있다.
자바스크립트의 타입을 다른 식으로 이해하는 방법은 자바스크립트는 형 강제화가 없다는 것이다. 엔진은 변수가 항상 선언되었을 때와 같은 타입의 값을 가져야한다고 고집하지 않는다. 변수는 선언되었을 당시에 string 값을 가질 수 있고, 그 다음 number 값을 가질 수도 있다.
42는 본질적으로 number 타입인데 이 타입은 변경될 수 없다. 문자열 "42"가 그렇듯이 형변환이라는 과정을 통해 number 타입에서 string 42가 될 수 있다.
자바스크립트 변수는 타입을 갖지 않기 때문에 만약 당신이 변수를 대상으로 typeof 연산자를 이용하는것은 '변수의 타입이 무엇인가?'를 묻는 것이 아니다. 대신, '변수의 값이 가지는 타입이 무엇인가?' 를 묻는다.
1 2 3 4 5 | var a = 42; typeof a; // number a = true; typeof a; // boolean | cs |
typeof 연산자는 항상 문자열을 반환한다. 그러므로
1 | typeof typeof 42; // string | cs |
첫번째 typeof 42는 "number"를 반환하고, typeof "number"는 "string"을 반환한다.
undefined vs "undeclared"
값을 가지지 않는 변수는 undefined라는 값을 가진다. typeof 연산자에 이 변수를 대입했을 때 "undefined"를 반환한다.
1 2 3 4 5 6 7 8 9 10 11 12 | var a; typeof a; // undefined var b = 42; var c; // later b = c; typeof b; // undefined typeof c; // undefined | cs |
이것은 많은 개발자들로 하여금 'undefined'가 'undeclared'와 동의어라는 생각을 하도록 유도한다. 그러나, 자바스크립트에서는 이 두 개념은 전혀 다른 개념이다.
'undefined' 변수는 접근 가능한 스코프에 선언되었으나 접근 당시에 값을 가지고 있지 않는 변수를 의미한다. 반대로, 'undeclared' 변수는 접근 가능한 스코프에서 선언된 적이 없는 변수를 의미한다.
1 2 3 4 | var a; a; // undefined b; // ReferenceError : b is not defined | cs |
한 가지 짜증나는 점은, 브라우저에 맡겨진 에러 메시지이다. 볼 수 있듯이, 'b is not defined' 메시지는 'b is undefined' 라고 충분히 혼동할 수 있다. 다시, 'undefined'와 'is not defined'는 명백하게 다르다. 만약에 브라우저에서 'b is not found'라던가 'b is not declared'라고 메시지를 띄웠다면 혼동은 좀 줄었을 것이다!
선언된적 없는 변수들과 typeof와 관련되 이 혼란을 가중시키는 특이한 동작이 있다.
1 2 3 4 | var a; typeof a; // undefined typeof b; // undefined | cs |
typeof 연산자는 'undeclared' 변수에도 "undefined" 를 반환한다. 주의할 점은 변수 b가 선언된 적이 없더라도 typeof b 에서는 오류가 발생하지 않는다는 점이다. 이것은 typeof 연산자의 특별한 안전 장치이다.
위와 비슷하게 만약 typeof 연산자가 선언되지 않은 변수에 사용됬을 때 "undeclared"를 반환했다면 좀 나았을것이다.
typeof Undeclared
그럼에도 불구하고, 이 안전 장치는 브라우저에서 다중 스크립트 파일이 전역 네임스페이스에 변수들을 로드해 공유하는 자바스크립트를 다룰 때 유용한 장치이다.
Note : 많은 개발자들이 어떤 변수들도 전역 네임스페이스에 존재해선 안되며 모듈과 제한된/분리된 네임스페이스에 존재해야한다고 생각한다. 이것은 매우 좋은 이론이나 실제로는 거의 불가능하다. 다행히 ES6에서 이것을 좀더 현실적으로 가능하게하는 모듈에 대한 일급 지원을 추가했다.
쉬운 예시로, 당신의 프로그램에서 DEBUG라고 불리는 전역 변수(플래그 값)에 의해 제어되는 '디버그 모드'를 실행했을 때를 상상해보라. 당신은 변수가 콘솔에 메시지를 로깅하는 작업 같은 디버그를 수행하기 전에 변수가 선언되어있는지 확인할 수 있을 것이다. 최상위 전역 선언인 var DEBUG = true 이 제품이 아닌 당신이 개발 중이거나 테스트중일 때만 로드하는 'debug.js' 파일에만 존제할것이다.
그러나, 당신은 DEBUG 전역 변수를 다른 코드에서 체크해 ReferenceError 예외가 발생하지 않도록 주의해야한다. 이 경우 typeof 가 안전 장치가 되어준다.
1 2 3 4 5 6 7 8 9 | // 이런, 이 코드는 예외가 발생합니다! if (DEBUG) { console.log("Debugging is starting"); } // 변수의 존재 여부를 체크하는데 안전한 코드 if (typeof DEBUG !== "undefined") { console.log("Debugging is starting"); } | cs |
이러한 검사는 사용자가 정의하지 않는 변수들을 대상으로 할 때도 유용하다. 만약 당신이 API에 내장된 것을 대상으로 검사를 한다면 이 방식이 예외도 발생하지 않고 유용할 것이다.
1 2 3 | if (typeof atob === "undefined") { atob = function() { ... }; } | cs |
Note : 만약 당신이 이미 존재하는 'polyfill(웹 개발에서 기능을 지원하지 않는 웹 브라우저 상의 기능을 구현하는 코드)'을 정의한다면 보통 var를 사용하지않고 atob을 선언하길 원할것이다. 만약 당신이 var atob을 if문 안에서 선언한다면 만약 if문이 실행되지 않더라도 그 선언은 최상위 스코프에서 이루어질 것이다(왜냐면 전역 변수인 atob이 이미 존재하거든!). 몇몇 브라우저들과 내장된 변수들(보통 'host objects'라고 불린다.)의 특이한 타입들은 이러한 선언의 중복에서 예외를 발생시킬 수도 있다. var의 생략은 이러한 선언의 함정을 예방한다.
typeof 의 안전 장치를 제외하고 전역 변수들의 검사를 하는 다른 방법은 브라우저의 기본 객체인 window의 속성이기도 한 모든 전역 변수들을 감시하는 것이다. 아래와 같이 검사할 수 있다(꽤 안전하게).
1 2 3 4 5 6 7 | if (window.DEBUG) { ... } if (!window.atob) { ... } | cs |
선언되지 않은 변수를 참조하는 것과 다르게, 이 코드에서는 당신이 존재하지 않는 객체의 속성(전역 window 객체에 있더라도)에 접근해도 ReferenceError 예외가 발생하지 않는다.
반면에, window를 참조해 전역 변수를 참조하는 것은 몇몇 개발자들이 꺼려하는 방식인데, 특히 당신의 코드가 전역 객체가 window라고 불리는 것이 보장되지 않는 다중 자바스크립트 환경에서 동작해야하는 경우에 그렇다.
기술적으로, typeof 연산자의 안전 장치는 전역 변수를 사용하지 않더라도 유용하다. 이러한 현상이 흔치 않고 몇몇 개발자들이 이상적이지 않은 접근 방식을 찾더라도 말이다. 다른이들이 그들의 프로그램이나 모듈에 복붙해가는 유틸리티 함수를 상상해보자, 그 함수는 프로그램의 변수가 확실히 정의되거나 사용가능한지를 검사하는 코드다.
1 2 3 4 5 6 7 8 | function doSomethingCool() { var helper = (typeof FeatureXYZ !== "undefined") ? FeatureXYZ : function() { ... }; var val = helper(); ... } | cs |
doSomethingCool() 은 FeatureXYZ 라는 변수를 검사하는데, 변수가 존재하면 사용하고, 존재하지 않으면 내부에 가지고있는 것을 사용한다. 이제, 이 코드를 다른이가 FeatureXYZ의 검사를 위해 그들의 프로그램이나 모듈에 사용한다고 가정해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // IIFE코드 ("Immediately Invoked Function Expressions" 참고) (function(){ function FeatureXYZ() { ... } // include 'doSomethingCool()' function doSomethingCool() { var helper = (typeof FeatureXYZ !== "undefined") ? FeatureXYZ : function() { ... }; var val = helper(); ... } doSomethingCool(); })(); | cs |
여기서 FeatureXYZ는 절대 전역 변수가 아니지만 typeof 의 안전 장치가 안전하게 검사를 하도록 하고있다. 그리고 중요한것은, 저기서 어떤 객체도 전역 변수를 window.___ 방식으로 사용했던 것처럼 사용할 수 없어 typeof 연산자가 더 유용하다는 것이다.
다른 개발자들은 함수 내에서 외부의 FeatureXYZ를 검사하는 doSomethingCool() 대신 외부에서 전달받은 것을 검사하는 '종속성 주입(DI)' 디자인 패턴을 더 선호한다.
1 2 3 4 5 6 7 | function doSomethingCool (FeatureXYZ) { var helper = FeatureXYZ || function(){ ... } var val = helper(); ... } | cs |
이러한 기능을 구현하는데는 다양한 방법이 존재한다. 어떤 패턴이 맞거나 틀리다고 할 수 없다. 각 접근법 마다 다양한 흥정이 존재하기 때문이다. 하지만 전반적으로 선언되지 않은 것에 대한 typeof 연산자의 안전장치는 더 많은 옵션을 제공한다.
리뷰
자바스크립트에는 일곱 가지 내장 타입들이 존재하며 typeof 연산자에 의해 구별된다.
- null, undefined, boolean, number, string, object, symbol
변수는 타입을 가지지 않고 값이 타입을 가진다. 이러한 타입들은 값들의 고유한 동작을 정의한다.
많은 개발자들이 'undefined'와 'undeclared'를 같은것으로 생각하고 있으나, 자바스크립트에서 이 둘은 명백히 다르다.
- undefined는 변수가 값을 가지지 않는 것이다.
- undeclared는 변수가 선언된 적이 없는 것을 의미한다.
자바스크립트는 불행히도 이 두 용어를 혼용하는데 에러 메시지(ReferenceError : a is not defined) 뿐만 아니라 typeof 연산자의 반환값("undefined")에서도 그러하다.
그러나, typeof 연산자의 안전장치가 특정 상황에서 선언되지 않은 변수를 대상으로 쓰이기도 한다.
'☕️ JAVASCRIPT' 카테고리의 다른 글
HTML Entities 치환 라이브러리, html-entities (0) | 2022.07.19 |
---|---|
도로명주소 API 연동하기 (0) | 2019.10.05 |
자바스크립트에서 소수점 계산이 이상하다?? (0) | 2019.08.30 |
[TYPES & GRAMMAR] 챕터 2 : VALUES - Array (0) | 2019.08.22 |
eval() 과 new Function() (0) | 2018.08.27 |