JavaScript
 Hoisting

             안오균
먼저,
지역 변수의 유효 범위는 블럭이 아닌 함수!

 function foo() {
   var bar = ‘bar’;
   console.log(bar); //--> ‘bar’
   if (true) {
     var bar = ‘baz’;
     console.log(bar); //--> ‘baz’
   }
   console.log(bar); //--> ‘baz’
 }
즉시 실행 함수로 유효 범위를 제한할 수 있음.

 function foo() {
   var bar = ‘bar’;
   console.log(bar); //--> ‘bar’
   if (true) {
     (function () {
       var bar = ‘baz’;
       console.log(bar); //--> ‘baz’
     }());
   }
   console.log(bar); //--> ‘bar’
 }
호이스팅(Hoisting)

 변수 선언과 함수 선언은 인터프리터에 의해
 함수의 맨 앞으로 끌어올려진다(옮겨진다).

 * hoist = 끌어올리다
용어가 좀 헷갈릴 수 있다.

변수 선언(Variable Declaration)
 var foo = ‘bar’;


함수 선언(Function Declaration)
 function foo() {}


함수 표현식(Function Expression)
 var foo = function () {};
 (function foo() {});
변수 선언인 경우

 function foo() {
   console.log(bar); //--> undefined
   var bar = 'baz';
   console.log(bar); //--> 'baz'
 }

아래와 동일
 function foo() {
   var bar;
   // 변수 선언은 함수의 맨 앞으로 끌어올려진다.(hoisted)
   console.log(bar);
   bar = 'baz'; // 할당은 실행 시점에 수행
   console.log(bar);
 }
for 문의 경우도 동일

 function foo(arr) {
   for (var i = 0, len = arr.length; i < len; i++) {
     console.log(arr[i]);
   }
 }

아래와 동일
 function foo(arr) {
   var i, len;
   for (i = 0, len = arr.length; i < len; i++) {
     console.log(arr[i]);
   }
 }
함수 정의인 경우

 function foo() {
   bar(); //--> 'bar'
   function bar() {
     console.log('bar');
   }
 }

아래와 동일
 function foo() {
   function bar() {
     console.log('bar');
   }
   bar();
 }
함수 표현식인 경우 (변수 선언과 동일)

 function foo() {
   bar(); //--> ReferenceError
   var bar = function () {};
   bar(); // 호출되지 않음
 }

아래와 동일
 function foo() {
   var bar;
   bar();
   bar = function () {};
   bar();
 }
변수 선언은 호이스팅으로 맨 앞으로,
변수 할당은 실행시점에 수행됨.

호이스팅으로 헷갈릴 수 있으니,
함수의 맨 앞 부분에 한 개의 var만 쓸 것을 권장.

 function foo() {
   var a = 1,
     b = 2,
     c = 3;
 }
그치만, 눈을 크게 떠야할 때가 있음.

 function foo() {
   var a = 1,
     b = 3,
     c = 4;
     d = 2;
 }




오류 없이 실행되며, 변수 d는 전역으로 선언됨.
린트 툴을 사용할 것을 권장.
디버깅이 어려운 경우도 있음.

 function foo() {
   var a = 1,
     b = getComplexValue(a), // 브레이크 포인트를 잡기 어려움
     c = b + 3;
 }


상황에 따라 var로 나누는 것도 나쁘지 않은 선택.

 function foo() {
   var a = 1;
   var b = getComplexValue(a);
   var c = b + 3;
 }
또한, 분기문 내에 함수 정의를 넣지 말 것.

 function foo(condition) {
   if (condition) {
     function bar() {
       console.log('first bar');
     }
   } else {
     function bar() {
       console.log('second bar');
     }
   }
   bar();
 }
 foo(true); //--> 'second bar'
 foo(false); //--> 'second bar'
함수 정의 호이스팅에 따라 아래와 같기 때문.

 function foo(condition) {
   function bar() {
     console.log('first bar');
   }
   function bar() {
     console.log('second bar');
   }
   if (condition) {
   } else {
   }
   bar();
 }

* ES5 strict 모드에서는 SyntaxError 발생
상황에 따라 동적으로 할당하고 싶다면.
 function foo(condition) {
   var bar;
   if (condition) {
     bar = function () {
        console.log('first bar');
     };
   } else {
     bar = function () {
        console.log('second bar');
     };
   }
   bar();
 }
 foo(true); //--> 'first bar'
 foo(false); //--> 'second bar'
개인적으로는, var는 맨 위에,
함수는 읽어내려가기 편하게 작성하는 것을 선호함.

function foo() {
  var a, b, c;

    doAll();

    function doAll() {
      doA();
      doB();
    }
    function doA() {}
    function doB() {}
}
하지만 리턴문 뒤의 함수 정의는,
문맥 상 그다지 좋은 것 같지 않음.

 function foo() {
   return bar();

     function bar() {
       return ‘baz’;
     }
 }

 * 함수 정의 호이스팅으로 문제 없이 실행됨.
