태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.


* 이번에는 자바스크립트의 가장 중요한 function에 대해서 살펴볼건데, 자바스크립트에서 함수는 쉽게 넘어가면 아주 쉽게 넘어갈수 있기도 하지만, 깊게 들어간다면 정말 깊게 들어갈수도 있는 부분이다. 자바스크립트에서는 이러한 부분이 가장 중요하기 때문에, 함수의 선언과 사용에 멈추는 것이 아니라 함수를 호출하는 방법에 의해 this가 결정되는 부분까지 알아보자.


- 이전 글

2012/12/10 - [속깊은 자바스크립트 강좌] 시작 (예고편)

2012/12/17 - [속깊은 자바스크립트 강좌] 자바스크립트의 Scope와 Closure 기초




* function과 자바스크립트

: 자바스크립트는 다른 언어들보다도 function이 아주 중요한 위치를 차지하고 있다. 다른 언어에서는 class나 object가 중요한 위치에 있다고 한다면, 자바스크립트는 그 위치에 function이 있는 것이다. 이러한 자바스크립트는 function과 함께 object, 그리고 closure의 특성까지 합쳐져서 현재의 새로운 자바스크립트만의 개발 패러다임이 형성된 것이다. 그렇다면 이 function이 선언되는 방법을 살펴보자.

function sayYeah() {    //#1: function declaration
    console.log("Yeah");
};
sayYeah();

var sayHo = function () {     //#2: function expression
   console.log("Ho");
};
sayHo();


: 위는 함수의 대표적인 두가지 선언 방식이다. 이 두가지는 거의 비슷하다. 일단 호출되는 방식은 위의 두가지가 동일하다. 하지만 위와 아래의 차이점은 바로 함수가 생성되는 시점이다. 위의 #1는 함수는 자바스크립트가 로드될 때 생성하게 되고, 현재 함수가 선언되는 해당 context에 함수와 같은 이름의 local 변수가 생성되고, 그 변수에 sayYeah() 함수가 들어가는 것이다. 그 반면, #2는 해당하는 행이 실제로 실행되기 전까지 함수가 생성되지 않는다. 이것은 run타임/parse타임의 차이라고 볼수도 있지만, 좀더 깊이 들어가게 되면 또 딱 'parse' 타임이라고 볼수도 없다. 만약 global에 function declaration을 했을 경우에는 거의 parse타임과 일치하게 되는 것이 맞지만 다른 function scope 안에 넣게 된다면, 예를 들면 아래와 같을 때에는 언제 생성되는지 알면 좋을 것이다.

function hello() {    // #3
    console.log(sayHello());
    function sayHello() {
        return "Whats up";
    }
}

: 위와 같은 경우 hello()는 글로벌 scope에 있지만, sayHello()는 아니다. 따라서 만약 내부 함인 sayHello()를 hello()가 parse되는 초기 parse타임에 같이 바로 정의를 하고 호출가능하도록 파싱을 한다면, hello()의 context에 부여하는 것은 언제 hello()가 호출될지도 모르고 hello가 어떠한 context를 가질지도 확실하지 않은데다 sayHello가 접근이 불가능한 scope에 있는 것이므로 다소 비효율적인 설계임을 느낄 수 있을 것이다. 따라서 실제 설계가 어떻게 되어있는지는 조금 뒤에 함수가 호출되면 내부적으로 일어나는 순서를 보면서 조금 깊게 들어가보자.



* function의 선언 방법

: 일단 위의 #1와 #2의 차이가 더 명확하게 와 닿는 예를 살펴보자. 바로 위의 sayHello() 함수의 예는 function declaration으로 되어있다. 이것을 그대로 function expression으로 바꿔보자.


function hello() {    // #4
    console.log(sayHello());   // error!!
    var sayHello = function () {    //#5
        return "Whats up";
    };
}


: #3의 경우는 문제 없이 잘 돌아가지만, #4의 경우는 에러가 난다. 왜냐하면 function expression은 #5가 실제로 실행되기 전까지는 sayHello 함수가 생성되지 않고, #3의 경우는 미리 함수가 생성되기 때문에 에러가 안난다. 위의 두가지 함수 선언 방법 이외에도 위의 두가지를 아래처럼 섞어서 사용할 수도 있다.


var sayWow = function sayWhat(what) {
    console.log(what);
    if (what && what.length < 10) {
        sayWhat(what + "-" + what));
    }
};

