2016년 1월 1일 금요일

javascript: Function Declaration and Expression ; 함수의 선언 방식

Javascript: 함수의 선언 방식

이번엔 Javascript에서 함수를 표현하고 정의하는 몇가지 방식과 각 방식들의 차이점, 특징들을 살펴보겠습니다.

함수 생성 방법

보통 Javascript에는 아래와 같이 세가지 함수 생성방법이 존재합니다.

Function Declaration( 이하 ‘FD’로 표현 합니다. )
함수 선언, 정해진 함수 선언문 형식으로 함수를 정의하며, C나 Java와 같은 언어에서 말하는 ‘선언’과 ‘정의’를 따로 구분하지 않는다. 함수의 선언문에 정의부가 항상 포함되어야 하는 구조로 ECMAScript1에서 정의하고 있다. 일반적인 특징은 정의부 마지막에 세미콜론(;)이 없으며 아래와 같이 정의된다( 마지막에 세미콜론이 있더라도 Browser가 이를 무시하는 경우가 대부분임 ).
function “함수명” ( [“매개변수 리스트 “] ) {
“함수 정의부”
}
Function Expression( 이하 ‘FE’로 표현 합니다. )
일반적으로 대입연산자(=)의 우변에 올 수 있는 표현식형태이거나 실행시간(run-time)에 동적으로 실행해야 하는 표현식 형태로 정의된다. 이름이 없는 형태로 많이 쓰이고, 구문분석시 함수의 선언문이 “표현식”으로 인지되면 “함수 선언”과는 다른 형태로 정의가 된다. 런타임에 실행되는 모든 표현식의 마지막이 세미콜론(;)으로 끝나듯, 함수 표현식도 세미콜론으로 정의부가 끝난다( Self-invoking function의 경우는 약간 예외가 될 수 있습니다. 이는 이후 내용에 설명하겠습니다 ).
var func = function [ “함수명” ] ( [“매개변수 리스트”] ) {
“함수 정의부”
};
Function creation with constructor( 이 방법은 그냥 넘어가겠습니다. )
new Function() 이라는 함수 생성자를 사용하여 생성하는 방식으로, 문자열로 복잡한 함수정의부를 구현해야 하는 단점으로 인해 현실적으로 잘 사용되는 방법은 아니다. 형태는 다음과 같다.
var func = new Function( “p1”, “p2”, “return p1 * p2 ” );

함수 생성방식별 특징

이 글을 쓰고 있는 2015년 12월 시점의 ECMAScript 의 최신 버전은 ECMA-262 6개정판( 2015년 6월 제정 )이지만 아마도 이를 100% 구현하고 있는 브라우저는 아직 없는것 같습니다. 아마 ECMA 5.x 버전을 대부분 지원할텐데요, ECMAScript 표준 자체에 브라우저별 세부 구현을 모두 정의하고 있는것이 아니라서 브라우저별로 약간씩 다른 결과를 보여줄 수는 있습니다. 이에 대해서는 제가 모두다 테스트를 해보지 못했기에, Google Chrome 47.0.x.x 버전을 기준으로 설명드리도록 하겠습니다. ECMAScript 기준으로 하면 5.1 버전 기준이 되겠네요.

함수 선언( Function Declaration )

앞선 포스트들( javascript: Hoisting / 호이스팅, javascript: Scope of variables and functions ; 변수와 함수의 범위 )에서 FD방식으로 정의된 함수들이 해당 Scope의 가장 처음으로 끌어올려진다는 사실을 아셨을 것입니다. 또한, 함수 선언문은 정의부에 또다른 함수 선언문을 가질 수 있다는 것도 아셨을 것입니다. 사실 이 것들이 가장 큰 특징들이며 function 키워드 앞에 공백문자를 제외하고 아무것도 오지 않아야 합니다. 이러한 호이스팅 특성 때문에 조건에 따라 함수를 정의하기가 어렵고 의도하지 않은 오버라이딩이 발생하기도 하지만, 어디서든 정의를 하면 어디서든 사용할 수 있다는 장점이 있습니다.

함수 표현( Function Expression )

