본문 바로가기

프론트엔드/javascript

[javascript] ES6 let의 블록스코프, 렉시컬환경 for문으로 알아보자!

ES6 등장 이전에 변수를 선언할 수 있는 유일한 방법은 var 키워드를 사용하는것 이였다.

하지만 var의 큰 단점때문에 현재는 거의 사용하지 않는다.

대신에 let과 const를 쓴다.

 

해당 내용은 공부한 내용을 자세하게 적어볼것이므로,

오늘은 이후에 블록스코프를 가지는 let을 for문에서 사용해보는 예제를 기록 할 것이다.

 

문제

let funcArr = []; 

for(let i = 0; i < 5; i++) { 
	var c = i * 2; 
	funcArr.push( (_)=>console.log(c) ); 
} 

funcArr.forEach( fn => fn() )

해당 코드는 for문 안에 c라는 변수가 함수레벨 스코프를 가지는 var로 선언이 되어있기 때문에

최종 출력값은

> 8 8 8 8 8

이 된다.

 

 

let으로만 바꿀경우는 아래와 같다.

let funcArr = [];
for(let i = 0; i < 5; i++) { 
	let c = i * 2; 
	funcArr.push( (_)=>console.log(c) ); 
} 

funcArr.forEach( fn => fn() )

왠지 let으로 선언된 c의 변수는 블록스코프라는 특징때문에 저 for문 블록 안에서만 유효할것 이라고 생각이 된다.

때문에 funcArr에 들어있는 익명함수들이 c를 찾지 못할것 이라고 생각하여 다음과 같은 출력 결과를 예상했다.

> undefined undefined undefined undefined undefined

 

하.지.만. 실제 출력은 아래와 같다.

> 0 2 4 6 8

 

 

 


 

해석

블록 밖에 있는 함수가 블록 안의 c변수 값을 어떻게 찾는것인지,

for문을 돌며 c 값이 달라지는데 과연 어떻게 접근한 것인지 해석해볼 필요가 있다.

 

 

1. for문 한줄씩 풀어보기

 

먼저 for문을 풀어 한줄씩 실행해보면 다음과 같다.

let funcArr = [];

let c = 0;
funcArr.push((_) => console.log(c));

let c = 2;
funcArr.push((_) => console.log(c));

let c = 4;
funcArr.push((_) => console.log(c));

let c = 6;
funcArr.push((_) => console.log(c));

let c = 8;
funcArr.push((_) => console.log(c));

해당 코드를 실행시키면 다음과 같은 에러가 발생한다

 

> Uncaigjt SyntaxError: Identifier 'c' has already been declared

해석해보면 c가 이미 선언되어있다는 뜻인데, 이는 let은 변수를 재선언할 수 없는 특징때문에 발생하는 문법에러이다.

 

 

 

떄문에 한줄씩 풀어서 추가하려면 아래 방식을 사용하면 된다.

let funcArr = [];
{ let c = 0; funcArr.push((_) => console.log(c)); }
{ let c = 2; funcArr.push((_) => console.log(c)); }
{ let c = 4; funcArr.push((_) => console.log(c)); }
{ let c = 6; funcArr.push((_) => console.log(c)); }
{ let c = 8; funcArr.push((_) => console.log(c)); }

funcArr.forEach(fn => fn());

> 0 2 4 6 8

 

 

2. 블록스코프와 렉시컬 환경(Lexical Environment)

let은 블록레벨 스코프와 더불어 블록에 의해 새로운 렉시컬 환경이 생성된다는 특징을 가진다.

무슨소리냐 하면..!

 

for문의 코드 블록이 실행되기 시작하면 새로운 렉시컬 환경을 생성하고, for문 코드 블록 내의 식별자와 값을 해당 환경에 등록한다. 그리고 새롭게 생성된 렉시컬 환경을 현재 실행중인 실행 컨텍스트의 렉시컬 환경으로 교체한다.

for문의 반복실행이 모두 종료되면 for문의 실행 이전 시점의 렉시컬 환경을 실행중인 실행 컨텍스트의 렉시컬 환경으로 되돌린다.

 

때문에 for문이 반복될 때마다 새로운 렉시컬 환경이 생성되고,

그 환경마다 각각 다른 c의 변수값이 저장되어 있는것이다. 

 

실제로 콘솔을 찍어보면

 

funcArr[0]과 funcArr[1] 두 함수는 서로다른 스코프값을 가지고 있다.

즉 funcArr 안의 함수는 모두 다른 스코프를 가지고 있는 서로 다른 함수라는 것이 확인된다.

 

 

 

 


 

참고사이트

stack overflow - Does JavaScript create a new execution context when executing a block?