: 이것은 named function 경우는 함수가 재귀함수일 경우 사용하게 된다. 함수 내부에서 재귀로 자기 함수를 다시 호출하고자 할 때에 sayWhat()을 다시 호출할 수 있다. 그냥 sayWow()를 해도 되기는 하지만 자바스크립트에서는 재귀함수를 위처럼 하는 것이 안전하다. 예를 들면 아래와 같은 경우 에러를 방지할 수 있기 때문이다.

var doStep = function nextStep(step) {
    if (step > 0) {
        console.log("Step! : " + this.name);
        nextStep.call(this, step - 1);
    }
};

var person = {name: "stranger", walk: doStep};
person.walk(5);
var unikys = {name: "unikys", run: doStep};
unikys.run(10);

: 위와 같이 다른 객체의 변수로 함수를 부여하는 경우 두 함수는 재귀함수가 각자 선언된 context를 따라가게 되므로 예상치 못하는 에러를 방지할 수 있게 되는 것이다. 이렇게 함수를 선언하게 되면 외부에서 함수를 접근할 수 있는 방법은 변수로 선언된 'doStep'으로만 할 수 있고, 함수 내부에서는 'nextStep'으로도 접근이 가능하다. 물론 위의 경우는 nextStep 내부 scope에서 doStep 변수가 접근이 가능하지만, 함수를 변수로 넘겨서 다른 scope로 넘어가는 경우 또 이야기가 달라질 수 있다.



* function의 활용

: 위처럼 함수를 선언했다면 이제는 활용을 해야하는 차례이다. 자바스크립트에서는 함수는 유연하고 다양하게 활용이 가능하다. 아래가 자바스크립트에서 함수가 활용되는 방법이다.

    • 단순한 함수로 사용
    • 변수로 선언해서 사용
    • 다른 함수의 인자로 넘겨서 사용
    • 동적으로 생성해서 사용
    • 객체를 생성할 때 사용
    • scope를 생성할 때 사용

- 단순한 함수로 사용하는 것은 모든 언어에서 일반적이다. 변수로 선언하는 것은 위의 #2인 경우로 볼 수 있다. 이것은 단순히 변수에 할당할 때 보다는 네임스페이스나 모듈에 함수를 부여할 때 이용된다. 간단한 예를 들자면 아래와 같다.

var unikys = {
    yell: function () {console.log("YELL!");}
};
unikys.yell();

- 좀 더 자세한 모듈과 네임스페이스는 아래의 글을 참고하면 된다.

2012/10/11 - [자바스크립트 라이브러리 만들기] 3. 기본 지식 - 모듈과 네임스페이스



- 다른 함수의 인자로 넘겨서 사용하는 것은 콜백 함수에서 자주 발견할 수 있다. 이러한 콜백함수를 인자로 넘겨줄 수 있는 강점이 자바스크립트의 가장 큰 장점중 하나이다. 아래는 일상에서도 쓰이는 간단한 예이다.

var callback = function (e) {
    console.log("Mouse clicked!");
};
document.body.addEventListener("click", callback);

: 위는 너무난 간단해서 무엇이 강점인지 모르겠지만, 조금더 깊이들어가서 event delegation 까지 가게 된다면 이러한 강점에 다시 한번 감탄하며 다른 언어에서도 이렇게 편하게 한다면 얼마나 좋을까 생각하게 될지도 모른다. 


- 다음으로 동적으로 함수를 생성해서 사용하는 것을 살펴보면 아래와 같다.

var func = new Function("a", "b", "return a+b;");
console.log(func(5, 6) === 11);


: 위와 같이 새로운 함수를 Function객체로 생성하여 사용할수도 있다. Function의 경우에는 인자의 수는 정해져있지 않으며, 전부다 생성되는 함수의 인자 변수명으로 사용되고, 마지막 인자만 함수 내부에 들어갈 소스코드 스트링으로서 활용된다. 이러한 경우 함수 내부의 소스코드가 스트링으로 동적으로 할당이 가능하게 되므로 사용자의 데이터나 서버의 데이터에 따라 다른 함수를 생성할 수 있게 된다.


- 객체를 생성할 때 사용되는 것은 바로 생성자이다. 이것은 constructor로 모든 자바스크립트 객체가 가지고 있는 생성자로의 레퍼런스의 주인이 되기도 하는 것이다. 바로위의 예에서 new Function() 과 같이 하는 것이 함수를 생성자로서 사용하는 것이다. 라이브러리에서 이러한 생성자로서의 활용을 많이 할 것이다. 


- 마지막으로 scope를 생성할 때에는 이전의 강좌에서 읽어보면 이해할 수 있을 것이다. 하지만 이것은 closure로 활용하게 됨으로써 각 function이 자기만의 저장소를 가질 수 있도록 해줄 수 있는 또 다른 응용 방법으로도 활용하게 될 것이다. 이것은 나중에 closure에 대해서 자세하게 다룰 때 다시 한번 공부해보자.