함수 표현방식은 주로 변수에 할당하는 경우가 많은데 그러다보니 대입연산자( = ) 우측에 함수 정의부가 오는 경우가 많습니다. 대입연산자 없이 Javascript인터프리터가 동적으로 해석해야 하는 일반적인 표현식으로 되어 있는 경우에도 함수 표현식으로서 해석이 됩니다. “함수 선언”으로 만들어진 함수가 해당 Scope의 가장 처음으로 Hoisting되는것과 대조적으로 일반 표현식이기 때문에 함수 정의부가 별도로 Hoisting 되지는 않습니다( 다만 해당 함수를 담을 변수자체는 해당 Scope 최 상단으로 끌어올려지게 됩니다). 이 특징은 조건에 따른 함수 정의를 가능하게 해줘서 조금더 유연게 설계를 할 수 있다는 장점이 있습니다. 그리고 이후에(언제가 될지는 모르겠지만) 몇몇 Javascript Design Pattern을 적용하기 위해서 필수적으로 필요한 표현식이기도 합니다.

함수 생성방식별 비교

아래의 코드를 먼저 보시죠.

<script type="text/javascript">
    fnc1();
    fnc2();

    var variable = "defined";

    function fnc1() {
        console.log( variable );
    }

    ( function fnc2() {
        console.log( variable );
    } );
</script>

실행결과
undefined
exception[ fnc2 is undefined ]

위의 예를 보시면 “함수 선언”과 선언문을 (, )로 감싸고 있는 두가지 형태의 함수 생성문을 보실 수 있습니다. fnc1는 FD로 정의되어 가장 위로 끌어올려지고, “fnc1();” 구문이 문제없이 실행됩니다. 다만 호이스팅에 의해 의도와는 달리 전역변수에 접근을 못하는 일이 생겼네요. 그리고 fnc2는 FE로 정의된 함수이며, 괄로로 둘어쌓여 있습니다. 이것은 Javascript 표현식이기 때문에 호이스팅 대상이 아닙니다. 그래서 “fnc2();” 표현식이 실행되는 시점에는 그 정의가 없어서 invoke시점에 예외가 발생됩니다. 물론 fnc2를 FE로 정의하고 이를 담는 참조변수가 없기때문에 이 함수를 이후에 다시 호출할 기회가 전혀 없기도 합니다.

아래는 FE방식으로 함수를 생성해서 사용하는 예입니다.

<script type="text/javascript">
    var printUserInput;
    if( prompt( "Enter Y/N" ) == "Y" ) {
        printUserInput = function() {
            console.log( "User said, Yes!" );
        };
    } else {
        printUserInput = function() {
            console.log( "User said, No!" );
        };
    }

    printUserInput();
</script>

위의 코드는 사용자가 정확하게 대문자로 ‘Y’를 입력하면 “User said, Yes!”라는 문자열을 콘솔에 출력하고, 아닌경우는 “User said, No!”라는 문자열을 콘솔에 출력합니다. 사용자가 항상 대문자로 Y를 입력하리란 보장이 없으므로 대소문자의 구분없이 비교하는 함수 하나를 더 만들어 보겠습니다.

<script type="text/javascript">
    // 이 코드는 FE를 예로 들기위해 Java API를 흉내내어 만든 코드입니다.
    // 이해가 잘 안되시는 부분이 있더라도 그냥 넘어가시면 됩니다!
    String.prototype.equalsIgnoreCase = function( operand ) {
        if( this === operand ) {
            return true;
        }

        if( typeof operand === "undefined"
            || typeof operand !== "string" ) {
            return false;
        }

        if( this.toLowerCase() === operand.toLowerCase() ) {
            return true;
        }

        return false;
    };
</script>

<script type="text/javascript">
    var printUserInput;
    if( "Y".equalsIgnoreCase( prompt( "Enter Y/N" ) ) ) {
        printUserInput = function() {
            console.log( "User said, Yes!" );
        };
    } else {
        printUserInput = function() {
            console.log( "User said, No!" );
        };
    }

    printUserInput();
</script>

위의 코드에서 나온 String.prototype….. 부분에 대한 설명은 추후 다른 포스팅에서 설명하기로 하고 그냥 넘어가겠습니다. 어쨌든 해당 코드의 의미는 모든 String 객체에다가 전역적으로 equalsIgnoreCase라는 함수를 정의한 것이고 이때, FE방식으로 해당 함수를 정의했습니다. 한가지 주의해야 할 점은 이 역시 FE이기 때문에 equalsIgnoreCase라는 함수를 사용하기 전에 미리 정의가 되어야 한다는 점입니다( <script> 태그를 별도로 분리한것은 별도의 모듈이란 의미에서 제가 임의로 분리를 한 것이니 크게 신경쓰지 마세요 ).

사실 대소문자를 구분없이 비교하고자 할때, 아래처럼 FD를 사용해서 함수를 만들 수도 있겠죠.

function equalsIgnoreCase( leftOperand, rightOperand ) {
// .... 구현부
}

