본문 바로가기

모던 자바스크립트

6.3 변수의 스코프

📢 자바스크립트는 함수 지향 언어이다. 

코드 블록

◾️ 코드 블록 {...} 안에서 선언한 변수는 블록안에서만 사용할 수 있다. 

{
  let message = 'Hello';
  console.log(message); // Hello
}

console.log(message); // ReferenceError: message is not defined

◾️ 고유한 작업을 수행하는 코드를 한데 묶는 용도로 활용할 수 있다. 

{
  let message = 'Hi';
  console.log(message); // Hi
}

{
  let message = 'Hello';
  console.log(message); // Hello
}
⚠️ 블록이 없으면 에러가 발생할 수 있다. 

◾️ 이미 선언된 변수와 동일한 이름을 가진 변수를 별도의 블록 없이 let으로 선언하면 에러가 발생한다. 

◾️ if, for, while 등에서도 {...} 안에서 선언한 변수는 오직 블록 안에서만 접근 가능하다.  

◾️ 변수의 범위를 블록 범위, 특히 if분기문 범위로 한정시킬 수 있어서 아주 유용하다. 

if(true){
  let message = 'Hello!';
  console.log(message); // Hello
}

console.log(message); // ReferenceError: phrase is not defined

◾️ 변수 i는 for 안에서만 사용할 수 있다.

◾️ let i는 {...} 밖에 있긴 하지만 for 옆 괄호 안에서 선언한 변수는 블록 {...}에 속하는 코드로 취급된다.

for (let i = 0; i < 3; i++) {
  console.log(i); // 0, 1, 2
}

alert(i); // ReferenceError: i is not defined

 

 

중첩 함수

◾️ 함수 내부에서 선언한 함수는 중첩(nested) 함수라고 한다. 

◾️ 중첩 함수는 코드를 정돈하는데 사용할 수 있다. 

◾️ 중첩 함수는 반환될 수 있다. 새로운 객체의 프로퍼티 형태로나 중첩 함수 그 자체로 반환된다. 

◾️ 반환된 함수는 어디서든 호출해 사용할 수 있다. (외부 변수에 접근할 수 있다. )

function sayHiBye(firstName, lastName){
  // 헬퍼(helper) 중첩 함수
  function getFullName() {
  	return firstName + ' ' + lastName;
  }
  
  console.log('Hello, ' + getFullName());
  console.log('Bye, ' + getFullName());
}

◾️ makeCounter는 숫자를 세주는 카운터 함수를 만든다. 카운터 함수는 호출될 때마다 다음 숫자를 반환한다. 

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. 환경 레코드(Environment Record) : 모든 지역 변수를 프로퍼티로 저장하고 있는 객체. (this값과 같은 기타 정보도 저장된다. )
  2. 외부 렉시컬 환경(Outer Lexical Environment)에 대한 참조 - 외부 코드와 연관됨

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

◾️ 네모 상자는 변수가 저장되는 환경 레코드를 나타낸다. 

◾️ 붉은 화살표는 외부 참조(outer reference)를 나타낸다. (전역 렉시컬 환경은 외부 참조 X → null)

렉시컬 환경이 하나만 존재한다. 

◾️ 전역 렉시컬 환경(global Lexical Environment) : 스크립트 전체와 관련된 렉시컬 환경

 

◾️코드가 실행되고 실행 흐름이 이어져 나가면서 렉시컬 환경은 변화한다. 

전역 렉시컬 환경의 변화를 보여준다. 

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

 

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

◾️ 렉시컬 환경은 명세서에서 자바스크립트가 어떻게 동작하는지 설명하는 데 쓰이는 이론상의 객체이다. ( 렉시컬 환경을 얻거나 조작하는 것은 불가능)
◾️ 자바스크립트 엔진들은 명세서에 언급된 사항을 준수하면서 엔진 고유의 방법을 사용해 렉시컬 환경을 최적화다. 

 

 

단계 2. 함수 선언문

 

◾️ 함수는 변수와 마찬가지로 이다. 

◾️ 다만 함수 선언문(function declaration)으로 선언한 함수는 일반 변수와는 달리 바로 초기화 된다는 점에서 차이가 있다.

◾️ 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다. ( 선언되기 전에도 함수를 사용 할 수 있는 이유 )

함수를 추가했을 때 전역 렉시컬 환경의 초기 상태

◾️ 이러한 동작 방식은 함수를 변수에 할당한 함수 표현식(Function Expression)에는 해당하지 않는다. 

 

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

 

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

