블로그 내 검색

2011. 12. 2.

프로 자바스크립트 테크닉 책 오타 코드의 Closure

요새 프로 자바스크립트 테크닉(원제 : pro javascript techniques)이라는 책을 읽고 있다.

jQuery 로 알려진 자바스크립트 행복전도사(?) john resig 이 쓴 책으로, 서두부터 기초따위 집어치우고 자바스크립트의 여러 요소 및 테크닉을 다루고 있다.

그런데 코드 오류가 몇몇 보인다.
특히 사람들이 자바스크립트에서 까다로워하는 Closure 에서 말이다;

책에 실린 33페이지의  Closure 의 오류를 설명하는 코드중 일부이다
 
var obj = document.getElementById("main");
var items = [ "click", "keypress" ];
for(var i = 0; i < items.length; i++) {
    (function() {
        obj[ "on" + items[i] ] = function() {
            alert( "Thanks for your " + items[i] );
        };
    })();
}
분명 클로저의 개념을 설명하고 그것을 바로잡는 예시인데, 바로잡지 못하고 있다.
예상 결과는 엘리먼트를 클릭하면 "Thanks for your click" 이 출력되고, 키보드를 누르면 "Thanks for your keypress" 가 출력되어야 하지만 위 예제를 실행하면 오로지 출력은 "Thanks for your undefined" 이 된다.

이유는  Closure 의 동작 방식 때문이다.
클로저는 값을 복사하는게 아니다.
스코프를 참조할 수 있는 것이고 따라서 위의 예제에서 i는 for문이 종료되며 2가 되고,
이벤트 핸들러에서 참조하는 배열은 items[2]가 되는데, 이것은 없는 참조이므로 undefined 가 되는 것이다.
올바른 코드로 고치려면  Closure 를 제대로 할당해줘야 한다.

var obj = document.getElementById("main");
var items = [ "click", "keypress" ];
for(var i = 0; i < items.length; i++) {
    (function() {
        // 변수를 참조해둬서 이벤트 핸들러의 클로저에서 접근할 수 있도록 한다
        var item = items[i];
        obj[ "on" + item ] = function() {
            alert( "Thanks for your " + item );
        };
    })();
}
이렇게 하면  Closure 에 item이라는 변수를 이벤트 핸들러들이 참조할 수 있게 되므로 정상적인 출력을 기대할 수 있다.
이 문제는 다음에 설명하는 privileged 메서드에서도 나타난다.
책 42페이지의 코드이다.

function User(properties) {
    for(var i in properties) {
        (function() {
            this["get" + i] = function() {
                return properties[i];
            };
            this["set" + i] = function(val) {
                properties[i] = val;
            };
        })();
    }
}
var user = new User({
    name: "Bob",
    age: 44
});
alert(user.getname());
alert(user.getage());

이 코드는 앞선 코드보다 더욱 위험한 폭탄 코드를 담고 있다.
객체에 자동으로 get, set 메서드를 추가해주는 코드이지만, 실제로는 window 객체에 해당 메서드를 추가하고 그것도 반환은 getname()을 호출하든,getage()를 호출하든 무조건 44만 반환하는 코드가 나온다.

글로벌 객체는 어느 경우에서든지 함부로 수정되어서는 안되는데 이것은 그냥 수정해버린다. 그것도 이벤트 핸들러와 함께.

위 코드에서

for(var i in properties) {
    (function() {
        // this의 잘못된 사용!!! 여기서의 this는 글로벌 객체, 즉 window이다.
        this["get" + i] = function() {
            // i 변수는 클로저 성격상 무조건 age를 가리키게 된다!!!
            return properties[i];
        };
        // 위와 마찬가지...
        this["set" + i] = function(val) {
            properties[i] = val;
        };
    })();
}

이 부분, this를 사용하는 부분이 크게 잘못되어 있다.
내부함수는 외부 함수의 컨텍스트를 따르지 않는다는 사실 때문이다. 내부 함수에서의 this는 글로벌이며 자연히 위 코드에서는 window["on" + items[i]]... 가 되어 버린다.

또한 클로저 스코프의 잘못된 활용으로 그나마도 모두 함수 호출 결과로 마지막에 세팅된 프로퍼티 값인 44만을 반환한다.

원하는 값을 얻으려면 다음과 같이 코드를 변화시켜야 한다.


function User(properties) {
    // this를 관례적인 이름인 that에 할당해둔다.
    // 이렇게 함으로서 내부 함수에서 이것에 접근이 가능하다.
    var that = this;
    for(var i in properties) {
        (function() {
            // 클로저의 참조를 변화시키기 위해  i를 prop에 할당해둔다.
            var prop = i;
            that["get" + prop] = function() {
                return properties[prop];
            };
            that["set" + prop] = function(val) {
                properties[prop] = val;
            };
        })();
    }
}

중요한 부분에서 저런 오타 코드를 싣고 있는다면 큰 문제가 될텐데;
아쉬운 부분이다.

하지만 책 내용은 참 알찬 편으로 자바스크립트를 공부할 마음이 있다면 적극 추천하고 싶다. 소소한 오타에 주의하면서 말이다. (사실 그렇게 많지도 않다.)

댓글 1개:

  1. 책 보는 중에 오류났었는데 도움이 되었습니다. 감사합니다.

    답글삭제