“이렇게 해서는 안된다.”라는건 아니지만 선택은 여러분의 몫입니다. 이 예제와 같은 케이스에서 저는 개인적으로 FE방식으로 하는게 코드의 양을 줄일수 있다고 보고 있습니다.

이번엔 FD와 FE를 섞은 코드를 한번 보시죠.

<script type="text/javascript">
    getBackToMe( function() {
        console.log( "나 왔어요!" );
    });

    function getBackToMe( callback ) {
        console.log( "돌아오면 연락 주세요." );
        callback();
    }
</script>

아주 기초적인 callback함수 사용예시를 만들어 봤습니다. 분명히 getBackToMe만 호출했는데, 내가 호출한 함수가 매개변수로 넘긴 FE를 대신 실행해 주었지요. 이런 패턴을 callback패턴이라고 하며 FE자체를 callback함수라고 합니다. 위의 예시는 가변적으로 넘어 올 수 있는 FE의 매개변수 등은 고려하지 않은 아주 단순한 예제입니다.

함수 표현식의 종류

지금까지의 예제에서는 FE를 모두 무명함수 즉, Anonymous Function으로 정의를 했습니다. 이름이 없다는 이야기죠. 사실 함수를 참조하는 참조변수의 이름을 함수의 이름으로 볼 수있겠지만, ECMAScript에서 이야기하는 함수 정의의 표준은 서두에서 설명드렸듯이 아래와 같습니다. 함수명과 매개변수 리스트는 생략이 가능하여 [, ]로 감싸져 있죠.

function [ “함수명” ] ( [“매개변수 리스트”] ) {
“함수 정의부”
};

따라서 FE는 아래 두가지로 또다시 구분이 가능해 집니다.

  • Anonymous Function Expression ( AFE ; 무명 함수 표현식 )
  • Named Function Expression ( NFE ; 기명 함수 표현식 )

각각의 예는 다음과 같습니다.

// Anonymous Function Expression( AFE )
var afe = function() {
    console.log( "I do not have name." );
};

// Named Function Expression( NFE )
var nfe = function functionWithName() {
    console.log( "My name is nfe." );
};

이 둘의 기능적인 차이는 단 1%도 없습니다. 다만, 최근 거의 모든 브라우저에 탑재된 Debugger를 사용하면서 Call Stack이나 Profiler 등을 확인할 경우에는 조금 다른 면이 있습니다.

NFE는 정확하게 각 함수의 이름을 표시해 주지만 AFE의 경우 ( anonymous function )과 같은 형태로만 보여줍니다. 기능이 복잡해져서 함수의 개수가 셀 수 없는 지경이고, 코드는 몇천, 몇만 라인이 되는 경우 anonymous function이 대부분이 되게되면 디버깅의 지옥을 맞보게 됩니다.

이 역시도 Javascript 엔진 마다 약간씩의 구현차이가 있어서 참조변수 이름을 표시해 주는 경우도 간혹 있습니다. 하지만 참조변수가 없는 callback 함수와 같은 것들은 죽었다 깨어나도 이름을 붙여줄 수가 없지요.

평소에 디버거에 별로 의존하지 않으시는 분들은 크게 걱정없이 AFE만을 사용하셔도 무방하겠지만, 디버거에 의존성이 높은 분들은 기왕이면 NFE로 코딩을 하시는편이 정신건강에 이로울것 같습니다.

그럼 디버깅 이야기는 여기서 마무리하고, 또다른 함수 표현식 이야기를 해 보겠습니다.
아래 코드를 한번 보시죠.

<script type="text/javascript">
    var module = ( function() {
        var somethingPrivate = function() {
            // 외부에 공개하지 않고 private하게 처리해야 하는 일들.
        };
        var initialize = function() {
            // 모듈 초기화 등등...
        };

        somthingPrivate();
        initialize();

        return function() {
            // 외부에 제공할 모듈 API등...
        };
    })();
</script>

Self-invoking Function의 단순한 좋은 예가 마땅히 떠오르지 않아 모듈 패턴이라고 불리우는 형태를 잠시 빌려왔습니다. 디자인 패턴에 대한 이야기가 아니니 그냥 휘리릭 넘어가고 함수 정의 형태자체를 설명하겠습니다. 보통 공통모듈, xx모듈, OO모듈 등등 어떤 모듈을 만들어 배포할 때 그 모듈을 사용하기 위해 무조건 처음에 반드시 실행되어야 하는 로직들이 있을것입니다. 하지만 외부에서는 별도로 해당 로직들을 호출하지 못하게 해야할 때, 사용하면 아주 좋은 패턴입니다.

