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