반응형

자바스크립트의 함수는 자바나 C/C++에 비해서 특이한 점들을 가지고 있다. 본 포스팅에서는 자바스크립트 함수의 특이한 점들을 기술한다.


1. 함수 호이스팅

 함수 호이스팅은 함수 선언문 형태로 정의하였을 때 실제 함수 코드의 위치와 관계없이 참조점을 가지는 것을 의미한다. 함수 호이스팅이 발생하는 이유는 자바스크립트에서 코드가 실행될 때 실행 문맥이 생성되는 과정에서 함수 선언문 형태와 함수 표현문 형태가 동작하는 방식이 다르기 때문이다. (함수 선언문과 표현문 형태의 차이는 이전 글에서 설명하였다.)

 실행 문맥은 자바스크립트의 실행 단위라고 볼 수 있다. 현재 어떤 문장이 실행되고 있을 때 그 문장이 실행되고 있는 환경인 것이다. 실행 문맥은 현재 실행중이 코드가 참조할 수 있는 변수 객체들을 가지고 있는데 이 변수 객체는 현재의 문맥에서 실행 가능한 함수도 포함된다.

 실행 문맥이 만들어 질때,  변수 객체들의 생성이 우선적으로 발생하고  코드가 실행되면서 이 변수 객체들이 초기화 된다.  자바스크립트에서 변수가 선언되어 있으면 그 실행 문맥이 만들어 질때 해당 변수에 대한 참조값을 우선 만들고, 코드가 실행 되면서 초기화 된다.

 여기에서 함수 선언문 방식과 표현식 방식이 다르게 동작하는 것이다. 함수 선언문 방식은 실행 문맥이 만들어 질때 해당 함수에 대한 초기화까지 같이 된다. 즉 해당 함수를 실제 호출할수 있는 상태의 참조점이 만들어지는 것이다. 이에 비하여 함수 표현식 방식은 함수 표현식으로 할당되는 변수의 참조점이 만들어지기는 하지만, 실제 그 함수가 실행될수 있는 형태로 초기화 되지 않는다.

> function testVar(){

... decFunc(); //1)

... expFunc(); //2)

... function decFunc(){

..... console.log('decFunc');

..... }

... var expFunc=function(){

..... console.log('expFunc');

..... }

... } 

> testVar()

decFunc //1)

TypeError: expFunc is not a function //2)

 상기 예제에서 1)의 경우 함수 함수 선언문 방식으로 생성되어 참조점과 초기화가 실행 문맥상에서 되어 있는데, expFunc의 경우 참조점은 만들어져 있는데, function(){} 부분이 실행되지 않아서 초기화되어 있는 상태가 아니다. 따라서 2)의 expFunc 는 undefined()와 똑같은 호출 결과가 되는 것이다. 실행 문맥에 대한 내용은 이전 포스팅에서 자세히 설명되어 있다.


2. 익명 함수 & 콜백 & 즉시 실행 함수

 익명함수는 함수 이름을 정의하지 않은 함수이다. 일반적으로 콜백을 받기 위해서 함수 호출시 인자로 익명 함수를 전달하기도 한다.

> setTimeout(function(){

... console.log('setTimeout');

... },1000); 

 함수를 정의함과 동시에 바로 실행하는 함수를 즉시 실행 함수라고 한다. 이 함수도 익명 함수를 응용한 형태이다. 익명 함수를 ()로 감싸고 ()로 호출할 수 있다.

> (function(param){

.. console.log('hello '+param);

... })('world')

hello world

 이런 패턴은 한번 실행되면 다시 함수를 호출할 수 없다. 이런 특징은 최초 한 번의 실행만을 필요로 하는 초기화 코드 등에서 사용될 수 있다. (jQuery 등의 호출 패턴에도 익명 함수 호출 방식을 이용한다. 이는 전역 네임 스페이스를 보호하기 위하여 함수 유효 범위를 제한하기 위해서 사용된다.)