module이라는 객체참조변수는 마지막 return에서 돌려주는 AFE를 받게 됩니다. 결국 somthingPrivate, initialize 함수 두개는 module이라는 변수를 통해서는 접근을 하지 못하게 됩니다. 대신에 위 스크립트가 해석될 때 딱 한번 실행이 됩니다. 그러면 module을 사용하고자 하는 사람들은 따로 초기화 등을 호출할 필요없이 그저 Open된 API를 활용하면 됩니다.

Self-invoking Function은 지난번 다른 포스팅( javascript: Scope of variables and functions ; 변수와 함수의 범위 )에서 별도의 Local Scope을 만들때도 사용해 봤었는데요, 사실 그렇게 사용하기 보다는 이렇게 디자인 패턴에 사용이 되어지는게 더 주된 용도입니다.

정리

아주 짤막하게 함수의 정의 방법과 각 유형별 특징들을 살펴봤습니다. 물론 이 포스팅에 모든것이 다 나온것은 아니지만, 각 방법들에 대한 주요특징들과 차이점 등은 잘 이해하셨으리라 생각됩니다.

FD이던 FE이던 함수의 구현부를 결정하는 알고리즘이 사실 더 중요할 수도 있겠지만, 일반적인 협업환경에서 사실 어떤 정의방법이 “정답이다.” 라고 말하기는 어렵습니다. 뜻하지 않은 Function Overriding 가능성을 줄이고, 가독성을 높이고, 코드의 중복을 최소화 하고, 기능별로 모듈화해서 모듈별 응집성을 높여서 유지보수성을 높이는 방향으로 함수를 정의하고 사용하는것이 목표가 아닐까요?

읽어주셔서 감사드리구요,
이번 포스팅은 여기서 마무리하겠습니다.

Written with StackEdit.


  1. 위키백과 ECMAScript 정의 참고

2015년 12월 30일 수요일

javascript: Scope of variables and functions ; 변수와 함수의 범위

Javascript: 변수와 함수의 범위

이번엔 Javascript의 변수와 함수들의 Scope(범위)에 대해서 이야기 해 보겠습니다.
기왕이면 일전에 한 포스팅(javascript: Hoisting / 호이스팅)을 먼저 한번 읽어보시는게 도움이 될것 같습니다.

( 이번 포스팅 부터는 StackEdit를 사용해서 포스팅 합니다. ^^ )

Scope이란?

Javascript에서 Scope(범위)은 함수단위로 관리됩니다. C, C++, C#, Java와 같은 언어는 Block단위로 scope이 관리되지만, 특이하게도 Javascript에서는 function단위로 scope이 관리됩니다.

참고:
블럭(Block)은 구문(Statement)들의 집합을 말합니다.
대부분의 언어에서 그렇듯, 중괄호({})로 둘러싸인 부분을 이야기 합니다.

아래의 두가지 코드를 보시죠.

// Java
public Class ScopeTest {
    public static void main( String[] args ) {
        int integer = 0;
        System.out.println( integer ); // 0

        {
            int integer = 1;
            System.out.println( integer ); // 1
        }

        System.out.println( integer ); // 0
    }
}
// Javascript
<script type="text/javascript">
    var integer = 0;
    console.log( integer ); // 0

    ( function() {
        var integer = 1;
        console.log( integer ); // 1
    } )(); // 무명 함수표현식을 사용한 1회성 실행함수

    console.log( integer ); // 0
</script>

위의 Java코드( Java를 모르셔도 상관없습니다. )를 보면 중괄호({})로 둘러싸인 코드 블럭이 보이실겁니다. 그 블럭은 별도의 Scope을 가지고 있어서 int integer라는 별도의 지역변수를 가질 수 있습니다.

반면 Javascript의 경우 Scope이 블럭단위가 아닌 function단위로 관리되기 때문에 약간 생소해 보일수도 있는 ( function() {…} )(); 표현식으로 별도의 Local Scope을 만들어 봤습니다( 이 표현식에 대해서는 다른 포스팅에서 상세히 다루도록 할게요… ).

사실 이전 포스팅(javascript: Hoisting / 호이스팅)에서 함수는 가장 처음으로 Hoisting된다고 설명을 드렸지만 조금더 정확히 말씀드리자면, 해당 Scope의 최 상단으로 끌어올려진다고 보시면 됩니다.

그럼 아래의 코드를 보시고 그 결과를 예상해 보세요.

<script type="text/javascript">
    var variable = true;
    init();
    console.log( variable );

    function init() {
        console.log( variable );
        var variable = false;
        dupInit();
        console.log( variable );

        function dupInit() {
            var variable = -100;
            console.log( variable );
        }
    }

    console.log( variable );
    dupInit();
