[자바스크립트] 함수지향 프로그래밍

류명운

·

2016. 8. 12. 10:12

반응형

[자바스크립트] 함수지향적 프로그래밍

  • 본 포스팅에서는 함수형 언어로서 자바스크립트의 면모를 다룬다.
  • 자바스크립트의 핵심적인 도구는 함수다.
  • 자바스크립트의 함수는 매우 강력하다.
  • 함수에 대한 이해 없이는 자바스크립트를 잘 다루기 어렵다.
  • 함수에 대한 이해는 객체를 이해하는 데 가장 중요한 기초를 이룬다.

1. 유효범위(Scope)

변수의 수명을 뜻한다.

자바스크립트의 지역변수는 함수에서만 유효하다.


1. 1 지역변수의 사용

function a (){
    var i = 0;
}
for(var i = 0; i < 5; i++){
    a();
    document.write(i);
}

→ 01234

: 반복문에서 호출한 a함수의 i 값은 a함수내에서 정의된 지역변수이기 때문에 적용되지 않는다.


1. 2 전역변수의 사용

function a (){
    i = 0;
}
for(i = 0; i < 5; i++){
    a();
    document.write(i);
}

: 무한반복을 발생시킨다.

: 반복문에서 호출한 a함수의 i 값은 반복문에서 선언한 전역변수를 가리키기 때문에 0으로 계속 초기화되어 무한반복이 발생한다.


* 하나의 객체를 전역변수로 만들고 객체의 속성으로 변수를 관리하는 방법

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());

→ 30


* 전역변수를 사용하지 않고 익명함수를 호출하여 사용하는 방법(자바스크립트에서 로직을 모듈화하는 일반적인 방법)

(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());
}())

→ 30


1. 3 유효범위의 대상(함수)

자바스크립트는 함수에 대한 유효범위만을 제공한다. 많은 언어들이 블록(대체로 {,})에 대한 유효범위를 제공하는 것과 다른 점이다.

즉, 자바스크립트의 지역변수는 함수에서만 유효하다. 이러한 유효범위의 방식을 정적 유효범위(static scoping), 혹은 렉시컬(lexical scoping)이라고 한다. 

for(var i = 0; i < 1; i++){
    var name = 'Myeonguni.com';
}
alert(name);

→ Myeonguni.com


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

→ 5

2. 값으로서의 함수와 콜백

2. 1 값으로서의 함수

자바스크립트에서는 함수도 객체다. 다시 말해서 일종의 값이다. 거의 모든 언어가 함수를 가지고 있지만 자바스크립트의 함수가 다른 언어의 함수와 다른 점은 함수가 값이 될 수 있다는 점이다.

function a(){}

: 함수 a는 변수 a에 담겨진 값이다.


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

: 함수는 객체의 값으로 포함될 수 있다. 이렇게 객체의 속성 값으로 담겨진 함수를 메소드(method)라고 부른다.


* 함수는 값이기 때문에 다음 예제코드와 같이 다른 함수의 인자로 전달 될수도 잇다.

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

→ 2 0

: 10행을 실행하면 increase와 값 1이 함수 cal의 인자로 전달된다. 함수 cal은 첫번째 인자로 전달된 increase를 실행하는데 이 때 두번째 인자의 값이 1을 인자로 전달한다. 함수 increase은 계산된 결과를 리턴하고 cal은 다시 그 값을 리턴한다.


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

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));
alert(cal('minus')(2,1));

→ 3 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);

→ 60.5


2. 2 콜백

2. 2. 1 처리의 위임

값으로 사용될 수 있는 특성을 이용하면 함수의 인자로 함수를 전달할 수 있다. 값으로 전달된 함수는 호출될 수 있기 때문에 이를 이용하면 함수의 동작을 완전히 바꿀 수 있다.

아래 예제코드의 인자로 전달된 함수 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)); // array, [20,10,9,8,7,6,5,4,3,2,1]

→ 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1


2. 2. 2 비동기 처리

콜백은 비동기처리에서도 유용하게 사용된다. 시간이 오래걸리는 작업이 있을 때 이 작업이 완료된 후에 처리해야 할 일을 콜백으로 지정하면 해당 작업이 끝났을 때 미리 등록한 작업을 실행하도록 할 수 있다.

아래 예제코드는 일반적인 환경에서는 동작하지 않고 서버 환경에서만 동작한다.

[datasource.json.js]

