JavaScript 범위 및 정도

JavaScript 범위 및 정도

범위란 무엇입니까?

범위는 참조 대상 식별자를 찾기 위한 규칙이며 식별자가 선언된 위치에 따라 유효한 범위를 갖습니다.

var x = "global scope";

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

f1();

위의 코드에서 global에 선언된 변수 x는 어디에서나 참조할 수 있지만 f1 함수에서 선언한 변수 x는 f1 내부에서만 참조할 수 있습니다. 이 규칙을 범위라고 합니다.

대부분의 C 파생 언어는 중괄호 { …를 블록 수준 범위로 사용합니다. } 지역화할 수 있습니다.

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

그러나 JavaScript는 함수 수준으로 제한되며 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효합니다.

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

ES6에 도입된 let 키워드는 블록 수준 범위에서 지역화를 허용합니다.

변수 및 전역 변수의 가시적 범위

자바스크립트에서 스코프는 글로벌 스코프와 로컬 스코프로 나눌 수 있고, 변수 관점에서 글로벌 변수와 로컬 변수로 나눌 수 있다.

자바스크립트는 다른 언어와 달리 진입점이 없기 때문에 전역 변수 선언이 용이하지만 전역 변수는 변수 이름 중복, 의도치 않은 재할당으로 인한 상태 변경 등의 문제가 있으므로 피해야 한다.

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

위의 코드는 일반적으로 리프팅으로 실행됩니다.

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

var, let 또는 const 키워드를 생략하고 변수를 선언하면 암시적으로 위젯의 속성을 의미합니다.

함수 범위나 전역 범위에서 변수 선언을 찾을 수 없는 경우 JavaScript 엔진은 전역 개체의 속성을 동적으로 구성합니다.

이를 암시적 세계화라고 합니다.

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가 정의되는 곳에서 상위 범위가 결정되면 정적 또는 어휘 범위라고 합니다.

JavaScript는 어휘 범위를 따르므로 함수가 선언될 때 상위 범위가 결정됩니다.

함수가 호출되는 위치는 범위 결정에 영향을 주지 않습니다.

가까이

클로저는 생성된 환경을 기억하는 함수입니다.

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

f1();

위의 코드에서 함수 f2는 함수 f1 내부에서 선언되며 JavaScript는 어휘 범위를 따르므로 상위 범위는 함수 f1입니다.

내부 함수 f2는 자신이 속한 어휘 영역인 전역 함수와 f1 함수를 참조할 수 있습니다.

내부 함수 f1이 호출되면 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이고 변수 개체, 범위 체인 및 바인딩할 개체가 결정됩니다.

이때 범위 체인은 전역 범위를 가리키는 전역 개체, f1 함수의 활성 개체, 함수 자체의 활성 개체를 순차적으로 바인딩합니다.

내부 함수는 JavaScript 엔진이 범위의 주소를 저장하는 실행 컨텍스트의 범위 체인을 검색하기 때문에 상위 범위에 액세스할 수 있습니다.

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

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

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

함수 f1이 호출되면 내부 함수 f2가 반환되고 함수 f1의 실행 컨텍스트가 소멸됩니다.

호출 스택에서 f1이 제거되었으므로 변수 x도 유효하지 않으므로 참조할 수 없는 것처럼 보이지만 코드를 실행한 결과는 변수 x의 값입니다.

내부 함수가 외부 함수보다 오래 걸리는 경우 내부 함수가 외부 함수 외부에서 호출되더라도 외부 함수의 로컬 변수에 액세스할 수 있는 함수를 클로저라고 합니다.

즉, 클로저는 내부 함수가 선언되었을 때의 환경인 스코프를 기억하고, 스코프 밖에서 호출되더라도 선언되었을 때의 스코프에 접근할 수 있는 함수입니다.

실행 컨텍스트 관점에서 내부 함수가 유효한 동안 외부 함수가 종료되더라도 내부 함수가 참조하고 내부 함수가 범위 체인을 통해 참조하는 한 외부 함수의 실행 컨텍스트 내의 활성 객체가 유효합니다.

클로저 사용

클로저는 생성 당시의 환경을 기억하므로 메모리 누수가 발생하지만 JavaScript에서는 유용합니다.

상태 지속성 억제 및 전역 변수 사용

클로저가 가장 유용한 상황은 현재 상태를 기억하고 변경 사항을 최신 상태로 유지하는 것입니다.

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 변수를 기억하는 클로저입니다.

버튼을 클릭하면 Closure 이벤트 핸들러가 호출되고 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 생성자 함수는 증가 및 감소 메서드로 인스턴스를 생성하고 생성 당시의 어휘 환경을 기억하며 카운터 변수를 공유합니다.

카운터는 바인딩된 속성이 아니라 변수이기 때문에 현재 카운터 생성자 함수 외부에서 액세스할 수 없습니다.

클로저의 이러한 속성을 통해 클래스의 전용 키워드처럼 사용할 수 있습니다.