</script>

이 코드의 실행결과는 아래와 같습니다.

Browser Console :
undefined
-100
false
true
true
[ Exception : dupInit() undefined ]

사실 위의 예제는 Hoisting을 잘 알고 계셔야 이해가 되실겁니다. 먼저 호이스팅이 되는 위치가 모두다 각 Scope의 최 상위라는것만 기억하시면 위의 결과에 대해 의문점이 해소되시리라 믿습니다. 가장 마지막 dupInit() 호출부에서 Exception이 발생하는 것에도 주목해 주세요. dupInit 함수의 scope은 init함수이므로 그 외의 범위에서는 접근할 수 없는 별도의 Local Scope이 됩니다.

위의 코드가 실제로 실행될 때는 아래와 같은 모습으로 변환이 되어 실행이 됩니다.

<script type="text/javascript">
    function init() {
        function dupInit() {
            var variable;
            variable = -100;
            console.log( variable );
        }

        var variable;
        console.log( variable );

        variable = false;

        dupInit();

        console.log( variable );
    }

    var variable;
    variable = true;

    init(); // undefined, -100, false
    console.log( variable ); // true
    console.log( variable ); // true
    dupInit(); // exception
</script>

저 역시 Java를 하다가 Javascript를 접하고 한참 지난 시점까지 이 Scope에 대한 이해없이 코딩을 해 왔습니다. 그저 Block이 기준이겠거니… 하고 말이죠. 하지만 이걸 알고, 모르고의 차이는 UI Script양이 많고 복잡한 경우에 어쩌면 밤을 안새고, 새고의 차이가 될 수도 있으니 꼭 기억해 두시기를 권장합니다.

사실 이 Scope에 대한 포스팅은 hoisting, function expression과 연관이 있어서 같이 설명을 드릴까.. 하다가 포스트 자체가 너무 길어지고 복잡해 질 것 같아서 분리했습니다. 이후에 이어질 function declaration / function expression 에 대한 내용과 함께 같이 봐 주시면 더 도움이 되리라 믿습니다.

이번 포스팅은 여기까지 하겠습니다.

Written with StackEdit.

2015년 12월 27일 일요일

javascript: Short Circuit Evaluation ; 단락 회로 평가

Short Circuit Evaluation, 한국말로 하자면 '단락 회로 평가'입니다. 이거 뭐임? 하는 분들도 계시겠지만, 많은 분들이 알고 있는 내용이기도 하며, javascript 뿐만 아니라 수많은 고급 프로그래밍 언어의 특징 중에 하나이기도 합니다.

원래 초기 컴퓨팅 파워가 엄청 비싼 시절(진공관 컴퓨터 밖에 없었을때요..)에 전력사용량 절감과 한 싸이클이라도 연산 속도를 빠르게 하기 위해 생겨나게 된 개념입니다.

거두절미 하고 아래 코드를 보시죠.
( 좋은 코드는 아닙니다만, 설명을 위한것이니 그냥 그러려니.. 해주세요. )

var code = 1;
var value = 0;
if( code++ || value++ ) {
    console.log( "Code" );
} else {
    console.log( "Default" );
}
console.log( "Code : " + code );
console.log( "Value : " + value );

논리적으로만 보자면,
Code
Code : 2
Value : 1
이 나와야겠죠?

하지만 실제로는
Code
Code : 2
Value : 0
이 나옵니다.

비밀은 '||' 연산자에 있습니다. 아주 흔히 사용되는 이 연잔사는 피연산자가 두개인 이항연산자 입니다. 즉, 연산자를 중심으로 좌변과 우변에 식이 올 수 있는데요, 'or'의 특성상 좌변의 평가결과가 참이면 우변은 참이던 거짓이던 전체결과는 무조건 참이므로 우변에 대한 평가를 생략합니다. '&&'의 경우 'and'의 특성상 좌변이 거짓이면 우변은 평가를 생략하게 되죠. 이 평가기법이 Short Circuit Evaluation, 또는 단락 회로 평가라고 하는 특징입니다.

이 단락회로평가가 평소에는 그저 조금더 빠른 연산을 보장하지만, javascript에서는 특히나 아래와 같은 활용이 가능해 집니다. ( 아래 코드는 sourceString이 undefined이거나 null인 경우에만 활용이 가능하며, sourceString에 숫자 '0'이 들어오지 않는다는 보장이 있을 경우에만 사용해야 합니다. )

function wrap( sourceString ) {
    var normalizedString = sourceString;
    normalizedString || ( normalizedString = "" );
    return "<my-tag>" + normalizedString + "</my-tag>;
}