{"title":"JavaScript","author":"egoing"}

[demo1.js]

$.get('./datasource.json.js', function(result){
    console.log(result);
}, 'json');

3. 클로저

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

* 자바스크립트를 이용한 고난이도의 테크닉을 구사하는데 필수적인 개념으로 활용된다.


3. 1 내부함수

자바스크립트는 함수 안에서 또 다른 함수를 선언할 수 있다. 다음 예제코드를 확인하자.

function outter(){
    function inner(){
        var title = 'Myeonguni.com'; 
        alert(title);
    }
    inner();
}
outter();

→ Myeonguni.com

: outter 함수 내부에는 inner 함수가 정의 되어 있다. 이런 inner 함수를 내부 함수라고 한다.


* 내부함수는 외부함수의 지역변수에 접근할 수 있다. 다음 예제코드를 확인하자.

function outter(){
    var title = 'Myeonguni.com';  
    function inner(){        
        alert(title);
    }
    inner();
}
outter();

→ Myeonguni.com


3. 2 클로저

클로저(closure)는 내부함수와 밀접한 관계를 가지고 있는 주제다. 내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근 할 수 있다. 이러한 메커니즘을 클로저라고 한다. 다음 예제코드를 확인하자.

function outter(){
    var title = 'Myeonguni.com';  
    return function(){        
        alert(title);
    }
}
inner = outter();
inner();

→ Myeonguni.com

: 디버깅을 통해 예제코드의 실행순서를 주의깊게 살펴보자. 7행에서 함수 outter를 호출하고 있다. 그 결과(이름이 없는 함수)가 변수 inner에 담긴다. 실행이 8행으로 넘어오면 outter 함수는 실행이 끝났기 때문에 이 함수의 지역변수는 소멸되는 것이 자연스럽다. 하지만 8행에서 함수 inner를 실행했을 때 Myeonguni.com이 출력된 것은 외부함수의 지역변수 title이 소멸되지 않았다는 것을 의미한다.


이렇듯 클로저란 내부함수가 외부함수의 지역변수에 접근 할 수 있고, 외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 않는 특성을 의미한다. 클로저를 이용한 조금 더 복잡한 다음 예제코드를 확인하자.

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

→ Ghost in the shell -> Matrix -> 공각기동대 -> Matrix

: 1) 클로저는 객체의 메소드에서도 사용할 수 있다. 위의 예제는 함수의 리턴값으로 객체를 반환하고 있다. 이 객체는 메소드 get_title과 set_title을 가지고 있다. 이 메소드들은 외부함수인 factory_movie의 인자값으로 전달된 지역변수 title을 사용하고 있다.

: 2) 동일한 외부함수 안에서 만들어진 내부함수나 메소드는 외부함수의 지역변수를 공유한다. 17행에서 실행된 set_title은 외부함수 factory_movie의 지역변수 title의 값을 '공각기동대'로 변경했다. 19행에서 ghost.get_title();의 값이 '공각기동대'인 것은 set_movie와 get_movie 함수가 title의 값을 공유하고 있다는 의미다.

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

: 4) factory_movie의 지역변수 title은 2행에서 정의된 객체의 메소드에서만 접근 할 수 있는 값이다. 이 말은 title의 값을 읽고 수정 할 수 있는 것은 factory_movie 메소드를 통해서 만들어진 객체 뿐이라는 의미다. JavaScript는 기본적으로 Private한 속성을 지원하지 않는데, 클로저의 이러한 특성을 이용해서 Private한 속성을 사용할 수 있게된다.

* Private 속성은 객체의 외부에서는 접근 할 수 없는 외부에 감춰진 속성이나 메소드를 의미한다. 이를 통해서 객체의 내부에서만 사용해야 하는 값이 노출됨으로서 생길 수 있는 오류를 줄일 수 있다. 자바와 같은 언어에서는 이러한 특성을 언어 문법 차원에서 지원하고 있다.


마지막으로 클로저와 관련해서 자주 언급되는 다음 예제코드를 확인하자.

var arr = []
for(var i = 0; i < 5; i++){
    arr[i] = function(){
        return i;
    }
}
for(var index in arr) {
    console.log(arr[index]());
}

→ 5 5 5 5 5

: 함수가 함수 외부의 컨텍스트에 접근할 수 있을 것으로 기대하겠지만 결과는 기대와 다르다.

