참고서의 첫 부분입니다, 당연스럽게도 예상하셨을 수도 있습니다. 그런데 정말로 간단합니다. JavaScript라고 딱히 많이 다른 건 아니에요. 그런데 보통 C나 Java 같은 언어와는 달리 JavaScript는 Typeless 계열에 속하는 언어입니다.

변수 그리고 상수

언제나처럼 JavaScript에도 상수 그리고 변수, 선언과 할당이라는 개념이 있습니다. 그리고 아래와 같이 var 키워드를 통해 무엇이든 담아낼 수 있습니다. 또한 마찬가지로 언제나 사용이 끝난 변수는 JavaScript에서 ‘가비지 컬렉션’이라는 작업을 통해 정리합니다. 만약에 현재 내용이 헷갈리신다면 아래의 ‘선언과 할당 그리고 초기화’ 섹션을 먼저 읽고 오셔도 좋습니다.

가장 오래 쓰이던 ‘var’입니다, 가장 많이 보시기도 했을 겁니다. 웹 사이트를 열어 소스를 보아 JavaScript 부분에는 항상 빠지는 경우가 거의 없기도 하죠. 하지만 문제점이 있었습니다. 예를 들면 가장 크게는 Hoisting(JavaScript에서 ‘선언문’을 모두 우선시하는 경향, 단 ‘초기화’와는 다른, 아래 문단에서 설명합니다)이 있습니다. 이 Hoisting으로 발생하는 문제는 실제로 문제 그 자체로 볼 수는 없지만 개발이 진행되면 될수록 오히려 더 많은 논리적인 실수를 일으키기도 쉽게 합니다. 그래서 이후에 let과 const가 JavaScript에 추가로 출범되었습니다. 먼저 let은 2번 이상 같은 이름의 변수를 ‘선언’할 수 없게 합니다. 그리고 const는 더 나아가 값의 ‘재할당’ 또한 금지합니다. 상수인 것이죠.

  • var; 선언과 값의 재할당 모두 제한 없음
  • let; 1번만 선언이 가능하며 값의 재할당은 제한이 없음
  • const; 상수이며 1번만 선언이 가능하며 값의 재할당 또한 할 수 없음

그렇기 때문에 let의 경우에는 2번 이상 같은 변수에 대해서 키워드를 사용할 수 없습니다.

선언과 할당 그리고 초기화

이 섹션에서는 위에서 헷갈리실 수 있는 값의 선언과 할당에 대해서 다룹니다. 만약 이미 현재 내용을 알고 계신 분들은 넘기셔도 됩니다.

사실 값의 선언과 할당 그리고 초기화, 이 3개는 처음에는 많이 헷갈릴 수 있습니다. 정말로 “아- 사실 3개 다 같은 거 아닐까?”라고 말할 수도 있는 것이죠. 그러나 이 셋은 확실히 이론상이나 사전적 정의로도 다른 의미를 가지고 있습니다. 그리고 앞으로 말할 JavaScript의 Hoisting에 대해서 알려면 확실히 알아야 하는 내용이기 때문에, 익혀두셔야 하는 내용이라고 생각합니다. 먼저 ‘선언’에 대해 이야기해봅니다. 선언은 초기화라는 작업과는 많이 다릅니다.

먼저 ‘선언’은 어떤 변수가 프로그램에서 사용되기 위해서 변수를 만드는 것을 뜻합니다. 보통 null 값으로 대체되거나 빈 값으로 변수를 ‘선언’합니다. 그리고 ‘할당’은 그 변수에 값을 배정하는 작업을 말합니다. 그리고 이 둘을 합치면 비로소 ‘초기화’가 됩니다. 사실 정말로 간단하고 그저 개념일 뿐입니다.

