변수 & 스코프 & 호이스팅
식별자Permalink
식별자는 어떤 값을 구별해서 식별할 수 있는
고유한 이름
을 말한다.
컴퓨터는 CPU를 사용해서 연산을 하고
메모리를 사용해서 데이터를 기억한다.
이 메모리 상에 존재하는 어떤 값을 식별 수 있는 이름은 모두 식별자라고 부르는데
변수, 함수, 클래스와 같은 것들이 모두 식별자이다.
변수 선언Permalink
식별자하면 가장 먼저 떠오르는 것이 변수일 것이다.
변수 선언은 변수를 생성하는 것으로
값을 저장하기 위한 메모리공간을 확보하고
변수 이름과 확보된 메모리 공간의 주소를 연결해서 값을 저장할 수 있게 준비하는 것이다.
자바스크립트에서는 이 선언을 위해서var, let, const
키워드를 사용한다.
변수 선언과 초기화Permalink
자바스크립트 엔진은
변수 선언
을
선언 단계
와초기화 단계
2단계에 거쳐 수행을 하는데
var 을 사용한 변수 선언은 선언 단계와 초기화 단계가 동시에 진행이 된다.
이 초기화 단계에서는 변수에 암묵적으로 undefined를 할당하여 초기화를 한다.
하지만 let과 const를 사용한 변수 선언은 초기화가 되지 않는다.
즉 선언만 되고 아직 초기화 되지 않는 변수가 머무르는 메모리 공간이 생기게 되는데
이러한 공간을Temporal Dead Zone
임시사각지대라고 부른다.
let과 const로 선언한 변수들은 이 TDZ를 거쳐간다.
💡 왜 선언과 동시에 초기화 단계를 거치는 걸까?
초기화 단계를 거치지 않으면 확보된 메모리 공간에는
이전에 다른 애플리케이션이 사용했던 값이 남아 있을 수 있기 때문에
메모리 공간을 확보하고 값을 할당하지 않은 상태에서 변수 값을 참조할 경우 쓰레기 값이 나올 수 있다.
이를 방지하기 위해서 암묵적으로 초기화를 수행하는 것이다.
💡 왜 초기화를 하지않고 TDZ를 거치는 걸까?
메모리 공간을 확보하고 값을 아예 참조하지 않고 있기 때문에
값을 할당하지 않은 상태에서 변수 값을 참조할 경우 참조에 대한 에러로 ReferenceError를 뿜게된다.
개발 단계에서 오류를 발견하고
초기화되지 않은 변수를 참조하는 것을 방지해서 코드의 동작을 예측하기 위함이다.
let 키워드는 var 과 달리 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다.
값의 할당Permalink
값을 할당하면
변수 선언을 통해 확보된 메모리 공간에 해당 값이 저장되고
이 메모리 공간은 변수의 이름을 통해 참조된다.
여기서 중요한 것은
변수 선언은 소스코드가 순차적으로 실행되는 시점인 런타임 이전에 먼저 실행되지만
값의 할당은 런타임에 실행이 된다.
호이스팅Permalink
자바스크립트 엔진은 소스코드를 순차적으로 실행하기에 앞서
먼저 소스코드의 평가 과정을 거치면서 소스코드를 실행하기 위한 준비를 한다.
이 평가 과정에서 자바스크립트 엔진은 모든 선언문(변수 선언문, 함수 선언문 등)을
소스코드에서 찾아내서 먼저 실행하고
평가 과정이 끝나면 모든 선언문을 제외하고 소스코드를 순차적으로 실행하게된다.
이 때문에 변수 선언이 소스코드의 위치에 상관없이 어디서든지 변수를 참조할 수 있게된다.
마치 변수 선언문이 코드의 선두로 끌어 올려진 것 처럼 동작하게 되는데
우리는 이러한 동작을 이해하기 쉽게 “끌어올리다” 라는 의미로 호이스팅이라는 단어를 사용하는 것이다.
즉, 실제로 끌어올리는 것은 아니다
💡 함수선언문 vs 함수 표현식
◾ 함수 선언문은 함수 전체가 호이스팅되기 때문에 선언 전에 호출이 가능하다.
함수 표현식은 변수 선언부만 호이스팅 되는데
함수 표현식은 변수 선언 방식에 따라 다르게 동작한다.
◾ var로 선언된 경우 호이스팅되면서 undefined로 초기화되므로,
선언 이전에 호출하면 undefined가 반환되고,
해당 변수에 할당된 함수를 호출하려고 하면 TypeError가 발생한다.
그러나 let과 const로 선언된 경우 선언 이전에 호출하려고 하면 ReferenceError가 발생한다.
스코프Permalink
스코프는 식별자가 유효한 범위로
모든 식별자는 자신이선언된 위치
에 의해 다른 코드가
식별자 자신을 참조할 수 있는 유효 범위가 결정되는데 이를 스코프라고 한다.
예를 들어, 어떤 경계 A의 외부에서 선언한 변수는
A의 외부뿐 아니라 A의 내부에서도 접근이 가능하지만
A내부에서 선언한 변수는 오직 A의 내부에서만 접근 할 수 있는 것이다.
스코프의 종류Permalink
스코프는
전역 스코프
와지역스코프
로 구분할 수 있다.
전역에서 선언된 변수는 전역 스코프를 갖는 전역 변수로
전역 변수는 어디서든지 참조를 할 수 있다.
지역에서 선언된 변수는 지역 스코프를 갖는 지역 변수로
지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효하다.
그리고, 지역 스코프는
함수 레벨 스코프
와블록 레벨 스코프
로 구분할 수 있다.
var, let, const 키워드 중 어떤 키워드로 선언하느냐에 따라 스코프도 다르게 생성된다.
함수 레벨 스코프Permalink
함수레벨 스코프는 함수에 의해서만 생성되는 지역 스코프로
var 키워드로 선언된 변수는 오로지 함수의 코드 블록 만을 지역 스코프로 인정하는 특성이 있다.
💡 전역 객체
브라우저 환경에서는 window 객체가 전역객체이고 Node.js 환경에서는 global 객체가 전역객체이다.
var 키워드로 선언한 전역 변수는 전역 객체인 window의 프로퍼티가 될 수 있다.
선언하지 않은 변수에 값을 할당하는 형태로 암묵적인 변수 선언이 가능하다.
블록 레벨 스코프Permalink
블록 레벨 스코프는 모든 코드 블록(함수, if 문, for 문 while문, try/catch 문 등)
쉽게 생각해서 중괄호를 통해 구분되는 블록들을 블록 레벨 스코프라고한다.
const와 let 키워드로 선언된 변수는 모두 블록 레벨 스코프를 따른다.
let 과 const의 차이Permalink
let은 초기화 단계에서 undefined로 변수를 초기화 시킨다. let 키워드로 선언된 변수는 재할당이 자유롭다. const는 선언 시에 반드시 선언과 동시에 초기화를 해야한다. const 키워드로 선언된 변수는 재할당이 금지된다.
💡 재할당 금지 !== 불변
const 키워드로 선언된 변수에 객체를 할당한 경우에는 값을 변경 할 수 있다.
const 키워드는 재할당을 금지할 뿐이지,
불변을 의미하지는 않는다.
객체가 변경되더라도 변수에 할당된 참조값은 변경되지 않기 때문이다.
var을 최소화 해야하는 이유Permalink
var은
변수 재선언이 가능하고
함수 레벨 스코프를 가지고
선언과 초기화가 동시에 이루어지기 때문에
개발을 하는 입장에서
전역 객체을 오염시키거나
예상치 못한 동작을 유발하는 원인이 될 수도 있다.
따라서 초기화되지 않은 변수를 사용하는 것을 방지하고
블록 스코프를 통해 명확한 변수 관리할 수 있는 let과 const를 이용하는 것이 좋다.
스코프 체인Permalink
함수는 전역 또는 다른 함수 내부에서 정의될 수 있는데
이것을 함수의 중첩 이라고 한다.
함수가 중첩될 때, 각 함수 는 자신만의 지역 스코프를 가질 것인데
스코프 체인은 이러한 지역 스코프들이 계층적으로 연결된 것을 의미한다.
즉 스코프들의 중첩으로 인해 계층적인 구조를 가지게 되는 것이다.
예를 들어, 외부 함수 내부 에 중첩 함수가 있으면
중첩 함수의 스코프는 외부 함수의 스코프를 상위 스코프로 갖게 된다.
어떻게 그것이 가능할까?
실행 컨텍스트Permalink
실행 컨텍스트
에 대해 간략하게 살펴볼 필요성이 있다.
실행 컨텍스트란
실행할 코드에 필요한 환경 정보들을 모아놓은 객체로,
자바스크립트 코드의 실행과 관련된 모든 정보를 담고 있다.
모든 자바스크립트 코드는 실행 컨텍스트 내부에서 실행되게 된다.
이 실행 컨텍스트는 스크립트 파일이 실행되거나 함수가 호출될 때 생성이 되는 조건이 있다.
함수가 호출되면 새로운 실행 컨텍스트가 생성되고, 함수 내부의 코드를 실행하기 전 함수 코드 평가 과정이 이루어진다.
이 과정에서
environmentRecord
가 생성되고
outerEnvironmentReference
가 결정되는데
environmentRecord 는
함수 안의 코드가 실행되기 전에 현재 컨텍스트와 관련된 코드의 식별자 정보가 저장되는 저장소이고
outerEnvironmentReference 는
현재 함수가 속한 외부 환경를 가리키는 참조로
함수 내부에서 변수를 찾을 때,
현재 environmentRecord에서 찾고
현재 렉시컬 환경에서 변수를 찾을 수 없다면 외부 환경에서 찾을 수 있도록 도와주는 것이 outerEnvironmentReference이다.
environmentRecord가 생성되고
outerEnvironmentReference가 결정되는
함수 코드 평가 과정이 모두 이루어지면
전역 공간에 있던 코드의 제어권이 함수가 호출되면서 함수 내부로 이동하게 되는데
함수 내부에서 또 새로운 environmentRecord가 생성되고 outerEnvironmentReference가 결정된다.
environmentRecord와 outerEnvironmentReference를 통해서
함수 내부에서 실행되는 코드는
함수의 지역 변수, 매개 변수 등에 접근할 수 있고
함수 외부에서 정의된 변수나 함수에도 접근할 수 있게 되는 것이다.
만약 최상위 스코프인 전역 스코프에서도 참조할 값을 찾지 못한다면
그때는 Reference Error 를 출력한다.
💡 environmentRecord
호이스팅이 가능했던 이유가
바로 이 environmentRecord 생성되면서
코드가 실행되기 전에 자바스크립트 엔진이
해당 환경에 속한 코드의 변수명 등을 모두 알게 되기 때문이다.
💡 렉시컬 환경
렉시컬 환경은 실행 컨텍스트의 한 부분으로, 변수와 함수의 스코프를 관리하는데
실행 컨텍스트가 생성될 때, 그 안에 렉시컬 환경이 포함된다.
그리고 Environment Record와 outerEnvironmentReference가 렉시컬 환경에 포함된다.
요약Permalink
var, let, const 차이는 무엇인가요?
var는 함수 스코프나 전역 스코프를 가지며 중복 선언이 가능합니다.
반면 let과 const는 블록 스코프를 가지며,
중복 선언이 불가능하고 선언 전에 참조하면 에러가 발생합니다.
let은 변수에 새로운 값을 할당할 수 있으며, const는 상수를 선언하는데 사용됩니다.
var와 let/const 는 호이스팅 과정에서 좀 다르게 동작하는데,
var로 로 선언한 변수는 호이스팅 과정에서 초기화까지 완료되어,
선언 전에 참조하면 undefined를 얻게 됩니다.
반면, let/const로 선언한 변수는 호이스팅 과정에서 메모리 공간만 확보되고 초기화는 실제 선언문이 실행될 때 이루어지기 때문에
선언 전에 참조하려고 하면 TDZ에 의해 ReferenceError가 발생합니다.
const로 선언된 변수도 블록 스코프를 가지며, 재할당이 불가능합니다.
하지만 객체나 배열의 경우, 내부 속성은 변경할 수 있습니다.
클로저에서 const를 사용하면, 변수가 재할당되지 않기 때문에,
변수를 고정된 값으로 유지하고 싶을 때 유용합니다.
reference
댓글남기기