변수(variable)는 프로그래밍에서 가장 기초적으로 사용되는 개념이다. 값을 저장하고 재사용하기 위해서 사용된다.
컴퓨터가 동작하는 방식은 인간의 뇌를 닮아있다. 인간의 뇌가 정보를 처리하는 방식에서 컴퓨터의 동작을 모델링 했으니 닮아 있을 수 밖에 없다.
간단하게 10 + 20 을 계산한다고 했을 때, 10을 읽고 10이라는 숫자를 기억한 후, 더하기 기호를 읽고, 더하기 연산을 한다는 것을 인식한 후, 피연산자 20을 읽고 기억한다. 그 후 두 숫자를 더하는 연산을 진행한 후 결과30을 기억한다. 이 과정에서 기억이라는 단어가 나왔다. 즉, 어떤 연산을 하는데 있어서, 기억, 즉 저장이라는 개념은 반드시 따라 나올 수 밖에 없는 것이다. 지금은 간단하게 연산만 진행했지만, 만약 이 데이터가 나중에도 사용될 여지가 있다면 장기 기억으로 옮겨져야 한다.
이 때 사용되는 것이 변수이다.
변수의 정의는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 혹은 그 공간을 식별하기 위해 붙인 이름을 말한다. 즉, 메모리 공간 자체도 변수라 부를 수 있고, 그 공간을 식별하기 위해 붙인 이름도 변수라고 할 수 있다는 것이다.
자바스크립트는 개발자에게 메모리 접근을 허용하지 않는다. C언어 같은 언매니지드 언어는 malloc 과 free 명령어를 통해서 혹은 포인터 변수를 통해서 직접 메모리 공간에 접근하거나, 메모리를 할당하고 해제할 수 있다. 하지만 자바스크립트는 언매니지드 언어라 이런 위험한 행위를 허용하지 않는다.
간단히 말하면 변수는 프로그래밍 언어에서 값을 저장하고 참조하는 메커니즘으로 값의 위치를 가리키는 상징적인 개념이다.
변수는 크게 세 가지 과정을 거쳐서 사용할 수 있게 만들어진다.
자바스크립트의 변수의 종류에는 크게 var, let, const 세 가지가 있다. es6 이전에는 var 로 만들어지는 변수만 사용할 수 있었는데, 이 var 변수는 메커니즘이 일반 프로그래밍 언어와 달라서 사용하는데 혼란을 야기하고는 했다. 그 중의 대표적인 예가 호이스팅과 스코프 문제였다.
우선 var 변수가 사용되기 까지 과정을 확인해야 한다.
선언이란 변수가 실행컨텍스트에 등록되고 메모리를 할당 받는 과정을 뜻한다. 실행 컨텍스트는 자바스크립트 내부 엔진에서 관리하는 전반적인 런타임 환경 관리자라고 생각하면 된다. 변수는 실행 컨텍스트 내에서 변수명과 메모리 주소로 key:value 형식으로 매핑 되어 저장된다.
var a;
해당 코드를 통해 변수를 선언한다.
var 일반적이지 않게 선언과 초기화가 동시에 일어난다. 선언을 하면 그 즉시 undefined 가 할당되어 초기화 된다.
초기화가 되었으면 이제 값을 할당할 수 있고, 할당을 하면, 값을 호출하여 사용할 수 있다. 할당이 되지 않더라도 호출하여 사용할 수 있지만, 그러면 undefined가 호출되게 된다.
호이스팅은 자바스크립트 만의 특이한 현상이다. 그리고 이 현상은 var 변수와 연계되서 매우 특이한 결과를 도출해낸다. 자바스크립트 엔진은 런타임 이전에 모든 변수와 함수의 선언부를 스캔해서 미리 저장해둔다. 이런 메커니즘은 마치 선언 코드들을 해당 스코프에서 맨 위로 끌어올린 듯해 보이게 만든다. 그래서 이 현상을 호이스팅이라고 이름 붙였다. 하지만 이 현상은 실제로 코드를 위로 올리지는 않고, 선언부를 미리 읽는 개념이다.
var 변수가 위에서 선언과 초기화가 동시에 일어난다고 했다. var 변수는 호이스팅이 일어나면 런타임 이전에 실행 컨텍스트에 모두 등록되게 된다. 그러면 아래와 같은 코드가 에러를 뱉는게 아니라, undefined를 출력하게 된다.
console.log(a);
var a = 10;
일반적인 언어에서는 당연히 reference error 즉 참조에러가 발생해야 정상이다. 선언도 되지 않고 초기화도 되지 않은 변수를 어떻게 호출할 수 있겠는가. 하지만 자바스크립트의 호이스팅 현상 때문에, 그리고 var 변수의 특성 때문에 가능하다. 호이스팅 현상이 일어나면서 var 변수는 런타임 이전에 실행 컨텍스트에 등록된다. 즉, 실행이 되기도 전에 선언이 자동으로 실행되는 것이다. 그리고 var 변수는 초기화가 선언과 동시에 일어난다고 했다. 즉 런타임에 들어가자마자 var 변수는 이미 선언되어있으니 undefined로 초기화 된다. 때문에 위의 코드는 에러를 뿜는게 아니라, undefined 를 출력한다.
let, const 변수에서도 호이스팅이 발생한다. 사실 자바스크립트의 모든 선언은 호이스팅이 된다. function, class 모두 호이스팅이 된다. 하지만 let과 const는 var과 다르게 위의 코드 처럼 작성하면 undefined를 출력하지 않고 참조에러를 뱉는다.
console.log(a);
let a = 10;
그 차이는 초기화 시점에서 야기된다. let, const 변수는 초기화 시점이 런타임에 본인의 코드 위치이다. 즉 var 변수는 선언과 동시에 초기화가 된다고 했는데, let, const 변수는 선언이 되고(호이스팅 되어 가장 처음에서 선언되고) 자신의 실제 코드 위치에 와서야 초기화가 실행되는 것이다.
{ let a } // 호이스팅 된 것처럼 보이는 코드 - 이 시점에는 선언만 된다.
console.log(a)
let a; // 이 시점에 초기화가 일어난다.
때문에 console.log(a) 는 초기화가 되지 않은 쓰레기값을 저장하고 있는 변수를 참조하지 않게 에러를 뿜는다.
그렇다면 호이스팅은 자바스크립트의 부작용이라고 할 수 있다. 일반적으로 에러를 띄워야 되는 상황에 에러를 띄우지 않는다면 부작용이 일어났다고 말할 것이다. 그렇다면 이런 예측이 힘든 상황을 자바스크립트 언어 개발자는 왜 만들었을까.
이건 자바스크립트가 만들어질 상황으로까지 거슬러 올라가야 한다. 필자는 자바스크립트가 근본 없는 언어라고 말하고 다닌다. 내가 가장 메인으로 삼는 언어지만 인정할건 인정해야 한다. 자바스크립트는 개발자를 위해 잘 짜여져서 만들어진 언어가 아니었다. 자바스크립트는 목적 자체가 프로그래밍이 아닌, 브라우저의 DOM 처리를 돕기 위한 스크립트 언어로 사용되기 위해 만들어졌다. 때문에, 이 언어를 배우고 사용해야 하는 사람들은 개발자가 아니라, 웹 디자이너 혹은 퍼블리셔였다. 그렇기 때문에 배우기 쉽고, 에러를 최대한 적게 띄우고 똑똑하게 돌아가는 언어가 필요했다. 호이스팅은 아무것도 모르고 개발하는 사람이라면 오히려 똑똑한 현상일수도 있다. 어디에 선언을 해놓든 호출해서 사용할 수 있다니.
자바스크립트 창시자인 브랜든 아이크 아죠씨는 var 호이스팅 현상은 함수 호이스팅을 구현하면서 생긴 부작용이라고 명시했다. 그리고 es6 에서 이를 해결하기 위해 let 변수를 도입했다고 했다.
즉, top-down 의 코드 진행 방식이 함수를 사용하는 데 있어 효율적이지 않다고 판단해서 함수 호이스팅을 만들려했고 그 부작용으로 var 변수 호이스팅이 발생했다는 것이다.
출처:
https://ui.toast.com/weekly-pick/ko_20200228
모던 자바스크립트 deep dive
https://v8.dev/blog/trash-talk