블로그 내 검색

2012. 2. 28.

자바스크립트 패턴 #3 - 플라이웨이트 패턴 (Flyweight Pattern)

자바스크립트 라이브러리 레벨에서 자주 쓰이는 플라이웨이트 패턴(Flyweight Pattern)은 사용시 굉장한 자원의 절약과 메모리의 낭비를 막아줍니다.

자바스크립트는 따로 C나 Java같은 컴파일 과정이 없어(현대식의 자바스크립트 엔진, 가령 구글 크롬의 V8엔진의 경우 컴파일 비슷한 과정과 재정렬을 한다고 들었던 것 같습니다만) 프로그램 시 프로그래머가 직접 컴파일러가 해주는 코드의 효율화에 더욱 신경써야 합니다.

플라이웨이트 패턴이란 프로그래밍시에 많은 수의 유사성을 가진 객체들이 생성되어질 때, 그 객체끼리는 공통된 부분과 공통되지 않은 부분을 분리하여 관리하여 공통되는 부분을 객체들이 공유하도록 하는 패턴입니다.

그리고 한번 생성된 공유 객체는 저장하여 재생성을 (풀링이나 캐싱 등으로)막아 메모리 사용을 줄입니다.

  • 공유되는 부분은 이미 생성되었다면 생성된 것을 공유하고, 아니라면 생성하고 공유함.
  • 한번이라도 생성한 객체는 다시 생성하지 않음

이때 객체를 생성하는 방법으로 팩토리를 이용하는 것이 일반적이지만, 객체 자신이 제공하는 특정 메서드를 통해 직접 공유 자원을 자신이 참조하는 방식이 되기도 합니다.

말로 하니까 굉장히 어렵습니다.

일단 어떠한 방식으로 구현하더라도 다음과 같은 개념들을 구현하게 됩니다.

Factory
객체를 제공하는 팩토리 구현입니다. 프로그램에서 쓰일 객체들을 공급합니다.

Intrinsic
객체들이 공유하는 본연의 특성들을 모은 것입니다.
공유되는 부분이죠.
간혹 불변(immutable) 이 되기도 합니다.

Extrinsic
Intrinsic 부분 외의 서로 다른 외부 특성입니다.

플라이웨이트 패턴에서 제일 많이 예제로 표현되는 것은 문서 편집기의 구현입니다.
편집기 구현때 문자 객체는 문서가 몇페이지가 넘어가든 단 1개만 생성되고, 그것에 대한 참조만을 가지고 위치를 달리하여 문서를 구성하게 됩니다. 실제 문자 하나하나 생성하다간 문서가 몇페이지 넘어가지 않고 Out of Memory를 볼 수 있겠지요.

또 다른 하나는 Java 언어의 스트링(String) 구현입니다.
스트링이 기본 타입으로 생성될 때, (즉 String h = "hello" 같은) JVM은 특정한 내부 팩토리 함수를 호출합니다. 팩토리 함수는 자신이 가진 플라이웨이트 사전을 뒤져 이미 있다면 그것을 리턴하고 없다면 내부적으로 스트링을 생성하여 사전에 넣고 리턴합니다.
// 두 String은 변수 같은 String 인스턴스를 참조한다.
String msg1 = "Hello world1";
String msg2 = "Hello world1";

if(msg1 == msg2 && msg1.equals(msg2)) {
    System.out.println("두 문자열은 완전히 동일하다");
}
String 객체는 훌륭한 플라이웨이트 패턴 구현이지요.

이제 자바스크립트 코드로 구현해 봅시다.

친숙한 게임인 스타크래프트를 예로 들어보겠습니다.
A 유저와 B 유저가 대결합니다. 그리고 게임이 시작되었고 서로 같은 종족인 저그를 선택했다고 가정합니다.

유닛의 구조를 다음과 같이 잡아봅시다.
var Unit = function(name, maxhp, cost, player, upgrade) {
    this.name = name;
    this.maxhp = maxhp;
    this.cost = cost;
    this.player = player;
    this.upgrade = upgrade;
    this.curentHp = maxhp;
}
Unit.prototype = {
    move: function() { ... },
    attack: function() { ... },
    defence: function() { ... }
}
서로의 유닛은 같은 객체입니다.
A가 생산한 히드라리스크와 B가 생산한 히드라리스크는 유닛이름, 최대체력, 생산비용이 같겠지요. 게임은 공정해야 하니까요.

이 부분은 그럼 공통 속성 (Intrinsic) 이 됩니다.

하지만 조종하는 플레이어와, 그 플레이어가 적용한 업그레이드 레벨은 다를 것입니다.
이 부분은 외부 특성 (Extrinsic)이 됩니다.

본격적으로 패턴을 적용해 봅시다
외부 특성은 빼고 공통 속성만 남겨보면 다음과 같이 됩니다.
var Unit = function(name, maxhp, cost) {
    this.name = name;
    this.maxhp = maxhp;
    this.cost = cost;    
}
이제 공통 속성을 관리해주면서 유닛을 생산할 팩토리 객체를 만듭니다.
모듈 패턴을 사용합니다.
var UnitCreator = (function() {
    
    // 한번 생성한 객체는 다시 생성하지 않기 위한 저장소
    // private 이다.
    var units = {};

    // 모듈 패턴을 사용한 public 속성들
    return {
        create: function(name, maxhp, cost) {
 
            var unit = units[name];

            // 저장소에 이미 있다면 그것을 리턴
            if(unit) {
                return unit;
            }

            // 없다면 생성하고 저장소에 저장
            else {
                units[name] = new Unit(name, maxhp, cost);
                return units[name];
            }
        }
    } 
    
})();
이제 게임에서 유닛의 조종자와 각종 기능을 수행할 컨트롤러를 만듭니다.
var UnitController = (function() {
    
    // 플라이웨이트 객체를 관리할 맵
    // 역시 private 속성.
    var unitsPlayedDictionary = {};

    var self = this;

    // 모듈 패턴을 사용한 public 속성들
    return {
        produceUnit: function(id, name, maxhp, cost, player) {
            
            var unit = UnitCreator.create(name, maxhp, cost);
            
            // 공유 자원과 객체 특성을 조합 (컴포지트 패턴)
            var unitOnPlay = {
                unit: unit,
                id: id,
                player: player,
                currentHp: unit.maxHp,
                upgrade: 0                
            };
            unitsPlayedDictionary[id] = unitOnPlay;
        },     

        kill: function(id) {
            var u = unitsPlayedDictionary[id];
            if(unit) {
                u.currentHp = 0;
                delete unitsPlayedDictionary[id];
            }
        },
        
        upgradePower: function(id) {
            var u = unitsPlayedDictionary[id];
            if(unit) {
                u.upgrade = u.upgrade + 1;
            }
        },
        
       /* 기타 메서드들 ... */
    }    
    
})();
위에 든 예제가 좀 허접합니다. 기회가 된다면 좀더 좋은 예제를 들어 설명해보고 싶네요.

패턴을 잘 살펴보면 몇가지 패턴이 모여 플라이웨이트 패턴을 만드는 것을 볼 수 있습니다. (모듈 패턴, 컴포지트 패턴)

유명 자바스크립트 라이브러리인 ExtJs, jQuery는 이 플라이웨이트 패턴을 적극적으로 활용하여 속도 및 메모리 효율성을 끌어올리고 있습니다.

