렉시컬 스코핑, 실행 컨텍스트와 클로저

|

렉시컬 스코핑(lexical scoping)

  1. 스코프는 함수를 호출할 때가 아니라 선언할 때 생김
var name = 'zero';
function log() {
  console.log(name);
}

function wrapper() {
  var name = 'nero';
  log();
}
wrapper(); // zero

함수를 처음 선언하는 순간, 함수 내부의 변수는 자기 스코프로부터 가장 가까운 곳(상위 범위에서)에 있는 변수를 계속 참조하게 됩니다. 위의 예시에서는 log 함수 안의 name 변수는 선언 시 가장 가까운 전역변수 name을 참조하게 됩니다. 그래서 wrapper 안에서 log를 호출해도 지역변수 name='nero'를 참조하는 게 아니라 그대로 전역변수 name의 값인 zero가 나오는 겁니다.

 

실행 컨텍스트(문맥; 코드의 실행환경)를 이해하기 위한 자바스크립트 동작과정

  1. 변수, 함수 선언, arguments 을 가진 활성 객체(Variable Object) 생성
  2. Scope Chain 생성 및 초기화
    • 변수 초기화 : 변수 값에 undefined 할당
  3. this 바인딩
  4. 코드 해석 및 실행
    • 변수 값 할당 : 변수에 실제 값 할당
var name = 'zero'; // (1)변수 선언 (6)변수 대입
function wow(word) { // (2)변수 선언 (3)변수 대입
  console.log(word + ' ' + name); // (11) 
}
function say () { // (4)변수 선언 (5)변수 대입
  var name = 'nero'; // (8)
  console.log(name); // (9) 
  wow('hello'); // (10) // hello zero
}
say(); // (7) // nero
'전역 컨텍스트': { // 1. 모든 것을 관리하는 환경, 페이지가 종료될 때까지 유지됨.
  // 2. 함수를 호출할때마다 함수 컨텍스트가 생김
  변수객체: { // 3. 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성
    arguments: null, // 함수의 인자
    variable: ['name', 'wow', 'say'], // 해당 스코프의 변수들
  },
  scopeChain: ['전역 변수객체'], // 자신과 상위 스코프들의 변수객체
  this: window,
}

// 4. 컨텍스트 생성 후 함수가 실행되는데, 사용되는 변수들은 변수 객체 안에서 값을 찾고, 없다면 스코프 체인을 따라 올라가며 찾습니다.
// 5. 함수 실행이 마무리되면 해당 컨텍스트는 사라집니다.(클로저 제외) 페이지가 종료되면 전역 컨텍스트가 사라집니다.

variable: [{ name: 'zero' }, { wow: Function }, { say: Function }]
//  wow랑 say는 호이스팅 때문에 선언과 동시에 대입
//  그 후 variable의 name에 'zero'가 대입
// (7)번에서 say();를 하는 순간 새로운 컨텍스트인 say 함수 컨텍스트가 생김
'say 컨텍스트': {
  변수객체: {
    arguments: null,
    variable: ['name'], // 초기화 후 [{ name: 'nero' }]가 됨
  },
  scopeChain: ['say 변수객체', '전역 변수객체'],
  this: window,
}
/* say를 호출한 후 위에서부터 차례대로((8)~(10) 실행하는데요.
variable의 name에 nero를 대입해주고 나서 console.log(name);이 있습니다.
name 변수는 say 컨텍스트 안에서 찾으면 됩니다. variable에 name이 nero라고 되어 있네요.
name이 콘솔에 찍힙니다. 그 다음엔 wow('hello')가 있는데요.
say 컨텍스트 안에서 wow 변수를 찾을 수 없습니다.
찾을 수 없다면 scope chain을 따라 올라가 상위 변수객체에서 찾습니다.
그래서 전역 변수객체에서 찾습니다. 전역 변수객체의 variable에 wow라는 함수가 있네요.
이걸 호출합니다.

(10)번에서 wow함수가 호출되었으니 wow 컨텍스트도 생기겠죠?
arguments는 word = 'hello'고, scope chain은 wow 스코프와 전역 스코프입니다.
여기서 중요한 게 lexical scoping에 따라 wow 함수의 스코프 체인은 선언 시에 이미 정해져 있습니다.
따라서 say 스코프는 wow 컨텍스트의 scope chain이 아닙니다.
variable은 없고, this는 window입니다.
*/

'wow 컨텍스트': {
  변수객체: {
    arguments: [{ word : 'hello' }],
    variable: null,
  },
  scopeChain: ['wow 변수객체', '전역 변수객체'],
  this: window,
}

/*
이제 컨텍스트가 생긴 후 함수가 실행 됩니다. say 함수는 아직 종료된 게 아닙니다.
wow 함수 안에서 console.log(word + ' ' + name);이 있는데요.
word랑 name 변수는 wow 컨텍스트에서 찾으시면 됩니다.
word는 arguments에서 찾을 수 있고, name은 wow 변수객체에는 값이 없으니,
scope chain을 따라 전역 스코프에서 찾으시면 됩니다.
전역 변수객체로 올라가니 variable에 name이 zero라고 되어 있네요.
그래서 hello zero가 되는 겁니다. hello nero가 아니라요.
wow 컨텍스트에 따르면 wow 함수는 애초에 say 컨텍스트와 일절 관련이 없었던 겁니다.

이제 wow 함수 종료 후 wow 컨텍스트가 사라지고, say 함수의 실행이 마무리됩니다.
따라서 say 컨텍스트도 사라지고, 마지막에 전역 컨텍스트도 사라집니다.
*/

변수 초기화 과정

  1. 변수 선언 - 변수를 활성 객체에 할당
  2. 변수 초기화 - 변수 값에 undefined 할당
  3. 변수 실제 값 할당 - 변수에 실제 값을 할당

클로저

실행이 끝난 함수의 스코프를 참조할 수 있는 함수

function parent() {
  var a = 'Parent is done';
  function child() {
    console.log(a);
  }
  return child;
}
var closure = parent();
closure();

위 내부함수의 정의대로라면 parent 의 내부함수인 child() 는 외부에서 접근이 불가능하다. 하지만 return 값에 child 를 넘김으로써 외부에서도 child 를 호출할 수 있게 된다. 따라서, child() 에서 parent 의 값을 참고하고 있다면, child() 를 밖에서 호출함으로써 parent() 의 변수에 접근이 가능하게 된다. 이것이 클로져

And