반응형

안녕하세요. 신기한 연구소입니다.

이번 포스팅은 자바스크립트의 중요한 개념 중 하나인 클로저(closure)에 대해 알아봅니다.

사실 클로저(closure)는 자바스크립트에서만 사용하는 개념은 아니고 함수형 프로그램에서 사용하는

중요한 부분입니다.

단순히 책을 보고 이해하기 어려운 부분이 있기에 좀 더 쉽게 설명해보려고 합니다.

그럼 클로저(closure)가 무엇인지 알아보도록 합니다.

사전적 의미는 폐쇄입니다.

우선 자바스크립트의 클로저(closure)를 이해하기 전 알아야 할 개념이 있습니다.

바로 렉시컬 스코프(lexical scope)입니다.

2021.09.05 - [Software/JavaScript] - [왕초보]자바스크립트에서 사용하는 렉시컬 스코프 쉽게 이해하기. JavaScript lexical scope.

 

[왕초보]자바스크립트에서 사용하는 렉시컬 스코프 쉽게 이해하기. JavaScript lexical scope.

안녕하세요. 신기한 연구소입니다. 자바스크립트를 공부하면 Scope(스코프)를 만나게 되는데요. 그중 렉시컬 스코프(lexical scope)의 의미가 참~이해하기 힘들더군요. 그래서 이번 포스팅은 렉시컬

tiboy.tistory.com

미리 읽어보고 오시면 좋겠네요.

자바스크립트는 함수의 스포크를 정할 때 어디서 실행했는지가 아닌 어디에 정의했는지에 따라서

상위 스코프를 정한다고 했습니다. 바로 이것을 렉시컬 스코프(lexical scope)라고 합니다.

 

그럼 클로저(closure)를 먼저 정의해보겠습니다.

어떤 함수가 있습니다.

그 함수 안에는 내부 함수가 존재합니다.

그 함수는 내부 함수의 상위 함수라고 부르겠습니다. 

내부 함수는 상위 함수의 지역 변수에 접근할 수 있습니다.

하지만 상위 함수는 내부 함수의 지역 변수에 접근할 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function outerFunc(){
    var a = 1;
 
    function innerFunc(){
        var b = 2;
        console.log(a + b);
    }
 
    console.log(a + b);
}
 
outerFunc();
 
cs

위 예제를 보면 9라인에서 오류가 납니다. 

innerFunc의 b에 접근할 수 없기 때문입니다.

클로저는 이렇게 내부 함수가 외부 함수의 지역 변수에 접근해서 사용을 합니다.

그리고 외부 함수보다 내부 함수의 수명이 길어야 합니다.

즉 외부 함수를 호출 한 뒤 외부 함수는 수명을 다해서 사라지더라도

내부 함수는 렉시컬 스코프를 유지하면서 외부 함수의 변수를 계속 참조하고 있어야 합니다.

또한 외부 함수의 지역 변수에 바로 접근을 할 수 없지만 클로저를 통해서

접근 및 사용이 가능하게 됩니다.

그리고 그 외부 함수의 지역 변수의 값을 유지해 줍니다.

다음 예제로 확인해 봅니다.

클로저를 만드는 방법 중 하나입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function outFunc(){
    var a = 1;
 
    function inFunc(){
        return a++;
    }
 
    return inFunc;
}
 
var testFunc = outFunc();
 
console.log(testFunc());
console.log(testFunc());
cs

먼저 외부 함수 outFunc를 선언하고

지역 변수 a를 설정합니다.

이 지역 변수 a는 현재 외부 함수에서만 사용 가능합니다.

이제 내부 함수 inFunc를 선언하고

렉시컬 스코프 범위의 외부 함수의 지역 변수인 a에 ++연산을 해서 접근합니다.

그리고 외부 함수는 내부 함수 자체를 객체로 리턴합니다.

 

11라인을 보면 외부 함수 outFunc()를 변수 testFunc에 담았습니다.

이제 이 전역 변수는 outFunc()의 리턴된 내부 함수 inFunc를 참조하게 됩니다.

13과 14라인을 실행하면 outFunc의 지역 변수 a를 ++로 연산한 값을 받게 됩니다.

초기화되지 않고 연결해서 1, 2가 출력됩니다.

 

바로 이 내부 함수가 클로저입니다.

전역 변수 testFunc()에 담기는 상황에 outFunc()는 return으로 내부 함수 inFunc()를 넘겨주고

수명을 다하게 됩니다.

그럼 지역 변수 a도 같이 사라지게 되겠지요?

하지만 내부 함수에서 외부 함수의 지역 변수인 a를 참조하고 있고

이 범위를 렉시컬 스코프라 하는데

내부 함수가 외부 함수의 렉시컬 스코프를 갖고 있는 겁니다.