* 내부 function이 선언되는 '정확한' 타이밍

: 이번에는 다른 함수의 내부에서 함수가 function declaration으로 선언했을 때 생성되는 시기에 대해서 조금더 깊에 들어가보자. 로컬에 함수가 있는 경우에는 흔히들 말하는 '컴파일 타임'에 파싱되어 정의되어있는 것으로 알고 있을 수가 있는데, 스크립트 언어에서 내부 함수는 약간 다른 파싱 프로세스를 가지는 경우가 많다. 자바스크립트의 경우도 그러한데, 일단 원론적으로 함수가 호출되면 이루어지는 내부적인 구조를 Ecmascript 5 로부터 가져와서 한번 살펴보자. 아래는 Ecmascript 5 에서 정의하고 있는 함수가 호출 되었을 때 하게 되는 일들을 쓴 것이다.


  1. If the function code is strict code, set the ThisBinding to thisArg.

  2. Else if thisArg is null or undefined, set the ThisBinding to the global object.

  3. Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).

  4. Else set the ThisBinding to thisArg.

  5. Let localEnv be the result of calling NewDeclarativeEnvironment passing the value of the [[Scope]] internal property of F as the argument.

  6. Set the LexicalEnvironment to localEnv.

  7. Set the VariableEnvironment to localEnv.

  8. Let code be the value of F’s [[Code]] internal property.

  9. Perform Declaration Binding Instantiation using the function code code and argumentList as described in 10.5.


: 여기서 argument라던가 this를 정의하고, 변수 등 scope를 정의하는 부분을 넘어가고 나면, 마지막 9번 항목에 보면 Declaration binding instantiation이 일어난다고 한다. 이것은 function code와 argumentList를 이용해서 내부 함수에서 정의된 함수를 변수나 로컬 함수처럼 binding 한다는 것이다. 즉, 함수 안에 함수를 쓰게 되면 해당하는 내부 함수는 외부 함수가 호출 될 때 9번 단계에 의하여 그때에 선언하게 되는 것이다. 굳이 이것까지 알 필요는 없겠지만, function declaration과 function expression이 서로 다른 시기에 파싱되어 정의된다는 점, 그리고 function declaration은 정확하게는 '컴파일 타임'은 아니라는 점은 지식으로 가지고 있다면 좋을 것이다.

 

: 위와 같은 사실을 알게 되었다면 맨위의 sayHello() 예에서 조금이라도 더 최적화를 하려면 어떻게 해야하는지 고민이 가능할 것이다. 만약 함수가 확실하게 사용될 것이라면 function declaration으로, 함수가 선택적으로 서로 다른 함수가 호출 될 것이라면 function expression을 사용하는 것이 위의 9번 단계에서 조금 더 나은 퍼포먼스를 보장할 수 있을 것이다. 하지만 사실 이러한 차이는 피부로는 와닿지 않으므로 어떻게 사용하든 상관은 없지만 머리로만 인식을 하고 있자.


* function 함수 생성 방법 성능 비교

: 위의 #1 function declaration과 #2 function expression 두가지 함수 선언 방법이 있지만, 두 가지 생성 과정이 다르기 때문에 성능 또한 다를 것이다. 이 두가지의 성능면에서 차이가 없다고 많이들 하겠지만 더 깊이 들어가면 두 가지 방법의 성능 차이가 있기는 있다. 다만 중요한 것은 이것은 자바스크립트 엔진에 따라 독립적인 차이가 되는 것이다. 그렇다면 아래의 두가지 소스에 대해서 전체적인 브라우져의 차이를 살펴보면 아래와 같다.

// test for #1 function declaration
function myfunc() {
    alert("yo");
}
id1.onclick = myfunc;

// test for #2 function expression
var myfunc = function () {
    alert("yo");
}
id1.onclick = myfunc;

: 파란색이 #1 function declaration에 대한 초당 처리 가능한 명령 수이고, 빨간색이 #2 function expression에 대한 초당 처리 가능한 명령 수이다. 전체적으로 보면 function declaration이 성능이 좋고, 파이어폭스에서는 function expression이 성능이 좋다. 따라서 고려하고 있는 타겟 브라우져에 따라 다른 개발 방법론을 만들어야 성능이 개선될 것이다. 하지만 일반화를 조금 시켜보면 function declaration이 대체로 성능이 좋기 때문에, 더 사용을 하면 된다는 것을 알 수 있다. 물론 전부다 적용되는 것은 아니므로 조심스러운 태도로 이러한 주장을 펼치면 될 것이다.



