JavaScript :: 클로저 Closure란?

Hayoung·2021년 6월 8일
0

JavaScript

목록 보기
5/5
post-thumbnail

클로저(closure)를 이해하기 위해서는 먼저 scope를 이해할 필요가 있다.
먼저 scope에 대해 간단히 알아본 뒤, 클로저에 대해 알아보고자 한다.

Scope

먼저, JavaScript에서 scope란 무엇일까?

scope란, 변수가 참조 가능한 유효 범위를 말한다.
"선언된 변수나 객체, 함수 등을 어느 범위까지 불러오는 것이 가능한가?"라는 질문에 대한 해답이 바로 scope이다.

Global scope, Local scope

JavaScript의 scope는 크게 global scope, local scope로 나뉜다.
local scope는 또다시 function scope, block scope로 나뉜다.

  • Global scope: 코드의 최상위(top level)의 범위
  • Local scope: 유효 범위가 한정된 scope
    • Function scope
    • Block scope

global scope에서 선언된 변수는 전역 변수(global variables)라고 하며, 프로그램 내 어디서든지 참조와 호출, 변경을 하는 것이 가능하다.

반대로, local scope에서 선언된 변수는 지역 변수(local variables)라고 하며,
해당 지역 변수가 선언된 scope의 외부로부터 해당 지역 변수에 접근하는 것이 불가능하다.

var name = "John";  // global scope, 전역 변수

function setNumber(){
  var number = 10;  // 지역 변수
  console.log(number);  // 10
  console.log(name);  // "John" (전역 변수이기 때문에 함수 내에서도 참조 가능)
} 

function sayName(){
  var name = "Jason";  // 지역 변수 (전역 변수인 name과는 별개)
  console.log(name);  // "Jason" (지역 변수인 name이 호출됨)
}

console.log(name);  // "John"
console.log(number);  // ReferenceError: number is not defined

전역 변수인 name은 어느 함수의 내부에서도, 외부에서도 참조할 수 있다.
함수 내에서 전역 변수 이름과 같은 이름의 지역 변수가 선언되면, 전역 변수와는 별개의 새로운 지역 변수가 생성된다.

변수를 참조할 때는 가장 가까운 scope의 변수를 참조하게 되기 때문에
sayName함수에서 console.log(name)을 했을 때는 John이 아닌 Jason이 출력된다.

또한, 지역 변수를 global scope에서 호출하는 등, 유효 범위를 벗어나서 변수를 호출하는 경우에는 ReferenceError: x is not defined와 같은 에러가 발생된다.

Lexical scope, Dynamic scope

간단하게 scope에 대해 알아보았다.
사실 scope가 결정되는 시점에 따라서도 scope를 나눌 수도 있다.

  • Lexical scope(정적 스코프)
  • Dynamic scope(동적 스코프)

Lexical scope(정적 스코프)

JavaScript를 포함한 Ruby, Java, Python 등의 언어에서는 이 lexical scope를 채용하고 있다.

lexical scope는 함수가 선언되는 시점에 따라 scope가 결정되는 특징을 가진 scope이다.
따라서, lexical scope에서는 함수가 선언될 때 scope가 생성된다.

코드를 살펴보며 lexical scope에 대해 자세히 살펴보자.

var number = 10; 

function printNumber() {
  console.log(number);  // 10
}

function updateNumber() {
  var number = 1000;  // updateNumber 내부에서도 number가 정의됨
  printNumber();  // updateNumber내에서 printNumber를 호출했을 때 number의 값은?
}

printNumber();  // 10
updateNumber(); // 10 (1000이 아님)

함수 printNumber는 변수 number를 출력하는 함수이다.
함수 updateNumber에서는 number를 다시 한번 정의를 하고 있고, printNumber를 호출하고 있다.

printNumber를 global 레벨에서 호출을 하면, 당연히 10이 출력된다.
그렇다면 updateNumber 내부에서 printNumber가 호출되면 어떻게 될까?

lexical scope 환경에서의 number의 scope는, number가 최초로 참조되는 printNumber가 선언된 시점에서 결정되며, 선언된 시점에서의 값을 그대로 값을 보존하게 된다.

updateNumber의 내부에서 지역 변수로 number를 재정의해준 뒤 printNumber를 호출해줬지만,
printNumber는 선언된 시점에 10이라는 값을 가지고 있는 전역 변수 number을 참조하고 있는 상태다.

즉, printNumber를 내부에서 호출한 updateNumber에서 새롭게 number가 정의된다고 해도, printNumber가 선언된 시점에서 참조된 number의 값인 10이 출력되는 것이다.

Dynamic scope(동적 스코프)

dynamic scope는 Perl 등의 언어에서 채용되고 있는 scope이다.
(JavaScript는 lexical scope를 따르기 때문에 JavaScript와 dynamic scope는 서로 관계가 없다.)

lexical scope와는 달리, dynamic scope는 함수를 호출, 실행하는 시점마다 scope가 재형성된다.

JavaScript는 lexical scope를 따르긴 하지만,
dynamic scope를 따른다고 가정하고 결과가 어떻게 달라지는지 예시 코드를 통해 살펴보자.

var number = 10; 