그래서 외부 함수의 수명이 다 했더라도 외부 함수의 지역 변수가

클로저(내부 함수)에 의해 참조되고 있으니 사라지지 않고 유지가 되는 겁니다.

 

자바에서 클래스 내부의 private로 선언된 변수를 getter/setter로 접근하는 방식과

비슷해 보입니다.

 

값을 숨겨두고 정의됨 함수에서만 그 값을 컨트롤할 수 있는 기능이

자바스크립트에서는 클로저로 가능하겠네요.

 

클로저(closure)의 의미에 대해 살펴봤습니다.

이제 클로저를 어떤 방식으로 만들고 사용하면 좋을지 고민할 시간이네요.

다음 포스팅에서 이 부분을 다뤄볼 생각입니다.

 

개발자는 팔로워가 아니라는 글이 생각납니다.

개념도 모르고 이해도 못한 상태에서 그냥 검색해서 남이 만든 소스를 그냥 가져 나르는(팔로잉) 팔로워가 아닌

개념도 정리하고 확실히 이해한 뒤 직접 구현하거나 검색해서 찾은 소스를 이해하고 제대로 사용하는

개발자(디벨로퍼)가 돼야겠습니다.

내일 또 개발이 아닌 정치?? 하러 회사에 출근합니다.

아~ 재미없네요. ㅎㅎㅎ

반응형
반응형

안녕하세요. 신기한 연구소입니다.

자바스크립트를 공부하면 Scope(스코프)를 만나게 되는데요.

그중 렉시컬 스코프(lexical scope)의 의미가 참~이해하기 힘들더군요.

그래서 이번 포스팅은 렉시컬 스코프(lexical scope)를 나름 쉽게 정리해보려고 합니다.

그럼 같이 살펴보겠습니다.

 

자바스크립트(javascript)는 변수, 함수 등을 선언하고 사용합니다.

기본적으로 함수와 변수를 어디에 포함되지 않고(전역지역) 선언하면 전역 함수, 전역 변수가 됩니다.

전역이라면 어디서든 접근이 가능하다는 의미입니다.

하지만 함수 내부에 선언된 변수와 내부 함수는 지역 변수, 함수가 됩니다.

즉 부모 함수의 밖에서는 접근할 수 없게 됩니다.

부모 함수안에 자식 함수를 만들고 그 자식 함수 안에 손자 함수를 만든다고 가정한다면

손자 -> 자식 -> 부모 -> 전역으로 접근해서 사용할 수 있게 됩니다.

같은 전역 지역에 선언된 함수 A, B 2개가 있다면 서로 내부의 변수와 함수에 접근하거나 사용할 수 없습니다.

그냥 전역에 선언된 함수만 알 뿐이죠. 그 안의 변수와 함수는 다른 함수들이 알 수 없거든요.

예제 소스로 확인해보겠습니다.

1
2
3
4
5
6
7
8
9
10
var a = 1//전역 지역의 전역변수
 
function funcA() {  //전역 지역의 전역 함수
    console.log(a);
    return 2;
}
 
 
console.log(a);
console.log(funcA());
cs

 

1라인의 a는 전역 지역에 선언된 전역 변수입니다.

3라인의 funcA()는 전역 지역에 선언된 함수입니다.

4라인의 a는 전역 변수를 사용하고 있습니다.

실행 결과는 다음과 같습니다.

1
2
3
4
1
1
2
 
cs

9라인의 결과 1, 10라인의 결과는 함수를 호출해서 4라인의 1, 5라인의 2가 순서대로 출력되었습니다.

 

다음은 지역에 대한 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = 1//전역 지역의 전역변수
 
function funcA() {  //전역 지역의 전역 함수
    var a = 3;
    console.log(a);
    console.log(b);
    return 2;
}
 
function funcB() {  //전역 지역의 전역 함수
    var b = 4;    
    console.log(a);
    console.log(b);
    return 2;
}
 
console.log(a);
console.log(b);
console.log(funcA());
cs

이 예제는 오류가 발생합니다.

바로 6라인 때문입니다.

변수 b는 전역 지역의 다른 함수 funcB에 선언되어 있기에 

funcA에서는 접근할 수 없습니다. 

오류를 수정해봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = 1//전역 지역의 전역변수
 
function funcA() {  //전역 지역의 전역 함수
    var a = 3;
    console.log(a);
    return 2;
}
 
function funcB() {  //전역 지역의 전역 함수
    var b = 4;    
    console.log(a);
    console.log(b);
    return 2;
}
 
console.log(a);
console.log(b);
console.log(funcA());
cs

이 코드 또한 오류가 발생했습니다.

바로 17라인 때문인데요.

