본문 바로가기

Development/Coding

유능한 C# 개발자가 착각하는 나쁜 자바스크립트 습관. Part 3

http://enterprisejquery.com/
How Good C# Habits can Encourage Bad JavaScript Habits 라는 제목으로 올려진 연재글 발.역.
* Java 개발자나 C# 개발자들이 자바스크립트를 코딩할 때 자주 쓰이는 잘못된 방법을 예를 통해 설명하고 옳은 방법(자바스크립트 다운 방법)을 제시해주고 있는 좋은 글이네요.



지난 포스팅 내용
  1. Having Variables & Functions in Global Scope
  2. Not Declaring Arrays & Objects Correctly

두번째 포스팅 내용
  1. Not Understanding False-y Values
  2. Not Testing & Setting Default Values Correctly
  3. Using the Wrong Comparison Operators
  4. Not Using the for…in Statement Correctly




파트 3

Introduction

변수와 함수 영역에 대한 헷갈리는 점을 알아보자고.


7. Misunderstanding Scope in JavaScript

자바스크립트 개발을 하면서 중요한 한가지는 블럭 영역과 함수 영역이 다르다는 것을 이해하는 것이야.
보통 시샵에서 블럭 안에서 변수를 선언하면 그 블럭 안에서만 접근 가능한 로컬변수가 되는데 자바스크립트는 좀 달라. 함수 영역 안에서 선언을 하면 그 영역 안에 있는 다른 함수 안에서 사용을 할 수도 있다는 것이야.

// Code written thinking that JavaScript has block scope
function eat() {
    var food = "bacon",
        isHungry = true;
 
    if ( isHungry ) {
        //A C# developer might think that timeToWait is
        //only accessible from within this if statement,
        //but that is not the case
        var timeToWait = 10;
 
        console.log( "Waiting " + timeToWait + " minutes" );
 
        chew();
    }
 
    function chew() {
        var bodyPart = "mouth";
 
        //The chew function also has access to the
        //timeToWait variable because it is part of the
        //eat function's scope
        console.log( "After waiting " + timeToWait +
            " minutes, " + "I am eating " + food +
            " with my " + bodyPart );
    }
}
 
eat();
//Waiting 10 minutes
//After waiting 10 minutes, I am eating bacon with my mouth


위 코드를 보면 시샵 개발자는 timeToWait 라는 변수가 chew 함수에서 접근이 가능하지 않을 것이라고 생각하겠지만 자바스크립트 로컬 변수의 영역을 생각해보면 그렇지 않음을 알게되지.

위 코드 eat 함수 상단에 모든 변수를 선언한다면 블럭 영역에 익숙한 개발자들은 좀 더 변수 접근 가능 범위에 대해 명확하게 이해되겠지? 보통은 아래 코드처럼 코딩할 거야.

// Code written understanding JavaScript has function scope
function eat() {
    //Declare all your variables at the top so that it is
    //obvious you are using function scope and not block
    //scope
    var food = "bacon",
        isHungry = true,
        timeToWait, bodyPart;
 
    if ( isHungry ) {
        timeToWait = 10;
 
        console.log( "Waiting " + timeToWait + " minutes" );
 
        chew();
    }
 
    function chew() {
        bodyPart = "mouth";
 
        console.log( "After waiting " + timeToWait +
            " minutes. " + "I am eating " + food +
            " with my " + bodyPart );
    }
}
 
eat();
//Waiting 10 minutes
//After waiting 10 minutes, I am eating bacon with my mouth


☞ 좋은 습관

어쨋든 함수 내에서 사용할 변수를 모두 상단에 배치하는 것은 좋은 방법이야. 그 변수들은 함수 내부 영역에서 어느 곳에서든지 사용할 수 있다는 것을 알아둬야하겠지.

무엇보다도 사용할 로컬 변수를 함수의 상단에 두어야하는 강력한 이유가 있다는 걸 알겠될거야.




8. Not Knowing Variable and Function Hoisting