위의 wrap함수는 "<my-tag>undefined</my-tag>"나 "<my-tag>null</my-tag>"와 같은 이상한 출력을 방지하기 위한 코드입니다.

또는 아래와 같은 함수가 있을수도 있겠죠?

var lightCost = false;
var heavyCost = false;
if( lightCost = lightCostOperation() && heavyCost = heavyCostOperation() ) {
   ....
}

위의 코드는 lightCost가 false인 경우 heavyCostOperation을 수행하지 않을 수 있습니다. 일반적으로 if-then-else 구문에서 각각의 조건블럭이 3개를 초과하면 가독성이 떨어진다고 볼 수 있는데, 이 단락 회로 평가를 활용하면 조건블럭을 줄일 수도 있고, 비용이 높은 연산을 자연스럽게 스킵하는 효과도 거둘 수 있습니다. 이 것은 대부분의 고급언어에서 많이 활용되고 있는 기법입니다.

이렇게 단락회로평가를 하지 않도록 하고 싶으시다면, '&&'나 '||' 대신에 '&', '|'를 사용하시면 됩니다.( 사실 '&', '|'는 Javascript의 Bitwise연산자 이기 때문에 피연산자가 정수로 변환될 수 있는 타입이라면 의도하지 않는 연산이 수행될 수도 있습니다. )

프로그래밍을 많이 해 보신 분들에게 단락 회로 평가는 그리 신기하거나 특이한 기능은 아닐 수도 있습니다. 하지만 이걸 모르면 심각한 버그를 키워낼 수 있는 만큼 기본 중의 기본이라고 할 수 있겠습니다.

단락회로 평가에 대한 소개는 여기서 마치도록 하겠습니다.

2015년 12월 22일 화요일

javascript: Hoisting / 호이스팅

Javascript의 Hoisting에 대해 소개합니다...


Hoisting( [명사] 끌어 올리기; 들어올려 나르기. / 네이버 영어사전).
단어의 뜻만 보자면 선뜻 이해가 안될 수도 있습니다.

가장 쉬운 예로,  함수를 가장 마지막에 선언하고, 그 함수를 가장 첫줄에서 호출해도 별 문제없이 동작하는 것에 그 비밀이 숨어있습니다. 이 호이스팅은 아주 훌륭한 특성입니다. 인터프리터 언어인 자바스크립트의 파싱성능(일반적인 멀티패스 방식의 컴파일링 알고리즘에서 구조/의미분석 단계가 더 빨라질수 있습니다)을 개선하고, 개발자는 별도의 선언없이 필요할때 마다 변수를 선언하여 사용할 수 있습니다.
아주 일반적인 아래의 코드를 보시죠.

console.log( myFunction() );
function myFunction() { return "내 함수";}

위에서 myFunction()이 호출될 당시에 사실 myFunction이란 것은 정의되지 않은 상태로 보입니다. 하지만 아무런 문제없이 Javascript Engine은 뒤에 정의된 myFunction을 정상적으로 호출해 줍니다.
이 현상(?)은 Javascript엔진이 해당 스크립트를 로드할 때 알아서 myFunction의 정의부를 맨 위로 끌어올려 주기 때문에 가능한 일입니다. 예를 들면 아래와 같이 말이죠.

function myFunction() { return "내 함수";}
console.log( myFunction() );

일단 간단히 설명 하자면, 함수 정의부는 모두 위로 끌어올려져서 모두다 미리 파싱이 되어진다는 것입니다. 이 이외에 함수표현식( FE; Function Expression )을 사용하는 경우는 약간 양상이 다른데, 이 이야기는 더 많은 할 이야기가 있으니 다른 포스팅에서 말씀드리도록 하겠습니다.

어찌보면 호이스팅은 자바스크립트 엔진의 어쩔 수 없는 선택인것 같기도 합니다. 하지만 이러한 점 때문에 정말 조심해야 하는 경우도 생깁니다. 다음 코드의 결과를 한번 생각해 보시죠.

var counter = 0;
counter++ ;
function printOut() {
    console.log( counter );
    var counter = 0;
    console.log( counter );
}
printOut();

결과는 아래와 같습니다.
undefined
0

도대체 왜일까요?
전역변수가 1이 되어
1
0
이 나와야할것 같은데 말이죠?

아래는 위의 코드에 자바스크립트 파서가 호이스팅을 적용한 결과입니다.

function printOut() { // function hoisted
    var count; // variable hoisted
    console.log( counter );
    counter = 0;
    console.log( counter );
}
var counter;  //variable hoisted
counter = 0;counter++ ;
printOut();

