생활코딩/

[JS] 함수 - 유효범위, 콜백, 클로저, arguments, 호출

hayjo 2020. 9. 3. 18:35

[강의 출처] opentutorials.org/course/743/6583

 

함수지향 - 생활코딩

함수지향 카테고리의 하위 수업들은 함수형 언어로서 자바스크립트의 면모를 다룬다. 자바스크립트의 핵심적인 도구는 함수다. 자바스크립트의 함수는 매우 강력하다. 함수에 대한 이해 없이��

opentutorials.org


 

유효범위(Scope):

-변수를 선언할 때 'var'는 해당 변수가 지역변수(local variable)임을 의미

-한번 지역변수로 선언한 이후에는 해당 범위(지역) 내에서는 지역변수로 사용됨

-자바스크립트는 함수 단위로 유효범위를 제공(if, for, while등의 블록 단위에는 제공 X)

-유효범위는 선언 맥락에 따라 유효범위가 결정되는 Lexical Scoping 방식을 따른다

var vscope = 'global';     // 전역변수
function fscope(){
    var vscope = 'local';  // 지역변수
    document.write(vscope);
};
function fscope2(){
    document.write(vscope);
    vscope = 'local';
};
function fscope3(){
    document.write(vscope);
};
fscope();  // 'local': 가깝게 선언된 변수(지역변수) 우선 이용
fscope2(); // 'global': 지역변수가 없기 때문에 전역변수 호출
fscope3(); // 'local'; fscope2()에서 vscope 재정의 했기 때문에 현재 vscope 값은 'local'

유효범위 관련하여 발생할 수 있는 이슈

 -예상치못한 다른 맥락에서 변수가 변경되어 무한루프 등의 상황 발생 가능

function a (){
    i = 0;
};
for(var i = 0; i < 5; i++){
    a();
    document.write(i);
};
/* 무한루프
 * for문은 외부에 있기 때문에 for문의 i는 전역변수로 선언됨
 * 함수 a()는 (var가 없기 때문에) 전역변수 i의 값을 0으로 재할당함
 * 계속 i = 0 상태이므로 루프가 완료되지 않음 */

전역변수를 사용해야 할 때 중복을 피하려면

 -전역변수를 생성 후 나머지 변수를 해당 변수의 프로퍼티 형태 생성하거나

 -변수가 필요한 경우 익명함수 내에 선언해서 전역변수를 만들지 않기

(function(){
  var MYAPP = {};               /* 전역변수 하나를 생성해서 */
  MYAPP.calculator = {          /* 나머지 변수를 그 변수의 소속으로 넣어주거나 */
      'left': null,
      'right': null
  }
  MAPP.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));
}())                            /* 아예 해당 변수를 익명함수 내에 선언해서 전역변수를 만들지 않거나 */

JS는 Static Soping/Lexical Scoping (↔Dynamic Scoping)을 따르기 때문에

함수를 호출한 위치는 해당 함수가 참조하는 지역변수에 영향을 주지 않는다 [참고]

var i = 5;
function a(){
    var i = 10;
    b();
}
function b(){
    document.write(i); // 정의될 때의 i는 var i = 5이므로
}
a();                   // 5: 사용시점에 따라 영향을 받지 않는다(Lexical Scoping)

 

값으로서의 함수와 콜백

-JS에서 함수는 객체로 처리되므로, 다른 객체나 배열에 포함되거나, 다른 함수의 인자나 리턴값으로 전달될 수 있다.

-이런 특성을 first-class citizen or first-class object, first-class entity, first-class value라고 부른다.

-콜백(callback): 함수를 호출하는 작업을 콜백이라고 하고, 그렇게 호출한 함수를 콜백함수라고 한다.

function cal(mode){
    var funcs = {
        'plus': function(left, right){return left + right},
        'minus': function(left, right){return left - rigth}
    }
    return funcs[mode];
}
console.log(cal('plus')(2,1)); // 3
/* cal('plus')는 funcs[mode]를 리턴하므로,
 * cal 내부의 프로퍼티인 funcs에 mode로 입력된 'plus'를 전달하여
 * funcs['plus']가 실행되고, 인자로 (2,1)이 전달된다 */

-콜백은 비동기처리에 유용하게 사용된다. 자바스크립트의 경우 Ajax를 주로 이용한다.

  • 비동기처리(Asynchronous): To-do처럼, 특정 연산을 바로 진행하지 않고 모아뒀다가 여건이 될 때 처리하는 방식

 