: 이렇게 서로 다른 퍼포먼스가 나오는 이유는 자바스크립트 엔진과 브라우져의 성능 차이이다. 다른 것보다도 자바스크립트 엔진이 이러한 것에 크게 영향을 미치므로 일반적인 자바스크립트 엔진의 특징과 서로 다른 엔진이 어떻게 다르게 작동하는지 살짝 맛볼 수 있을 것이다.


* 사실 이번에 this에 대한 이해까지 다루려고 햇지만, function에 대해서 약간 깊이 들어간 것 같아서 이번에는 function declaration과 function expression의 차이에 대해서 공부한 것으로 마무리하고 다음에 function이 호출되는 방법과 this(context)의 이해에 대해서 한꺼번에 공부하자. 



* 정리

    • 함수는 변수로, 인자로, 리턴값으로, scope를 생성할 때 다양하게 활용된다.
    • 함수를 선언하는 방법은 크게 function declaration과 function expression이 있다. 
    • new Function()을 통해서 스트링으로 함수를 동적으로 생성할 수 있다.
    • function declaration은 컴파일 타임, 정확히는 외부 함수/글로벌 환경이 생성될때 파싱된다.
    • function expression은 해당하는 줄이 실행 될 때 파싱된다.
    • 멤버 변수로 정의할 때/변수로 활용할 때 function expression이 유용하다.
    • 대체적인 퍼포먼스는 function declaration이 좋지만 단정할 수는 없다.


* function declaration vs function expression 차이점 끝


- 다음 편

2013/01/10 - [속깊은 자바스크립트 강좌] 함수를 호출하는 방법과 this의 이해

2013/01/21 - [속깊은 자바스크립트 강좌] Closure의 이해 / 오버로딩 구현하기

2013/01/30 - [속깊은 자바스크립트 강좌] Closure 쉽게 이해하기/실용 예제 소스

2013/02/13 - [속깊은 자바스크립트 강좌] 쉬어가기: 웹 개발 방법론의 변화/자바스크립트의 재발견

2013/02/22 - [속깊은 자바스크립트 강좌] 객체지향의 기본: prototype

2013/10/04 - [속깊은 자바스크립트 강좌] 상속, new와 Object.create의 차이

2013/10/29 - [속깊은 자바스크립트 강좌] 글로벌(전역) 변수와 window 객체

2013/11/06 - [속깊은 자바스크립트 강좌] 변수 선언 방법에 대하여

2016/11/13 - [속깊은 자바스크립트 강좌] 마무리(는 책으로!)



저작자 표시 비영리 동일 조건 변경 허락
신고

