Chapter 05 - norux/javascript_study GitHub Wiki

5. 실행 컨텍스트와 클로저

  • 실행 컨텍스트(Execution Context)의 개념
  • 활성 객체(Activation Object)와 변수 객체(Variable Object)
  • 스코프 체인(Scope Chain)
  • 클로저(Closure)

5.1 실행 컨텍스트 개념

  • C언어의 함수 콜스택과 유사한 개념
  • 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념 또는 실행 가능한 자바스크립트 코드 블록이 실행되는 환경

실행 컨텍스트가 형성되는 세가지 경우

  1. 전역 코드
  2. eval() 함수로 실행되는 코드
  3. 함수안의 코드를 실행할 경우

실행 컨텍스트의 정의

현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행 코드가 실행되면, 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다.

console.log("Global Context");

function ExContext1() {
  console.log("EC1");
};

function ExContext2() {
  ExContext1();
  console.log("EC2");
};

ExContext2();

Question: 실행 컨텍스트의 생성과정(EC의 스택) 그림으로 그리기!!

5.2 실행 컨텍스트 생성 과정

  • 활성 객체와 변수 객체
  • 스코프 체인
function execute(param1, param2) {
  var a = 1, b = 2;

  function func() {
    return a + b;
  }

  return param1 + param2 + func();
}

execute(3, 4);

Question: 실행 컨텍스트를 그려보자!!

5.2.1 활성 객체 생성

활성 객체: EC가 생성될때, 해당 컨텍스트에서 실행에 필요한 여러가지 정보를 담은 객체

5.2.2 arguments 객체 생성

5.2.3 스코프 정보 생성

[ [scope]] 프로퍼티로 참조되며, 스코프 정보는 리스트 형태를 띄고 있다.

5.2.4 변수 생성

EC 내부에서 사용되는 지역변수가 생성된다.

  • 참고: 활성객체와 변수객체는 같은 객체이다.

변수의 생성초기화 는 분리되어 이루어진다. 여기서는 생성 만 이루어지므로, 지역 변수 a, b에는 undefined가 할당된다. func도 생성만 된다. 초기화는 표현식이 실행되기 전까지는 이루어지지 않는다.

5.2.5 this 바인딩

마지막 단계로 this 바인딩이 이루어진다.

5.2.6 코드 실행

EC가 생성되고 변수 객체가 만들어진 후에 코드에 있는 표현식들의 실행이 이루어 진다. 이 과정에서 초기화 과정이 일어난다.

  • 참고: 전역 EC는 일반 EC와 약간 다르다. arguments 객체가 없고, 전역객체 하나만을 포함하는 스코프 체인이 있다.

Node.js에서는 최상위 코드가 전역코드가 아니다.

//브라우저 var a = 10; b = 15; console.log(window.a); //10 console.log(window.b); //15 // //Node.js var a = 10; b = 15; console.log(global.a); //undefined console.log(global.b); //15


## 5.3 스코프 체인
C언어는 for, if문에서의 {} 블록 역시 스코프 유효범위에 들어가지만, 자바스크립트는 오직 **함수만** 유효범위의 단위가 된다.

[ [scope]] 프로퍼티로 각 함수 객체내에서 연결 리스트로 관리되며, 이를 스코프 체인이라 한다.

|    | 스코프 체인 |
|:--:|:--------:|
| 3  | ....     |
| 2  | 변수객체 2 |
| 1  | 변수객체 1 |
| 0  | 변수객체 0 |
[스코프체인 리스트]

각 함수는 [ [scope]] 프로퍼티로 자신이 생성된 EC의 스코프 체인을 참조한다.

### 5.3.1 전역 실행 컨텍스트의 스코프 체인
```javascript
var var1 = 1;
var var2 = 2;

Question: 전역 실행 컨텍스트 그림 그려보기

5.3.2 함수를 호출한 경우 생성되는 실행 컨텍스트의 스코프 체인

var var1 = 1;
var var2 = 2;
function func(){
  var var1 = 10;
  var var2 = 20;
}

func();

Question: 실행 컨텍스트 그림 그려보기

스코프 체인 정리

  • 각 함수 객체는 [ [scope]]프로퍼티로 현재 컨텍스트의 스코프 체인을 참조한다.
  • 새로운 EC가 생성되면, 현재 실행되는 함수 객체의 [ [scope]]프로퍼티를 복사하고 새롭게 생성된 변수객체를 해당 체인 제일 앞에 추가한다.
  • 스코프 체인 = 현재 실행 컨텍스트의 변수객체 + 상위 컨텍스트의 스코프 체인
var value = 10;

function printValue() {
  return value;
}
function printFunc(func) {
  var value = 100;
  console.log(func());
}
printFunc(printValue);

Question: 위 함수의 결과는 어떻게 될까? 실행 컨텍스트 그림 그려보기

hint. 각 함수가 실행될 때의 실행 컨텍스트가 무엇인지를 생각해야 한다.

with 키워드

자바스크립트에는 스코프체인을 사용자가 임의로 수정할 수 있는 with라는 키워드가 있다.

with는 eval과 함께, 사용하지 말아야 할 키워드이다. "Pure javascript evil.."

with 키워드는 특정 객체를 [ [scope]]리스트 최상단에 올리는 역할을 한다.

.

호이스팅

함수의 생성과정을 이해했다면, 함수의 호이스팅을 이해할 수 있을 것이다.

foo(); //TypeError bar(); //실행됨 var foo = function() { ... } function bar() { ... } var x = 1;



## 5.4 클로저

```javascript
function outerFunc(){
  var x = 10;
  var innerFunc() = function (){
    console.log(x);
  }
  return innerFunc;
}

