스코프와 클로저

2020. 3. 5. 09:58웹/Javascript

스코프와 클로저 

 

 

스코프

  • 현재 접근할 수 있는 변수들의 범위

  • 어떠한 변수가 스코프 안에 선언되었으면 해당 스코프 안에서 변수에 접근해서 읽거나 쓸 수 있고, 스코프 밖에서는 해당 변수에 접근할 수 없음.

 

예제)

<div id="div0">DIV 0</div>
<div id="div1">DIV 1</div>
<div id="div2">DIV 2</div>


<script>
    var i;                         --- ①
    for(i=0; i<3; i++) {
        document.getElementById("div" + i).addEventListener("click", function() {
        alert("Clicked Div" + i);  --- ②
    });
}
</script>


-- 출력 결과 --
DIV0 클릭 : Clicked Div2
DIV1 클릭 : Clicked Div2
DIV2 클릭 : Clicked Div2

 

설명

버튼 클릭 시 예상과는 다른 결과 도출. 이러한 원인은 스코프가 생성되고 유지되는 방법에서 발생되는 문제.

②에서 각각의 이벤트 핸들러를 위한 콜백 함수가 변수 i에 선언된 변수들에 접근할 수 있는 스코프 생성. 

변수 i는 글로벌 스코프에 존재. 모두 같은 스코프의 변수 i를 참조.

for-loop 를 통해서 각각의 div 에 순서대로 클릭 이벤트 핸들러를 부여할 때 ①에서 선언한 변수 i가 0에서부터 3까지 증가한 뒤, 이후에 for-loop가 끝나고 나서도 계속 유지. 

이러한 이유로 인해 ②에서 alert 시 변수 i의 값은 이미 for-loop가 끝난 후의 값인 3으로 출력

 

                 

 

함수 레벨 스코프(Function-level Scope)

  • 함수의 코드 블럭만을 스코프로 인정

  • 전역 함수 외부에서 생성한 변수는 모두 전역 변수

  • for 문의 변수 선언문에서 선언한 변수를 for 문의 코드 블록 외부에서 참조 가능

 

블럭 레벨 스코프 (Block-level Scope)

  • 모든 코드 블럭(function, if, for, while, try/catch 등) 내에서 선언된 변수는 코드 블럭 내에서만 유효

  • 블럭 외부에서는 참조 못함

  • 코드 블럭 내부에서 선언한 변수는 지역 변수

 

키워드 VAR 

  • ES6 이전에 사용하던 변수

  • 함수 레벨 스코프(Function-level Scope)

 

키워드 LET

  • ES6 이후에 나온 변수

  • 블럭 레벨 스코프(Block-level Scope)

 

키워드 CONST

  • ES6 이후에 나온 변수

  • 블럭 레벨 스코프(Block-level Scope)

 

 

예제)

<div id="div0">DIV 0</div>
<div id="div1">DIV 1</div>
<div id="div2">DIV 2</div>


<script>
    for(let i=0; i<3; i++) {
        document.getElementById("div" + i).addEventListener("click", function() {
        alert("Clicked Div" + i); 
    });
}
</script>


-- 출력 결과 --
DIV0 클릭 : Clicked Div0
DIV1 클릭 : Clicked Div1
DIV2 클릭 : Clicked Div2

 

설명

키워드 var를 사용한 경우 global scope에 존재하여 콜백함수에서 i 접근 시 동일한 스코프 변수를 참조.

하지만 키워드 let으로 생성한 변수는 블럭 레벨 스코프를 가지고 있어 이벤트 핸들러의 콜백 함수에서 i에 접근할 때 서로 다른 스코프 변수를 가지기 때문에 정확한 결과 도출.

 

                 

 

 

 

 

클로저

  • 특정 함수가 참조하는 변수들이 선언된 렉시컬 스코프(Lexical Scope)는 계속 유지되는데, 그 함수와 스코프를 묶어서 클로저라고 함

  • 클로저를 통해서 각 함수는 자기만의 고유한 값을 보유하고 스코프 체인을 유지하면서 그 체인 안의 모든 변수의 값들을 유지.

function outer () {
    var count = 0;
    var inner = function () {
        return ++count;
    };
    return inner;
}

var increase = outer();
console.log(increase());  
console.log(increase());  


-- 출력 결과 --
1
2

 

설명

count 변수의 경우 outer의 로컬 변수이기 때문에 함수 내부에서만 접근 가능. 

count 변수에 접근하는 또 다른 함수 inner 를 outer 함수의 반환값으로 지정하고, 이를 글로벌 영역의 increase 변수에 할당함으로써, count 변수에 접근할 수 있게 됨.

이처럼 자바스크립트에서 서로다른 스코프가 형성됬음에도 불구하고, 자식 스코프가 부모 스코프의 변수 환경의 정보를 기억하고 접근할 수 있는 상황을 클로저의 개념이라 할수 있음.

 

 

예제)

<div id="div0">DIV 0</div>
<div id="div1">DIV 1</div>
<div id="div2">DIV 2</div>


<script>
    var i;                           
    for(i=0; i<3; i++) {
        document.getElementById("div" + i).addEventListener("click", (function(index) {  --- ①
            return function() {                                                          --- ②
                alert("Clicked Div" + index);
            }  
    }(i)));                                                                              --- ③
}
</script>


-- 출력 결과 --
DIV0 클릭 : Clicked Div0
DIV1 클릭 : Clicked Div1
DIV2 클릭 : Clicked Div2

 

설명

즉시 실행 함수(IIFE, Immediate Invoke Function Expression) 을 통해 스코프 체인을 생성.

익명 함수를 통해 새로운 함수를 선언해 반환. 반환된 함수는 index 변수를 상위 스코프 체인에 추가한 뒤, addEventListner() 함수의 2번째 인자로 들어가는 구조. 즉, 새로운 스코프가 클릭 이벤트 핸들러의 콜백 함수의 상위에 추가되면서 문제 해결. 

 

       

 

 

클로저의 단점

  • 메모리를 소모

  • 스코프 생성과 이후 변수 조회에 따른 퍼포먼스 손해

 

메모리 소모

  • setTimeout 이나 이벤트의 콜백 함수 등으로 등록된 함수들이 메모리에 계속 남아있게 되면, 해당하는 클로저도 같이 메모리에 계속 남아 있게 됨. 

  • 루프를 돌면서 클로저를 계속 생성하는 설계는 지양.

  • 클로저를 생성할 때는 하나의 커다란 클로저를 생성하기보다는 각 변수나 함수들의 생명주기를 분석한 다음 효출적으로 나누면 좋음.

스코프 생성과 이후 변수 조회에 따른 퍼포먼스 손해

  • 클로저 하위에 있는 함수에서 상위에 있는 변수에 접근하고자 할 때 클로저를 생성한 스코프를 탐색해야 하는 문제.

  • 각 함수별로 생성된 스코프를 따라 상위 스코프로 올라가면서 찾고자 하는 값이 있는 스코프까지 탐색 필요.

  • 최근 자바스크립트 엔진들이 이러한 스코프 체인 내의 변수 탐색에 대한 최적화를 하고 있기 때문에 과하게 사용하지 않으면 됨.

 

 

 

 

출처 : 속깊은 Javascript 책

' > Javascript' 카테고리의 다른 글

이벤트 전달 방식(버블링, 캡처, 위임)  (0) 2020.06.15
call, apply, bind 메서드  (0) 2020.03.10
스코프 체인  (0) 2020.03.05
실행 컨텍스트  (0) 2020.03.05
this 정체  (0) 2020.03.05