클로저(Closure)

-내부함수가 외부함수의 맥락(Context: 지역변수 등) 에 접근가능한 특성

-내부함수란 함수 내에 선언된 함수를 뜻함. 이때 내부함수를 갖고 있는 함수를 외부함수라고 부름

function outter(){            // 외부함수
    var word = 'hello world';
    function inner(){         // 내부함수
         alert(word);
    }
    inner();
}
outter();

/* 특정한 함수 내에서만 사용되는 함수가 있을 경우,
 * 따로 떨어뜨려놓으면 군집성이 떨어지므로 내부함수 형태로 선언하기도 함 */
 
/* 위 함수의 경우 변수 word는 inner()의 입장에서는 전역변수이기 때문에
 * word가 outter()의 지역변수이더라도 inner() 안에서 접근이 가능함 */

-특이하게 외부함수가 종료되더라도 내부함수가 외부함수의 맥락에 접근 가능한데, 이런 특성을 클로저라고 부름

function outter(){
    var word = 'hello world';
    return function(){
        alert(word);
    }
}
inner = outter();
inner(); // 'hello world'라고 정상 출력됨

-클로저를 통해 아래와 같은 (python class 스타일) 객체 구현이 가능

function bread(element){
    return {
        get_element: function(){  // 함수 bread는 종료되었으나
            return element;       // get_element는 bread의 맥락에 접근가능
        },
        set_element: function(_element){
            element = _element    // 함수 set_element는 bread의 지역변수 element를 변경함
        }
    }
}

console.log(cookie.get_element()); // 'Cheese'
console.log(scone.get_element());  // 'Cheese'

scone.set_element('Chocolate');

console.log(cookie.get_element()); // 'Cheese'
console.log(scone.get_element());  // 'Chocolate'

/* 외부함수인 bread는 return 시점에서 종료되었으므로
 * 변수 element에 대한 접근은 메소드 get_element, set_element를 통해서만 가능하고,
 * 따라서 데이터 접근 채널이 한정되므로 안전해짐 */

-클로저 관련하여 발생할 수 있는 이슈

var arr = [], arr2 = []
for(var i = 0; i < 5; i++){
  arr[i] = function(){      // 이 맥락에서 i는 외부변수에서 정의된 지역변수가 아니기 때문에 
    console.log(i);         // 이 구문 이후로도 값이 변할 수 있음
  },
  arr2[i] = function(id){
    return function(){     // function(id)는 function()를 리턴하는 시점에서 종료되어
     return id;            // id는 해당 시점의 i값을 가리키는 외부변수로 고정됨
    }
  }(i);
}
// 만약 여기에서 i = 7;처럼 i값을 재할당한다면 arr[index]()는 7을 리턴함
for(var index in arr){
  console.log(arr[index]());   // 5 5 5 5 5
  console.log(arr2[index]());  // 0 1 2 3 4
}

 

arguments

-함수의 매개변수(parameter)를 정의하지 않고 사용하거나, 인자의 수가 정의한 것과 달라도 에러가 나지는 않음

-함수 내부에서 arguments라는 객체를 통해 인자에 접근 가능

-함수.length -> 함수 정의시에 입력한 인자의 개수

-arguments.length -> 호출시 입력받은 인자의 개수

function sum(){
    var _sum = 0;
    for(var i = 0; i < arguments.length; i++){
        document.write(i+' : '+arguments[i]+'<br />');
        _sum += arguments[i];
    }
    return _sum;
}
document.write('result : ' + sum(1, 2, 3, 4, 5));


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

 

함수를 호출하는 다른 방법

-함수는 function 객체의 인스턴스이므로, function의 apply 메소드로도 호출이 가능

o1 = {val1:1, val2:2, val3:3}
o2 = {v1:10, v2:50, v3:100, v4:25}
function sum(){
    var _sum = 0;
    for(name in this){      // 이 경우 this의 맥락은 호출할 때 정해짐
        _sum += this[name];
    }
    return _sum;
}
console.log(sum.apply(o1)) // 6
console.log(sum.apply(o2)) // 185

/* apply 메소드의 첫번째 인자에 입력된 o1, 혹은 o2는
 * var this = o1; 처럼 정의된 것과 같은 효과,
 * 함수가 마치 o1.sum, o2.sum처럼 적용된 것과 같은 효과를 냄 */