위에서 테스트 했던 것처럼 자바스크립트는 블럭 영역 대신 함수 영역으로 변수의 접근 범위를 잡고 있어. 그 뒤에는 hoisting 이라고 불리는 자바스크립트의 구조가 있기 때문에 가능해지지.

- 붕뜬 변수

자바스크립트에서 함수 안의 모든 변수는 같은 영역을 공유하고 있어. 함수 내에서 자바스크립트는 모든 변수를 살펴보고 그 함수의 상단으로 붕 띄워 선언을 하는 과정을 가지고 있어.
그리고 코드가 진행하면서 그 변수들이 선언된 위치에서 값을 할당하게되는데 이 과정을 variable hoisting 이라고 해.

다음 코드를 보자고.

//Code You Might Write
function sayHello( firstName, middleName, lastName ) {
    var fullName = firstName + " " + middleName + " " +
        lastName;
 
    if ( fullName === "Carlos Rey Norris" ) {
        var nickName = "Chuck";
 
        console.log( "Hello " + nickName + " " +
            fullName.split(" ")[2] );
    }
}
 
sayHello( "Carlos", "Rey", "Norris" ); //Hello Chuck Norris


위 코드에서 자바스크립트는 fullName 과 nickName 이란 변수를 선언하지만 실제 변수가 선언되고 초기화되는 과정은 조금 변해.

아래의 코드가 실제 자바스크립트가 처리하는 순서야.

//How JavaScript Interprets It
function sayHello( firstName, middleName, lastName ) {
    //Hoists all the variable declarations to the top
    //of the function and sets to undefined
    var fullName = undefined,
        nickName = undefined;
 
    //fullName assignment remains in original position
    fullName = firstName + " " + middleName + " " + lastName;
 
    if ( fullName === "Carlos Rey Norris" ) {
        //nickName assigned remains in original position
        nickName = "Chuck";
 
        console.log( "Hello " + nickName + " " +
            fullName.split(" ")[2] );
    }
}
 
sayHello( "Carlos", "Rey", "Norris" ); //Hello Chuck Norris



- 헷갈리는 것 1

아리까리한 상황을 한번 보자고.

//Where You Can Get Into Trouble
for ( var i = 0; i < 10; ++i ) {
    for ( var i = 0; i < 5; ++i ) {
        console.log( "Hello" );
    }
}

개발자의 의도는 Hello 를 50번 출력하는 것인데 실제 코드를 돌리면 무한 루프에 빠지게 되지. 시샵 개발자 관점에서는 루프 컨디션의 i 는 서로 다르지만 자바스크립트에서는 붕 뜬 변수 과정을 통해 같은 i 가 한번 선언되지. 그래서 바깥 루프의 i 는 10에 도달할 수 없게되고 코드는 끝나지 않아.


- 헷갈리는 것 2

변수의 영역 정의 때문에 헷갈리는 코드 하나 더. 아래 코드의 someVariable 는 두 곳에서 선언되고 있어. 딱 보면 이상없이 동작할 듯 하지만 그렇지 않아.

//Where You Can Get Into Trouble
 
console.log( someVariable ); //undefined
var someVariable = 42; //Global variable
console.log( someVariable ); // 42
 
function doSomething() {
    console.log( someVariable ); // undefined
    //Why is someVariable undefined?
    //Developer expected to see 42 from global variable
 
    someVariable = 1
    console.log( someVariable ); // 1
 
    console.log( window.someVariable ); // 42
    //Why is window.someVariable 42?
    //Developer thought he just set global variable to 1
 
    if ( false ) {
        var someVariable = 0;
    }
}
 
doSomething();
 
console.log( someVariable ); // 42
//Why is someVariable is 42?
//Developer expected to see 1 from global variable

위 코드를 자바스크립트가 해석하는 코드로 변환해보면 아래처럼 진행해.

//How JavaScript Interprets It
 