끝.
부록. 좀 더 자세히 알아보면.

 function test(a, b) {
   console.log(a, b, c, d);
   var c = 10;
   function d() {}
 }
 test(10);


위와 같은 함수가 전역 범위에서 실행될 때.
다음과 같은 식으로 실행됨.

1.    foo()가 호출되면,
2.    foo()의 실행 컨텍스트가 생성되고,
3.    실행 컨텍스트는 함수를 실행하기 전 활성화 객체를 생성한다.
4.    활성화 객체에는 파라미터 정보, 변수 선언, 함수 정의가 포함된다.
5.    함수가 실행되고 변수를 만났을 때엔,
     유효범위 체인에 따라 활성화 객체에서 먼저 찾고, 상위 탐색을 진행한다.
이 때, 활성화 객체에는 다음 값들이 할당된다.

 - 파라미터로 전달된 값
   파라미터의 이름과 값을 할당
   없는 경우 undefined로 할당

 - 모든 함수 선언
   함수의 이름과 정의를 할당
   이미 정의되어 있는 경우 덮어씀

 - 모든 변수 선언
   변수의 이름과 undefined를 할당
   이미 정의되어 있는 경우 할당하지 않음
즉, 아래 코드가 실행되기 전,
 function test(a, b) {
   console.log(a, b, c, d);
   var c = 10;
   function d() {}
 }
 test(10);

활성화 객체(Activation Object)는 다음과 같이 생성된다.
 AO = {
    a: 10,
    b: undefined,
    c: undefined,
    d: <reference to function ‘d’>
 };
실행 시점에서 변수를 만났을 때엔,
활성화 객체의 값을 찾거나 설정한다.

 function test(a, b) {
  console.log(a, b, c, d);
  // 각각 AO[‘a’], AO[‘b’], AO[‘c’], AO[‘d’]의 값
  // 10, undefined, undefined, function
  var c = 10; // AO[‘c’] = 10; 과 동일하다.
  function d() {}
 }
 test(10);
함수 표현식은 활성화 객체에 할당되지 않는다.

 function test() {
   var foo = function bar() {};
   // foo는 AO에 존재하지만, bar는 존재하지 않는다.
   // bar의 접근을 시도하면 ReferenceError가 발생한다.
   (function baz() {});
   // 마찬가지로 baz도 AO에 할당되지 않는다.
 }
호이스팅은 활성화 객체의 할당 방식에 따른 현상.

 function foo() {
   console.log(bar); //--> function ‘bar’
   // AO[‘bar’] = <reference to function ‘bar’>
   var bar = 10;
   function bar() {};
   var bar = 20;
 }
정말 끝.

