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

🎯 변수의 유효 범위, 렉시컬 환경

 

📝 렉시컬 환경

function printName() {
  return 'Hyunsol'
}

function findName() {
  return printName()
}

function sayMyName() {
  return findName()
}

sayMyName()
  • 렉시컬 환경이란 특정 코드가 작성, 선언된 환경(장소)을 의미한다.

 

  • 총 4개의 실행 컨텍스트(1개의 글로벌 실행 컨텍스트와 3개의 개별적 실행 컨텍스트)가 생성된다.
  • 각각의 실행 컨텍스트는 하나의 개별적인 소우주이다.
  • 바꿔 말하면, sayMyName 함수, fineName 함수, printName 함수의 렉시컬 환경은 global이다.
  • 만약 findName()이라는 함수 내에 let yourName = 'blahblah'라고 선언되어 있다면, 변수 yourName의 렉시컬 환경은 findName이다.
내가 사용하고자 하는 변수, 함수 등이 어떤 렉시컬 환경에 속해 있는지에 따라서 이용 가능한 변수가 달라진다.
즉, 어떤 변수나 함수의 값은 '어디에서 선언했는지', 즉 렉시컬 환경이 어디인지에 따라서 결정된다.
function printName() {
  return 'Hyunsol'
}

function findName() {
  return printName()
}

function sayMyName() {
  return findName()
}

sayMyName()
  • sayMyName 함수 내에서 findName 함수가 호출되기는 했으나 '어디에서 호출했는지'가 아니라 '어디에서 선언했는지'가 중요하다.
  • findName 함수는 글로벌 실행 컨텍스트, 즉 global 함수 내에서 선언이 되어있다.
  • findName 함수의 렉시컬 환경은 글로벌 실행 컨텍스트
  • 글로벌 실행 컨텍스트 내에 존재하는 global이라는 객체 내의 property로 지정되어 있다.
  • findName, printName, sayMyName 함수 모두 global 객체의 property 중 하나이다.

 

📝 단계 1. 변수

  • 1. 환경 레코드
    • 모든 지역 변수를 프로퍼티로 저장하고 있는 객체
    • this 값과 같은 기타 정보도 여기에 저장된다.
  • 2. 외부 렉시컬 환경에 대한 참조
    • 외부 코드와 연관됨

'변수'는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐이다. 따라서 '변수를 가져오거나 변경'하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경함을 의미한다.

  • 위 코드는 렉시컬 환경이 하나만 존재한다.
    • 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오거나 변경함을 의미
  • 전역 렉시컬 환경은 외부 참조를 갖지 않기 때문에 화살표가 null을 가리킨다.

 

📕 렉시컬 환경의 변화

  • 1. 스크립트가 시작되면 선언한 변수 전체가 렉시컬 환경에 올라간다.
    • 이때 변수의 상태는 특수 내부 상태(special internal state)인 'uninitialized’가 된다.
    • 상태의 변수를 인지하긴 하지만, let을 만나기 전까진 이 변수를 참조할 수 없다.
  • 2. let phrase 값을 할당하기 전이기 때문에 프로퍼티 값은 undefined이다.
    • 변수를 사용할 수 있다.
  • 3. 값 할당
  • 4. 값 변경

 

📝 단계 2. 함수 선언문

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

  • 함수 표현식은 해당하지 않는다.

 

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

  • 함수를 호출해 실행하면 새로운 렉시컬 환경이 자동으로 만들어진다.

 

say("John")을 호출하면 일어나는 내부 변화

  • 호출 중인 함수를 위한 내부 렉시컬 환경내부 렉시컬 환경이 가리키는 외부 렉시컬 환경을 갖게 됩니다.
    • 현재 실행 중인 함수인 → say
    • 전역 렉시컬 환경은 phrase와 함수 say를 프로퍼티로 갖는다.

1. 코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색 범위로 잡는다.

2. 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장

3. 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복

  • 전역 렉시컬 환경에 도달할 때까지 변수를 찾지 못하면 엄격 모드에선 에러가 발생한다.

  • 1. 함수 say 내부의 alert에서 변수 name에 접근 → 내부 렉시컬 환경을 살피기 → 내부 렉시컬 환경에서 변수 name을 찾음
  • 2. alert에서 변수 phrase에 접근 → phrase에 상응하는 프로퍼티가 내부 렉시컬 환경엔 없다. → 검색 범위는 외부 렉시컬 환경으로 확장 → 외부 렉시컬 환경에서 phrase를 찾음

 

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

function makeCounter() {
  let count = 0;

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

let counter = makeCounter();

 

📕 first

  • makeCounter()가 실행되는 도중엔 본문(return count++)이 한 줄짜리인 중첩 함수가 만들어진다. 현재는 중첩 함수가 생성되기만 하고 실행은 되지 않은 상태이다.

모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다.

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

 

📕 second

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

 

📕 Third

function makeCounter() {
  let count = 0;

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

let counter = makeCounter();

console.log(counter()) // ?

  • 1. 자체 렉시컬 환경에서 변수를 찾기
  • 2. 익명 중첩 함수엔 지역변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황 → <empty>
  • 3. counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾는다.
    • 변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄진다.

 

📌 참고

반응형