[자바스크립트] 클로저(Closure)
미음제
·2022. 3. 23. 16:56
클로저
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.
클로저를 이해하려면 Javascript가 어떻게 변수의 유효 범위를 지정하는지(Lexical Scoping)를
먼저 이해해야 한다.
mdn 문서에 명시된 클로저의 정의는 위와 같다. 우선 조금 더 쉽게 얘기하자면 클로저는 내부 함수가 외부 함수의 Context에 접근할 수 있다는 것을 의미한다.
function outter(){
function inner(){
let name = "MJ";
console.log(name);
}
inner();
}
outter();
outter() 함수를 외부 함수, inner() 함수를 내부 함수라고 한다. 이 코드를 실행하면 name 변수의 값인 "MJ"가 출력된다.
function outter(){
let name = "MJ"; // 외부 함수에 선언된 지역변수
function inner(){ // inner 함수는 name 이라는 지역 변수가 없음
console.log(name);
}
inner();
}
outter();
그렇다면 내부 함수의 변수 name을 외부 함수의 지역 변수로 선언하면? 내부 함수의 지역 변수가 사라져 에러가 발생할 것 같지만 첫 번째 예시와 마찬가지로 같은 결과가 출력된다. 즉, 내부 함수는 외부 함수의 지역 변수에 접근할 수 있다는 것을 의미한다.
다시, 코드를 수정하고 살펴보자면 다음과 같다.
function outter(){
let name = "MJ"; // 외부 함수에 선언된 지역변수
return function (){ // inner 함수는 name 이라는 지역 변수가 없음
console.log(name);
}
}
let innerFunction = outter();
innerFunction();
innerFunction이라는 변수에 outter() 함수의 return 값으로 함수가 전달된다. outter() 함수는 할 일을 마쳤기에 소멸되었고 innerFunction 변수에는 함수가 담겨있다. 이때 innerFunction()을 실행하면? outter() 함수가 소멸되어 name 이란 변수를 찾을 수 없기 때문에 에러가 발생하지 않을까? 싶지만 앞선 예제와 마찬가지로 잘 동작된다.
앞서 언급한 것처럼 내부 함수는 외부 함수의 지역 변수에 접근할 수 있는데 외부 함수가 실행을 마치고 소멸되더라도 내부 함수는 이전과 동일하게 외부 함수의 지역 변수에 그대로 접근할 수 있다. 이러한 동작 방식을 클로저라고 한다.
함수에서 return 문이 실행되었다는 것은 할 일을 마치고 퇴근한 것과 같다. 그렇다면 innerFunction()을 실행하면 console.log(name); 을 실행해야 하는데 name이라는 지역 변수가 없음에도 실행이 가능한 이유는 무엇일까?
우선 함수는 호출되었을 때가 아니라 선언되었을 때 Scope가 지정되고 기억된다는 것을 알아야 한다. 내부 함수에서 지역 변수가 없다면 가장 가까운 곳의 변수를 참조한다. outter() 함수가 선언되었을 때, 지역 변수로 name이라는 변수가 선언되어 있고, 내부 함수가 같은 Scope에 존재한다. 이때 내부 함수는 지역 변수가 없어 가장 가까이 있는 외부 함수(outter() 함수)의 name이란 변수에 접근하는 것이다.
렉시컬 스코프(Lexical Scope)
즉, 렉시컬 스코프는 함수를 어디서 호출했는지가 아니라 어디서 선언이 되었는지에 따라 결정된다.
let a = 1;
function test1(){
let a = 11;
test2();
}
function test2(){
console.log(a);
}
test1(); // 1
test2(); // 1
위의 예제를 살펴보면 다음과 같다. test1() 함수와 test2() 함수가 마지막 줄에서 호출되고 있다. test 1()은 실행되면 지역변수로 a에 11이란 값을 할당하고 test2() 함수를 호출한다. test2() 함수는 a를 출력해야 하는데 a라는 지역 변수가 test2() 함수 내부에 존재하지 않는다. 이때 test2() 함수가 참조할 변수는? test1() 함수 내부의 지역 변수 a를 참조할 것 같지만, 함수가 선언된 위치에서 가장 가까운 전역 변수인 a = 1을 참조한다. 즉, 호출의 위치는 스코프 결정에 영향을 미치지 않는다.
다시 mdn 문서의 말을 빌려와 렉시컬 스코프를 설명하면 다음과 같다.
여기서 "lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다.
단어 "lexical"은 이런 사실을 나타낸다. 중첩된 함수는 외부 범위(scope)에서 선언한 변수에도 접근할 수 있다.
클로저를 이용해 객체를 정의한다고 하면 다음과 같다.
const customerSetName = (name) => {
return{
setName : (newName) => {
name = newName;
},
getName : () => {
let customerName = name;
return customerName
}
}
}
let personMinJe = customerSetName('MinJe');
let personMJ = customerSetName('MJ');
console.log(personMinJe.getName()); // MinJe
console.log(personMJ.getName()); // MJ
personMJ.setName('MINJE');
console.log(personMinJe.getName()); // MinJe
console.log(personMJ.getName()); // MINJE
customerSetName() 함수는 return 값으로 객체를 반환한다. 이 객체는 setName()과 getName() 메서드를 가지고 있다. 각각의 메서드들은 customerSetName()의 지역변수인 name을 사용한다.
customerSetName()이라는 외부 함수에서 만들어진 각각의 메서드는 동일한 지역변수 name을 사용한다. personMJ.setName('MINJE'); 실행문을 통해 psersonMJ 객체의 setName()과 getName()이 동일한 외부 함수의 지역변수를 사용했음을 알 수 있다.
이를 통해 Javscript는 Private 속성을 다룰 수 있다. return 된 객체에서 만 name을 읽고 수정할 수 있다는 것이다. Javascript는 Java와 같이 문법적으로 Private 속성을 지원하지 않는데 이를 통해 정보 은닉을 실현할 수 있다.
아직까지 기본적인 개념을 이해하려고 하고 있고, 그 위주로 글을 작성했다. 다양한 예시들을 더 살펴보고 업데이트를 해주어야 한다...
참고
https://opentutorials.org/module/532/6544
https://poiemaweb.com/js-scope
'프로그래머스 > 데브코스 프론트엔드' 카테고리의 다른 글
[TIL] 2022-03-23/24 / 3, 4일차 (0) | 2022.03.24 |
---|---|
[자바스크립트] 이벤트 루프(Event Loop) (0) | 2022.03.23 |
[자바스크립트] 원시타입(Primitive Type)과 참조타입(Reference Type) (0) | 2022.03.23 |
[TIL] 2022-03-22 / 2일차 (0) | 2022.03.22 |
[TIL] 2022-03-21 / 1일차 (0) | 2022.03.21 |