추가로 개인적으로 약간 헷갈렸던 부분이 있습니다. const의 경우엔 “값이 ‘상수’이기 때문에 Object 또는 Array일 경우에 프로퍼티 또는 원소를 추가할 수 있느냐”에서 많은 생각을 했었습니다만 실제로는 그렇지 않았고, 각 값은 ‘상수’로 취급되지 않음을 알 수 있었습니다. 위와 같이 말이죠. 왜냐하면 값의 재할당과 선언이 불가능한 것이지 실제로 각 프로퍼티를 수정하고 추가, 제거하는 행위는 그 변수의 ‘리스트’를 수정하는 것이고 변수 자체를 수정하는 것은 아니기 때문입니다. 물론 Object.freeze 메서드를 사용하면 프로퍼티까지도 재할당을 막을 수 있습니다(단, 이 메서드는 얕은 보호를 합니다. 말 그대로 이중 오브젝트와 같은 더 깊이 있는 프로퍼티에는 적용되지 않습니다).

Hoisting 그리고 Scope

여기에서부터는 꽤 중요한 내용입니다. JavaScript에서의 Hoisting 규칙에 대해서 설명합니다. 그리고 let과 const가 왜 생겨났는지의 이유이기도 합니다. Hoisting은 한국어로 ‘끌어올리다’라고도 하는데, 인터프리터는 var 키워드로 선언된 변수를 상단으로 미리 끌어올립니다. 먼저 아래의 코드를 살펴보겠습니다.

간단한 for 함수입니다. 먼저 첫 번째 코드 블록을 살펴보면 평소대로 저희는 for 함수에서 함수가 끝나는 대로 for 함수의 블록이 끝이 났으니 k는 사실상 유효하지 않아야(undefined) 합니다. 그러나 실제로 밑에서 변수 k의 값을 확인해보면 undefined가 아닌 10 임을 확인할 수 있습니다. 간단히 바로 아래, 두 번째 코드 블록이 정확히 무슨 일이 일어나고 있는지 나타내고 있습니다. 두 번째 코드 블록에서 확인할 수 있듯, 인터프리터는 선언(var k)을 앞으로 hoisting(끌어올립니다)합니다. 실제로는 k는 부모 스코프에 정의되어 k의 값이 함수가 끝난 뒤에도 그대로 유지되는 것이죠.

 그러나, let은 실제로 위와 같이 실행되지는 않습니다. 이를 통해서 하나 더 알 수 있는 사실이 있습니다. var 키워드와 let, const 키워드를 사용하여 만든 변수의 유효 범위(Scope라고 합니다)는 각각 모두 다르다는 것이죠. 그리고 그 두 개의 단위를 우리는 각각 Function scope와 Block scope라고 부릅니다.

  • Function scope; var
  • Block scope; let, const

이 스코프(Scope)라는 것은 간단히 말해서 특정 대상의 유효 범위입니다. 전체를 보았을 때 저희는 그것을 global scope라고 부릅니다. 모두에게서 액세스 할 수 있으니 global이라고 보는 것이죠. 그리고 특정 블록이나 함수 스코프의 내부를 local scope라고 합니다. Local scope 끼리는 서로 액세스 할 수 있으나 global scope에서는 보지 못합니다.

함수 자체에 대해서는 추후에 다룰 것입니다만 일단은 함수 스코프에 대해서 다루려고 합니다. (*여기에서는 ES5 문법의 함수를 기준으로 합니다) 우리가 기본적으로 함수를 선언하는 순간, JavaScript는 그에 해당하는 스코프 버블을 생성합니다. 마치 블록과 같이 말이죠. 그러나 이런 함수에 변수를 넣어두는 것은 딱히 좋은 방법은 아닙니다. 일단은 굳이 스코프 생성을 위해서 함수까지 써야 하나?라는 생각도 있고요. 물론 그래서 IIFE(Immediately invoked function expression)라고 하는 함수 표현식을 사용하기도 합니다. 그래도 만족스럽지는 않을 겁니다.

그래서 우리는 블록 스코프 단위를 사용합니다. 언제나 if 그리고 function 등의 키워드 뒤에 붙은 중괄호는 대부분 블록 스코프라고 보셔도 무방합니다. 하지만 언제나 var 키워드는 이 블록 스코프를 지원하지 않으며 Hoisting 시킨다는 점 또한 언제나 중요하게 알고 넘어가셔야 합니다.

