본문 바로가기
Frontend/JavaScript

자바스크립트 스코프와 클로저

by Forsaken Developer 2023. 3. 24.
728x90
728x90

자바스크립트 스코프와 클로저

스코프란?

스코프는 참조 대상 식별자를 찾기 위한 규칙으로 식별자가 어디에 선언 되었는지에 따라 유효한 범위를 가진다.

var x = "global scope";

function f1 () {
  var x = "function scope";
  console.log(x);
}

f1();

위 코드에서 전역에 선언된 변수 x는 어디에서든 참조할 수 있지만 함수 f1 내에 선언된 변수 x는 f1 내에서만 참조 할 수 있는데 이러한 규칙을 스코프라고 한다.

C에서 파생된 대부분의 언어는 블록 레벨 스코프로 중괄호 { …}를 통한 지역화가 가능하다.

var x = 0;
{
  var x = 1;
  console.log(x); // 1
}
console.log(x);   // 1

하지만 자바스크립트는 함수 레벨 스코프이고 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하다.

let x = 0;
{
  let x = 1;
  console.log(x); // 1
}
console.log(x);   // 0

ES6에서 도입된 let 키워드를 사용하면 블록 레벨 스코프를 통한 지역화가 가능하다.

변수의 가시영역과 전역변수

자바스크립트에서 스코프는 전역 스코프와 지역 스코프로 나눌 수 있고 변수의 관점에서는 전역 변수와 지역 변수로 나눌 수 있다.

자바스크립트는 다른 언어와 다르게 Entry Point가 없어서 전역에 변수를 선언하기 쉬운데 전역 변수는 변수이름의 중복, 의도치 않은 재할당으로 인한 상태 변화 등의 문제로 사용을 자제해야한다.

console.log(a);
var a = 1;

위 코드는 호이스팅에 의해서 정상적으로 실행된다.

a=1; //window.a = 1;
console.log(a);

var, let, const 키워드를 생략하고 변수를 선언하면 암묵적으로 window객체의 프로퍼티를 의미한다.

함수 스코프와 전역 스코프 어디에서도 변수의 선언을 찾을 수 없다면 자바스크립트 엔진은 전역 객체의 프로퍼티를 동적으로 생성한다.

이를 암묵적 전역이라고 한다.

var f1 = function() {
  a=1;
  var a=2;
  a++;
  console.log(a);
}

f1(); //3

동일한 이름의 변수가 선언되어 있을 때 변수를 사용하면 함수 스코프 내에서는 지역 변수를 의미한다.

var x = 'global';

function f1() {
  var x = 'local';
  console.log(x); //local

  function f2() {
    console.log(x); // local
  }

  f2();
}
f1();
console.log(x); // global

내부 함수는 외부 함수의 변수에 접근할 수 있는데 참조하려는 변수명으로 전역 변수와 지역 변수가 모두 존재한다면 실행 컨텍스트의 스코프 체인에 의해 전역변수의 참조 우선순위가 밀려 지역 변수를 참조한다.

렉시컬 스코프

var x = 1;

function f1() {
  var x = 2;
  f2();
}

function() f2() {
  console.log(x);
}

f1();  //1
f2();  //1

위 코드의 실행결과는 함수의 상위 스코프가 어디인지에 따라 결정된다. f2가 어디서 호출되었는지에 따라 상위 스코프를 결정한다면 이를 동적 스코프라고 하고 f2가 어디에서 정의되었는지에 따라 상위 스코프를 결정한다면 정적스코프 혹은 렉시컬 스코프라고 한다.

자바스크립트는 렉시컬 스코프를 따르므로 함수를 선언한 시점에 상위 스코프가 결정된다.

함수를 어디에서 호출하였는지는 스코프 결정에 영향을 주지 않는다.

클로저

클로저는 자신이 생성되었을 때의 환경을 기억하는 함수라고 할 수 있다.

function f1() {
  var x = 1;
	var f2 = function() {
    console.log(x);
  }
  f2();
}

f1();

위 코드에서 f2 함수는 함수 f1의 내부에서 선언 되었고 자바스크립트는 렉시컬 스코프를 따르므로 상위 스코프는 f1 함수이다.