공통된 속성은 라이브러리가 제공하는 유용한 기능이 되겠지요.

플라이웨이트 패턴은 분명 컴퓨터 입장에서는 효율이 좋습니다.
그러나 프로그래머 입장에서는 굉장히 불편한 패턴입니다. 과용시 코드의 가시성이 굉장히 떨어지며 유지보수가 힘든 스파게티 코드가 될 위험이 많습니다.

디자인 패턴이란 언제나 과용은 금물이죠;

포스팅하며 참고한 사이트들입니다.

2012. 2. 27.

자바스크립트 패턴 #2 - 모듈 패턴 (Module Pattern) (수정)

모듈 패턴은 전통적인 소프트웨어 공학에서 클래스 사용에 private 과 public 으로 나뉜 캡슐화를 제공하는 방법이다.

자바스크립트에서의 모듈 패턴은 전역 영역에서 특정 변수영역을 보호하기 위해 단일 객체 안의 public/private의 변수를 포함할 수 있는 각 클래스 형식의 개념을 구현하는데 사용된다. 이 패턴으로 당신이 페이지에 추가한 추가적인 자바스크립트가 다른 스크립트와 이름이 충돌하는 것을 줄여줄 수 있다.


(발번역이지만 내용 전달은 되었길 빕니다;)

자바스크립트는 private, public 등의 접근 제한자를 언어차원에서 지원하지 않습니다.
하지만 모듈 패턴을 사용하여 그런 접근 제한을 구현해낼 수 있습니다. 요새 CommonJS, AMD (Asyncronouse Module Definition) 등의 자바스크립트의 표준화의 진행은 자바스크립트의 모듈화에 주안점을 두고 있지요. 그 근간은 이 모듈 패턴입니다.

고급 사용자라면 더욱 미려하고 시적인 모듈 패턴을 구사할 수도 있겠지만 저의 지식이 그에 못미치는 관계로 기초중에 기초만 설명해볼까 합니다.

모듈 패턴을 이해하려면 클로저와 컨텍스트에 대한 지식이 조금은 필요합니다.

그럼 간단한 코드를 예로 들어보죠.
서버에서 데이터를 얻어와 반환하는 코드의 한 예시입니다.
이때 한번 요청한 데이터는 캐싱되어야 합니다.
// 데이터 캐시
var dataCache = {};

// 데이터 캐시 아이디
var id = 0;
    
var url = '/default/data';
// ... 기타 사용 변수

var connectServer = function() { ... }
var sendRequest = function() { ... }
var parseData = function(data) { ... }
var getData = function() {
    connectServer();
    var data = sendRequest();
    dataCache[id++] = data;
    return parseData(data);
}
위와 같은 코드는 물론 잘 동작합니다.
하지만 전역 공간에 변수가 무분별하게 선언되어 있습니다. 이는 위에서 모듈 패턴의 정의에서 말한것과 같이 추가적인 스크립트(외부 라이브러리든 다른 개발자에 의해) 가 있을 경우 이름이 충돌할 수 있습니다. 함수 이름도 getData같은 아주 흔한 이름이기에 출동 가능성은 더욱 높습니다.
(혹시 이를 대비해 함수 이름을 어렵고 길게 만들자는 생각을 하신분은 없으시겠죠)

그리고 모든 변수들은 public 접근제한 상태입니다.
connectServer,  sendRequest,  parseData 이 세개는 특별한 경우 외엔 다른 곳에서 쓰일 필요가 없습니다.

private 접근제한이 적당할 것 같습니다

전역에 변수를 선언하고 싶지 않으려면 다음과 같이 익명 함수를 통한 선언을 해 볼 수 있습니다.
// 데이터 캐시
var dataCache = {};

// 데이터 캐시 아이디
var id = 0;

// 익명 함수로 감싸 전역 객체를 더럽히지(?)
// 않는다.
// 하지만 여전히 캐시는 전역에...
(function() {
    
    var url = '/default/data';
    // ... 기타 사용 변수
    
    var connectServer = function() { ... }
    var sendRequest = function() { ... }
    var parseData = function(data) { ... }
    
    var getData = function() {
        connectServer();
        var data = sendRequest();
        dataCache[id++] = data;
        return parseData(data);
    }
    
    getData();
    
})();
자. 어떻습니까. 모든 변수들이 private 스코프가 되었군요. 익명 함수로 인해 전역 스코프 접근도 없게 되었습니다.
그런데 이런 방식으로는 코드의 재사용을 전혀 할 수가 없습니다. 매 데이터 요청 시 저런 긴 코드를 쓸 생각이 아니라면 좀더 생각해 봅시다.
// 데이터 캐시
var dataCache = {};

// 데이터 캐시 아이디
var id = 0;

// 별도의 네임스페이스 적용. 
// 역시 캐시는 밖에 있지만 함수는 재활용이 가능하다.
var getData = function(url) {        
    
    url = url || '/default/data';
    // ... 기타 사용 변수
    
    var connectServer = function() { ... }
    var sendRequest = function() { ... }
    var parseData = function(data) { ... }
    
    connectServer();
    var data = sendRequest();
    dataCache[id++] = data;
    return parseData(data);
}
이건 나름 좋은 방법 같습니다.

필요한 함수들이 private되어 감춰졌습니다.

최소한으로 전역 영역에 자신을 노출하면서 기능을 재사용할 수도 있습니다. GetData라는 것은 일종의 네임스페이스 역할을 한다고 볼 수 있겠네요.그러나 아직 부족합니다.

저 개념을 좀더 발전시켜 봅시다.

데이터를 일관된 방식으로 요쳥하고 데이터 파서를 지정할 수 있는 객체를 제공하는 모듈을 만듭니다.
그리고 같은 요청시의 캐시 문제도 해결해 봅시다.
var spec = {
    url: '/some/path/data',
    callback: function(data) { ... }, // 콜백 지정
    parser: function jsonParser(data) { ... } // 파서 지정
};
// 모듈화. 생성 인자로 객체를 받는다.
// spec 객체를 바탕으로 객체 생성.
var dataModule = (function(spec) {
    
    // private 영역 시작

    // 데이터 캐시
    var dataCache = {};
    
    // 데이터 캐시 아이디
    var id = 0;
    
    var url = spec.url || '/default/data';
    // ... 기타 사용 변수
    
    var connectServer = function() { ... }
    var sendRequest = function(opt) { ... }
    var parseData = spec.parser || function(data) { ... };
    
    var callback = spec.callback || function() { };    
    var headers = spec.headers || {};

    // private 영역 끝.
    
    
    // 필요한 것만 공개. 접근 제한은 public이 된다
    // 리턴되는 객체의 메서드들은 클로저로서
    // private 영역의 변수에 접근이 가능하다.
    return {
        send: function() {
            connectServer(spec.url, spec.method);
            var data = sendRequest(headers);
            dataCache[id++] = data;
            return parseData(data, callback);
        },
        cache: function(id) { return dataCache[id]; },
        getLastCacheId: function() { return id; }
    } 
    
})(spec); // 익명 함수를 바로 실행

// @Test 코드
// 데이터 요청
var rs = dataModule.send();
console.log(dataModule.getLastCacheId()) // 마지막 요청 아이디