Javascript hoisting

  • 1.
  • 2.
    먼저, 지역 변수의 유효범위는 블럭이 아닌 함수! function foo() { var bar = ‘bar’; console.log(bar); //--> ‘bar’ if (true) { var bar = ‘baz’; console.log(bar); //--> ‘baz’ } console.log(bar); //--> ‘baz’ }
  • 3.
    즉시 실행 함수로유효 범위를 제한할 수 있음. function foo() { var bar = ‘bar’; console.log(bar); //--> ‘bar’ if (true) { (function () { var bar = ‘baz’; console.log(bar); //--> ‘baz’ }()); } console.log(bar); //--> ‘bar’ }
  • 4.
    호이스팅(Hoisting) 변수 선언과함수 선언은 인터프리터에 의해 함수의 맨 앞으로 끌어올려진다(옮겨진다). * hoist = 끌어올리다
  • 5.
    용어가 좀 헷갈릴수 있다. 변수 선언(Variable Declaration) var foo = ‘bar’; 함수 선언(Function Declaration) function foo() {} 함수 표현식(Function Expression) var foo = function () {}; (function foo() {});
  • 6.
    변수 선언인 경우 function foo() { console.log(bar); //--> undefined var bar = 'baz'; console.log(bar); //--> 'baz' } 아래와 동일 function foo() { var bar; // 변수 선언은 함수의 맨 앞으로 끌어올려진다.(hoisted) console.log(bar); bar = 'baz'; // 할당은 실행 시점에 수행 console.log(bar); }
  • 7.
    for 문의 경우도동일 function foo(arr) { for (var i = 0, len = arr.length; i < len; i++) { console.log(arr[i]); } } 아래와 동일 function foo(arr) { var i, len; for (i = 0, len = arr.length; i < len; i++) { console.log(arr[i]); } }
  • 8.
    함수 정의인 경우 function foo() { bar(); //--> 'bar' function bar() { console.log('bar'); } } 아래와 동일 function foo() { function bar() { console.log('bar'); } bar(); }
  • 9.
    함수 표현식인 경우(변수 선언과 동일) function foo() { bar(); //--> ReferenceError var bar = function () {}; bar(); // 호출되지 않음 } 아래와 동일 function foo() { var bar; bar(); bar = function () {}; bar(); }
  • 10.
    변수 선언은 호이스팅으로맨 앞으로, 변수 할당은 실행시점에 수행됨. 호이스팅으로 헷갈릴 수 있으니, 함수의 맨 앞 부분에 한 개의 var만 쓸 것을 권장. function foo() { var a = 1, b = 2, c = 3; }
  • 11.
    그치만, 눈을 크게떠야할 때가 있음. function foo() { var a = 1, b = 3, c = 4; d = 2; } 오류 없이 실행되며, 변수 d는 전역으로 선언됨. 린트 툴을 사용할 것을 권장.
  • 12.
    디버깅이 어려운 경우도있음. function foo() { var a = 1, b = getComplexValue(a), // 브레이크 포인트를 잡기 어려움 c = b + 3; } 상황에 따라 var로 나누는 것도 나쁘지 않은 선택. function foo() { var a = 1; var b = getComplexValue(a); var c = b + 3; }
  • 13.
    또한, 분기문 내에함수 정의를 넣지 말 것. function foo(condition) { if (condition) { function bar() { console.log('first bar'); } } else { function bar() { console.log('second bar'); } } bar(); } foo(true); //--> 'second bar' foo(false); //--> 'second bar'
  • 14.
    함수 정의 호이스팅에따라 아래와 같기 때문. function foo(condition) { function bar() { console.log('first bar'); } function bar() { console.log('second bar'); } if (condition) { } else { } bar(); } * ES5 strict 모드에서는 SyntaxError 발생
  • 15.
    상황에 따라 동적으로할당하고 싶다면. function foo(condition) { var bar; if (condition) { bar = function () { console.log('first bar'); }; } else { bar = function () { console.log('second bar'); }; } bar(); } foo(true); //--> 'first bar' foo(false); //--> 'second bar'
  • 16.
    개인적으로는, var는 맨위에, 함수는 읽어내려가기 편하게 작성하는 것을 선호함. function foo() { var a, b, c; doAll(); function doAll() { doA(); doB(); } function doA() {} function doB() {} }
  • 17.
    하지만 리턴문 뒤의함수 정의는, 문맥 상 그다지 좋은 것 같지 않음. function foo() { return bar(); function bar() { return ‘baz’; } } * 함수 정의 호이스팅으로 문제 없이 실행됨.
  • 18.
  • 19.
    부록. 좀 더자세히 알아보면. function test(a, b) { console.log(a, b, c, d); var c = 10; function d() {} } test(10); 위와 같은 함수가 전역 범위에서 실행될 때.
  • 20.
    다음과 같은 식으로실행됨. 1. foo()가 호출되면, 2. foo()의 실행 컨텍스트가 생성되고, 3. 실행 컨텍스트는 함수를 실행하기 전 활성화 객체를 생성한다. 4. 활성화 객체에는 파라미터 정보, 변수 선언, 함수 정의가 포함된다. 5. 함수가 실행되고 변수를 만났을 때엔, 유효범위 체인에 따라 활성화 객체에서 먼저 찾고, 상위 탐색을 진행한다.
  • 21.
    이 때, 활성화객체에는 다음 값들이 할당된다. - 파라미터로 전달된 값 파라미터의 이름과 값을 할당 없는 경우 undefined로 할당 - 모든 함수 선언 함수의 이름과 정의를 할당 이미 정의되어 있는 경우 덮어씀 - 모든 변수 선언 변수의 이름과 undefined를 할당 이미 정의되어 있는 경우 할당하지 않음
  • 22.
    즉, 아래 코드가실행되기 전, function test(a, b) { console.log(a, b, c, d); var c = 10; function d() {} } test(10); 활성화 객체(Activation Object)는 다음과 같이 생성된다. AO = { a: 10, b: undefined, c: undefined, d: <reference to function ‘d’> };
  • 23.
    실행 시점에서 변수를만났을 때엔, 활성화 객체의 값을 찾거나 설정한다. function test(a, b) { console.log(a, b, c, d); // 각각 AO[‘a’], AO[‘b’], AO[‘c’], AO[‘d’]의 값 // 10, undefined, undefined, function var c = 10; // AO[‘c’] = 10; 과 동일하다. function d() {} } test(10);
  • 24.
    함수 표현식은 활성화객체에 할당되지 않는다. function test() { var foo = function bar() {}; // foo는 AO에 존재하지만, bar는 존재하지 않는다. // bar의 접근을 시도하면 ReferenceError가 발생한다. (function baz() {}); // 마찬가지로 baz도 AO에 할당되지 않는다. }
  • 25.
    호이스팅은 활성화 객체의할당 방식에 따른 현상. function foo() { console.log(bar); //--> function ‘bar’ // AO[‘bar’] = <reference to function ‘bar’> var bar = 10; function bar() {}; var bar = 20; }
  • 26.