var inner = outerFunc();
inner();   // 10

Question: 그림 그려보기

inner()가 실행될 때, 이미 outerFunc의 실행 컨텍스트는 종료된 후이다. 하지만, innerFunc의 실행 컨텍스트의 스코프체인에는 outerFunc의 변수객체는 남아있게 된다. 이것이 클로저라는 개념이다.

  • 클로저: 이미 생명 주기가 끝난 외부함수의 변수를 참조하는 함수
  • 자유변수: 클로저로 참조되는 외부변수(즉, 위 예제에서는 x)
function outerFunc(arg1, arg2) {
  var local = 8;
  function innerFunc(innerArg){
    console.log((arg1 + arg2)/innerArg + local));
  }
  return innerFunc;
}
var exam1 = outerFunc(2, 4);
exam1(2);
}

Question: 위 결과 예측해보기

5.4.2 클로저의 활용

성능, 자원적인 면에서 약간 손해를 볼 수 있으므로 무차별적으로 사용해서는 안된다.

함수형 프로그래밍에서 많이 활용된다. 7장에서 자세한 설명을 한다.

5.4.2.1 특정 함수에 사용자가 정의한 객체의 메서드 연결하기

function HelloFunc( func ) {
  this.greeting = "hello";
}

//HelloFunc 생성자 함수에 call 메서드 동적 생성
HelloFunc.prototype.call = function( func ) {
  func ? func(this.greeting) : this.func(this.greeting);
}

function saySomething( obj, methodName, name ) {
  return ( function( greeting ) {
    return obj[methodName]( greeting, name );
  } );
}

// 생성자함수 newObj()
function newObj( obj, name ) {
  obj.func = saySomething( this, "who", name );
  return obj;
}

// who 메서드 동적 생성
newObj.prototype.who = function( greeting, name ) {
  console.log( greeting + " " + name || "everyone" ) );
}

var objHello = new HelloFunc();

var obj1 = new newObj( objHello, "zzoon" );
obj1.call();

//출력결과
//hello zzoon

5.4.2.2 함수의 캡슐화

I am XXX, I live in XXX, I'm XX years old

  • XXX에는 사용자의 입력을 받아 위 문장을 만들기 위해서, 배열을 이용하여 만들 수 있다.
var bufAry = [
  "I am ",
  "",
  ", I live in ",
  "",
  ", I'm ",
  "",
  " years old"
];
//이후 bufAry[1], bufAry[3], bufAry[5]에 사용자의 인풋을 넣음

위와 같은 코드는 bufAry가 전역변수이기 때문에, 외부에 노출되어 있다는 단점이 있다. 이 코드를 클로저를 활용하여 바꾸면 다음과 같다.

var getCompletedString = ( function() {
  var bufAry = [
    "I am ",
    "",
    ", I live in ",
    "",
    ", I'm ",
    "",
    " years old"
  ];

  return ( function( name, city, age) {
    bufAry[1] = name;
    bufAry[3] = city;
    bufAry[5] = age;
  } );
} )();

var str = getCompletedString( "heebum", "seoul", 19 );

Question: 실행 컨텍스트 그림 그려보기

5.4.2.3 setTimeout()에 지정되는 함수의 사용자 정의

setTimeout()함수: 함수를 인자로 넘겨주어 실행한다. 하지만, 함수에 인자를 함께 넘겨주지는 못한다. 인자를 넘겨주기 위해 클로저 함수를 이용한다.

function callLater( obj, a, b) {
  return (function(){
    obj["sum"] = a + b;
  });

var sumObj = {
  sum : 0
}

var func = callLater( sumObj, 1, 2 );
setTimeout( func, 500);  //0.5초 후 func() 함수 (클로저) 실행

5.4.3 클로저를 활용할 때, 주의사항

5.4.3.1 클로저의 프로퍼티 값은 쓰기 가능하므로, 그 값이 여러번 호출 될 때 항상 변할수 있음에 유의해야 한다.

5.4.3.2 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있는 경우도 있다.

function func() {
  var x = 1;
  return {
    func1 : function(){ console.log( ++x ); }
    func2 : function(){ console.log( ~x ); }
  };
}

var exam = func();
exam.func1();
exam.func2();

5.4.3.3 루프 안에서 클로저를 활용할 때는 주의하라

function countSeconds( howMany ) {
  for( var i = 1; i <= howMany; i++ ) {
    setTimeout( function() { console.log( i ); }, i * 1000 );
  }
}

countSeconds( 3 );

위의 예제는 1, 2, 3 을 1초 간격으로 출력하는 예제지만, 4, 4, 4 가 1초 간격으로 출력된다. setTimeout의 인자로 들어가는 익명함수는 자유변수 i를 참조하는데, 이 함수가 실행되는 시점은 countSeconds() 함수의 실행이 종료된 이후 이기 때문에 자유변수 i는 이미 4가 된 상태인 것이다.

HB's Guess: 비동기 프로그래밍으로 응용 할 수 있을 것 같음