먼저 함수 정의부를 가장먼저 위로 끌어올리고, 그다음 변수의 선언문을 끌어올립니다.

자바스크립트의 이러한 호이스팅 특성을 모르는 경우라면 이건 버그라고 생각될 수도 있습니다. 그래서 혼란을 겪게 될 수도 있습니다. 로직이 매우 복잡한 경우에는 더욱 그렇구요. 대체로 이 호이스팅은 아주 훌륭하게 동작합니다. 하지만 모든 함수의 정의부가 모두 처음에 로딩되기 때문에 정의된 함수가 Running Scope에 많은 경우라면 성능이 떨어지는 경우도 발생할 수 있습니다.( 이 부분 역시 차후에 함수 표현식에 대해 포스팅할 때 말씀드리겠습니다. )

그래서 일반적으로 버그의 가능성을 줄이고자 모든 선언은 코드블럭의 최상단에 하는것을 추천 합니다. 또는 "use strict"라는 컴파일러 지시자를 통해 이런한 부작용을 인터프리터의 도움을 받아 회피할 수도 있습니다. 이 use strict에 대해선 W3Schools를 참고해 주세요. ("use strict" 는 IE8, 9 등 일부 구식 브라우져에선 지원하지 않으니 이점 참고해 주세요.)

"use strict";
var counter = 0;
counter++ ;
function printOut() {
    console.log( counter ); // Error
    var counter = 0;
    console.log( counter );
}
printOut();

호이스팅을 정리하면,

  1. 함수 정의부 호이스팅
  2. 변수 선언부 호이스팅
위와 같이 크게 두 단계가 가장 먼저 수행됨을 항상 생각해 주세요. 뜻하지 않는 버그를 피하기 위해선 항상 변수를 각 실행 블럭의 가장 최상단에 정의한다고 생각하시면 됩니다.

호이스팅에 대해선 여기까지만 하겠습니다.

2015년 12월 21일 월요일

내 두번째 오픈 소스 프로젝트( ws-string-binder.js )

프로젝트를 할때마다 이건 어디다가 만들어 놔야지... 했던건데 gitHub, Node Package Manager 공부하는 과정에서 오픈소스로 올려봤습니다..

2015년 12월 16일 수요일

javascript: Date Object

Javascript Date Object를 소개합니다...


생각날 때 마다 하나의 주제에 대해서 강좌를 써내려갈까 합니다.
언젠가는 많이 모이겠거니... 하는 마음으로요... ㅎㅎ

그럼 시작합니다.

Date객체는 아래와 같이 생성할 수 있죠.

  1. new Date();
  2. new Date( milliseconds );
  3. new Date( dateString );
  4. new Date( year, month, day, hours, minutes, seconds, milliseconds );

Date 객체가 가지고 있는 프로퍼티와 메소드들은 W3Schools에 아주 자세히 나와있으니 한번 살펴보세요.

1. new Date();

이 방법은 가장 단순하게 Date객체를 생성하는 방법이며, 현재 날짜와 시간으로 객체를 생성합니다. 년, 월, 일, 시, 분, 초, 밀리초 가 객체 생성시점으로 초기화 됩니다.


2. new Date( milliseconds );

이 방법은 1970년 1월 1일 0시 0분 0초 0밀리초 부터 넘겨진 milliseconds 까지의 밀리초를 기준으로 객체를 생성하는 방법입니다. 1970년 이전의 날짜를 지정할 때는 음의 정수로 설정할 수 있습니다. 참고로 이 글을 작성하고 있는 2015년 12월 16일 오후 10시 37분 경은 1450273039059 로 표현이 됩니다.

지금이 2015.12.16 22:37 경이라고 할 때,
  • new Date();
  • new Date( 1450273039059 );
위 두 객체는 매우 유사한 값을 같은 객체가 됩니다.


3. new Date( dateString );

여기서 말하는 dateString은 설명이 어려우니 또한번 W3Schools를 방문해 보세요.


4. new Date( year, month, day, hours, minutes, seconds, milliseconds );

위의 3가지 방법과는 달리 파라미터가 유난히 많습니다.
개인적으로는 1번 방법과 함께 가장 중요한 생성자라고 생각이 됩니다.

일반적으로 Date 객체를 사용해서 날짜비교, 날짜 차이계산 등의 연산을 많이 하게 됩니다.

하지만 Date객체의 특성을 제대로 이해하지 못하면 자칫 버그를 만들어낼 가능성이 있습니다.

