본문 바로가기

Programming/Javascript

7. Javascript 함수 (6) - 내부 함수 + 스코프와 클로저 맛보기

javascript image

 

내부 함수(inner function)의 정의는 사실 특별할 것이 없습니다. Javascript 함수 코드 내부에서, 다시 함수를 정의하는 것이 가능하기 때문에 함수 내부에 정의된 또 다른 함수를 내부 함수라고 칭합니다. 

 

기본적인 내부 함수 특성

내부 함수는 아래 박스안의 내용과 같은 속성을 갖게 됩니다. 이 특성들과 연계된 Javascript의 주요 개념들도 존재하니 우선 예제 코드들과 함께 이러한 속성에 익숙해져야 합니다. 지금은 아주 간단하게 보일지 몰라도 향후 복잡한 코드에서는 이러한 성질들을 순간적으로 잊어버리는 경우가 있기 때문이죠. 

 

1. 내부 함수는 일반적으로 자신을 선언한 부모 함수 내부에서만 호출이 가능하다.
2. 내부 함수는 자신을 선언한 부모 함수의 변수에 접근이 가능하다. 
3. 단, 부모 함수 외부에서도 내부 함수를 호출할 수 있는 방법은 존재한다.

 

위와 같이 내부함수와 관련된 특성을 세 가지로 정리해 보았습니다. 지금은 크게 와닿지 않을 수도 있는데요, 우선 알기 쉽게 다음과 같이 비유해서 기억을 해두겠습니다.

 

"1. 자식은 자신의 부모님만 저녁밥을 먹으라고 부를 수 있다. "

"2. 자식은 자신의 부모님이 먹는 저녁밥도 함께 먹을 수 있다."

"3. 단, 부모님이 허락한다면 특별히 바깥에서 저녁을 먹을 수 있다."

 

나름대로 위와 같이 단순한 비유를 들어보았습니다(^^;;) 이러한 내부 함수의 특성을 잘 기억해두고, 다음 예제 코드를 살펴보도록 하겠습니다. 

 

// 부모 함수
function parent(){
    var variA = 'parent A';
    var variB = 'parent B';

    // 내부 함수
    function child(){
        var variB = 'child B';

        console.log(variA);
        console.log(variB);
    }

    // 내부함수 실행
    child();
}

// 부모 함수의 실행
parent();
// 내부 함수의 실행
child();

/* 결과
parent A
child B

ReferenceError: child is not defined 
*/

 

부모 함수와 내부 함수의 접근 범위

 

위 예제 코드에서는 parent( )라는 부모함수를 선언하고, 내부에 child( ) 함수를 선언했습니다. child( ) 함수는 부모 함수와 동일한 이름의 variB 변수를 선언하고, variA와 variB라는 함수를 출력하는 함수입니다. 

 

우선, parent( ) 함수를 실행한 결과를 살펴봅시다. 자연스럽게 child( ) 함수가 실행되었는데, 스트링 "parent A"와 "child B"가 출력된 것을 볼 수 있습니다. 여기서 살펴볼 수 있는 특성은 무엇일까요? 우선 child( ) 내부에서 [variA]라는 변수가 정상적으로 호출되었다는 사실입니다. 즉, 내부 함수는 자신을 선언한 부모 함수의 변수에 얼마든지 접근이 가능하다는 것을 확인할 수 있는 것이죠. 

 

또한, 부모 함수에도 [variB]가 선언되어 있고 내부 함수에도 [variB]가 동일한 이름으로 선언되어 있습니다. 이때 내부 함수에서 [variB]를 사용하게 되면 내부 함수의 [variB] 즉 "child B"가 출력됩니다. 동일한 이름이 있다면 내부 함수 자신의 변수를 먼저 불러온다는 성질도 확인할 수 있습니다. 즉, 똑같은 반찬이 있다면 부모님 것이 아니라 자기 것을 먼저 먹는다는 의미가 되겠죠. 

 

마지막으로 parent( ) 함수를 호출해준 다음, 시험삼에 child( ) 함수를 외부에서 호출해 보았습니다. 이는 부모 함수 외부에서 실행한 것이기 때문에, 정상적으로 실행되지 않고 ReferenceError가 발생합니다. 해당 범위에서는 child라는 함수가 정의되어 있지 않다고 나오네요. 말씀드렸다시피 내부 함수는 부모 함수의 내부에서만 호출이 가능하다는 성질을 다시 한번 확인할 수 있습니다. 

 

여기까지 말씀드린 성질들은 결국, 함수와 내부 함수의 유효 범위에 대해서 설명한 예제입니다. 특히 자신의 부모 함수가 가진 변수에 접근해 사용이 가능한 것은 Javascript의 스코프 체이닝이라는 특성 때문인데요, 이는 추후 더 자세히 살펴볼 예정이니 우선은 간단한 성질로서 숙지하고 넘어가도록 하겠습니다. 

 

 


 

함수 외부에서 내부 함수 실행하기

내부 함수로서 선언된 함수는, 자신을 선언한 부모 함수의 외부에서 호출할 수 없다는 사실을 위에서 살펴보았습니다. 하지만, 이 내부 함수를 부모 함수 외부에서 실행할 수 있는 방법이 존재합니다. 이는 Javascript의 클로저라는 아주 중요한 성질과 연계되는 방법입니다. 클로저 역시 추후 따로 자세히 학습할 예정이니 우선은 작동 방식에 대해서만 익숙해지도록 하겠습니다. 

 

아래 예제 코드를 살펴보도록 하겠습니다. 아래 예제에서는 child( ) 함수를 parent( ) 외부에서 호출하여 child( ) 함수를 parent( ) 함수 내부에서 실행한 것과 동일한 동작을 하도록 의도하였습니다. 

 

// 부모 함수
function parent() {
    var variA = "PARENT A";

    // 내부 함수 표현식으로 선언
    var child = function() {
        console.log(variA);
    }

    return child;
}

var childResult = parent();
childResult();

 

위 함수가 앞서 살펴본 예제와 다른 점은 무엇일까요? 우선 내부 함수를 "표현식"으로 선언하여 child라는 함수 변수를 만들었다는 점 입니다. 그리고 parent( ) 함수 마지막에 이 child라는 함수 변수를 리턴하였습니다. 

 

그리고 parent( ) 함수 외부에서는 별도의 childResult라는 변수에 parent( )를 호출한 결과를 대입하였습니다. 그럼 결과적으로 childResult는 child라는 함수 변수와 동일한 함수 선언을 참조하는 또 다른 함수 변수가 된 것입니다. 

 

이 상태에서 parent( ) 함수 외부에서 childResult( )를 호출하면, 어떤일이 벌어질까요? 위에서 살펴본 성질대로라면, variA라는 parent( ) 함수가 가진 변수를 사용해야 하기 때문에 에러가 발생해야 합니다. 하지만, 이러한 형태로 함수를 리턴하여 실행하는 경우에는 동일하게 variA에 접근이 가능하여 정상적으로 "PARENT A"가 출력됩니다. 이렇게 함수를 리턴하여 이미 실행 범위가 종료된 부모 함수 스코프를 참조하는 함수(childResult)를 Javascript에서는 클로저로 칭합니다. 클로저는 대단히 중요한 개념이기 때문에 별도로 상세하게 학습할 예정이니, 우선은 스코프 체이닝과 마찬가지로 기본적 사용 형태에만 익숙해 지기를 바랍니다.