자바스크립트에 관심이 있거나 공부한다면 한번쯤 들어봤을 개념이다.
“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.
자바스크립트에서 젤 중요한 개념이다.
- 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
- 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념
실행 컨텍스트는 다음과 같은 것들을 이용하면 콜스택(call stack)에 쌓이게 된다.
자바스크립트는 단일 스레드 프로그래밍 언어이다. 그렇기 때문에 단일 호출 스택이 있는데 단일 호출 스택이라 함은 한 번에 하나의 일만 처리할 수 있다는 말이다.
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
그렇다. 자바스크립트는 대충 이런 원리로 실행된다.
실행 컨텍스트(execution context)란 ?
var a = 1; // 전역 컨텍스트
function outer () { // outer 컨텍스트
function inner () { // inner 컨텍스트
console.log(a); // undefined
var a = 3;
console.log(a); // 3
}
inner();
console.log(a); // 1
}
outer();
console.log(a); // 1
위와 같이 코드를 구성했을 때 실행 컨텍스트의 스택은 다음과 같은 순서로 실행된다.
그리고 이러한 정보들이 생성된다.
- VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만, 최초 실행 시의 스냅샷을 유지한다. 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 복사해서 LexicalEnvironment를 만든다.
- 주로 활용하는 것은 LexicalEnvironment이다. 즉, VariableEnviroment는 스냅샷 유지를 목적으로 사용한다.
- LexicalEnvironment의 내부에는 environmentRecord와 outerEnvironmentReference로 구성돼 있다.
- environmentRecord로 인하여 호이스팅이 발생한다.
- outerEnvironmentReference로 인하여 스코프와 스코프체인이 형성된다.
자바스크립트는 코드를 실행하기전에 식별자를 수집한다.
현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
- 매개변수 식별자
- 함수 자체
- 함수 내부의 식별자
- 전역 실행 컨텍스트는 변수 객체를 생성하는 대신 전역 객체를 활용한다.
- 브라우저의 Window 객체, Node의 Global 객체 등이 이에 해당한다.
- 이들은 Host Object로 분류된다.
즉, 코드가 실행 되기 전에 자바스크립트의 엔진은 이미 실행 컨텍스트에 속한 변수명들을 모두 알고 있게 되는 셈이다.
개발자 황준일님 감사합니다.. 블로그 글 보고 공부 많이 했어요..
이렇기에 ' 식별자들을 코드의 최상단으로 끌어올린다.' 라는 호이스팅이라는 개념이 생겨나는데, 물리적으로 끌어올린 것이 아닌, 실행 컨텍스트 관점에선 이미 식별자들의 정보를 알고 있으니 식별자 정보를 수집하는 과정을 이해하기 쉬운 방법으로 나타낸 추상화한 가상 개념이다.
var x = 'global';
function foo () {
var x = 'function scope';
console.log(x);
}
foo(); // ? ->function-scope
console.log(x); // ? -> global
모든 변수는 스코프를 갖는다. 변수의 관점에서 스코프를 구분하면 다음과 같이 2가지로 나눌 수 있다.
자바스크립트는 함수 레벨 스코프(function-level scope)를 따른다. 함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다(참조할 수 없다)는 것이다.
단, ECMAScript 6에서 도입된 let keyword를 사용하면 블록 레벨 스코프를 사용할 수 있다.
var x = 'global';
function foo() {
var x = 'local';
console.log(x);
}
foo(); // local
console.log(x); // global
전역변수 x와 지역변수 x가 중복 선언되었다. 전역 영역에서는 전역변수만이 참조 가능하고 함수 내 지역 영역에서는 전역과 지역 변수 모두 참조 가능하나 위 예제와 같이 변수명이 중복된 경우, 지역변수를 우선하여 참조한다.
다음은 함수 내에 존재하는 함수인 내부 함수의 경우를 살펴보자
var x = 'global';
function foo() {
var x = 'local';
console.log(x);
function bar() { // 내부함수
console.log(x); // ?
}
bar();
}
foo();
console.log(x); // ?
또한 중첩 스코프의 경우 가장 인접한 지역을 우선 참조한다.
var foo = function ( ) {
var a = 3, b = 5;
var bar = function ( ) {
var b = 7, c = 11;
// 이 시점에서 a는 3, b는 7, c는 11
a += b + c;
// 이 시점에서 a는 21, b는 7, c는 11
};
// 이 시점에서 a는 3, b는 5, c는 not defined
bar( );
// 이 시점에서 a는 21, b는 5
};
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // ?
bar(); // ?
결과를 에측해보자.
나는 10,1로 예측했다. 근데 결과는 !? 1, 1 이다. 대체 왜?
렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다. 자바스크립트는 렉시컬 스코프를 따르므로 함수를 선언한 시점에 상위 스코프가 결정된다.
함수를 어디에서 호출하였는지는 스코프 결정에 아무런 의미를 주지 않는다. 위 예제의 함수 bar는 전역에 선언되었다. 따라서 함수 bar의 상위 스코프는 전역 스코프이고 위 예제는 전역 변수 x의 값 1을 두번 출력한다.
전역변수 사용을 억제하기 위해, 즉시 실행 함수(IIFE, Immediately-Invoked Function Expression)를 사용할 수 있다.
이 방법을 사용하면 전역변수를 만들지 않을 수 있다.