본문 바로가기
IT/JavaScript

자바스크립트 핵심 가이드 - (함수 2탄 Scope, Closoure)

by 최고영회 2013. 12. 23.
728x90
반응형
SMALL

9. 유효범위 (scope)

프로그래밍 언어에서 유효범위는 변수와 매개변수의 접근성과 생존 기간을 제어한다. 유효범위는 이름들이 충돌하는 문제를 덜어주고 

자동으로 메모리를 관리하기 때문에 매우 중요한 개념이다.

var foo = function ( ) {

      var a = 3, b = 5;

      var bar = function ( ) {

          var b = 7, c = 11;

          console.log('2) a : ' + a + ', b : ' + b + ', c : ' + c);

          // 이 시점에서 a 는 3, b 는 7, c 는 11 임

          a += b + c;

          console.log('3) a : ' + a + ', b : ' + b + ', c : ' + c);

          // 이 시점에서 a 는 21, b는 7, c 는 11 임

      };

      console.log('1) a : ' + a + ', b : ' + b + ', c : ' + ('undefined' || c));   

      // 이 시점에서 a 는 3, b 는 5, c 는 정의되지 않음.

      bar();

      console.log('4) a : ' + a + ', b : ' + b + ', c : ' + ('undefined' || c));   

     // 이 시점에서 a 는 21, b 는 5, c 는 정의되지 않음.

};

c 언어 유형의 구문을 가진 모든 언어는 블록(중괄호로 묶인 문장들의 집합) 유효범위가 있다.

자바스크립트의 블록 구문은 마치 블록 유효범위를 지원하는 것처럼 보이지만 불행히도 블록 유효범위가 없다.

이러한 혼란은 오류의 원인이 될 여지가 충분하다. 

자바스크립트는 함수 유효범위가 있다. 즉 함수 내에서 정의된 매개변수와 변수는 함수 외부에서는 유효하지 않다.

반면에 이렇게 내부에서 정의된 변수는 함수 어느 곳에서도 접근 가능 하다.

 

오늘날 대부분의 언어에서는 변수를 가능한 늦게, 즉 처음 사용하기 바로 전에 선언해서 사용할 것을 권하고 있다.

하지만 자바스크립트에서는 블록 유효범위를 지원하지 않기 때문에 이러한 권고가 적용되지 않는다.

대신에 자바스크립트에서는 함수에서 사용하는 모든 변수를 함수 첫 부분에서 선언하는 것이 최선의 방법이다.

 

아래 결과를 정확히 이해 할 수 있어야 한다.

 var scope = "global";

 function f() {

        console.log(scope);     // Prints "undefined", not "global"

        var scope = "local";      // Variable initialized here, but defined everywhere

        console.log(scope);     // Prints "local"

}

f ( ) 의 첫번째 라인에서 'global' 이 찍힐 것 같은가? 틀렸다. undefined 가 찍힌다.

이유인 지그 javascript 에서 변수의 scope 는 함수로 blocking 되고 두번째 줄에 변수를 선언과 초기화를 했지만 실제로는 해당 함수(f) 의 가장 위로 올라가서 선언되게 되는 것이다. 

때문에 var scope; 가 맨 위로 올라가게 되고 그 다음줄에서 console.log(scope) 하기 때문에 undefined 가 찍히게 되는 것이다. 즉 위의 내용은 아래와 같이 해석되는 것이다.

 var scope = "global";

 function f() {

        var scope;                   // Variable defined

        console.log(scope);     // Prints "undefined"

        scope = "local";           // Variable initialized here, but defined everywhere

        console.log(scope);     // Prints "local"

}

 

유효 범위의 변경

apply() 메서드와 call() 메서드를 이용하면 유효 범위를 변경할 수 있습니다.

apply() 메서드

apply() 메서드는 자신이 원하는 유효 범위로 변경할 때 사용합니다. 메서드의 인수는 배열로 지정합니다. 다음은 apply() 메서드를 사용하는 예제입니다.

function callName(a,b){
    return this.name(a,b);
}

var Car = {
    name : function(a,b){ return “kia” +a+ b;}
};

var Car2 = {
    ame : function(a,b){return “deawoo” +a+ b;}
}

callName.apply(Car,[1,2]);    // kia12
callName.apply(Car2,[1,2]);    // deawoo12

callName.apply(Car,[1,2]);과 callName.apply(Car2,[1,2]);는 ‘Car과 Car2의 유효 범위에서 callName() 메서드를 실행하고 그 인수는 1과 2로 지정한다’는 의미입니다.

다음 그림은 위의 예제의 유효 범위를 그림으로 표현한 것입니다. callName() 메서드는 메모리에 다음 그림과 같이 위치하며 this는 window 객체를 가리킵니다.

apply() 메서드를 사용하여 callName() 메서드를 Car 객체의 유효 범위에서 실행하면 callName() 메서드의 위치는 다음 그림과 같이 바뀌고, this는 Car 객체를 가리키게 됩니다.

call() 메서드

call() 메서드는 apply() 메서드와 같이 자신이 원하는 유효 범위로 변경할 때 사용합니다. 그러나 메서드의 인수를 배열로 지정하지 않는 다는 점이 apply() 메서드와 다른 점입니다.

다음은 apply() 메서드를 사용하여 callName() 함수의 인수를 1과 2로 설정하는 예제입니다.

callName.apply(Car,[1,2]);

call() 메서드를 사용하여 callName() 함수의 인수를 1과 2로 설정하는 코드는 다음과 같습니다.