무명 함수의 결과를 받는 객체의 이름은 뭘로 지정해도 상관 없습니다. dataModule로 지정해 두었을 뿐 다른 프로그래밍시에는 다른 이름이 될 수도 있겠죠.

모듈은 별도의 정의된 이름 공간에 두면 문제가 없습니다. 한번 사용하고 말 것이라면 내부에 선언하고 바로 처리해도 되겠지요.
모듈 패턴은 사용하기에 따라 굉장한 용도가 있습니다.

어떻게 보면 new 를 사용한 객체의 생성보다 더욱 자바스크립트스러운 객체 생성방법이라고도 생각됩니다.

영어에 자신이 있으신 분은 고수가 쓰신 이 포스팅을 읽어보시길 추천드립니다.

Step by Step :: 토비의 스프링 3.0 - 명품 설명이란 이런것

"난 스프링에게 고마움을 느낀다. 스프링을 통해 이전보다 더 나은 개발자가 됐다고 느끼기 때문이다."
- 저자 Toby (이일민)



스프링은 이제 자바프로그래머의 기본 소양 정도로 되어가는 듯 싶습니다.

타국에서는 어떤지 모르겠지만 적어도 한국에서는 좀 과장하면 자바를 한다면 스프링은 반드시 알아야 자바프로그래머라고 명함을 겨우 내밀 수 있을 정도입니다.

보통 전문 서적은 해외가 빠르기에 한국에도 스프링 번역서가 몇권 소개되었지만 번역체의 남발이나, 어설픈 예제 코드, 이해가 힘든 설명등으로 좌절했던 사람도 많을 겁니다.

이 책은 그런 걱정은 안하셔도 될 것 같습니다. 이 책이 나왔기 때문이죠.

책 저자가 한국인이고, 예제는 진짜 기초적인 코드로 고급 스프링의 개념을 설명하고 있습니다.

다른 책처럼 다짜고짜 스프링을 처음부터 클래스패스에 구겨넣고 설정은 이러저러합니다 라는 식으로 "갖다 쓰는 법"을 설명하지 않습니다.
먼저 스프링을 어떻게 사용하게 되는지, 사용하면 뭐가 좋은지, 사용에 있어 스프링이 어떤 방식으로 돌아가는지를 설명합니다.

1부에서는 스프링의 적용을 착실히 계단식으로 설명합니다.
신입 사원도 만들지 않을 듯한 초난감한 UserDao 클래스를 일단 독자에게 던져 두고, 그 코드를 계단식 방법으로 차근차근 검토하며 리팩토링을 수행하면서 그곳에서 나온 문제들을 스프링에서는 이런 식으로 해결할 수 있다는 방식이 이 책의 주된 설명 법입니다.

  • 원시적인 문제 해결 - 단순한 방법...메서드 분리 등
  • 어설픈 방식의 문제 해결 - 디자인 패턴 적용 등
  • 나름 수려한 문제 해결 - 패턴 적용의 고급화 등
  • 스프링 적용 문제 해결 - 위 처리가 내장된 스프링으로 문제해결 보완
의 단계를 순차적으로 설명합니다.

절대 먼저 스프링 갖다 붙이지 않습니다. 이해한 뒤에 사용법을 설명합니다.

저 자체도 서블릿을 모르고 스트러츠나 스프링을 갖다쓰는 프로그래밍에 회의적인 터라 이러한 이 책의 설명 방식에 완전히 매료되었습니다.

뭔가에 대해 설명할 때 알기 쉽게 편하게 설명한다는 것은 좀 안다의 수준을 넘어 그것에 대해 이미 즐길 정도로 알아야 하는데, 건방지게 제가 감히 평가하자면 이 책의 저자인 토비님은 그 경지에 오른 분 같습니다. 


2부에서는 실제 활용법과 공부에 대해 좀 더 자세히 설명합니다.
2부의 제목도 "선택" 입니다.

각종 스프링의 설정과 애노테이션 활용, @MVC등...
여러 예제를 통해 다양한 기술적 선택지를 설명하지만 책의 저자분은 절대 뭔가에 대한 선택은 프로그래머에게 맡깁니다.

이 역시 다른 책에서 흔히 보이는 외길 설명과 차별화되는 부분입니다.

또한 이 책은 개발에 있어 테스트의 중요성을 굉장히 강조하며, 테스트를 위한 장이 따로 할당되고 전반부 부분의 예제 소스에 대해서는 철저한 테스트 소스까지 작성해가는 법을 설명하며 테스트를 설명합니다. 이정도까지...? 라고 할 정도의 테스트 설명이지만, 읽다 보면 아! 이런 유연한 테스트가! 라며 감탄하실 수 있을 거라고 생각됩니다.

독자의 이해를 돕기 위해 중요한 부분은 중복해서 설명을 여러번 해주기도 합니다. 두께에 놀랄 필요 없습니다. 집중해서 읽는다면 1부(전반부) 부분은 쉽게쉽게 장이 넘어가기 때문이니까요.

이 책의 단점은 다소 비싼 가격과, 1400페이지가 넘는 볼륨에 자칫 읽기 어려워 보인다는 것 빼고는 없다고 생각되네요.

책을 쓴 저자님이게 감사 말씀 올립니다. 읽을 때마다 배우는 책인 것 같습니다.

저자 블로그 후기 : 토비의 스프링이 나오기까지

2012. 2. 26.

URL의 쿼리스트링 가져오기

얼마전 자바스크립트로 URL 쿼리스트링을 파싱하는 방법에 대해 질문을 받아 포스팅으로 올립니다.

쿼리스트링은 DOM 객체중에 location의 search 속성을 사용하여 얻어올 수 있습니다.
// 만일 
// http://example.com/index.html?say=Hello+World+!&name=javarouka&mail
// 이라면
var queryStrinfg = window.location.search;

// 결과는 '?say=Hello+World+!&name=javarouka&mail'
console.log(queryStrinfg);
쿼리스트링은 키와 값이 '=' 로 구분되어 있고 그 셋이 '&' 로 나열됩니다.

그렇다면 정규표현식이나 split 함수를 사용하여 파싱할 수 있습니다.
주의할 점은 반드시 값에 URLDecode가 필요하며, 공백 문자는 '+' 로 대체되니 주의해야 합니다.

먼저 '+' 를 공백으로 교체하고, 나머지 문자열에 대해 URLDecode을 적용하면 괜찮을 것 같습니다.

아래 코드에서는 정규표현식을 쓰는 방법을 사용했습니다.
그럼 아래 코드로 풀어봅시다.
var QueryObject = function() {

    var o = {};

    var q = location.search.substring(1);
    if (q) {

        // 실제 그룹화 정규식.
        var vg = /([^&=]+)=?([^&]*)/g;
        
        // 인코딩된 공백문자열을 다시 공백으로
        var sp = /\+/g;

        // 정규식을 사용하여 값을 추출
        var decode = function(s) {
            if (!s) {
                return '';
            }
            return decodeURIComponent(s.replace(sp, " "));
        };

       // 한번씩 exec를 실행하여 값을 받아온다.
        var tmp; 
        while (tmp = vg.exec(q)) {
            (function() {
                var k = decode(tmp[1]);
                var v = decode(tmp[2]);
                var c;
                if (k) {
                    o[k] = v;
                    // getXXX 형식의 자바빈 타입으로 사용하고 싶다면
                    // 윗 라인을 지우고, 아래와 같이 하면 됩니다.
                    //c = k.charAt(0).toUpperCase() + k.slice(1);
                    //o["get" + c] = function() { return v; }
                    //o["set" + c] = function(val) { v = val; }
                }
            })();
        }
    }
    return o;
};