var someVariable = undefined;
console.log( someVariable ); //undefined
someVariable = 42; //Global variable
console.log( someVariable ); // 42
 
function doSomething() {
    //Because of variable hoisting var is
    //moved to the top of the function and set to undefined
    var someVariable = undefined;
    console.log( someVariable ); // undefined
 
    someVariable = 1
    console.log( someVariable ); // 1
    //This above line of code didn't set the global
    //instance, but the local one that was hoisted
 
    console.log( window.someVariable ); // 42
 
    if ( false ) {
        someVariable = 0;
    }
}
 
doSomething();
 
console.log( someVariable ); // 42
//Because of variable hoisting in
//doSomething the code inside never updated
//the global variable

if 안에 있는 var someVariable = 0; 문장 때문에 변수 띄우는 작업을 거치고 함수 내부 가장 위에서 로컬 영역의 someVariable 가 선언된 상태야. 그렇게 되면 왜 이런 결과가 나오는지 이해가 되지.

doSomething 함수 내부의 변수 중 window.someVariable 을 제외하고 모든 someVariable 변수는 로컬 변수를 말하는 변수들이지.



- 그러면 함수 띄우기는

변수 띄우는 과정을 이해했다면 이제 함수 쪽을 보자고. 함수 띄우는 과정은 변수 띄우는 과정과 조금은 달라. 그 전에 함수 선언과 함수 표현에 대해 이해해야하는데 가장 쉬운 방법은 function 키워드 다음으로 쓰여진 것이 함수 선언 이라는 것이야.

//Example with various function declarations
 
try {
    sayHello(); //Error because not defined
} catch (e) {
    //Property 'sayHello' of object [object DOMWindow]
    //is not a function
    console.log(e.message);
}
sayGoodbye(); //Goodbye
 
//Function Expression
var sayHello = function() {
    console.log("Hello");
};
 
sayHello(); //Hello
sayGoodbye(); //Goodbye
 
//Function Statement
function sayGoodbye() {
    console.log("Goodbye");
}
 
sayHello(); //Hello
sayGoodbye(); //Goodbye


함수 선언은 자바스크립트 내부에서 함수 표현으로 변형되고 변수들과 함께 hoisting 과정을 거치게 되지. 즉 아래의 코드처럼 처리가 되고 있어.

//How JavaScript Interprets It
 
var sayHello = undefined,
    sayGoodbye = undefined;
 
//Function Statement was converted to a
//function expression and both the variable
//and assignment were hoisted to the top of
//the function
sayGoodbye = function() {
    console.log("Goodbye");
};
 
try {
    sayHello(); //error
} catch(e) {
    //Property 'sayHello' of object [object DOMWindow]
    //is not a function
    console.log(e.message);
}
sayGoodbye(); //Goodbye
 
//The declaration of the Function Expression was
//hoisted to the top of the function, but the
//assignment stayed in the original location
sayHello = function() {
    console.log("Hello");
};
 
sayHello(); //Hello
sayGoodbye(); //Goodbye
 
//sayGoodbye Function statement was here, but got
//completely hoisted to the top of the function
 
sayHello(); //Hello
sayGoodbye(); //Goodbye


위 코드에서 보듯 함수 표현은 변수 띄우기와 같은 방법으로 처리되고 있고 함수 선언은 조금 변형되어 처리되고 있지. 함수 선언은 함수 표현으로 바로 변형되어 hoisting 됨과 동시에 정의되고 있어.


☞ 좋은 습관

자바스크립트는 변수의 영역을 제어하는데 함수 영역을 사용하고 있고 각 변수들은 붕 띄워져 함수 상단에 선언된다는 것.
그래서 가능하면 코드의 이해를 돕기 위해 변수 선언은 함수 상단에서 해야한다는 점.




9. Not Using Closures Correctly Or At All

모질라 개발자 네트워크에 클로저에 대한 상세한 설명이 있어. 중요한 정의는 아래를 보면 됨.

“A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.”


