글 작성자: 망고좋아
반응형

 

변수의 유효 범위와 클로저

  • 자바스크립트는 함수 지향 언어이다.
  • 함수를 동적으로 생성할 수 있고, 생성한 함수를 다른 함수에 인수에 넘길 수 있으며, 생성된 곳이 아닌 곳에서 함수를 호출할 수 있다.

 

 

중첩 함수

function sayHiBye(firstName, lastName) {

  // 헬퍼(helper) 중첩 함수
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}
  • 함수 내부에서 선언한 함수는 ‘중첩(nested)’ 함수라고 부른다.
  • 중첩 함수는 새로운 객체의 프로퍼티 형태나 중첩 함수 그 자체로 반환될 수 있다.
  • 이렇게 반환된 중첩 함수는 어디서든 호출해 사용할 수 있다.
function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

 

 

렉시컬 환경

단계 1. 변수

  • 자바스크립트에선 실행 중인 함수, 코드 블록 {...}, 스크립트 전체는 렉시컬 환경(Lexical Environment) 이라 불리는 내부 숨김 연관 객체(internal hidden associated object)를 갖는다.

렉시컬 환경 객체 구성

  1. 환경 레코드
    • 모든 지역 변수를 프로퍼티로 저장하고 있는 객체
    • this 값과 같은 기타 정보도 여기에 저장된다.
  2. 외부 렉시컬 환경에 대한 참조
    • 외부 코드와 연관됨
  • ’변수’는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐입니다. '변수를 가져오거나 변경’하는 것은 '환경 레코드의 프로퍼티를 가져오거나 변경’함을 의미한다.

  • 위 두 줄짜리 코드엔 렉시컬 환경이 하나만 존재한다.
  • 이렇게 스크립트 전체와 관련된 렉시컬 환경은 전역 렉시컬 환경이라고 한다.
  • 네모 상자는 변수가 저장되는 환경 레코드, 화살표는 외부 렉시컬 환경에 대한 참조, 전역 렉시컬 환경은 외부 참조를 갖지 않기 때문에 화살표가 null을 가리킨다.

  1. 스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다.
    • 변수의 상태는 특수 내부 상태인 'uninitialized’가 된다.
    • 자바스크립트 엔진은 'uninitialized’ 상태의 변수를 인지하지만, let을 만나기 전까지 이 변수를 참조할 수 없다.
  2. let phrase 두두등장. 아직 값을 할당하기 전이기 때문에 프로퍼티 값은 undefined. phrase는 이 시점 이후부터 사용 가능
  3. phrase에 값이 할당
  4. phrase의 값이 변경

 

중간 정리

  • 변수는 특수 내부 객체인 환경 레코드의 프로퍼티이다.
  • 환경 레코드는 현재 실행 중인 함수와 코드 블록, 스크립트와 연관되어 있다.
  • 변수를 변경하면 환경 레코드의 프로퍼티가 변경된다.

 

⚠️ 렉시컬 환경은 명세서에만 존재

  • 이론상의 객체이므로 렉시컬 환경을 얻거나 조작하는 것은 불가능하다!(휴! 다행이다!)

 

 

단계 2. 함수 선언문

  • 함수 선언문(function declaration)으로 선언한 함수는 일반 변수와는 달리 바로 초기화된다는 점에서 변수와 차이가 있다.
  • 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다.
  • 선언되기 전에도 함수를 사용할 수 있는 것은 이 때문이다.

  • let say = function(name)...같이 함수를 변수에 할당한 함수 표현식(function expression)은 해당되지 않는다.

 

 