Variable Typing

진짜로 변수를 정의하는 과정은 쉽습니다만 시작하기 전에 꼭 알아두셔야 할 사항입니다. 위에서 말했듯이 JavaScript는 Typeless 언어입니다. Type, 데이터형의 정의 자체는 간단합니다. 프로그래밍 언어 자체는 우리가 컴퓨터를 조작하기 위해 필요한 일종의 번역된 샘플입니다. 실제로는 컴파일 과정에서 Assembly의 형태로 바뀌어 기계어로 처리됩니다. 컴퓨터에게는 숫자만이 전부일지라도 저희에게는 아닙니다. 저희에게는 숫자도 있지만 문자 그리고 더 나아가서는 리스트나 그런 형태의 미리 ‘약속’되어 어떻게 사용할지 알고 있습니다. 그래서 컴퓨터도 몇 가지의 약속을 외웠습니다. 아래와 같이 말이죠.

2는 Number이고 ‘A’는 String입니다.

다들 이미 다른 언어를 경험하신 분께는 익숙한 내용일 것입니다. 그렇지만 실제로 여기에서 주의 깊게 살펴보아야 할 점은 다른 언어와 비교했을 때 JavaScript는 이 형태의 변환이 굉장히 쉽게 이루어진다는 것입니다. 여기에서 조금 더 나아가자면 실제로 JavaScript는 데이터형이 없는 Typeless 언어인 것도 알 수 있습니다.

 하지만 착각해서는 안됩니다. 실제로 Untyped(데이터형이 없는)라지만 약간 다른 Untyped 언어입니다. 보통 런타임은 각 값에 대한 비트를 처리합니다. 그러나 JavaScript에서는 태그 값을 처리하는 대신에 실제로는 그러한 태그 값을 이용해 또 다른 동작을 합니다. 그러므로 “실제로 Untyped라는 범주에 속하느냐”라는 질문 또한 있을 수 있습니다. 그리고 또 하나, 프로그래밍 언어 이론에서는 데이터형이 없는 것을 하나의 데이터형으로도 봅니다. 실제로 JavaScript와 같이 데이터형이 정해지지 않은 프로그래밍 언어에서는:

  1. 프로그램이 항상 생성됩니다.
  2. 따라서 데이터형이 항상 맞아떨어집니다.
  3. 그러므로 한 개의 데이터형만이 존재합니다.

하지만 다른 여러 개의 데이터형이 존재하는 언어에서는:

  1. 프로그램이 항상 생성되지 않을 수 있습니다.
  2. 왜냐하면 데이터형이 항상 맞아떨어지지 않을 수 있기 때문입니다.
  3. 그 이유는 프로그램이 여러 데이터형을 가지고 있기 때문입니다.

그래서 Untyped(데이터형이 없거나 정해지지 않은, Weakly Typed라고도 불립니다)에 속하는 프로그래밍 언어는 동적인 데이터형을 가지고 있으며, Typed(데이터형이 정해진)에 속하는 프로그래밍 언어는 정적인 데이터형을 가지고 있습니다. 하지만 더 정확히는 JavaScript는 Typed, Dynamically typed 그리고 Weakly typed에 속합니다.

  • Weakly; Won’t enforcing typing correction
  • Strongly; Allows an explicit cast format
  • Dynamic; Requires only valid format of value
  • Static; Type less-so enforced
  • Typed; Distinguishes type format always
  • Untyped; Unexpected

항상 정확히 무언가를 잘 쓰려면 저는 그 특징을 잘 알아야 한다고 생각합니다. 여기에서 일단 첫 번째 참고서는 끝입니다. 이전에 작성했던 것보다는 확실히 양이 배로 늘어났지만 그만큼 저도 오류를 확인해야 했기에 많은 공부가 되지 않았을까 합니다. 감사합니다.