클로저는 특정한 영역에 있는 변수를 접근하기 위해 사용하는데 아래의 코드를 비교해보며 클로저로 코드를 풀어가는 방법을 보자고.



- 클로저 없이 처리해보자

의도는 이렇슴. 10개의 DOM 에 이벤트를 걸어주는 코드인데 각 DOM 은 아이디로 구분이 되어 있어. 물론 잘 작동하지만 원래 의도한 대로 동작하지 않아.

//broken
var unorderedList = $( "ul" );
for (var i = 0; i < 10; i++) {
    $("<li />", {
        id: i,
        text: "Link " + i,
        click: function() {
            console.log("You've clicked " + i);
        }
    }).appendTo( unorderedList );
}


실행해보면 결과가 우리가 원한게 아니지. 이벤트가 걸린 함수가 마치 한 곳을 바라보는 것처럼 느껴질거야.


- 클로저로 루프를 사용해보자

//working
var unorderedList = $( "ul" ), i;
 
for (i = 0; i < 10; i++) {
    $("<li />", {
        id: i,
        text: "Link " + i,
        click: function(index) {
            return function() {
                console.log("You've clicked " + index);
            }
        }(i)
    }).appendTo( unorderedList );
}


DOM 에 적용하는 이벤트 함수를 바로 실행되는 익명함수로 정의하고 있어. 익명함수는 말 그대로 이름이 없는 함수로 코드는 해당 시점에서 즉시 실행되고 있어. 익명함수 블럭에 i 를 전달하면서 즉시 실행되로록 하는 작업을 해주고 있어. 생성되는 <li> 엘리먼트에 고유한 익명함수 이벤트가 바인딩되는 거라고 이해할 수 있지.

이 기술의 핵심은 변수의 영역을 이벤트를 실행해주는 함수 내부 영역으로 잡아주고 있다는 것이야.
이벤트가 등록되면서 이벤트 함수에 i 값을 전달해줄 수 있게 되는 것이지.


- 일반함수로 감싸진 클로저로 처리하자

위 코드는 좀 이해하기 어렵다면 아래의 코드를 보자고. 익명함수를 포함하는 일반함수에 이벤트 핸들러에 전달할 값을 전달하고 그 값을 처리하고 있어.

var unorderedList = $( "ul" ), i;
 
for (i = 0; i < 10; i++) {
    $("<li />", {
        id: i,
        text: "Link " + i,
        click: clickEventHandler(i)
    }).appendTo( unorderedList );
}
 
function clickEventHandler( index ) {
    return function() {
        console.log("You've clicked " + index);
    }
}



☞ 좋은 습관

위의 예제에서 보듯 클로저는 특정 변수의 상태를 유지하는데 매우 유용하게 사용될 수 있어. 물론 이런 이유말고도 클로저를 사용하는 많은 방법들이 있겠지. 내가 자주 사용하는 리베일링 모듈 패턴도 시샵에서 사용하던 프라이빗 메소드와 프로퍼티를 시뮬레이션하기 위한 컨셉으로 클로저를 사용하고 있어.

클로저에 대해 더 많은 코드를 보려면 Script Junkie 의 Juriy Zaytsev 가 쓴 Use Cases for JavaScript Closures 포스팅을 참고하도록 해.




이제 응용해봐.

- 유용한 툴

웹에서 즉시 자바스크립트 코드를 실행해보기 위해 JSLint 를 사용해봐. Douglas Crockford 가 만들었다고!
또는 DoctorJS 를 사용해도 좋지.

- 추가 소스들

Douglas Crockford 의 비디오 시리즈

추가로 MS 의 스크립트 정키 사이트에서 강력하게 추천하는 글들

그리고 Julian Bucknall 의 JavaScript for C# Programmers.
좀 오래되긴 했지만 여전히 짱이야.


Conclusion

복잡하고 오묘한 자바스크립트 랭기지.






끝.


- 잘못된 점 지적해주셔서야 선무당이 되지 않습니다요.