내부 함수 f2는자신이 속한 렉시컬 스코프인 전역함수와 f1함수를 참조할 수 있다.

내부 함수 f1이 호출되면 자신의 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이고 변수 객체와 스코프 체인, this에 바인딩할 객체가 결정된다.

이때 스코프 체인은 전역 스코프를 가리키는 전역 객체와 f1함수의 활성 객체, 함수 자신의 활성 객체를 순차적으로 바인딩한다.

내부 함수가 상위 스코프에 접근할 수 있는 것은 스코프의 주소를 차례대로 저장하고 있는 실행 컨텍스트의 스코프 체인을 자바스크립트 엔진이 검색하기때문에 가능하다.

function f1() {
  var x = 1; // 자유변수

	/*클로저*/
	var f2 = function() { 
    console.log(x);
  }
  return f2;
}

var inner =  f1();
inner();  //1

함수 f1을 호출하면 내부함수 f2가 반환되고 f1 함수의 실행 컨텍스트는 소멸된다.

f1이 콜스택에서 제거되어 변수 x 또한 유효하지 않아 참조 할 수 없어 보이지만 코드의 실행 결과는 변수 x의 값이다.

이처럼 외부함수보다 내부함수가 더 오래 유지되는 경우 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는 함수를 클로저라고 부른다.

즉 클로저는 내부함수가 자신이 선언 되었을 때의 환경인 스코프를 기억하고 그 스코프 밖에서 호출되어도 선언 되었을 때의 스코프를 접글 할 수 있는 함수이다.

실행 컨텍스트 관점에서는 내부함수가 유효한 상태에서 외부함수가 종료되어도 외부함수 실행 컨텍스트 내의 활성 객체는 내부함수가 참조하는 한 유효하며 이를 내부함수가 스코프 체인을 통해 참조한다.

클로저의 활용

클로저는 자신이 생성될 때의 환경을 기억하기때문에 메모리 차원에서는 손해지만 자바스크립트에서 유용하게 사용된다.

상태 유지 및 전역 변수의 사용 억제

클로저가 가장 유용하게 사용되는 상황으로 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다.

var box = document.querySelector('.box');
    var toggleBtn = document.querySelector('.toggle');

    var toggle = (function () {
      var isShow = false;
      return function () {
        box.style.display = isShow ? 'block' : 'none';
        isShow = !isShow;
      };
    })();

    toggleBtn.onclick = toggle;

즉시 실행함수가 반환하는 함수는 자신이 생성되었을 때의 렉시컬 환경에 속한 변수 isShow를 기억하는 클로저다.

버튼을 클릭하면 이벤트 핸들러인 클로저가 호출되고 isShow의 값이 변경되며 변경된 최신 상태를 계속 유지한다.

전역 변수는 언제든지 누구나 접근할 수 있고 변경할 수 있기때문에 의도치 않게 값이 변경되어 오류가 발생할 수 있다.

이를 통해서 전역 변수를 사용하지않고 상태를 기억 할 수 있다.

정보은닉

function Counter() {
  /*자유 변수*/
  var counter = 0;

  /*클로저*/
  this.increase = function () {
    return ++counter;
  };

  /*클로저*/
  this.decrease = function () {
    return --counter;
  };
}

const counter = new Counter();

console.log(counter.increase()); // 1
console.log(counter.decrease()); // 0

생성자 함수 Counter는 increase, decrease 메소드를 가지는 인스턴스를 생성하는데 자신이 생성되었을 때의 렉시컬 환경을 기억하고 변수 counter를 공유한다.

이때 counter는 this에 바인딩된 프로퍼티가 아닌 변수이기 때문에 생성자 함수 Counter 외부에서 접근할 수 없다.

이러한 클로저의 특징을 통해서 클래스의 private 키워드처럼 활용할 수 있다.

728x90
반응형

'Frontend > JavaScript' 카테고리의 다른 글

자바스크립트 this  (0) 2023.03.23
자바스크립트 함수  (0) 2023.03.22
자바스크립트 object 객체  (0) 2023.03.21
자바스크립트 Array 객체  (0) 2023.03.19
자바스크립트 연산자  (0) 2023.03.17

댓글