본문 바로가기
💻CODING/javascript

[js]자바스크립트 스코프, 클로저(ft. 지역/글로벌/렉시컬 scope)

by 코딩하는 갓디노 2021. 1. 8.

스코프, 클로저



스코프(scope)

  • scope는 영어로 범위라는 뜻
  • 함수 스코프는 자바스크립트 함수의 범위, 즉 { } 안을 의미
  • 스코프는 함수 호출할 때가 아니라 함수선언할 때 생깁니다. 
  • 함수 선언도 변수와 같이 스코프의 적용을 받습니다. 

 

스코프의 원칙

변수는 자신을 감싸고 있는 함수 밖을 벗어날 수가 없습니다. 

 

스코프의 종류

전역 스코프(global scope)

전역에 선언되어있어 어느 곳에서든지 해당 변수에 접근할 수 있다는 의미

 

지역 스코프(local scope)

해당 함수 내에서만 접근할 수 있어 함수를 벗어난 곳에선 접근할 수 없습니다. 

 

함수 스코프(function-scoped)

함수를 선언할 때마다 새로운 스코프를 생성하게 됩니다. 
함수 범위 내에 선언한 변수는 해당 함수 안에서만 접근할 수 있는데 이를 함수 스코프라고 합니다. 
함수 스코프가 바로 지역 스코프의 예라고 할 수 있습니다.

 

예제1)

var a = 1; // 전역 스코프

function print() { // 함수 스코프
 console.log(a); //a는 변수 선언한 값이 스코프 안에 없으면 함수 밖 위에서 부터 찾는다.(위의 위의 줄 a)
}
print(); //결과: 1

 

예제2)

var a = 1; // 전역 스코프

function print() { // 지역(함수) 스코프
 var a = 2;
 console.log(a); //2 (바로 위의 줄 a에 고정됨-렉시컬 스코프)
}
print(); //결과: 2
console.log(a); //결과: 1 (맨 위의 줄 a에 고정됨)

 

스코프 체인(scope chain)

  • 변수 선언(const/let/var) 값을 찾을 때는 함수 스코프를 기준으로 한단계씩 위로 올라가면서 찾습니다. (부모 개념과 같이 위로 올라감, 형제간 아님)
  • 쉽게 이해하기 위해 웹에디터 상으로 함수 선언된 부분의 블럭 { } 을 닫아서 확인해본다. 그 위의 선언된 부분이 상위 단계.
  • 스코프 간의 상하 관계를 스코프 체인이라고 합니다.
  • 전역 스코프에서까지 변수를 못찾을 경우 결과는 undefined가 나옵니다.

 

즉 스코프 기준으로 변수 선언을 찾습니다. 
지역 스코프 내에 없으므로 전역스코프에서 찾습니다.
체인처럼 한단계씩 위로 올라가면서 변수 선언 값을 찾기 때문에 스코프 체인이라고 합니다. 

 

렉시컬 스코프(정적 스코프, lexical scoping)

함수 선언 코드가 적힌 순간 스코프가 정해지고 
변수는 스코프 범위와 스코프 체인 내에서 변수 선언된 것을 찾고,
찾은 후, 변수 선언된 것에 고정되어 있습니다.
모든 변수가 정해져있다는 의미이고, 이것을 렉시컬 스코프, 정적 스코프라고 합니다. 

 

클로저(closure)

비동기 안에서 변수가 {} 바깥의 변수 선언을 참조하면, 클로저 문제가 발생합니다. 

클로저는 외부함수(포함하고 있는)의 변수에 접근할 수 있는 내부 함수를 일컫습니다. 
스코프 체인으로 표현되기도 합니다. 
함수와 함수가 접근할 수 있는 스코프가 클로저 관계를 맺습니다. 

반복문과 비동기 함수가 만날때 에러가 발생하고,
클로저의 특성을 이용해서 문제를 해결할 수 있습니다.  

 

반복문과 비동기 함수(오류 발생)

1. for (var i = 0; i < 100; i++) {
2.   setTimeout(function() {
3.     console.log(i); //이미 100이 되었으므로 100만 출력
4.   }, i * 1000); //1-99까지 출력
5. }
//결과: 100이 백번 찍힘
--------------해석-------------------
 setTimeout(function() {
     console.log(100); 
   }, 1 * 1000); 
   
    setTimeout(function() {
     console.log(100); 
   }, 2 * 1000); 
   
   ...
   
    setTimeout(function() {
     console.log(100); 
   }, 99 * 1000); 

 

순서

3줄 console.log(i)의 i가 지역 스코프 내에서 i 변수 선언을 찾지만,
지역 스코프 내에 없으므로 전역스코프에서 찾습니다.
맨 위의 줄 var i=0 에서 찾는다. 그 값에 고정이 되어있습니다. 
i는 이미 100이 되어있습니다. 

스코프 밖의 i는 1-100까지 출력되어 
1부터 100초까지 100만 계속 출력됩니다.

 

이유

비동기 함수 안의 변수는 함수가 실행되는 순간에 값이 결정됩니다. 
비동기 함수, setTimeout의 함수가 i*1000 초가 되는 순간 함수 스코프 안의 
console.log(i); 를 실행하고, 
i를 찾았을 때 i가 이미 100이 되어있습니다. 
i*1000는 0-99까지 변경되지만 함수 안 console.log(i)는 계속 i입니다. 

 

해결 방법

1. for (var i = 0; i < 100; i++) {
2.   function closureFn(j) {
3.     setTimeout(function() {
4.       console.log(j);
5.     }, j * 1000);
6.   }
7.   closureFn(i);
8. }

매개변수 j 와 closureFn 함수가 클로저의 관계입니다. 

비동기 함수를 새로운 함수를 만들어 감싸주고, 
2줄 매개변수 j를 넣어줍니다. -> var j =0; 와 같기 때문에 j가 값을 간직해줍니다. 
4번 j는 함수 스코프의 성격으로 var j=0; 을 바라봅니다.

1.for (let i = 0; i < 100; i++) {
2.  function closureFn(j) { //j가 8번에 실인자 i의 값을 간직함
3. 	//var j = 0; 매개변수 j -> var j=0; 
4.    setTimeout(function() {
5.      console.log(j); //j값은 변수선언을 함수 스코프 성격으로 2번의 j값에서 찾음
6.    }, j * 1000); //j는 2번 j값
7.  }
8.  closureFn(i);
9. }
-----------------해석-------------------
  function closureFn(j) { //j=0
  	//var j = 0; 
    setTimeout(function() {
      console.log(j); //j=0
    }, j * 1000); //j=0
  }
  closureFn(0); 

  function closureFn(j) { //j=1
  	//var j = 1; 
    setTimeout(function() {
      console.log(j); //j=1
    }, j * 1000); //j=1
  }
  closureFn(1); 
  
.....

  function closureFn(j) { //j=99
  	//var j = 99;
    setTimeout(function() {
      console.log(j); //j=99
    }, j * 1000); //j=99
  }
  closureFn(99); 

 

즉시 실행 함수로 표현

for (let i = 0; i < 100; i++) {
  (function closureFn(j) {
    setTimeout(function() {
      console.log(j);
    }, j * 1000);
  })(i);
}

 

결과

비동기 함수는 실행하는 순간, 스코프내에서 변수선언을 찾습니다.
없을 경우, 스코프 체인을 이용하여, 한 단계씩 위로 올라가면서 찾습니다. 
클로저의 특성, 매개변수j와 함수의 관계를 이용하여 문제를 해결합니다. 

반응형

댓글