클로저를 알기 전에 실행 컨텍스트에 대해 알아야한다.
실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경이다.
자바스크립트엔진은 스크립트를 실행하다가 함수를 만나게되면 실행컨텍스트를 생성한다. 이 실행컨텍스트에 함수가 실행될 때의 환경을 저장하게 됩니다. 이렇게 만들어진 실행컨텍스트는 자신만의 스코프(유효범위)를 갖게된다.
실행 가능한 코드
- 전역 코드: 전역 영역에 존재하는 코드
- Eval 코드: eval 함수로 실행되는 코드
- 함수 코드: 함수 내에 존재하는 코드
실행 컨텍스트가 생성되는 시점
- 전역 공간 -> 익명 함수 실행
eval 함수 사용 시(사용 잘 안함)- 함수 사용 시
- {} 코드 블럭 사용 시
// ------------------------ (1)
var a = 1;
function outer() {
function inner() {
console.log(a);
var a = 3;
// ------------------ (2)
}
inner(); // ------------ (3)
console.log(a);
// --------------------- (4)
}
outer(); // --------------- (5)
console.log(a);
// ------------------------ (6)
실행 컨택스트는 자바스크립트 엔진에서 돌아가기 위해 스택에 순차적으로 쌓인다.
렉시컬 스코프는 코드를 작성한 순간 스코프가 정해진다.
JavaScript는 정적 스코프(static scope)를 채택하고 있다.
var myName = "guno"; // 전역 변수로 선언
function log() {
console.log(myName); // 전역 스코프에 있는 myName과 연결되어 있음!
}
function wrapper() {
myName = "byun"; // 전역 스코프에 있는 myName을 변경함
log(); // byun (전역 스코프에 있는 변경된 myName을 가져온다)
}
log(); // guno
wrapper(); // byun
var myName = "guno";
function log() {
console.log(myName);
}
function wrapper() {
var myName = "byun"; // 함수 내 지역 스코프로 myName을 선언 -> 전역 스코프의 myName에 영향을 주지 않는다.
log(); // guno (전역 스코프에 있는 myName은 변하지 않으므로 유지)
}
log(); // guno
wrapper(); // guno
- 클로저는 함수와 함수가 선언된 어휘적(정적) 환경의 조합(관계)이다 -MDN-
- 함수 안에 함수를 선언한 환경에서의 관계를 의미한다.
- 해당 함수안에 함수를 선언한 환경은 내부 함수에서 외부 함수로 지역변수를 접근할 수 있지만 외부 함수의 실행이 끝나고 외부 함수가 소멸된 이후에도 내부 함수가 외부 함수의 변수에 접근할 수 있는 것을 의미한다.
클로저란 함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 만드는 문법
function helloName(name){
const hello = "hello, "; // 지역 스코프라서 함수가 종료되면 메모리에서 사라진다.
return function () {
console.log(hello + name);
}
}
const guno = helloName("guno");
guno(); // 실행 시점에 hello 변수에 접근 가능하다
// 결과값 "hello guno"
외부에서 접근이 불가능한 영역을 closure를 통해 접근할 수 있다.
3번 예시
function counting() {
let i = 0;
for (i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
}
// 결과값 setTimeout의 2번째 인자 i는 변하지만 1번째 인자 익명함수의 i는 변하지 않는다
함수 안의 변수는 "실행"될 때 값이 결정된다 (JS는 동기적이기 때문에 순차적으로 실행된다)
5
5
5
5
5
IIFE를 사용
function counting() {
let i = 0
for (i = 0; i < 5; i++) {
(function (number) {
setTimeout(function () {
console.log(number);
}, number * 1000);
})(i);
}
}
let을 for문 안에 넣는다
function counting() {
for (let i = 0; i < 5; i++) { // let은 블록 수준 스코프이기 때문에 매 루프마다 클로저가 생성된다
setTimeout(function () {
console.log(i);
}, i * 1000);
}
}
function을 기준으로 스코프가 정해진다. 함수의 유효 범위는 어디서 정의됐느냐에 따라 달라진다.
function addFuc(init) {
function add(num) {
return init + num;
}
return add;
}
let add1 = addFuc(1);
console.log(add1(1)); // 2 <----- (1)
console.log(add1(2)); // 3
let add2 = addFuc(2);
console.log(add2(1)); // 3
console.log(add2(2)); // 4
1번에서 add1은 add 함수에서 온 것이다.
지역 스코프에서 num은 1이 된다. init은 add 함수에 없어 scope chain을 통해 부모 함수의 init을 클로저를 통해서 접근할 수 있다.
고로 지역 스코프의 num은 1 부모 함수의 init은 1로 둘을 더해 2라는 결과값을 도출할 수 있다.
add 함수가 만들어지는 시점에서 그의 부모 함수 addFuc가 가지고 있는 유효범위, 변수들을 add함수가 동봉해서 가지고 있다 언제든지 호출하면 거기에 접근할 수 있다.
실행 컨텍스트1
실행 컨텍스트2(poiemaweb)
setTimeout, 실행 컨텍스트의 관계
스코프
클로저