실행 컨텍스트 글을 작성하며 제대로 설명하지 못하였던 개념인 Environment Record
내부에서 수행되는 호이스팅, 스코프 그리고 클로저에 대해 알아보도록 하겠습니다.
호이스팅이란 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미합니다. 쉽게 말하면 코드는 위에서 아래로 실행 되기에 코드 실행 전에 식별자 정보를 수집하는 동작으로 인해 식별자들을 코드의 최상단으로 올려준다라고 생각하시면 됩니다. 그럼 호이스팅의 과정을 알아보기 위해 변수와 함수에서의 동작을 확인해보겠습니다.
var
console.log(전역변수); // undefined
var 전역변수 = 100;
cosoel.log(전역변수); // 100
var
의 경우에는 호이스팅 되었을 때 변수 명과 함께 값이 초기화되어 undefined
로 저장됩니다. 그래서 선언문 전에 변수를 호출하면 undefined
인 것을 볼 수 있습니다. 이후 선언문 다음에 호출하면 제대로 된 값이 나오는 것을 볼 수 있습니다.
const
, let
console.log(나변수); // reference Error
let 나변수 = 1;
console.log(나변수); // 1
const
, let
의 경우에는 변수명에 대한 호이스팅이 수행되지만, 값을 초기화하지 않습니다. 그렇기에 선언문 이전에 호출하면 에러를 띄우고, 선언문 이후에 호출해야 변수 값이 나오는 것을 볼 수 있습니다.
함수(); // "난 함수다"
function 함수 () {
console.log("난 함수다");
}
함수 선언문의 자바스크립트 엔진이 함수 선언과 동시에 완성된 함수 객체를 생성해서 환경 레코드에 기록합니다. 그렇기에 함수 선언문 이전에 함수를 호출해도 함수가 실행됩니다.
함수1(); // undefined => Type Error
var 함수1 = () => {
console.log("함수 1이다");
}
함수2(); // Reference Error
const 함수2 = () => {
console.log("함수 2이다");
}
함수2(); // "함수 2이다"
함수 표현식의 경우에 var
변수에 담고있는 함수는 호이스팅 단계에서 undefined
로 초기화되어 호출 될 수 없기에 에러가 발생합니다. 그리고 const
변수에 담고있는 함수는 변수 호이스팅과 동일하여 선언문 이전에는 값이 없기에 에러가 발생하고 선언문 이후에 값이 호출됩니다.
현재 실행되는 컨텍스트를 말하며 식별자에 대한 유효범위를 의미합니다. 만약 식별자가 해당 스코프 내에 존재하지 않는다면 상위 스코프인 직전 컨텍스트에 접근하여 식별자가 있는지 확인하게 되는데 이를 스코프 체인(scope chain) 이라고 합니다. 그리고 이를 가능하게 하는 것은 바로 Lexical Environment
의 outer Enviroment Reference
입니다.
let a = 1;
function 외부함수() {
function 내부함수() {
console.log(a); // undefined (1)
let a = 5;
function 내부안의내부함수() {
console.log(a); // 5 (2)
console.log(b); // Error : b is not defined (3)
}
내부안에내부함수();
}
내부함수();
console.log(a); // 1 (4)
}
외부함수();
위 코드의 console.log()
들에 대해 순서대로 하나씩 알아보겠습니다.
(1) console.log(a)
: 내부함수의 a
가 함수 이전에 선언되었는데 console.log에는 undefined
로 나옵니다. undefined
는 내부함수의 a
변수를 가리킨다고 보시면 됩니다.
(2) console.log(a)
: 내부안의내부함수의 console.log(a)
는 현재 실행 컨텍스트에 a
식별자가 없기에 직전 컨텍스트인 내부함수에서 식별자를 찾게됩니다. 그 결과로 5가 호출됩니다.
(3) console.log(b)
: 현재 실행 컨텍스트에 b
가 없기에 내부함수에 접근하여 식별자를 찾습니다. 이곳에도 b
식별자를 찾을 수 없기에 외부함수에 접근하고 결국 식별자를 찾지 못해 없다는 결론을 내리고 error를 발생시킵니다.
(4) console.log(a)
: 외부함수의 실행 컨텍스트에는 a
가 없기에 직전 컨텍스트에서 선언 된 a
의 값을 호출합니다.
실행 컨텍스트가 추가가 될 때마다 outer environment Reference
로 직전 컨텍스트를 참조하게 됩니다. 이 outer
를 통해 하위 스코프에서 상위 스코프로의 접근 즉 직전 컨텍스트에 대해 접근을 하게 됩니다. 이로 인해 현재 컨텍스트에서 변수를 발견하지 못하거나 call stack에 동일한 변수명이 있다면 자바스크립트 엔진이 outer
를 활용해 안쪽에서 부터 바깥쪽으로 탐색해가며 식별자 결정
을 하게됩니다.
이 때 찾던 식별자를 발견하게 되면 더 이상 상위 스코프로의 탐색을 진행 하지않고, 식별자 결정
이 됩니다.
변수 섀도잉
- 동일한 식별자로 인해 상위 스코프에서 선언된 식별자의 값이 가려지는 현상
클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다.
(모던 JavaScript 튜토리얼에서..)
다시 말해 내부 함수가 외부 함수의 context에 접근할 수 있는 것을 의미합니다.
function 외부함수() {
const name = "goodjam";
function 내부함수() {
console.log(name);
}
return 내부함수();
}
const a = 외부함수(); // "goodjam"
클로저는 내부함수와 밀접한 관계를 가지고 있습니다. 내부함수는 외부함수의 지역 변수에 접근할 수 있습니다. 외부 함수가 실행이 종료되어 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있습니다. 이러한 메커니즘을 클로저라고 합니다.
function 친구저장(name) {
return {
이름보기 : function () {
return console.log(name);
},
이름수정 : function (changeName) {
return name = changeName;
}
}
}
const 고등학교친구 = 친구저장("홍길동");
const 대학교친구 = 친구저장("김철수");
console.log(고등학교친구.이름보기()); // "홍길동"
console.log(대학교친구.이름보기()); // "김철수"
고등학교친구.이름수정("홍길순");
console.log(고등학교친구.이름보기()); // "홍길순"
console.log(대학교친구.이름보기()); // "김철수"
위의 코드처럼 친구저장
함수의 name
매개변수에 접근하는 return
의 내장 함수들이 있는데 친구저장
함수가 변수에 선언되어 실행되고, 종료되어도 내부 함수에서는 계속 외부함수의 값을 접근할 수 있습니다. 그리고 동일한 외부 함수에서 선언된 내부함수, 메소드는 외부함수의 지역변수를 공유합니다.
고등학교친구
, 대학교친구
는 동일한 외부함수를 공유하고있지만 결과는 서로 다르게 나옵니다. 그 이유는 외부함수가 실행될 때마다 새로운 지역변수를 포함하는 클로저가 생성되기에 둘은 서로 다른 독립된 객체가되기 떄문입니다.
const
와 let
은 변수명 선언만 된다.const
, let
변수와 동일하게 처리된다.outer
를 통해 직전 컨텍스트에 접근하여 식별자를 탐색한다. 이를 스코프체인이라고 한다..
.
.
.
.
참고사이트
자바스크립트를 모르는 친구의 고개를 끄덕이게 하기 (feat.실행 컨텍스트)
10분테코톡-하루의 실행컨텍스트
생활코딩-클로저
MDN - 클로저