2012. 2. 21.

자바스크립트 패턴 #1 - 싱글톤 패턴 (JavaScript's Singleton Pattern) (수정)

자바스크립트의 함수는 new 로 생성자로 사용할 때마다 새로이 생성된 객체를 리턴합니다.

하지만 특수한 상황에서는 하나의 함수에서 생성되는 객체가 오직 한개만을 가져야 할 때가 있습니다. 그럴 경우 사용되는 디자인 패턴이 Singleton Pattern 입니다.

보통 하나의 객체를 전역적으로 공유해야 할 때 많이 쓰이지요.

자바스크립트에서는 그냥 전역영역에 객체를 하나 생성해서 두면 되지만 전역 객체의 사용은 매우 안좋은 코딩이며 절대 사용해서는 안되는 것입니다.

Java의 경우에는 생성자를 private 로 지정하고 별도의 스태틱 팩토리 메서드를 통해 인스턴스를 반환받는 형식으로 구현됩니다. 아래 처럼요.
public class JavaSingleton {

    // private 접근제한으로 객체를 미리 생성.
    // 간혹 생성자에서 늦은 초기화로 생성하기도 하나,
    // 객체의 실제 생성시간 차이로 인한 멀티스레딩 오류가 있으므로 비추천.
    private static JavaSingleton ME = new JavaSingleton();

    // private 생성자
    private JavaSingleton() { }

    // 스태틱 팩토리 메서드.
    // 언제나 하나의 객체만 리턴.
    public static JavaSingleton getInstance() {
         return  JavaSingleton.ME;
    }
}

public class JavaSingletonTester {
    public static void main(String [] args) {
     
        // 일반적인 new 생성 불가능.
        // JavaSingleton single = new JavaSingleton();

        // 두 객체는 동일.
        JavaSingleton ins1 = JavaSingleton.getInstance();
        JavaSingleton ins2 = JavaSingleton.getInstance();   

    }
}
자바스크립트의 경우 위 방법으로는 구현이 되지 않습니다.
명시적인 private 선언도 없고, 저런 생성자 자체를 막는 방법도 존재하지 않기 때문이죠.

다른 방법을 생각해봐야 합니다.

만일 함수가 생성자로 호출된다면 자신이 생성할 객체를 따로 가지고 있다가, 재차 호출 시 그 객체만을 반환하게 하는 방식을 생각해 봅시다.
이러한 방식이 될 것입니다.
// 싱글톤을 생성해주는 모듈 객체. 
// 익명 함수 실행 결과를 받습니다.
var SingletonTester = (function(){
    
    // 실제 싱글톤 적용 객체
    function Singleton(args) {
        
        // 내부 작업...
        var args = args || {};
        this.a = args.a;
        this.b = args.b;  
    }

    // 인스턴스 객체. 
    // 다수의 객체 생성을 제한하는 역할입니다
    var INSTANCE;

    // 외부에 공개될 객체를 반환합니다.
    // 모듈 패턴(Module Pattern)이라고 부릅니다
    return {        
        getInstance: function ( args ){
            if (INSTANCE === undefined) {
                instance = new Singleton( args );
            }
            return INSTANCE;
        }
    };
    
})(); // () 연산자로 선언과 동시에 바로 실행.

// 테스트
var singletonTest = 
    SingletonTester.getInstance( { a: "hello" } );
singletonTest.a; // a 
 - 위 코드는 Essential JavaScript Design Patterns For Beginners 를 참고 하였습니다.
위 방법대로 하면 getInstance를 사용하는 한 언제나 동일한 객체를 얻어낼 수 있습니다.
여기서 좀더 욕심을 부려 봅시다.

위 방식은 싱글톤을 적용할 객체마다 위의 코드를 써줘야 한다는 단점과 반드시 getInstance 함수를 사용하여 객체를 얻어내야 한다는 번거로움이 있고, 실수로라도 new 연산자를 사용할 경우 엉뚱한 객체가 얻어지기도 합니다.

위의 단점을 해소한 재사용이 가능하며, new를 쓰든 getInstance를 사용하든지간에 싱글톤이 적용가능하도록 코드를 약간 다르게 구현해 봅시다.

재사용하려면 몇가지 고려가 필요한데, 함수를 전달하여 그것을 싱글톤 생성자로 바꿔주려면 인자를 유연하게 넘겨줘야 하며, 동적으로 getInstance 함수도 지정해줘야 합니다.
그리고 private으로 관리되는 유일 객체 변수도 가져야 합니다.

생성자의 인자 전달 문자는 arguments 함수와 apply를 사용한 기법으로, 유일 객체 변수는 클로저화 시키는 방법밖엔 없는 것 같습니다.

아래 코드로 가기 전에 자바스크립트에서 new 연산자의 동작을 한번 봅시다.
  1. 생성자의 프로토타입을 연결한 새 빈 객체를 생성한다.
  2. 전달된 인자와 함께 생성된 객체를 컨텍스트로 함수를 실행한다.
  3. 함수 실행 결과가 객체이면 그 객체를 리턴하고, 아닐 경우 위에서 생성한 객체를 리턴한다.
아래는 코드.
/* 
    @name Singletonify - By javarouka (MIT Licensed)
*/
var Singletonify = function(cons) {
    
    // 유일 객체 변수
    var INSTANCE;
    
    // 클로저 생성
    var c = function() {
        // 유일 객체가 정의되지 않았다면 객체를 생성.
        if(INSTANCE === undefined) {
            
            // 여기서부터 new 연산자의 내용을 흉내냅니다.
            
            // 새 함수를 선언하고 인자로 전달받은 함수의 프로토타입으로 연결합니다.
            var F = function() {};
            F.prototype = cons.prototype;
            
            // 객체를 생성하고 생성된 객체를 컨텍스트로 호출합니다.            
            var t = new F();
            var ret = cons.apply(t, Array.prototype.slice.call(arguments));
            
            // 이때, 반환값이 객체이면 객체를, 아니라면 위의 객체를
            // 생성 객체로 지정합니다.
            INSTANCE = (typeof ret === 'object') ? ret : t;             
        }
        
        // 객체를 리턴합니다.
        return INSTANCE;
    }

    // 팩토리 메서드로도 접근할 수 있게 합니다
    c.getInstance = function() {
        return c.apply(null, Array.prototype.slice.call(arguments));
    }

    // 생성자를 대체한 클로저를 리턴
    return c;
};

// 테스트 함수
function javarouka(value) {
    this.v = value;
}

// 싱글톤화
var Single = Singletonify(javarouka);

// 테스트
var s1 = Single.getInstance("hello");
var s2 = new Single("javascript");
var s3 = new Single("world");

console.log(s1 === s2); // true
console.log(s2 === s3); // true