이 글을 공유하세요.

  • 하얀늑대 2013.02.18 10:30 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 강좌 잘 보고 있습니다.
    궁금한 것이 있습니다.
    'function의 선언방법' 에서 3번째 예제 코드에 보면
    .
    .
    var doStep = function nextStep(step){
    if (step > 0){
    console.log("step! : " + this.name");
    .
    .
    에서 this가 왜 붙어야 하는지 궁금합니다.
    실행해보니
    로그: step! : stranger
    로그: step! :
    로그: step! :
    로그: step! :
    로그: step! :
    이렇게 처음 한번만 stranger를 불러올 수 있었습니다.
    this를 제거하면 stranger가 아예 출력되지 않았구요.
    이해가 잘 안되는데 설명 좀 부탁드리겠습니다.

    • Unikys 2013.02.18 11:01 신고  댓글주소  수정/삭제

      예리한 지적이십니다! 재귀로 호출하는 부분이 조금 수정되어야겠네요^^; this를 넣어서 사용하려고 하다가 잘못 들어가게 되었네요. 자바스크립트에서 this 때문에 고생하는 이유를 몸소 보여드렸네요 ㅎㅎ 알려주셔서 감사합니다!

      아, 그리고 this를 붙인 이유는 전혀 다른 함수가 새로운 객체에 붙을 수 있는 것을 다음 강좌에 대비해서 미리 보여드리려고 붙여봤습니다. 위의 person과 unikys는 전혀 다른 객체인데 같은 doStep 함수를 이용하고 있으면서, 서로가 또 다른 이름으로, person은 walk라는 멤버함수로, unikys는 run이라는 멤버함수로 다른 이름을 사용하게 됩니다. 서로 다른 객체지만 this를 통해서 각자의 객체 정보를 유지하고, 원래 함수에는 nextStep이라는 이름을 부여함으로써 서로 다른 객체 부여된 walk나 run이라는 이름에 구애받지 않고 재귀함수를 구현할 수 있다는 개념입니다. 만약 nextStep이라는 이름을 주지 않는다면, person객체는 walk 함수를 호출하는 재귀함수를, unikys는 run함수를 호출하는 재귀함수를 따로따로 만들어야하지만 그것을 간단하게 하나로 통일 시켜줄 수 있는 것이지요. 객체지향 개념에서 자주 이용하실 수 있을 것이지만, 사실 자바스크립트에서는 퍼포먼스의 이유로 재귀함수 자체의 사용을 자제하는 것이 좋아서 자주 이용은 안하게 되실거라 생각하니 개념을 이해하고 계시면 될 것 같습니다. 추가적으로 this에 대해서는 바로 다음편 강좌를 읽어보시면 이해하실 수 있으리라 생각합니다^^

  • 청은 2013.03.08 11:33 신고  댓글주소  수정/삭제  댓글쓰기

    항상 응원하고 있습니다. 건필하세요!

  • 2016.06.15 10:18  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • Unikys 2016.06.17 01:16 신고  댓글주소  수정/삭제

      의견 감사드립니다.
      적어주신대로 하는 것이 더 좋을 것 같네요!
      재귀 구조만 설명하려고 하다보니 테스트는 안 해본게 실수네요 ㅠ

  • KoRoGhOsT 2017.04.01 16:22 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 재귀함수를 쓸 때, 기명함수를 쓰느냐, 익명함수를 쓰느냐에서 아직 기명함수를 썼을 때의 이점이나, 이 때는 익명함수로 못써서 기명함수로만 써야 한다라는 예제를 찾지 못하였는데요...
    위에서 언급해주신 예제도 아래처럼 익명함수로 하더라도, 결과값이 정상적으로 나오더라구요.
    제가 뭔가를 놓치고 있는 것일까요?

    var doStep = function (step) {
    if (step > 0) {
    console.log("Step! : " + this.name);
    doStep.call(this, step - 1)
    }
    };
    var person = { name: "stranger", walk: doStep };
    person.walk(3);
    var unikys = { name: "unikys", run: doStep };
    unikys.run(5);

    • Unikys 2017.04.02 13:34 신고  댓글주소  수정/삭제

      안녕하세요. 솔직히 말해서 프로그래밍에서는 '반드시 이렇게 해야 한다'는 경우는 거의 없습니다. 대부분의 알고리즘이든 뭐든 work around는 존재하기 마련이지요. 하지만 '이렇게 하면 더 안전하다'는 경우는 아주 많습니다. 해당하는 예는 그러한 경우로, 여기서 재귀함수는 콜백 없는 단순 로직을 처리하고 있지만 만약 예를 들면 setTimeout 등을 이용하면서 재귀 처리한다고 할 때 외부에서 doStep 변수가 변경이 되거나 다른 컨텍스트로 넘어가버리면 해당 함수 안에서 재귀호출로 doStep을 참조하려고 하면 원래 의도하던 바와 다르게 재귀함수 호출이 될 수도 있겠지요. 따라서 재귀 호출이 필요할 때에는 기명함수를 사용하는 것이 나을수도 있습니다. 물론 대부분의 이러한 예제들은 그냥 '이러한 사용이 가능하다'의 예로 보시면 되고 지금과 같이 의문을 가지고 계속 생각하시면 좋을 것 같습니다. 특히 언급하신 예는 조금은 특수한 상황에서 조금 더 안전한 프로그래밍을 하기 위한 예이니까 그냥 알아만 두시고 특별하게 너무 과민하게 신경은 쓰시지 않으셔도 될 것 같습니다. 그냥 간단하게 이러한 것을 사용하는 예를 말씀드리자면, Worker를 사용하지 않고 대량 계산을 할 때 사용하는 기법으로 setTimeout과 재귀호출을 이용하는 방법이 있습니다. 대량 계산을 하나의 재귀함수로 setTimeout 없이 하게 되면 브라우저가 멈추기 때문에 UX를 최대한 살리고자 대량 계산을 여러번으로 쪼개서 재귀적으로 처리하는 기법이지요. 위의 재귀함수를 안전하게 호출해야 하는 경우는 그럴 때의 경우라고 생각하시면 되는데, 그러한 계산을 브라우저에서 하는 경우는 브라우저 게임 등이 아니면 거의 없으리라 생각합니다.
      그래도 이러한 의문을 가지고 계시는 것을 보니 생각을 더 넓게 하고 계시는 것 같네요 :)

질문이나 의견을 댓글로 달아 주세요

티스토리 툴바