ABOUT ME

-

Today
-
Total
-
  • [생활코딩] JavaScript 함수 : scope, callback, closure, arguments, apply&call
    Study/JavaScript 2019. 12. 7. 10:36
    반응형

     

     


    자바스크립트의 핵심적인 도구는 함수다. 자바스크립트의 함수는 매우 강력하다.

    함수에 대한 이해 없이는 자바스크립트를 잘 다루기 어렵다.

    또한 자바스크립트에서 함수는 객체를 이해하는 데 가장 중요한 기초를 이룬다. 

     


    유효범위 Scope

     

    - 유효범위(Scope)는 변수의 수명을 의미한다.
    지역변수는 특정 함수 안에서 선언된 변수이며, 유효범위는 함수 안이다.
    전역변수는 함수 바깥에서 선언되고, 유효범위는 애플리케이션 전역에서 접근이 가능하다. 즉 모든 함수에서 이 변수에 접근할 수 있다.

    예시)

    var vscope = 'global'; 
    function fscope(){ 
        var vscope = 'local'; 
        alert(vscope); 
    } 
    fscope(); // 'local'


    ▶ var vscope = 'local'; 이것은 지역변수이고 / var vscope = 'global'; 이것은 전역변수이다.
    지역변수와 전역변수의 유효범위의 차이 때문에,
    같은 이름의 지역변수와 전역변수가 동시에 정의되어 있다면 지역변수가 우선한다는 것을 알 수 있다.
    따라서 위 예시의 fscope 함수 결과는 local이 알림창으로 뜰 것이다.



    예시)

    var vscope = 'global'; 
    function fscope(){ 
        vscope = 'local'; 
        alert(vscope); 
    } 
    fscope(); // 'local'


    ▶ 이 함수의 결과는 local이 알림창으로 뜰 것이다. 왜냐하면 함수 fscope의 지역변수를 선언할 때 var를 사용하지 않았기 때문이다. var를 사용하지 않은 지역변수는 전역변수가 된다. 따라서 변수값은 local로 변경된 것이다.

     


    전역변수는 사용하지 않는 것이 좋다. 여러가지 이유로 그 값이 변경될 수 있기 때문이다. 

    함수 안에서 전역변수를 사용하고 있는데, 누군가에 의해서 전역변수의 값이 달라졌다면 어떻게 될까? 

    함수의 동작도 달라지게 된다. 이것은 버그의 원인이 된다. 

    또한 함수를 다른 에플리케이션에 이식하는데도 어려움을 초래한다. 

    함수의 핵심은 로직의 재활용이라는 점을 상기하자. 
    지역변수는 같은이름의 지역변수가 존재해도 다른 객체로 취급되어 변수명의 재사용성이 훨씬 높아진다.
    따라서 지역변수를 사용하는 것을 권장한다.
    변수를 선언할 때는 꼭 let 혹은 const를 붙이는 것을 습관화해야 한다.

    전역변수를 사용해야 하는 경우라면 그것을 사용하는 이유를 명확히 알고 있을 때 사용하도록 하자.


    불가피하게 전역변수를 사용해야 하는 경우하나의 객체를 전역변수로 만들고 객체의 속성으로 변수를 관리하는 방법을 사용한다.

    예시)

    MYAPP = {} 
    MYAPP.calculator = { 
        'left' : null, 
        'right' : null 
    } 
    MYAPP.coordinate = { 
        'left' : null, 
        'right' : null 
    } 
      
    MYAPP.calculator.left = 10; 
    MYAPP.calculator.right = 20; 
    function sum(){ 
        return MYAPP.calculator.left + MYAPP.calculator.right; 
    } 
    document.write(sum()); 
    


    ▶ MYAPP이라는 객체를 전역변수로 만든 후에 calculator, coordinate와 같이 속성을 부여하여 변수를 관리한다.


    예시)

    (function(){ 
        var MYAPP = {} 
        MYAPP.calculator = { 
            'left' : null, 
            'right' : null 
        } 
        MYAPP.coordinate = { 
            'left' : null, 
            'right' : null 
        } 
        MYAPP.calculator.left = 10; 
        MYAPP.calculator.right = 20; 
        function sum(){ 
            return MYAPP.calculator.left + MYAPP.calculator.right; 
        } 
        document.write(sum()); 
    }()) 
    


    ▶ 전역변수를 사용하고 싶지 않다면 위와 같이 익명함수를 호출함으로서 이러한 목적을 달성할 수 있다. 

    MYAPP을 지역변수로 설정한다. 이는 자바스크립트에서 로직을 모듈화하는 일반적인 방법이다.


    - 유효범위의 대상 : 자바스크립트는 함수에 대한 유효범위만을 제공한다. 즉, 자바스크립트의 지역변수는 함수에서만 유효하고, for문이나 if문에서는 지역변수로서의 의미를 갖지 않는다.

     
    - 정적 유효범위 : 자바스크립트는 함수가 선언된 시점에서 유효범위를 갖는다. 

    정적 유효범위(static scoping), 혹은 렉시컬(lexical scoping)이라고 한다. 
    사용되는 시점이 아닌! 함수가 선언되는 그 시점에서의 변수를 바라본다.

    예시)

    var i = 5; 
      
    function a(){ 
        var i = 10; 
        b(); 
    } 
      
    function b(){ 
        document.write(i);  // 5
    } 
      
    a(); 
    


    ▶ 여기서 실행결과는 5가 나오는데, 그 이유는 함수 b가 정의되는 시점에서 변수 i는 전역변수인 5에 해당하므로, 

    함수b가 함수a에 들어가서 사용되는 시점이 아니라 정의되는 시점의 변수 i를 불러오게 된다. 따라서 결과는 5이다.

     

     

    값으로서의 함수와 콜백

     

    JavaScript에서는 함수도 객체다. 다시 말해서 일종의 값이다. 거의 모든 언어가 함수를 가지고 있다.

    JavaScript의 함수가 다른 언어의 함수와 다른 점은 함수가 값이 될 수 있다는 점이다. 

    function a(){} 
    
    var a = function (){} 
    


    위는 함수의 두 가지 표현에 해당한다.

    a = { 
        b:function(){ 
        } 
    }; 
    


    그리고 객체의 속성 값으로 담겨진 함수를 메소드(method)라고 부른다.

    function cal(func, num){ 
        return func(num) 
    } 
    function increase(num){ 
        return num+1 
    } 
    
    alert(cal(increase, 1)); 


    함수는 값이기 때문에 다른 함수의 인자로 전달 될수도 있다.

    function cal(mode){ 
        var funcs = { 
            'plus' : function(left, right){return left + right}, 
            'minus' : function(left, right){return left - right} 
        } 
        return funcs[mode]; 
    } 
    alert(cal('plus')(2,1)); 


    함수는 함수의 리턴 값으로도 사용할 수 있다.


    var process = [ 
        function(input){ return input + 10;}, 
        function(input){ return input * input;}, 
        function(input){ return input / 2;} 
    ]; 
    var input = 1; 
    for(var i = 0; i < process.length; i++){ 
        input = process[i](input); 
    } 
    alert(input); 
    


    함수는 배열의 값으로도 사용할 수 있다.




    변수,매개변수,리턴값 등 다양하게 사용될 수 있는 것을 first-class object(citizen) 이라고 부르는데

    함수(function)가 이에 해당한다


    값으로 전달된 함수는 내부적으로 호출될 수 있기 때문에, 원래 함수의 기본적인 동작 방법을 변경할 수 있다. 
    즉,  함수가 다른 함수의 인자로 사용됨으로써 그 함수의 내용을 완전히 바꿀 수 있는것이 바로 콜백함수이다.
    아래 예시를 보면 인자로 전달된 함수 sortNumber의 구현에 따라서 sort의 동작방법이 완전히 바뀌게 된다.

    function sortNumber(a,b){ 
        // 위의 예제와 비교해서 a와 b의 순서를 바꾸면 정렬순서가 반대가 된다. 
        return b-a; 
    } 
    var numbers = [20, 10, 9,8,7,6,5,4,3,2,1]; 
    alert(numbers.sort(sortNumber)); 
    


    ** 여기서는 sortNumber 가 콜백함수이다


    콜백은 비동기처리에서도 유용하게 사용된다.

    시간이 오래걸리는 작업이 있을 때 이 작업이 완료된 후에 처리해야 할 일을 콜백으로 지정하면

    해당 작업이 끝났을 때 미리 등록한 작업을 실행하도록 할 수 있다 (ex : Ajax)

     

     

    클로저 Closure

     

    클로저(closure)는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것을 가르킨다.

    자바스크립트는 함수 안에서 또 다른 함수를 선언할 수 있는데, 내부함수는 외부함수의 지역변수에 접근할 수 있다.

    내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도

    내부함수가 외부함수의 변수에 접근 할 수 있다. 이러한 메커니즘을 클로저라고 한다. 
    즉, 클로저란 내부함수가 외부함수의 지역변수에 접근 할 수 있고, 외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 않는 특성을 의미한다.

    function factory_movie(title){
        return {
            get_title : function (){
                return title;
            },
            set_title : function(_title){
                title = _title
            }
        }
    }
    
    let ghost = factory_movie('Ghost in the shell');
    let matrix = factory_movie('Matrix');
     
    alert(ghost.get_title());  // 'Ghost in the shell'
    alert(matrix.get_title());  // 'Matrix'
     
    ghost.set_title('공각기동대');
     
    alert(ghost.get_title());  // '공각기동대'
    alert(matrix.get_title());  // 'Matrix'


    위와 같이 클로저를 활용하여 private variable을 사용할 수 있다.

    똑같은 외부함수 factory_movie를 공유하고 있는 ghost와 matrix의 get_title의 결과는 서로 각각 다르다. 그것은 외부함수가 실행될 때마다 새로운 지역변수를 포함하는 클로저가 생성되기 때문에 ghost와 matrix는 서로 완전히 독립된 객체가 된다.

     

    원래는 함수가 실행되면 소멸되는 것이 자바스크립트의 기본이지만, 객체의 메소드를 통해 지역변수에 접근하여 내부함수를 다시 호출하여 사용할 수 있다.
    Private 속성은 객체의 외부에서는 접근 할 수 없는 외부에 감춰진 속성이나 메소드를 의미한다. 이를 통해서 객체의 내부에서만 사용해야 하는 값이 노출됨으로서 생길 수 있는 오류를 줄일 수 있다. 

     

    arguments

     

    함수안에서 사용할 수 있도록 그 이름이나 특성이 약속되어 있는 일종의 유사 배열이다.

    실제 배열이라고 할 수는 없고  arguments 객체의 인스턴스이다.

    arguments.length : 함수로 전달된 '인자'의 개수를 알아낼 수도 있다
    - arguments[i] : '인자'의 인덱스도 알 수 있다

    * 이러한 특성에 반복문을 결합하면 함수로 전달된 인자의 값을 순차적으로 가져올 수 있다.


    - arguments.length는 함수로 전달된 실제 '인자'의 수를 의미하고, 함수.length는 함수에 정의된 '매개변수'의 수를 의미한다. 


    ex)

    function one(arg1){ 
        console.log( 
            'one.length', one.length, 
            'arguments', arguments.length 
        ); 
    } 
    
    one('val1', 'val2'); 
    
    // one.length == 1  // arguments.length == 2  
    


    이것을 활용해서 함수의 매개변수 갯수와 전달되는 인자의 갯수 일치 여부를 통해 에러를 발생시키는 코드를 작성할 수 있다.

     

     

    함수의 호출 apply & call

     

    객체는 속성(just 값) & 메소드(함수)를 가지고 있다.
    함수 역시 객체이기 때문에 메소드를 가지고 있는데, 자바스크립트가 제공하는 내장된 객체의 메소드를 가지고 있다.

    function func(){ 
    } 
    func(); 
    


    위의 예제에서 함수 func는 Function이라는 객체의 인스턴스다. 따라서 func는 객체 Function이 가지고 있는 메소드들을 상속하고 있다. 


    - 함수호출 : 기존의 함수호출 방식말고 함수.apply()나 .call()을 통해서도 함수를 호출할 수 있다.

    apply와 call 은 메소드에 해당한다.

    - JavaScript에서 함수는 독립적인 객체로서 존재하고, apply나 call 메소드를 통해서 다른 객체의 소유물인 것처럼 실행할 수 있다. 

     


    함수.apply(null, [a, b...])  

    - [a,b...] 에 함수에 인자의 값이 들어간다.
    - null에 자리에는 객체 등이 들어 가는데 apply()를 쓰는 상황은 this를 사용하는 독립적인 함수가 있을 때 apply를 사용해서 매 번 다른 객체를 넣어주면 함수가 그 객체의 메소드처럼 사용된다.
    - 쉽게 말하자면 여러 객체에서 쓸 수 있는 '공용함수' 처럼 쓰기 위해서 만들어진 기법인 듯하다.

    ex 1)

    let o1 = {val1 : 1, 
              val2 : 2, 
          	  val3 : 3};
          
    let o2 = {v1 : 10, 
              v2 : 50, 
              v3 : 100, 
              v4 : 25};
    
    function sum(){ 
        let _sum = 0; 
        for(name in this){ 
            _sum += this[name]; 
        } 
        return _sum; 
    } 
    
    alert(sum.apply(o1)) // 6 
    alert(sum.apply(o2)) // 185 


    => sum이라는 함수를 o1 객체의 속성인 것 처럼 사용할 수 있다.

    ex 2)

    let o1 = {val1 : 1, 
              val2 : 2, 
          	  val3 : 3};
    
    function sum(arg1,arg2){ 
        let _sum = 0; 
        for(name in this){ 
           console.log(_sum += this[name] + arg1 + arg2) ; 
        } 
        return _sum; 
    } 
    
    sum.apply(o1,[1,2]) // 15 
    반응형

    댓글