console.log(s1.v); // hello
console.log(s2.v); // hello
console.log(s3.v); // hello
위 코드의 포인트는 new를 사용한 객체 생성을 흉내내는 것과, 함수의 동작을 덮어 써버리는 것입니다.
좀 매끄럽지 않고 억지스러운 부분도 있는것 같습니다.
코드상 오류나 더 좋은 방법이 있다면 리플로 ...

참고자료

포스팅에는 아래 링크의 자료를 많이 참고했습니다.

작은 자유:  javascript singleton (자바스크립트 싱글턴)
http://ohgyun.com/248

Essential JavaScript Design Patterns For Beginners
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript

Rhio.Kim's Blog: javascript singleton(자바스크립트 싱글 패턴) story
http://rhio.tistory.com/tag/%EC%8B%B1%EA%B8%80%ED%86%A4

2012. 2. 11.

CSS Object Model - clientWidth, offsetHeight, scrollLeft...

자바스크립트로 페이지의 요소를 컨트롤하려면 자주 보는 속성들이 있습니다

innerWidth, clientWidth, offsetHeight, scrollLeft ...

막상 처음 자바스크립트를 활용해서 페이지를 멋지게 꾸미려는사람을 혼돈의 카오스로 인도하는 속성들입니다.

뭐 이렇게 많은지...잘 정리해서 적재 적소에 써먹어 봅시다.

위에 나열한 속성들은 문서의 시각적 요소 (visual view) 에 대한 속성들로서, 사실 오래전부터 브라우저별로 구현되어 있었고 불문율같은 반표준 프로퍼티지만, 정식 표준은 아니며 지금 정규 표준이 제안되고 있다고 합니다.

CSS Object Model

W3C에서는 이렇게 명시하고 있습니다.

This is a draft document and may be updated, replaced or obsoleted by other documents at any time
(이것은 초안이며 갱신되거나 교체 혹은 언제든 무용지물이 될 수 있다.)

하지만 대다수의 브라우저가 지원하고 있고, 실제 쓰이고 있어서 크게 바뀔 일은 없을것 같습니다. http://www.w3.org/TR/cssom-view/에 가면 해당 설명을 확인할 수 있습니다.
영어에 자신이 있는 사람은 이 포스트를 읽기 보다 표준제안 문서의 정보를 참고하길 권합니다.

Window 관련

