클로저(Closure)란?
FE BE 개발 메모장/Javascript

클로저(Closure)란?

클로저를 알기 위해서는 우선 먼저 기본적인 개념 몇가지를 알아야 한다고 생각한다. 

 

일급 객체, 일급 함수

 자바스크립트의 함수는 일급 함수이며, 함수는 사실 객체이므로 일급 객체라고도 한다.  클로저를 다루기 전에 일급 객체에 대헤서 반드시 알아야 한다. 

 

어휘적 범위 지정(Lexical scoping)

Lexical Scoping은 함수를 어디에 선언했는지에 따라 스코프가 결정되는 것을 말한다. 우선 스코프라는 것은 함수를 선언할 때 생기는 개념이라 정적스코프라고도 불린다. 함수를 어디에 선언하던 스코프에는 영향을 끼치지 않는다. 아래 예시를 보자. 

var browsers = "Chrome";

function browser() { 
  console.log(browsers);
}

function defaultBrowser() {
  browsers = 'safari';
  browser();
}

defaultBrowser();

함수를 선언하는 순간 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳에 존재하는 변수를 참조하려고 한다. 위의 예시를 보면 defaultBrowser() 함수안에서 browsers라는 변수를 찾게 된다. 함수 내부에 존재하지 않으므로 가까운 전역 변수를 찾아내 값을 바꿔버린다.(재할당) 그리고 browser()함수내부의 console.log(browsers)는 DefaultBrowser()함수 내부에서 호출되어 'safari'로 바뀐 browsers가 출력된다. 

 

그래서 defaultBrowser()의 값은 'safari'이다.

 

var browsers = "Chrome";

function browser() {
  console.log(browsers);
}

function defaultBrowser() {
 var browsers = 'safari';
 browser();
}

defaultBrowser();

 

다음은 defaultBrowser()함수 내 var키워드를 작성하여 변수를 선언해주었다. 결과는 'chrome'이 출력 된다.

 

defaultBrowser()함수 안에 var라는 키워드를 사용해 변수를 선언하고, 변수 browsers는 defaultBrowsers() 함수안의 지역 스코프로 인식되어 browser()함수에 어떤 영향을 끼치지 않는다. 그렇기 때문에 browser()을 실행시켰을때, browsers라는 변수를 찾지 못하면, 상위 스코프에서 찾게되고, browser() 함수의 상위 스코프인 전역(Global) 영역에 있는 변수 browsers를 찾아 그 값을 출력한다.

 

일반적으로 함수의 인수와 지역 변수를 속박 변수라고 하는데, 위의 코드를 보면  defaultBrowser()함수 내의 browsers()는 속박 변수라 하고,  defaultBrowser()함수 바깥에 선언된 변수는 자유 변수이다. 그리고 속박 변수만 포함된 함수는 닫힌 함수, 자유 변수를 가지고 있는 함수를 열린 함수라고도 하는데, browser()가 열린 함수, defaultBrowser()은 닫힌 함수가 되겠다.

 

이렇게 함수를 어디에 선언했는지에 따라 범위가 결졍되는 것을  Lexical scoping이라하고, 함수가 어디서 호출됬는지에 따라 범위가 결정되는 방식을 Dynamic scoping이라고 한다.

 

클로저(Closure)가 뭔데?? 

MDN에서 설명하는 내용으로 "클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다." 라고 설명하고있다. 위에서 본 것과 같이 함수 내부에서 자유 변수를 활용하여 폐쇄적인 함수를 만드는 방식을 클로저라고 생각한다.

 

가장 기본적인 예로 클로저 방식을 사용한 카운터 예제이다.

function makeCounter() {
  let count = 0;
 
  return function() {
    return count++;
  }
}

let counter1 = makeCounter();
let counter2 = makeCounter();

console.log(counter1())  // 0
console.log(counter2())  // 0
console.log(counter1())  // 1
console.log(counter2())  // 1

 counter1()과 counter2()를 호출할 때마다 makecounter()의 지역 변수인 count 값을 출력한 후에 count값을 1씩 증가시킨다. 

 

 count라는 자유변수는 클로저의 내부 상태로 저장되고, 지역 변수이기 때문에 함수 외부에서 읽거나 사용할 수가 없다. 

makecount()라는 함수 내부의 익명의 함수는 클로저의 내부상태를 바꿔주는 메서드 역활을 맡고있다. counter"n"()를 호출할 때마다 count를 1씩 더해주는 역활을 해준다.

 

우리는 이 makeCounter()라는 함수가 무슨 기능을 가지는지 알수는 있지만 어떤 코드가 작성되있는지 알수가 없다. 마치 객체지향 프로그램에서 객체의 프로퍼티를 외부로부터 은폐하는 행위를 캡슐화라고 하는데, 클로저는 캡슐화된 객체라고도 할 수 있겠다.

클로저를 통한 은닉화

 기존 JavaScript에서 객체지향 프로그래밍은 Prototype을 통해 객체를 다루는 것을 말하는데, Prototype을 통한 객체를 만드는데 주요한 문제중 하나는 비밀 변수(Private variables)에 대한 접근 권한 문제가 존재한다.

 

function Hello(name) {
  this._name = name;
}

Hello.prototype.say = function() {
  console.log('Hello, ' + this._name);
}

var hello1 = new Hello('aa');
var hello2 = new Hello('bb');
var hello3 = new Hello('cc');

hello1.say(); // 'Hello, aa'
hello2.say(); // 'Hello, bb'
hello3.say(); // 'Hello, cc'
hello1._name = 'anonymous';
hello1.say(); // 'Hello, anonymous'

 위 예제에서 hello() 로 생성된 객체들은 모두 _name 이라는 변수를 가지게 된다. name앞에 Underscore( _ )를 포함했는데 이 뜻은 JavaScript 네이밍 컨벤션을 따져봤을 때 Private Variable으로 쓰고싶다는 의미를 가지게 된다. (단순 의미일뿐 위의 예제처럼 외부에서 쉽게 접근이 가능하다.)

 

클로저를 사용하게 되면 변수에 직접 접근하는 것을 제한할 수 있다.

function hello(name) {
  var _name = name;
  return function() {
    console.log('Hello, ' + _name);
  };
}

var hello1 = hello('aa');
var hello2 = hello('bb');
var hello3 = hello('cc');

hello1(); // 'Hello, aa'
hello2(); // 'Hello, bb'
hello3(); // 'Hello, cc'
hello1._name = 'anonymous';
hello1.say(); // 'Hello, aa'

 

예제처럼 외부에서 _name에 접근할 방법을 차단해줬기 때문에 은닉화를 쉽게 해결할 수 있다.

 

 

 

클로저

developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures

https://im-developer.tistory.com/106?category=831368%20%EC%B6%9C%EC%B2%98:%20%20[Code%20Playground]

poiemaweb.com/js-scope#3-function-level-scope

poiemaweb.com/es6-block-scope