callName.call(Car,1,2);
실행 함수의 확인

유효 범위를 알기 위해 실행 중인 함수를 확인하려면 arguments.callee 프로퍼티와 arguments.callee.caller 프로퍼티를 이용합니다.

Arguments 객체

arguments.callee 프로퍼티와 arguments.callee.caller 프로퍼티의 Arguments 객체는 함수의 인수를 배열의 형태로 가지고 있는 객체입니다. 다음은 Arguments 객체로 함수의 인수를 확인하는 예제입니다.

function test(){
    console.log(arguments);
}

test (1, 2, 3);     // [1, 2, 3]

주의해야 할 사항은 Arguments 객체는 배열의 형태를 가지고 있을 뿐 배열이 아니며 브라우저마다 지원하는 방식이 다르다는 것입니다.

arguments.callee 프로퍼티

arguments.callee 프로퍼티는 현재 실행되고 있는 함수를 가리킵니다. 다음 예제에서 arguments.callee는 calleeScope() 함수 자신을 가리킵니다.

function  calleeScope(){
     console.log(arguments.callee);
}
arguments.callee.caller 프로퍼티

arguments.callee.caller 프로퍼티는 해당 함수를 실행 시킨 함수를 가리킵니다. 다음 예제에서 test2() 함수를 실행하면, arguments.callee.toString()가 test() 함수의 함수 코드가 출력하고 그 다음으로 arguments.callee.caller.toString()가 test2()의 함수 코드가 출력됩니다.

function test(){
    console.log(arguments.callee.toString());
    console.log(arguments.callee.caller.toString());
}

function test2(){
    test();
}

test2();

 

 

 

10. 클로저(closure)

내부 함수에서 자신을 포함하고 있는 외부 함수의 매개변수와 변수들을 접근할 수 있다.

myObj 에 함수를 할당한 것이 아니라 함수를 호출한 결과를 할당한다. 

var myObj = function ( ) {

   var value = 0;

   return {

      increment : function(inc){

          value += typeof inc === 'number' ? inc : 1;

      },

      getValue : function( ){

          return value;

      }

   };

}( );

myObj.increment();

console.log( myObj.value ); // undefined,    myObj.getValue() : 1

myObj.increment(2);

console.log( myObj.value ); // undefined,    myObj.getValue() : 3

 

3. 호출 > 생성자 호출 패턴의 Quo .. 와 비교 해서 살펴 보자.

3. 호출에서 언급한 Quo 는 사실 get_status 라는 메소드가 있지만 status 에 바로 접근이 가능하기 때문에 get_status는 무의미하다. 

아래와 같이 구현 해보자. 

var q = function(status) {

         return {

             get_status : function() {

                return status;

             }

         };

};

var mq = q('confused');

document.writeln(mq.status);           // undefined

document.writeln(mq.get_status());   // confused 

q 를 호출 하면 get_status 메소드가 있는 객체를 반환한다. 이 객체에 대한 참조는 mq 에 저장된다.

q 가 이미 반환된 뒤에도 q 의 status 에 계속해서 접근할 수 있는 권한을 가지게 된다.

get_status 는 status 매개 변수의 복사본에 접근할 수 있는 권한을 갖는 것이 아니라 매개변수 그 자체에 대한 접근 권한을 갖는다.

이러한 것이 가능한 것은 함수가 자신이 생성된 함수, 즉 자신을 내포하는 함수의 문맥(context)에 접근할 수 있기 때문이다.

이러한 것을 클로저(closer) 라고 부른다.


 // DOM 노드의 색을 노란색으로 지정하고 흰색으로 사라지게 하는 함수 정의

var fade = function ( node ) {

    var level = 1;

    var step = function ( )  {

        var hex = level.toString(16);

        node.style.backgroundColor = '#FFFF' + hex + hex;

        if( level < 15 ){

          console.log('level : ' + level);

          level += 1;

          setTimeout(step, 100);

        }

    };

    step();

  };

 

 fade(document.body);

fade 는 level 값을 1로 설정하고 step 이라는 함수를 정의하고, setTimeout을 호출해 이 함수를 100밀리 초 후에 실행하게 한다.

여기 까지 수행한 후 fade 는 종료 된다.

1/10초 뒤에 step 함수가 호출 된다. fade 함수는 이미 반환됐지만 함수 안의 변수는 이를 필요로 하는 내부 함수가 하나 이상 존재하는

경우 계속 유지 된다.

내부 함수가 외부 함수에 있는 변수의 복사본이 아니라 실제 변수에 접근한다는 것을 이해해야 한다.

 

scope 체인상으로 외부함수의 모든 로컬변수를 내부 함수가 참조가 가능하여 외부함수의 메모리도 공유해서 

사용이 가능한 구조가 closure(클로저) 이다. 

자바스크립트의 클로저는 scope chain 의 연결을 의미하며 이를 이용하여 다양한 기법을 만들 수 있다.

function add(a){

   return function (b) {return a + b;}

}

 

var a1 = add (3);  // function(b) { return a + b;} 의 참조를 a1 에게 준다.

var a2 = a1(5);     // add 함수의 argument 인 a = 3 과 지금 내부 함수 argument 인 b = 5 를 더한다.

console.log(a2);  // 8 을 출력 


 

 

자바스크립트의 모든 함수는 클로저다.

 

자바스크립트에서 함수가 실행될 때는 함수가 실행되는 위치가 아닌, 함수가 정의 될 당시의 유효범위에서 실행된다.

 


728x90
반응형
LIST