먼저 윈도우 요소 속성을 나타내는 인터페이스입니다.
(http://www.w3.org/TR/cssom-view/#extensions-to-the-window-interface)
partial interface Window {
    MediaQueryList matchMedia(DOMString media_query_list);
    readonly attribute Screen screen;

    // viewport
    readonly attribute long innerWidth;
    readonly attribute long innerHeight;

    // viewport scrolling
    readonly attribute long scrollX;
    readonly attribute long pageXOffset;
    readonly attribute long scrollY;
    readonly attribute long pageYOffset;
    void scroll(long x, long y);
    void scrollTo(long x, long y);
    void scrollBy(long x, long y);

    // client
    readonly attribute long screenX;
    readonly attribute long screenY;
    readonly attribute long outerWidth;
    readonly attribute long outerHeight;
};
innerHeight, innerWidth
브라우저의 뷰포트(viewport), 즉 사용자에게 보이는 영역을 나타냅니다. 하지만 특정 브라우저에서는 저 속성을 지원하지 않아 직접 구해야 합니다.
document 의 최상위 루트 엘리먼트(즉, html)의 넓이로 뷰포트를 구합니다.

다음과 같은 코드가 나오겠지요.
function getViewport() {      
    if(window.innerWidth !== undefined && window.innerHeight !== undefined) {
        return [ window.innerWidth, window.innerHeight ];
    }
    // 해당 속성이 없을 경우, document.documentElement
    // 혹은 document.body의 치수로 대체
    var doc = document.documentElement || document.body;  
    return [ doc.clientWidth, doc.clientHeight ];
}

지금 이 블로그를 보는 당신의 브라우저의 뷰포트 넓이,높이는 [0, 0]입니다.

scrollX(Y), pageX(Y)Offset
뷰포트가 실제 볼 수 있는 페이지에서 얼마나 이동했는지 알 수 있는 스크롤링 계열 속성들도 필요합니다.
드래그 앤 드롭 구현시에 화면 중앙 레이어 표시, 화면 강제 스크롤링등에 쓰이는 속성 및 함수들도 window 인터페이스에 정의되어 있습니다.
뷰포트가 얼마나 X축으로 스크롤 되었는지 알려주는 scrollX, pageYOffset 두개인데 이것들은 언제나 같습니다.
Y축의 scrollY, pageYOffset도 마찬가지입니다.
하지만 IE는 개성이 강해서 위 두 속성을 쓸 수 없고, 직접 계산으로 구해야 합니다.
function getPageOffset() {
    if(window.pageXOffset !== undefined  && window.pageYOffset !== undefined ) {
        return [ window.pageXOffset, window.pageYOffset ];
    }
    var doc = document.documentElement || document.body;
    return [ doc.scrollLeft, doc.scrollTop ];
}

지금 이 블로그를 보는 당신의 브라우저 뷰포트 스크롤 X,Y 은[0, 0] 입니다.

scroll, scrollTo, scrollBy
scroll(x, y), scrollTo(x, y): 같은 기능으로 윈도우를 해당 좌표로 스크롤 시키는 함수입니다.
scrollBy(x, y): 현재 스크롤에 인자값을 더한 위치로 스크롤시킵니다.



screenX, screenY (혹은 screenLeft, screenTop)
현재 스크린(사용자 기본 출력)에서 브라우저가 위치한 곳을 나타냅니다. IE, opera, safari에서는 screenX, screenY를 지원하지 않고 대신 screenLeft, screenTop를 지원합니다.

지금 이 블로그를 보는 당신의 브라우저의 스크린에서의 X,Y 좌표는 [0, 0]입니다.

outerWidth, outerHeight
브라우저 자체의 크기, 즉 상태바 주소입력창 메뉴 테두리...등을 모두 합한 크기를 리턴하는 속성입니다. 이번에도 IE는 outerWidth, outerHeight를 지원하지 않으므로, 직접 구해야 합니다.
offsetHeight라는 속성은 객체의 테두리(border)까지 합한 실제 크기를 가진 속성입니다.
위의 함수에서 사용한 방법처럼 문서 최상위 루트를 구한 뒤 offsetWidth, offsetHeight 속성으로 구할 수 있습니다.

지금 이 블로그를 보는 당신의 브라우저의 실제 크기는 [0, 0] 입니다.

Screen 관련

다음은 screen 객체의 인터페이스입니다.
(http://www.w3.org/TR/cssom-view/#the-screen-interface)
interface Screen {
      readonly attribute unsigned long availWidth;
      readonly attribute unsigned long availHeight;
      readonly attribute unsigned long width;
      readonly attribute unsigned long height;
      readonly attribute unsigned long colorDepth;
      readonly attribute unsigned long pixelDepth;
};
출력 장치. 즉, 모니터가 됩니다.

availWidth, availHeight : 윈도우 작업표시줄을 제외한 실제 스크린 크기
width, height : 실제 스크린 크기
colorDepth, pixelDepth : 스크린에서 색상, 해상도 수치를 반환.

0, 0, 0, 0

Element 관련

다음은 HTMLElement에 대한 것들입니다.
(http://www.w3.org/TR/cssom-view/#extensions-to-the-element-interface)

친숙한 프로퍼티가 많습니다.
이 포스트를 쓰게 된 원인중 하나인 clientLeft, offsetWidth 이런 것들을 가진 태그들의 속성이죠.

본래 W3C문서에는 Element와 HTMLElement를 구분짓고 있으나 편의상 합쳐 쓰겠습니다.
HTMLElement는 Element를 상속한 인터페이스지만, IE에서는 (IE8버전기준) HTMLElement를 완벽히 지원하고 있지 않습니다.
interface Element {
 
    ClientRectList getClientRects();
    ClientRect getBoundingClientRect();
 
    // scrolling
    void scrollIntoView(optional boolean top);
 
    attribute long scrollTop;   // scroll on setting
    attribute long scrollLeft;  // scroll on setting
 
    readonly attribute long scrollWidth;
    readonly attribute long scrollHeight;
 
    readonly attribute long clientTop;
    readonly attribute long clientLeft;
    readonly attribute long clientWidth;
    readonly attribute long clientHeight;
 
    readonly attribute Element offsetParent;
    readonly attribute long offsetTop;
    readonly attribute long offsetLeft;
    readonly attribute long offsetWidth;
    readonly attribute long offsetHeight;
};

getBoundingClientRect()
자신의 오프셋 정보를 객체형태로 반환합니다. 이때 계산은 뷰포트에 대한 상대 위치입니다.
top, bottom, left, right, width, height를 가지고 반환됩니다.

scrollIntoView()
스크롤을 이 메서드를 실행한 엘리먼트가 보일 때까지 스크롤 해 줍니다.

scrollWidth, scrollHeight
엘리먼트가 부모의 overflow 속성 지정 등에 가려져 실제 크기가 보이지 않더라도(스크롤바 등으로 감춰져 있더라도) 그 크기를 나타내 줍니다.

cilentTop, clientLeft
offsetTop 속성, 즉 기준이 되는 오프셋과 실제 박스의 left, top 수치의 차이, border의 픽셀값과 같습니다.

clientWidth, clientHeight
실제 객체의 크기를  나타냅니다.
크기 계산 시 padding은 포함하고, margin, border, 스크롤바는 제외합니다.

offsetTop, offsetLeft
offsetParent로 찾을 수 있는 객체에 대해 얼마나 떨어져 있는가 계산된 수치.
offsetParent는 부모의 position이 static이 아닌 속성일 때입니다.
부모중에 위치 지정 포지션(position: static이 아닌 것)이 없을 땐 offsetParent는 body를 기준으로 계산됩니다.

offsetWidth, offsetHeight
offsetParent로 찾을 수 있는 객체에 대해 상대적으로 크기가 계산된 수치입니다.
쉽게, 스크롤바와 border까지 포함한 개체의 크기라고 생각하시면 됩니다.


부록
아래 DIV 영역의 width, height는 200px 이며, border는 10px입니다.
안 이미지의 width, height는 300px 이고 margin, padding, border 는 0px 입니다.
버튼을 눌러보세요.

* 모바일, 동적 뷰 환경 등 특수한 환경에서는 이미지 크기가 자동조절되거나 스크롤바가 나오지 않아 원하는 대로 테스트가 되지 않을 수 있습니다.
















참고 이미지입니다.
출처는 인터넷에 떠도는 이미지를 가져왔습니다.


웹 사용자가 브라우저의 자바스크립트를 활성화하지 않았을 때

현대의 웹페이지는 자바스크립트를 거의 사용하고 있습니다.

심지어는 ActiveX나 플래시, Silverlight 등의 플러그인 조차도 자바스크립트의 도움을 받고 있는 현실입니다.

정석대로라면 우아한 낮춤(Gracefull degradation)을 사용하여 사용자의 접근성을 해치지 말아야 하지만, 스크립트가 멀쩡히 지원되는데도 어떠한 이유에서, 혹은 잘 모르고 자바스크립트가 동작하지 않아 사이트를 이용하지 못하는 사람들도 있을 수 있습니다.

만일 사이트가 자바스크립트의 도움이 절실하다면 <noscript> 태그를 사용하여 사용자에게 안내해주는 방법이 주로 쓰이는데 이 경우 안내 문구에 사용자에게 자바스크립트의 가동 방법까지 친절하게 안내해주는 페이지가 있어 소개합니다.


뭔가 심심하게 <noscript> 안에 자바스크립트를 켜주세요~ 이런 문구 하나 넣는 것보다 위 링크를 참조하게 하는 것도 괜찮을 것 같네요.

2012. 2. 7.

JavaScript의 Function의 속성 및 메서드 (수정)

# 개요 

자바스크립트에서 제일 중요한 요소는 함수입니다.
흔히 일급 객체 (first-class-object) 라고 불리는 자바스크립트 함수는 자바스크립트에서 거의 모든 것을 할 수 있습니다. 보통 언어에서 흔히 지원되는 함수의 동작인 실행, 값의 반환같은것은 당연하게 수행하며, 자신이 반환값이 되기도 하고 인자로서 넘겨지기도 하며, 객체의 프로퍼티나 변수 할당도 가능합니다.

심지어 변수 스코프 경계를 짓거나, new 연산자와 쓰여 다른 객체를 생성하는 생성자 역할 및 클로저로서도 동작하는 그야말로 만능 엔터테이너입니다.

그리고 많은 분들이 재미있어(?) 하시는 prototype 속성을 가진 객체이기도 합니다.

함수는 어떤 속성과 메서드를 갖는지 한번 알아봅시다.


함수의 속성들 

자바스크립트에서는 함수도 엄연한 Object이기에 속성을 가지고 프로그래머가 임의로 정의할 수도 있습니다. 또한 기본으로 여러 속성을 가지고 있기도 합니다.

그런 것들은 대부분 Function이라는 빌트인 함수의 프로토타입에서 상속된 것입니다.

arguments
함수가 실행될 때 바인딩되는 늦은 바인딩 변수입니다.
함수가 생성되었을 당시에는 null 값을 가지고 있습니다. 런타임이 굉장히 중요한 함수죠.

유사 배열 객체(array-like object)로서 함수가 실행되었을 때 전달되어 초기화된 인자를 순서대로 0번 인덱스부터 첨자로 하여 배열 형식으로 가지고 있습니다.
function javarouka() {
    // 인자를 따로 선언하지 않아도 런타임시 주어진 인자로 초기화됩니다.
    // 그리고 배열과 같은 기능을 하는 속성 length가 있습니다.
    for(var i=0; i < arguments.length; i++) {
        console.log(i + "째 인자의 값은 " + arguments[i]);
    }
    // false. 배열이 아닙니다.
    console.log (arguments instanceof Array)               
}
console.log(javarouka.arguments) // 실행 전에는 null 입니다.
javarouka("hello", "javascript", "function", "property!"); // 잘 출력되겠죠?
절대 배열로 착각하여 arguments.push() 나 arguments.slice() 같은 배열 메서드를 사용하지 마세요. 만일 사용하고 싶다면
Array.prototype.push.call(arguments, "밀어넣을 요소");
var aryArgs = Array.prototype.slice.call(arguments); // 진짜 인자 배열 얻기
형식으로 배열의 함수를 컨텍스트만 자신으로 바꿔서 사용하면 됩니다.
또한 실행중인 자기 자신을 참조하는 callee 속성도 눈여겨 볼만 합니다.
var i = 0;
(function() {
    if(i < 10) {
        document.writeln(i++);
        // arguments.callee은 자기 자신을 참조하는 속성입니다.
        setTimeout(arguments.callee, 100);
    }
})();
// 0123456789 가 출력될 겁니다
혹자는 이 arguments 속성이 자바스크립트에서 함수 호출의 유연함을 강조한다고 하지만 개인적으로는 혼돈에 빠뜨리는 속성이라고 생각합니다.
MDN에서는 이 속성을 Deprecated 로 정의하고 있지만, 기존 소스에서 사용되는 빈도가 높아 없어지진 않을 것 같습니다.

length
함수 생성시 지정한 받을 인자의 개수를 나타냅니다.
대다수의 브라우저에서 지원합니다. 하지만 경험상 막상 쓸일이 그리 없었던 듯 합니다.
function nonblock(val1, val2) {
    /* 뭔가 열심히 하는 로직 */
}
console.log(nonblock.length) // 2
주의할 점이 있는데, arguments.length의 값과는 다를 수 있습니다.
이쪽은 선언적인 인자의 숫자이며, arguments.length는 실행시 인자의 숫자입니다.

constructor
Object에서 상속받은 속성입니다. 자신을 생성한 함수를 가르킵니다.
일반적으로 콘솔이나 alert등으로 출력을 시도할 경우 네이티브 함수의 toString() 값인 function Function() { [native code] } 을 볼 수 있을 것입니다.
실제 반환되는건 Function 객체입니다.
function Niceguy() {
    /* ...이런저런 구현... */
}
// Niceguy 라는 함수객체는 네이티브코드에서 생성됩니다
console.log(Niceguy instanceof Function) // true.
console.log(Niceguy.constructor) // function Function() { [native code] }

// 반면, 생성자로 쓰일 경우 생성된 객체의 constructor가 됩니다.
var javarouka = new Niceguy();
console.log(javarouka.constructor) // Niceguy
caller
자신을 실행시킨 함수를 반환하는 비표준 속성입니다.
만일 속성값이 null이라면 글로벌 컨텍스트에서 실행한 것입니다.

다음 예제를 보세요.
var obj = {
    a: function() {
        return obj.a.caller;
    },
    b: function(f) {
        return f();
    }
}
obj.a(); // null
obj.b(obj.a); // obj.b 반환. toString()은 function (f) { return f(); } 가 됩니다.
name
함수의 이름을 반환하지만 이 경우 반드시 함수 선언문으로 한 경우에만 반환됩니다.
비표준이며, IE는 지원하지 않습니다.

(2012-02-17 추가)

함수 선언문 뿐 아니라 이름을 가진 함수 리터럴(named function-literal) 일 경우에도 name 속성이 나오는군요. 이때 함수가 할당된 변수가 name이 되는게 아닌 함수 리터럴에 지정한 이름이 name이 됩니다.
// 함수 선언문
function func1() { /* ... */ }
func1.name // "func1"

// 함수 표현식 변수할당
var func2 = function() { /* ... */ }
func2.name // "" ( or undefined or null? )

// 이름을 가진 함수 표현식
var func3 = function myFunc() { /* ... */ }
func3.name // "myFunc"

prototype
함수(내장함수도 물론 포함합니다)만이 가지고 있는 속성으로 자바스크립트 객체지향의 근간입니다. 이것만 왜 붉은 색이냐 하면 그만큼 중요하기 때문입니다.

설명은 기존의 포스트를 참고해 주세요
http://blog.javarouka.me/2011/12/prototype-property-and-prototype-chain.html


함수의 메서드들

함수도 객체이기에 메서드들을 가질 수 있습니다.
보통 함수에 사용자가 뭔가를 정의할일은 거의 없습니다. 라이브러리 레벨의 범용성을 추가할 때 사용되는게 대부분입니다.

아래 소개할 함수 대부분도 call이나 apply등을 빼면, 고급 기능에서나 사용될 법 한 것들입니다.

apply(컨텍스트 [, 인자의 배열 ])
call(컨텍스트 [, 인자1, 인자2....])
기능이 같으므로 함께 설명합니다.
이 메서드를 사용하면 함수를 실행시키는 컨텍스트를 직접 지정할 수 있습니다. 쉽게 말하면 this 변수에 연결될 객체를 선택할 수 있습니다.

apply, call 둘다 첫번째 인자는 활성 객체이며 두번째 인자부터만 다릅니다.
apply는 함수를 실행할 인자를 배열 형식으로 절달하며, call은 인자를 하나하나 나열하는 식이죠.
첫번째 컨텍스트를 전달하지 않거나 null을 주게 되면 글로벌 컨텍스트로 지정됩니다.

다음의 예제를 보세요.
function Computer(name, price) {    
    this.name = name;
    this.price = price;  
}
function Notebook(name, price) {
    // 호출 객체를 현재 생성될 객체로 지정합니다.
    Computer.apply(this, arguments);
    this.type = "portable";
}

var vaio = new Notebook("VAIO", 2250000);

vaio.name // "VAIO"
vaio.price // 2250000
vaio.type // "portable"
위의 Notebook 함수를 생성자로 써서 만든 객체 vaio는 생성자에서 Computer를 호출함으로써 name과 price를 자신의 속성으로 지정할 수 있지요.

보통은 저런 용도로 쓰진 않지만요...

bind(컨텍스트 [, 인자1, 인자2....])
위의 call, apply와 비슷하지만 이번에는 주체가 함수가 됩니다.
함수의 스코프 체인과 프로토타입을 첫번째 인자의 것으로 모두 바꾸고 나머지 인자를 함수에 전달하여 실행합니다.

말로 하니 복잡한데 예제를 봅시다.
var name = "Lee";
var other = {
    name: "Kim"
}
function getName() {
    return this.name;
}
getName() // 전역 참조. 결과는 "Lee"

var wasBound = getName.bind(other);
wasBound(); // 바인드 된 객체 참조. 결과는 "Kim"
함수를 특정 객체의 스코프 체인에 묶어버리는(bind) 기능입니다.
아직은 크롬과 파이어폭스에서만 지원됩니다.

toString
Object에서 상속한 메서드로서 본래 기능은 해당 값의 문자열 형식을 반환하게 되어 있습니다. 대부분의 자바스크립트 엔진 구현에서는 함수의 toString을 호출하면 함수 소스코드의 내용이 나옵니다. 네이티브 코드로 구현된 부분은 소스코드의 내용이 공개되지 않습니다.

문자열 결합 연산자 (+) 나 alert, document.write() 등의 인자가 되거나 해서 문자열이 필요한 경우 자동으로 호출되어 연산됩니다.
function javarouka() {
    /* 뭔가 한다 */
}
// toString을 override
javarouka.toString = function() {
    return "Happty Programer";
}
// 문맥상 문자열 연산이 필요할 때 자동으로 호출됩니다
console.log("블로그의 주인은 " + javarouka + "입니다");
valueOf
역시 Object에서 상속된 메서드로서 값의 원시값(primitive value) 값을 반환하게 되어 있습니다. 역시 대부분의 엔진에서는 함수 자체를 그대로 반환합니다.
-, /, * 등의 산술 연산에 함수를 직접 쓸 경우 자동으로 호출됩니다.

혹시라도 함수로 숫자 연산을 하고 싶다면 오버라이딩 해 두면 덧셈뺄셈등의 연산이 가능해 집니다. (그런데 그럴 일이 있을지 모르겠습니다)
function one(i18n) {
    if(i18n === "ko") return "하나";
    else if(i18n === "jp") return "いち";
    return "one";
}
one.valueOf = function () { return 1; }

// 원시 값 연산이 필요한 경우 자동으로 호출됩니다.
var result = 38 - one;
console.log(result) // 37
toSource
함수의 소스코드를 문자열로 반환합니다. 대부분의 경우 toString과 결과가 같습니다.

2012. 2. 1.

What is Closure? - 자바스크립트 클로저 학습 사이트

재미있는 사이트네요.

JSLint를 써서 학습 예제도 돌려볼 수 있게 구현했네요.

http://nathansjslessons.appspot.com/lesson?id=1010&lang=en

영어에 자신이 있다면 번역 제의를 하고 싶군요;

개발자와 framework

프레임워크...

"소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것"

어느때부턴가 뭔가를 개발하려면 습관적으로 어떤 프레임워크부터 쓸까 하는 생각부터 들게 되었습니다.  개발을 위한 설계서나 제안서에는 "이 제품에는 프레임워크 A와 B를 썼습니다" 라는 것이 들어가고, 개발자들 사이에서는 프레임워크가 패션 잡화가 유행하듯 스쳐 지나가며 빠르게 발전하고 있기도 하구요.

생각해보니 저 조차도 프레임워크와 함께 프로그래밍을 해왔더군요. 첫 프로그래밍 입문할 때 사용했던 Struts1부터 시작해서, PHP를 한창 쓸 때 잠깐 만져본 CodeIgniter에, 웹 크롤러를 만들 때 사용했던 Mybatis, 최근 공부하고 있는 Spring Framework 까지...

프레임워크를 사용하면 효율성이 증가하고 개발이 편해진다는 것은 개발 초보도 알고 있습니다. 하지만 너무 막연하게 그렇게 생각하는 것은 아닌지 스스로 생각해볼 필요가 있다고 생각됩니다.


프레임워크 각자의 철학


  • 개발 속도 향상
  • 개발 스킬에 크게 영향받지 않는 결과 코드 품질
  • 유지보수 용이
  • 테스트 용이
...

위에 나열한 것들 많은 이유가 있겠지요.

저는 프레임워크를 사용하는 이유는 어플리케이션에 일관된 철학을 부여하는 것이 제일 큰 이유라고 봅니다. 

프레임워크는 프로그래머가 코딩한 결과물을 자신만의 철학으로 "이용하여" 서로 연계하여 구동시킵니다. 요새 Java 프레임워크를 평정한 Spring Framework는 IoC와 DI라는 개념하에 모든 개발자의 코드를 관리해주며 심지어는 자신의 구성에도 IoC와 DI의 철학으로 만들어져 있지요.

반면 Ruby on Rails 같은 프레임워크는 모든 코드를 관례에 맞춰 구성요소들이 강하게 결합되어 있으며 그 규칙에 맞춰 코딩하면 굉장한 시너지를 내는 "설정보단 관례" 라는 철학이 있죠.

프레임워크 각자의 철학을 무시한 채 프레임워크의 사용성인 면에만 충실하여 사용한다면 프레임워크의 장점은 거의 사라집니다. 물론 프레임워크의 사용 이점도 거의 사라집니다.


프레임워크와 개발자 IoC

프로그래머들이 회사나 기타 이유로 인해 프레임워크의 "사용법" 만을 익힌 채, 프로젝트를 수행하게 되고 프레임워크의 철학따윈 모른 채 기계적으로 코딩하면서 결국 자신의 스킬이 프레임워크에 귀속된, 창의적인 개발자라기 보다 수동적이고 뭔가에 귀속된 부속품같이 되어버리는 경우는 정말 최악이라 볼 수 있습니다.

그리고 이런 경우가 꽤 많다는 점이 사실입니다;

제가 많은 사람을 만나는 것도 아니고 Spring Framework 를 완전히 이해한 것도 아니지만 (이해라는 것을 논하는 것도 우스울 지경인 수준입니다), 지금 한창 유행하는 Spring Framework 를 이해하고 쓰는 개발자를 전 아직 만나보지 못했습니다. 대부분 단순히 사용법만을 인지하고 그것에 맞춰 코딩할 뿐이죠.

A > B > C > D 로 이어지는 단순 코딩만 할줄 아는 사람은 ㄱ > ㄴ > ㄷ > ㄹ 같은 개발에는 어려움을 느낄 수 밖에 없습니다. 새로운 프레임워크나 새로운 환경에 가게 되면 그런 기술은 바로 쓸모없어집니다.

프레임워크를 활용해야지 프레임워크가 개발자를 활용하면 힘들어집니다.


평준화 감옥

프레임워크를 사용하게 되면 어느정도는 개발자의 창의성을 제한될 수 밖에 없습니다.
물론 지나친 창의성은 다수 팀 개발 시 독이 되지만, 적절한 프로그래밍적 위트나 창의성은 결과물의 품질을 한층 뛰어나게 만듭니다.

그러나 프레임워크는 말 그대로 개발의 프레임워크가 때때로 개발자를 옥죄는 감옥이 되어 개발 방식이 획일화되고 프로그래머의 사고력은 단순해 진다고 생각합니다.
물론 뛰어난 개발자는 그 안에서 효율성을 찾겠지만...그건 예외로 하구요.

코드 수준을 일정수준으로 보장하지만 프레임워크의 특성상 거꾸로 생각하면 일정 수준으로 끌어 올리기는 어렵다는 뜻도 됩니다...프레임워크에 너무 의존된 개발의 종착지는 그냥 단순한 글자를 타이핑하는 손가락 노가다꾼일지도 모릅니다.

프레임워크를 사용하지만 절대 그것의 철학을 사용해야지 기능만을 사용하는 것은 피해야 합니다. 그런 기능적 용도는 프로그램 라이브러리의 API 활용으로 족합니다.


선무당

프레임워크를 사용하면 좋다는 장점들의 그림자를 주목해야 합니다.

프레임워크를 물론 잘 쓰면 좋습니다.
그러나 잘못쓰면 그 장점은 온데 간데없고 프레임워크를 쓰기만 한 그냥저냥 코드가 됩니다. 오히려 코드를 해석하기는 더욱 힘들고 유지보수는 물론이고 결과 품질도 매우 나쁘며, 테스트는 말할 것도 없죠.

관련 블로그 링크 하나 겁니다.

분노가 느껴집니다.


프레임워크의 사용은

위에 악담을 늘어놓긴 했지만 사실 현재 프레임워크 없이 뭔가를 개발한다는 것은 분명 굉장한 삽질입니다.
프레임워크의 사용은

  • 프레임워크에 사용되어지는 개발자가 아닌 프레임워크를 사용하는 개발자.
  • 프레임워크의 철학을 잘 이해하고 그것을 코드에 적용하는 개발.
  • 프레임워크를 기술이 아닌 도구로서 접근.
  • 프레임워크에 대해 충분한 학습.

이런 생각의 바탕에 둬야 하지 않을까요?