3. 내부 함수

 함수 내부에 정의된 함수를 내부 함수라고 한다. 내부 함수는 자신을 정의한 부모 함수의 변수들을 접근할 수 있다. 내부 함수를 함수 호출 결과로 반환하지 않는 이상, 내부 함수는 그 함수를 정의한 함수 내에서만 호출 가능하다.

> function outerFunc(){

... var a=1;

... var b=2;

... function innerFunc(){

..... var b=3;

..... console.log('inner a:'+(a++)+' b:'+(b++));

..... }

... console.log('outer a:'+(a++)+' b:'+(b++));

... innerFunc();

... console.log('outer again a:'+a+' b:'+b);

... }

undefined

> outerFunc()

outer a:1 b:2

inner a:2 b:3

outer again a:3 b:3

> innerFunc()

ReferenceError: innerFunc is not defined

 내부 함수를 반환하여 부모 함수의 변수를 감추는 방법을 클로저라고 한다. 이는 내부 함수의 특성을 이용한 것이다.

> function outerFunc(){

... var a=1;

... function innerFunc(){

..... return a++;

..... }

... return innerFunc;

... }

undefined

> var inner=outerFunc();

undefined

> inner()

1

> inner()

2


4. 연속 호출

 어떤 객체의 메소드가 this를 반환한다면, 그 객체의 메소드를 연속해서 호출할 수 있다.

> var castObj={

... name:'hello',

... setName:function(inName){this.name=inName;return this;},

... disName:function(){console.log(this.name);return this;}

... }

undefined

> castObj.disName().setName('world').disName()

hello

world

{ name: 'world', setName: [Function], disName: [Function] }


5. 클로저

 자유변수는 참조하는 변수가 지역 변수가 아닌 경우를 뜻한다. 클로저는 '자유변수에 엮여 있는 함수'라는 뜻이다. 자바스크립트에서는 생명주기가 끝난 외부 함수의 변수를 참조하는 함수를 클로저라고 한다. 이는 실행 컨텍스트와도 관련이 있다.

 일반적으로 어떤 함수의 내부 함수는 그 함수 밖에서는 접근할 수 없다. 다만 내부 함수를 함수 반환값으로 리턴할 경우에는 접근이 가능하데, 반환된 내부 함수의 실행 컨텍스트의 [[SCOPE]]는 해당 내부 함수가 정의된 부분에 따라 [[SCOPE]]가 정해진다. 다시 말해 [내부함수 변수 객체]->[외부함수 변수 객체] -> [글로벌 변수 객체] 형태로 내부 함수의 [[SCOPE]]가 정해지고, 전역 실행 컨텍스트에서 반환된 내부 함수를 호출하게 되면 이[[ SCOPE]]를 그대로 가져가게 되는 것이다. 다음 그림을 참고하기 바란다.

 위 그림에서 inFunc 함수의 [[SCOPE]]는 3개의 링크를 가지고 있다. 그런데 outFunc가 호출되고 나서, inFunc를 반환하게 되면 inFunc 를 반환된 값으로 호출하게 되는데, 이때의 [[SCOPE]]는 outFunc, inFunc 모두 [[SCOPE]]로 가지게 되고 outFunc의 변수들을 inFunc에서 참조하게 되는 것이다. (상기 그림의 inFunc[[SCOPE]]가 가르키는 [2] outFunc Variable은 잘못 표기된 것임, [2] inFunc Variable이 맞음)

 클로저는 스코프체인의 첫 번째 객체가 아닌 그 이후의 객체를 참조하기 때문에 성능을 저하시키고 메모리 부담을 증가시킬 수 있다. 따라서 주의해서 사용해야 하지만, 이 기능은 자바스크립트의 강력한 기능이기 때문에 잘 활용할 수 있어야 한다. 클로저는 객체지향 프로그래밍 방식에서 캡슐화(정보 은닉)을 가능하게 해주며 함수형 프로그래밍에 활용된다. 

반응형
Posted by alias
,