-
[JS/클로져] 자바스크립트의 Lexical scoping과 ClosureFrontend 2019. 4. 23. 15:25
하.. 너무 이해하기 어렵고 힘들었는데
최대한 MDN이랑 W3Cschools 글을 읽고 또 읽어서 이해한 내용을 정리해보려고 한다.
내가 정리한 내용들은 모두 아래 링크로 들어가면 나온다.
Lexical Scoping
function init() { var name = 'Mozilla'; // name is a local variable created by init function displayName() { // displayName() is the inner function, a closure alert(name); // use variable declared in the parent function } displayName(); } init();
그니까 init()라는 함수가 호출되면 name이라는 지역변수와 displayName()이라는 함수를 새롭게 생성하게 된다.
여기서 displayName()함수는 init()라는 함수 안에 생성된 함수이기 때문에 init()함수 안에서만 실행가능하다.
displayName()함수는 함수 안에 지역변수가 없지만 자신의 부모 함수인 init()함수의 지역변수에 접근할 수가 있다.
그렇기 때문에 위 코드에서 init()를 실행하면 alert창에 'Mozilla'를 띄우게 된다.
그러나 만약에 displayName()함수 안에 var name = 'Webkit'이 있었다면 alert창에는 Mozilla대신 Webkit이 출력됐을 것이다.
Lexical scoping은 함수를 어디에서 호출하는지에 따라 범위가 결정되는 것이 아니라 함수를 어디에 선언했는지에 따라 결정된다. (반대로 어디서 호출됐는지에 따라 범위가 결정되는 방식은 Dynamic scoping)이다.
위 예제에서 displayName()은 init()함수 안에서 선언되었기 때문에 displayName의 상위 스코프는 init()이다.
var x = 1; function foo() { var x = 20; bar(); } function bar() { console.log(x); } foo(); bar();
위 코드를 실행했을 때, 과연 콘솔 창에는 어떤 값이 출력될 것인가? 만약에 변수의 범위가 함수 호출 시에 결정난다면 처음에 foo 함수를 실행했을 때, foo 함수 안에서 실행하고 있는 bar 함수의 변수 스코프가 함수 foo로 결정되어 20이란 값이 출력될 것이다. 그러나 자바스크립트는 렉시컬 스코프 방식을 따르고 있기 때문에 변수의 범위가 함수를 호출할 때가 아니라 선언할 때 결정된다.
그러므로 위 코드에서 함수 bar()는 전역에서 선언되었으므로 함수 bar의 상위 스코프는 전역이다. 그러므로 x는 1을 2번 출력하게 된다.
Closure
: A closure is the combination of a function and the lexical environment within which that function was declared.
- MDN
클로져는 함수와 그 함수가 선언되었을 때의 Lexical 환경의 조합.
: A closure is a function having access to the parent scope, even after the parent function has closed.
-W3Cschools
클로져는 부모 함수의 실행이 끝났더라도 상위 스코프에 접근할 수 있는 함수.
function makeFunc() { var name = 'Mozilla'; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); myFunc();
맨 위 예제 코드와 실행결과는 같지만 안을 살펴보면 조금 다른 점이 있다.
바로 displayName()이라는 내부 함수가 외부 함수의 실행이 끝나기 전에 return되고 있다는 점이다.
바로 여기서 클로져의 정의를 설명하자면 이렇다.
클로져는 함수와 그 함수가 선언되었을 때의 Lexical 환경 내부를 결합한 것인데
여기서 Lexical환경은 클로져가 생성되었을 때, scope내의 모든 지역변수를 포함한다.
그러니까 위 예제 코드와 같이 설명해보면 이렇다.
myFunc은 makeFunc()이 실행될 때 생성된 displayName란 함수 인스턴스를 참조하고 있다.
이 displayName의 인스턴스는 name이란 변수가 존재하는 lexical 스코프 참조를 계속 유지하고 있다.
이 이유 때문에 makeFunc()함수 내부의 변수 name은 makeFunc()함수가 실행한 이후에 지워지는 것이 아니고
myFunc이란 변수에 의해 계속 존재하게 된다.
그리고 myFunc()을 호출하면 "Mozilla"라는 문구가 alert창에 출력된다.
그럼 이 closure는 왜, 언제 쓰는 것일까?
예를 들면 함수 addNum()을 실행할 때마다 count변수가 1씩 증가하는 함수를 만든다고 해보자.
var count = 0; function addNum() { var count = 0; count++; return count; } addNum(); addNum(); addNum();
위 함수에서 addNum()함수를 실행하여 addNum()함수 내부의 count 변수를 1 증가시켜도
addNum()함수의 실행이 끝나게 되면 지역변수 count는 사라져버리고 addNum()은 계속해서 1을 return 한다.
var count = 0; function addNum() { count++; return count; } addNum(); addNum(); addNum(); console.log(count) // 3
그렇다고 지역변수 count를 없애버리면 addNum()을 실행할 때마다 count를 증가시킬 수 있지만
count변수가 전역변수이기 때문에 다른 코드를 사용하여 count변수의 값을 바꿀 수 있다.
프로그램이 복잡해져 혹시라도 다른 코드에서 count변수를 사용하는 경우에 에러가 발생할 수 있는 것이다.
우리는 count변수의 값을 오로지 addNum함수를 통해서만 바꾸고 싶은데,
그럴 때 바로 필요한 것이 closure와 self-invoking function(or Immediate-invoking function 자기실행함수, 즉시실행함수)이다.
var addNum = (function() { var count = 0; return function() { count += 1; return count; } })(); addNum(); addNum(); console.log(addNum()) // 3
즉시실행함수는 함수명을 지정하거나 함수를 변수에 저장하지 않고 바로 실행하는 함수이다.
위 즉시실행함수가 리턴하는 함수는 count 변수의 값을 기억하고 계속 유지하는 클로져다.
즉, 위 코드에서 즉시 실행함수를 addNum이란 변수에 대입하여 함수를 호출했다.
그러면 addNum함수가 실행되면서 함수 내부의 클로져 실행을 통해 count변수에 1을 증가시키는 데
이 증가된 값을 계속 가지고 있다가 addNum()을 또 실행시키면 이미 1증가된 count를 또 1증가시켜 반환한다.
즉, count변수를 함수 외부에 선언하지 않고 addNum()의 실행을 통해서만 1씩 증가시킬 수 있다.
반응형'Frontend' 카테고리의 다른 글
[JS/반복문] 자바스크립트, 가장 기초적인 반복문 for문의 동작 원리 (for문/이중for문/break/continue) (4) 2019.04.28 [JS/Arguments] 자바스크립트, Arguments 객체에 대해서. (0) 2019.04.24 [JS/입력값검증] 숫자 3자리마다 ,(콤마)찍기 / 숫자와 콤마를 제외한 문자 입력 제한 / 0으로 시작 못하게 제한 / keyup이벤트 (0) 2019.04.19 [자바스크립트 기본서 추천] Do it! 웹프로그래밍을 위한 자바스크립트 기본편 (0) 2019.04.14 [JS] 자바스크립트, 가장 기초적인 조건문 정리 (if문/if-else문/삼항연산자/switch문) (0) 2019.04.12 COMMENT