단계 3. 내부와 외부 렉시컬 환경

  • 함수를 호출해 실행하면 새로운 렉시컬 환경이 자동으로 만들어진다.
  • 이 렉시컬 환경엔 함수 호출 시 넘겨받은 매개변수와 함수의 지역 변수가 저장된다.

  • 내부 렉시컬 환경은 실행 중인 함수인 say에 상응
  • 내부 렉시컬 환경엔 함수의 인자인 name으로부터 유래한 프로퍼티 하나가 있다. say("John")을 호출했기 때문에, name의 값은 "John"이 된다.
  • 외부 렉시컬 환경은 전역 렉시컬 환경, 전역 렉시컬 환경은 phrase와 함수 say를 프로퍼티로 갖는다.
  • 내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다.
  • 코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색 범위로 잡는다.
  • 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장한다.
  • 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복된다.

 

변수 검색 진행 방식

  • 함수 say 내부의 alert에서 변수 name에 접근할 땐, 먼저 내부 렉시컬 환경을 살펴본다. 내부 렉시컬 환경에서 변수 name을 찾았다.
  • alert에서 변수 phrase에 접근하려는데, phrase에 상응하는 프로퍼티가 내부 렉시컬 환경엔 없다. 따라서 검색 범위는 외부 렉시컬 환경으로 확장된다. 외부 렉시컬 환경에서 phrase를 찾았다.

 

 

단계 4. 함수를 반환하는 함수

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

  • makeCounter()를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어지고 여기에 makeCounter를 실행하는데 필요한 변수들이 저장된다.
  • makeCounter()를 호출할 때도 두 개의 렉시컬 환경이 만들어진다.
  • makeCounter()가 실행되는 도중엔 본문(return count++)이 한 줄짜리인 중첩 함수가 만들어진다는 점이 위쪽에서 살펴본 say("John")과 다르다.
  • 현재는 중첩 함수가 생성되기만 하고 실행은 되지 않은 상태이다.

 

중요!

  • 모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다는 점이다.
  • 함수는 [[Environment]]라 불리는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장된다.

  • 따라서 counter.[[Environment]]{count: 0}이 있는 렉시컬 환경에 대한 참조가 저장된다.
  • [[Environment]] 프로퍼티 덕분에 호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있는 이유이다.
  • [[Environment]]는 함수가 생성될 때 딱 한 번 값이 세팅되고 영원히 변하지 않는다.
  • counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성된다.
  • 이 렉시컬 환경은 counter.[[Environment]]에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조한다.

  • 중첩 함수에서 count 변수가 필요한데, 먼저 자체 렉시컬 환경에서 변수를 찾는다.
  • 익명 중첩 함수엔 지역 변수가 없기 때문에 렉시컬 환경은 비어있다.
  • 그래서 count().의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾는다.
  • 그다음 count++가 실행되면서 count 값이 1 증가해야 하는데, 변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄진다.
  • 실행 종료 후 상태는 아래와 같다.

 

 

클로저

  • 클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다.
  • 자바스크립트의 함수는 숨김 프로퍼티인 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억한다. 함수 본문에선 [[Environment]]를 사용해 외부 변수에 접근한다.

 

 

가비지 컬렉션

  • 함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거된다.
  • 자바스크립트에서 모든 객체는 도달 가능한 상태일 때만 메모리에 유지된다.
  • 하지만 호출이 끝난 후에도 여전히 도달 가능한 중첩 함수가 있을 수 있다.
  • 이때는 이 중첩 함수의 [[Environment]] 프로퍼티에 외부 함수 렉시컬 환경에 대한 정보가 저장된다. -> 도달 가능한 상태
function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g.[[Environment]]에 f() 호출 시 만들어지는
// 렉시컬 환경 정보가 저장된다.
  • 렉시컬 환경 객체는 다른 객체와 마찬가지로 도달할 수 없을 때 메모리에서 삭제된다.
  • 해당 렉시컬 환경 객체를 참조하는 중첩 함수가 하나라도 있으면 사라지지 않는다.
function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g가 살아있는 동안엔 연관 렉시컬 환경도 메모리에 살아있다.

g = null; // 도달할 수 없는 상태가 되었으므로 메모리에서 삭제

 

 

📌 참고

반응형