: 위의 예제코드는 다음 예제코드와 같이 변경해야 한다.

var arr = []
for(var i = 0; i < 5; i++){
    arr[i] = function(id) {
        return function(){
            return id;
        }
    }(i);
}
for(var index in arr) {
    console.log(arr[index]());
}

→ 0 1 2 3 4

4. arguments

함수에는 arguments라는 변수에 담긴 숨겨진 유사 배열이 있다. 이 배열에는 함수를 호출할 때 입력한 인자가 담겨있다. 다음 예제코드를 확인하자.

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

→ result : 10

: sum 함수의 정의부분에서 인자에 대한 구현이 없음에도 인자를 전달 할 수 있는 것은 arguments라는 특수한 배열이 있기 때문이다. arguments는 함수안에서 사용할 수 있도록 그 이름이나 특성이 약속되어 있는 일종의 배열이다.


4. 1 매개변수의 수

매개변수와 관련된 두가지 수가 있다. 하나는 함수.length이고 다른 하나는 arguments.length이다. arguments.length는 함수로 전달된 실제 인자의 수를 의미하고, 함수.length는 함수에 정의된 인자의 수를 의미한다. 다음 예제코드를 확인하자.

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

5. 함수의 호출

function func(){
}
func();

자바스크립트는 함수를 호출하는 특별한 방법을 제공한다. 함수를 객체라고 말할 수 있는데, 위의 예제코드에서 함수 func는 Function이라는 객체의 인스턴스다. 따라서 func는 객체 Function이 가지고 있는 메소드들을 상속하고 있다.

앞으로 이야기하려는 메소드는 Function.apply와 Function.call이다. 이 메소드들을 이용해서 함수를 호출하는 다음 예제코드를 확인하자.

function sum(arg1, arg2){
    return arg1+arg2;
}
alert(sum.apply(null, [1,2]))

→ 3

: 함수 sum은 Function 객체의 인스턴스이기 때문에 객체 Function의 메소드 apply를 호출 할 수 있다. apply 메소드는 두개의 인자를 가질 수 있는데, 첫번째 인자는 함수(sum)가 실행될 맥락이다. 맥락의 의미는 다음 예제코드를 통해서 살펴보도록 하겠다. 두번째 인자는 배열인데, 이 배열의 담겨있는 원소가 함수(sum)의 인자로 순차적으로 대입된다. Function.call은 사용법이 거의 비슷하다.


* 다음 예제코드를 통해 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){
        _sum += this[name];
    }
    return _sum;
}
alert(sum.apply(o1)) // 6
alert(sum.apply(o2)) // 185

→ 6 185

: 우선 o1과 o2라는 두개의 객체를 만들었다. 각 객체는 다른 속성 이름을 가지고 있고 속성의 수도 다르다.

: 그 다음엔 함수 sum을 만들었다. 이 함수는 객체의 속성을 열거할 때 사용하는 for in 문을 이용해서 객체 자신(this)의 값을 열거한 후에 각 속성의 값을 지역변수 _sum에 저장한 후에 이를 리턴하고 있다.

: 객체 Function의 메소드 apply의 첫번째 인자는 함수가 실행될 맥락이다. 이렇게 생각하자. sum.apply(o1)은 함수 sum을 객체 o1의 메소드로 만들고 sum을 호출한 후에 sum을 삭제한다. 다음 예제코드와 비슷하다. (실행결과가 조금 다를 것이다. 그것은 함수 for in문으로 객체 o1의 값을 열거할 때 함수 sum도 포함되기 때문이다.)

o1.sum = sum;
alert(o1.sum());
delete o1.sum();

: sum의 o1 소속의 메소드가 된다는 것은 이렇게 바꿔 말할 수 있다. -> 함수 sum에서 this의 값이 전역객체가 아니라 o1이 된다는 의미다.

: 일반적인 객체지향 언어에서는 하나의 객체에 소속된 함수는 그 객체의 소유물이 된다. 하지만 자바스크립트에서 함수는 독립적인 객체로서 존재하고 apply나 call 메소드를 통해서 다른 객체의 소유물인 것처럼 실행할 수 있다.


* 만약 apply의 첫번째 인자로 null을 전달하면 apply가 실행된 함수 인스턴스는 전역객체(브라우저에서는 window)를 맥락으로 실행하게 된다.


함수지향 프로그래밍 기본 소양 익히기 포스팅 끝 ! *^^*


반응형