변수 b는 funcA 함수내 지역 변수이기에 전역에서 바라볼 수 없습니다.

안에서 밖으로 바라볼 수 있지만 밖에서는 안으로 못 가는 상황이네요.

밖에서 (전역) 함수 내 선언된 변수(지역)을 바라볼 수 없다로 정리됩니다.

이 내용은 중요하니 잘 기억해두세요.

다시 오류를 수정해봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = 1//전역 지역의 전역변수
 
function funcA() {  //전역 지역의 전역 함수
    var a = 3;
    console.log(a);
    return 2;
}
 
function funcB() {  //전역 지역의 전역 함수
    var b = 4;    
    console.log(a);
    console.log(b);
    return 2;
}
 
console.log(a);
console.log(funcA());
console.log(funcB());
cs

이제 오류 없이 실행이 됩니다.

결과는 어떻게 나올까요?

1
2
3
4
5
6
1
3
2
1
4
2
cs

1라인은 전역 변수 a를 출력합니다. 그래서 1.

2라인은 funcA에서 지역 변수 a를 출력합니다. 그래서 3.

3라인은 funcA의 return으로 2.

4라인은 funcB에서 전역 변수 a를 바라보기에 1입니다.

주의할 점은 funcA를 실행했고 a가 3으로 변할 거라 생각할 수 있지만 전역 변수 a를 바라봅니다.

이 설명을 다시하면

자바스크립트는 변수와 함수를 호출 시점에 선언하고 사용하는 것이 아닌

최초 로딩되는 순간 전역 지역 기준으로 바라보게 됩니다.

즉, 1라인의 변수 a, 함수 funcA, funcB는 전역 지역의 변수와 함수로

프로그램 실행 전 선언 단계에서 바라보게 됩니다.

하지만 funcA는 내부에 var a라고 지역변수를 선언했기 때문에

전역 변수 a가 아닌 지역 변수 a를 바라보게 되는 것이며

funcB에서 11라인의 console.log(a)는 로딩 때 이미 전역 변수 a를 바라보고

funcA 내부의 var a는 바라볼 수 없기에

funcA에서 var a = 3;으로 한다고 해서 전역 변수 a값이 재할당 되지는 않습니다.

 

전역 기준으로 선언이 되면 해당 전역 객체들을 기본적으로 바라보게 되고

함수 내 지역 변수가 선언되면 전역 기준으로 서로 내부는 들어갈 수 없게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = 1//전역 지역의 전역변수
 
function funcA() {  //전역 지역의 전역 함수
    var a = 3;
    console.log(a);
    return 2;
}
 
function funcB() {  //전역 지역의 전역 함수
    var b = 4;    
    console.log(funcA());
    console.log(b);
    console.log(a);
    return 2;
}
 
console.log(funcB());
cs

그럼 위 예제를 다시 보겠습니다.

funcB를 호출했습니다.

11라인으로 내부에서 전역의 funcA()를 호출했습니다.

내부에서는 로딩 때 이미 선언된 함수이기에 위로 바라보면서 funcA를 호출할 수 있습니다.

funcA는 지역 변수 var a를 할당했기에 전역 변수 a의 값 1이 아닌 3을 출력합니다.

이렇게 다른 함수의 내부 변수 값을 받을 수 있게 됩니다. 

그리고 return 2를 출력하고

12라인에서 b값 4를 출력합니다.

11라인에서 funcA를 호출해서 funcA의 지역 변수 a에 3으로 할당했지만

13라인은 전역 변수 a의 값 1을 출력합니다.

 

위 소스를 실행하면 자바스크립트 엔진은 먼저

전역 변수 a와 함수 funcA, funcB를 정의합니다.

그리고 funcA와 funcB 또는 전역 지역에서는 기본적으로 a를 찾으면 전역 변수 a를 바라봅니다.

이미 정의를 했기 때문이지요. (lexical scope or static scope)

하지만 함수 내부에서 같은 이름으로 지역 변수를 선언한다면 그 값을 바라보게 됩니다.

안에서 밖의 변수와 함수를 바라볼 수 있지만 반대로 접근은 할 수 없습니다.

 

렉시컬 스코프(lexical scope)를 풀어서 정의해보겠습니다.

변수나 함수가 전역 환경에서 선언이 되었다면 렉시컬 스코프(lexiclal scope)는 전역이 됩니다.

변수나 함수가 함수 내부처럼 지역 환경에 선언이 되었다면 해당 변수나 함수의 렉시컬 스코프(lexical scope)는

해당 함수의 지역이 됩니다.

이렇게 자바스크립트의 렉시컬 스코프를 나름 정리해봤습니다.

잘못된 정보나 혹은 다른 추가 정보가 있다면 정정하겠습니다.

즐 코딩하세요.

반응형