function printNumber() {
  console.log(number);  // 10
}

function updateNumber() {
  var number = 1000;  // updateNumber 내부에서도 number가 정의됨
  printNumber();  // printNumber 함수를 호출한 이때 scope가 재형성된다
}

printNumber();  // 10
updateNumber(); // 1000 (10이 아님!)

global 레벨에서 호출된 printNumber의 출력 결과는 변함 없이 10이다.
dynamic scope에서는 함수가 호출될 때마다 scope가 재형성되기 때문에 printNumber()의 시점에서도 scope가 재형성되지만,
이때 printNumber가 참조할 수 있는 것은 전역 변수인 var number = 10 뿐이므로 결과는 10이 출력된다.

이제 updateNumber를 살펴보자.
updateNumber 내부에서 호출된 printNumbernumber의 값이 1000이 된 시점에서 scope가 재형성된다.
이에 따라 printNumber에서 출력되는 변수 numberupdateNumber에서 정의된 var number = 1000를 참조하게 되는 것이다.

Scope Chain

function foo() {
  var b = a * 2;
  console.log(b);
}

var a = 2;

foo(); // 4

위의 코드에서 변수 a는 함수 foo 안에 선언되어 있지 않는데도 불구하고 ReferenceError가 발생되지 않는다.
그 이유는, 함수 foo의 외부 scope에 존재하는 변수 a를 참조하기 때문이다.

이처럼 현재 scope에 변수가 존재하지 않는 경우, scope를 타고 올라가서 외부 scope로 참조 범위를 넓혀가는 시스템을 scope chain이라고 한다.


클로저 Closure?

드디어 본론으로 넘어왔다💦
서론이 굉장히 길었지만, JavaScript가 따르는 Lexical scope의 성질을 이해하고 있는 것이 클로저를 이해하는 데에 도움이 될 것이다.

클로저란 무엇일까?

  • A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
  • 클로저는 주변의 상태 (lexical environment)의 참조와 함께 번들로 묶인 함수의 조합입니다. 즉, 클로저는 내부 함수에서 외부 함수의 scope에 접근을 가능하게 해줍니다. JavaScript에서 클로저는 함수가 생성될 때마다 생성됩니다. (papago 왈)
  • Closures - MDN

즉 간단히 말하자면 클로저는,
함수가 선언될 때 그 외부 주변의 상태를 참조하며(lexical scope를 기억하여),
내부 함수로부터 외부의 (함수) scope에 참조할 수 있게 해주는 것을 말한다.

Closure의 예제 코드 살펴보기 🔍

클로저의 정의에 대해 보는 것만으로는 이해하기가 힘들다.
간단한 예시를 보며 실제로 클로저가 어떻게 사용되는지, 클로저가 무엇인지 살펴보자.

function setName() {
  var name = 'John';
  console.log(name);
  
  function alertName() {
    alert(name);
  }
  return alertName;
}

var callName = setName();
callName();

위 코드에서, 클로저는 어느 부분을 말하는 것일까?
답은, var callName = setName();이다.

자세히 코드를 살펴보자.

먼저 setName이라는 함수를 선언했고, 그 내부에는 지역 변수인 nameJohn이라는 값으로 초기화되었다.
그리고 함수 alertName이 함수 setName의 내부에 선언되어 있다.

이때 변수 name은 함수 setName 내부에 존재하는 지역 변수이기 때문에, 함수 setName이 호출되지 않는 이상 (원칙상) 참조하는 것이 불가능하다.
(위의 Global scope, Local scope에서도 언급했다!)

함수 setName의 내부를 보자.
함수 setName는 내부에서 선언된 함수인 alertName을 return하고 있다.
그래서 전역 변수인 callName에 할당되는 것은 사실상 함수 alertName이 된다.

다시 말해, var callName = setName()를 풀어 쓰면

var callName = function alertName() {
  alert(name);
}

가 되며,

마지막 행인 callName()을 다시 쓰면

alert(name);

이 된다.

이렇게 실행된 함수 alertNamename의 값을 alert로 출력해야하지만, alertName의 내부에는 name의 값이 존재하지 않는다.

따라서 함수 alertName은 외부 scope인 setName에서 name 값을 참조하게 되고, setName의 지역 변수인 name으로부터 John이라는 값을 얻게 된다.
그렇게 alert에는 John이라는 값이 출력된다.

결국 사실상 함수 setName이 호출되지 않았는데도 불구하고,
함수 setName 내부의 지역 변수인 name가 외부에서 호출
되고 있는 것이다!

이것이 바로 클로저이다.

Closure의 활용

  • 지역 변수를 상위 scope에서 사용할 수 있게 하기 때문에, 전역 변수의 사용을 줄일 수 있다. 때문에 전역 변수의 남용을 막을 수 있어, 안정성을 높일 수 있다.
  • 객체 지향 프로그래밍의 정보 은닉과 캡슐화를 행할 수 있다.

잘못된 내용이나 부족한 부분이 있다면 마구마구 지적해주시면 감사하겠습니다🙇‍♀️

profile
Frontend Developer. 블로그 이사했어요 🚚 → https://iamhayoung.dev

0개의 댓글