자바스크립트에서 함수객체는 매우 특별한 toString을 제공합니다. 다른 객체들과 달리 본인을 투영(reflaction)할 수 있습니다. 이 특징을 이용한 두 가지 패턴을 살펴보면서 함수객체의 재정의를 할 수 있는 하이브리드형 템플릿을 살펴 봅니다.
함수객체의 toString
자바스크립트의 함수객체는 toString 메서드를 통해 함수의 정의 내용 전체를 문자열로 반환하는 기능을 갖고 있습니다.
예를 들어 다음과 같은 결과를 얻을 수 있습니다.
function test(){ return 1; } console.log( test.toString() ); // "function test(){return 1;}"
이 기능은 거의 모든 브라우저에서 호환됩니다. 단지 구형 파폭4.x 에서는 ”가 나오기 때문에 문제를 일으킵니다(..만 파폭 구형지원이란건 요즘 세상에 하지 않으니까요 ^^)
여러 줄이 있는 문자열 생성에 응용하기
이러한 특성은 여러 줄로 된 문자열을 js로 생성하기 위한 편법에 이용됩니다.
var str = function(){/* 여러줄로 된 문자열을 만들기가 매우 편리합니다 */} console.log( str.toString().replace( /^function.*(){/*s|s*/}$/g, '' ) ); //몸체의 문자열만 나온다
정규식을 약간 보기 쉽게 해석해보면 | 앞부분은
- ^ – 처음부터
- function – 이 나오고
- .* – 크롬같은 넘들은 분명히 function()라고 해도 function () 이렇게 괄호 앞에 공백을 자동으로 추가하곤 합니다. 그 외에 기명함수도 있으니까..
- (){/* – 이스케이프 치우면 (){/* 입니다. 즉 괄호와 주석시작까지
- s – 공백문자 하나가 나온다. 이 경우는 줄 바꿈 문자입니다
뒷부분은 간단히 줄바꿈문자 하나, */} 에 대응해서 앞부분과 뒷부분 모두 지워버립니다.
근데 이건 뭐 그렇다고 치고 진짜 중요한건 이게 아닙니다.
함수 생성 템플릿으로 쓰기
일반적으로 함수를 작성할 때 함수 내부의 로직을 곰곰히 보면 그 함수만의 기능과 다른 함수의 기능을 불러오는 부분이 있습니다. 예를 들어 다음의 두 함수를 보죠.
function A( val ){ val = val * val; return val; } function B( val ){ val = val + 10; return val; }
A, B함수의 경우 거의 내용이 전부 같고 오직 val = val… 이 부분만 다릅니다. 따라서 정석으로는 다른 부분만 함수로 만드는게 맞습니다. 하지만 그게 쉽지 않은 경우도 많습니다.
- 다른 부분이 작은 코드 파편이고 한 번에 몰려있지 않고 함수 내부의 코드 위 아래에 분산 되어있다면 공통 부분을 뽑아내 함수를 만들기 곤란하거나 너무 많은 함수를 만들게 된다.
- 성능이 대단이 중요한 함수의 경우 함수 내부에서 작은 코드 조각의 독립성을 위해 다른 함수를 부르는 부하를 갖기 곤란한 경우 그냥 함수를 여러 판 만들게 된다.
하지만 위와 같은 이유로 같은 코드를 갖고 있는 함수를 2개 이상 만들면 수정시 두 군데를 수정해야하기 때문에 반드시 버그로 연결됩니다. 실무 세계에서 저런 대상의 함수는 처음 예처럼 단순하지 않습니다.
function animation( option ){ //애니메이션 일반 준비작업 100줄 option.target에 따라 해야할 준비작업 //애니메이션 일반 실행작업 100줄 option.target에 따라 해당 결과를 반영하는 작업 }
이런 예에서 언틋 생각하기엔 성능을 위해서 if, switch를 통해 option.target에 따른 분기처리를 해주면 될것 같지만, 이렇게 되면 새로운 target 타입이 나왔을 때 대응할 수 없게 됩니다. 확장을 외부로 열어둘 방법이 필요한데, 성능을 위해 option.target.ready(), option.target.result() 를 부르는게 부담스러운 경우에 해당됩니다.
여튼 코드 조각에 따라 함수를 여러판 만들되 코드는 단일하게 유지하면서 코드의 검사를 인터프리터에게 맡길 수 있는 중간 타협점이 바로 함수 생성 템플릿입니다.
일단 코드로 보면서 생각해보죠.
var template = function( option ){ //option ready '@targetReady' //option run '@targetResult' };
우선 템플릿 대상함수를 완전히 함수로 정의하되 코드 조각이 삽입될 부분을 문자열로 지정합니다. 위의 코드에서 ‘@targetReady’ 와 ‘@targetResult’ 가 이에 해당됩니다.
이러면 js는 함수를 인터프리팅할 때 나머지 모든 로직을 잘 검증해주고 템플릿이 삽입될 곳은 식문이 들어가 있으므로 에러없이 잘 통과시키게 됩니다.
이제 이 템플릿을 이용해 코드를 주입합니다.
var str = template.toString() .replace( "'@targetReady'", 'option.target.style.display = "block";' ) .replace( "'@targetResult'", 'option.target.style.left = result + "px";' );
이렇게 str을 생성하면 함수의 템플릿 문자열 부분만 변경된 완전한 함수구문이 생성됩니다. 이걸로 새로운 함수를 만들어내면 되겠죠.
var domAnimation = ( new Function( '', 'return ' + str ) )();
이제 domAnimation은 target이 dom객체일 때의 애니메이션 함수가 되었습니다.
new Function 의 보강
new Function 을 통해 함수를 생성하는 로직은 깔끔하고 좋지만 여기에는 큰 문제가 하나 있습니다. new Function은 스코프를 인식하지 않는다는 점입니다. 따라서 이를 보강하려면 new Function을 호출하는 시점에 인자로 보내야만 합니다. 위의 domAnimation 이 공용 객체인 animation이라는 객체를 참조하는 코드라면 아래와 같이 정리할 수 있습니다.
var domAnimation = ( new Function( 'animation', 'return ' + str ) )( animation );
인자로 지역변수화 시켜 스코프를 세탁하면 되죠.
결론
개인적으로 이 발견은 일이년 정도 전에 하고 회사 프레임웍 여기저기에 응용하는데, 장점이라면 템플릿함수 태반은 인터프리터가 검증해준다는 점이고, 단점은 템플릿 코드가 문자열로 치환되니까 안정성에서 보호되지 않는다는 점입니다.
반대로 안정성을 보호하지 않아도 될 정도의 작은 코드 조각이 아니라면 함수 템플릿의 대상으로 쓰지 않는게 좋다는 의견이기도 합니다 ^^;
recent comment