함수 say("John")을 호출했을 때 내부 변화(현재 실행 흐름은 붉은색 화살표로 나타낸 줄에 멈춰있는 상황)

◾️ 함수가 호출 중인 동안은 호출 중인 함수를 위한 내부 렉시컬 환경내부 렉시컬 환경이 가리키는 외부(전역) 렉시컬 환경 두 개를 갖게 된다. 

  • 내부 렉시컬 환경은 현재 실행 중인 함수인 say에 상응한다. 내부 렉시컬 환경엔 함수의 인자인 name으로 부터 온 프로퍼티가 있다. 
  • 외부 렉시컬 환경은 전역 렉시컬 환경이다. 전역 렉시컬 환경은 phrase와 함수say를 프로퍼티로 갖는다. 

◾️ 내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다. 

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

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

◾️ 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복된다. 

 

변수 검색 진행 과정

  1. 함수 say 내부의 alert에서 변수 name에 접근할 땐, 먼저 내부 렉시컬 환경을 살펴본다. (변수 name을 찾음)
  2. alert에서 phrase에 접근하려는데, 내부 렉시컬 환경에 없기 때문에 검색 범위는 외부 렉시컬 환경으로 확장된다. (외부 렉시컬 환경에서 phrase를 찾음)

(

단계 4. 반환 함수

function makeCounter() {
  let count = 0;

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

let counter = makeCounter();

◾️ makeCounter()를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어진다. 그리고 이 렉시컬 환경 객체에는 makeCounter를 실행하는데 필요한 변수들이 저장된다. 

◾️ makeCounter()가 실행되는 도중에 한줄짜리 본문을 가진 중첩 함수가 만들어진다. (중첩함수가 생성되기만 하고 실행은 되지 않은 상태)

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

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

 

◾️ counter.[[Environment]]엔 {count:0}이 있는 렉시컬 환경에 대한 참조가 저장된다. 
    → 호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있다. 

      [[Environment]]는 함수가 생성될 때 딱 한 번 그 값이 세팅되고, 영원히 변하지 않는다. 

◾️ counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 만들어진다. 그리고 이 렉시컬 환경은 counter.[[Environment]]에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조하게 된다. 

◾️ 실행 흐름이 중첩 함수의 본문으로 넘어오면 count 변수가 필요한데, 먼저 자체 렉시컬 환경에서 변수를 찾는다. 익명 중첩 함수엔 지역 변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황이다. (<empty>)

◾️ counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾는다. 

◾️ 변수값 갱신은 변수가 저장된 렉시컬 환경에서 이루어진다. 

실행이 종료된 후의 상태

 

⚠️ 클로저(closure)

◾️ 클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다. 
◾️ 자바스크립트에선 모든 함수가 자연스럽게 클로저가 된다. (예외 new Function 문법)
◾️ 자바스크립트의 함수는 숨김 프로퍼티인 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억한다. 
◾️ 함수 내부의 코드는 [[Environment]]를 사용해 외부 변수에 접근한다. 

 

 



📝 오늘 새롭게 배운 내용 📝

 

✅ 변수 let은 {} 블록 범위이다. 

✅ 렉시컬 환경!!! 에는 지역 변수의 내용을 저장하고 있다. 

✅ 변수의 검색 범위는 점점 넓혀간다. 내부에서 외부렉시컬 환경으로..

✅ 모든 함수는 자기의 고향을 항상 기억하고 있다. 

✅ 클로저 !!!! 아직도 이해가 잘 안됨 . . . .  내 안에 있는 변수가 아닌 외부 변수를 기억하고 있고...? 기억 하고 있으니깐 외부 변수에 접근을 할 수 있는 그런 함수..? 그게 가능한 이유는 함수의 숨김 프로퍼티가 자기가 어디서 만들어졌는지를 기억하기 때문...? 

 

 

📌 [JAVASCRIPT_INFO 변수의 스코프]

 

변수의 스코프

 

ko.javascript.info

 

📌 [MDN 클로저]

 

클로저

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

developer.mozilla.org

 

 

 

 

 

 

 

'모던 자바스크립트' 카테고리의 다른 글

6.5 전역 객체  (0) 2020.08.22
6.4 오래된 'var'  (0) 2020.08.21
6.2 나머지 매개변수와 전개 문법  (0) 2020.08.19
6.1 재귀와 스택  (0) 2020.08.15
4.8 객체를 원시형으로 변환하기  (0) 2020.08.13