아래 코드를 한번 살펴봐 주세요.
var today = new Date();
var userInput = new Date( "2015-12-16" );
if( today === userInput ) {
    // L1. 입력날짜가 오늘인 경우 수행할 로직
} else {
    // L2. 입력날짜가 오늘이 아닌경우 수행할 로직
}
위의 코드에서 이상한 점은 무엇일까요? 과연 위의 코드가 의도대로 동작을 할까요?
위에서 L1 로직에 진입하기 위해서는 로또에 당첨될 만큼의 운이 따라줘야 합니다.
console.log( today );
// Wed Dec 16 2015 22:54:55 GMT+0900 (대한민국 표준시)
console.log( userInput );
// Wed Dec 16 2015 09:00:00 GMT+0900 (대한민국 표준시)
위의 결과 예제를 보시면 이해가 되실겁니다.

여기서 첫번째 중요한 사실이 있습니다.
Date객체를 사용할 때는 반드시 시간을 밀리초까지 고려해야 한다는 점입니다.

그럼, 아래의 코드를 봐주세요.
var dateFrom = new Date( "2015-12-16 23:00:00.000" );
var dateTo = new Date( 2015, 11, 16, 23, 0, 0, 0 );
console.log( dateFrom == dateTo ); // false
console.log( dateFrom > dateTo ); // false
console.log( dateFrom < dateTo ); // false
console.log( dateFrom.getTime() == dateTo.getTime() ); // true
console.log( dateFrom.getTime() > dateTo.getTime() ); // false
console.log( dateFrom.getTime() < dateTo.getTime() ); // false
첫번째~세번째 출력결과가 모두 false, 네번째는 true 입니다.
여기서 dateTo를 초기화 할때 12월을 11로 설정한 것은 javascript에서 month 파라미터를 0~11까지범위로 나타내기 때문입니다. 처음 Date객체를 사용하시는 분들은 실수하기 딱 좋은 부분이니 주의해 주셔야 합니다.

다시 본론으로 돌아와서, 도대체 왜 첫번째 ~ 세번째는 모두 false일까요? 같지도 다르지도 않다는 뜻일까요? 첫번째 수식의 비밀은 '==' 연산자에 있습니다. Java와 유사하게 '==' 연산자는 두 객체 자체를 비교합니다. dateFrom과 dateTo는 각각 인스턴스화 된 개별 객체입니다. 그래서 두 객체가 다르다고 표현을 하게 됩니다. 희안하게도 '>', '<' 연산자에 대해서는 operator overloading이 되어 있는지.. 두 객체의 값을 비교하게 됩니다.

어쨌든 제가 말씀드리고자 하는것은, 두 Date객체의 값을 비교할 때는 네번째 방법이 가장 안전하다는 것입니다. 대소비교는 물론이고 등위비교를 할 때도 모두 안전한 방법입니다.
만약 시간, 분, 초, 밀리초는 무시하고 싶을 경우 밀리초값에 약간의 산수를 더하면 정확하게 할 수 있게 됩니다.
var dateFrom = new Date( "2015-12-16" );
var dateTo = new Date( 2015, 11, 16, 23, 25, 49, 555 );
dayFrom = parseInt( dateFrom.getTime() / 1000 / 60 / 60 / 24, 10 );
dayTo = parseInt( dateTo.getTime() / 1000 / 60 / 60 / 24, 10 );
console.log( dateFrom.getTime() == dateTo.getTime() ); // false
console.log( dayFrom == dayTo ); // true
위와 같이 밀리초, 초, 분, 시간 단위를 역산해서 날짜까지만 숫자로 변환을 할수가 있습니다.

오늘은 여기까지 하겠습니다.


내 첫 오픈소스 프로젝트( wsCalendar )

어디다가 올리기 부끄러운 코드지만 용기를 냈습니다.

하지만 git, node 등... 그저 소스를 오픈하기 위해서 배워야할 것들이 또 나를 기다리고 있었다는 사실...ㅠㅠ

이것 저것 공부하려고 여기저기 돌아다니다 보니...
절대 강자들이 즐비한 글로벌 세상에서 정말 보이지 않을 정도로 작은 나 자신을 느꼈습니다.


다음달, 그 다음달 이 코드가 무한하게 부끄러워 지길 바라면서...

저의 첫 오픈소스 프로젝트를 소개합니다.

wsCalendar for jQuery plugin




gitHub( GIT Repository )
https://github.com/tcpip98/wsCalendar

NPM( Node Package Manager )
https://www.npmjs.com/package/wscalendar

제 3의 어딘가... ( 고맙게도? 내 소스를 소개해준곳 )
http://www.jqueryscript.net/time-clock/Feature-rich-Date-Picker-Plugin-with-jQuery-wsCalendar.html