티스토리 뷰

Programming language/javascript

함수2

hello-world 2013. 2. 6. 14:19
728x90
반응형

초기화 시점의 분기 - Javascript Pattern 참고 및 정리

초기화 시점의 분기는 최적화 패턴이다.
어떤 조건이 프로그램의 생명주기 동안 변경되지 않는게 확실한 경우 조건을 단 한번만 확인.

예/
1. 브라우저 탐지
2. XMLHttpRequest 내장객체 지원여부
3. DOM 엘리먼트 스타일 확인
4. 이벤트 핸들러 작업


//변경이전( 이벤트 핸들러 작업 )
var utils={
addListener:function( el, type, fn ){
if( typeof window.addEventListener==='function'){
el.addEventListener( type, fn, false );
}else if( typeof document.attachEvent==='function'){
el.attachEvent('on'+type, fn);
}else{
el['on'+type]=fn;
}
},
removeListener:function(el, type, fn){
if( typeof window.removeEventListener==='function'){
el.removeEventListener( type, fn, false );
}else if( typeof document.detachEvent==='function'){
el.detachEvent('on'+type, fn);
}else{
el['on'+type]=null;
}
}
}

이 코드는 약간 비효율적이다.
utils.addLitener()나 utils.removeListener()를 호출 할때마다 똑같은 확인 작업이 반복해서 실행된다.
초기화 시점 분기를 이용하면, 처음 스크립트를 로딩하는 동안에 브라우저 기능을 한번만 확인한다.

//변경이후
//인터페이스
utils={
addListener:null,
removeListener:null
}
//구현
if( typeof window.addEventListener==='function'){
utils.addListener=function(el, type, fn){
el.addEventListener(type, fn, false);
}
utils.removeListener(el, type, fn){
el.removeEventListener(type, fn, false);
}
}else if( typeof document.attachEvent==='function'){
utils.addListener=function(el, type, fn){
el.attachEvent('on'+type, fn);
}
utils.removeListener=function(el, type, fn){
el.detachEvent('on'+type, fn);
}
}else{
utils.addListener=function(el, type, fn){
el['on'+type]=fn;
}
utils.removeListener=function(el, type, fn){
el['on'+type]=null;
}
}


이 패턴을 쓸때 브라우저의 기능을 섣불리 가정하지 말아야 한다.
브라우저가 window.addEventListener를 지원하지 않는다고 해서 
이 브라우저가 IE이고 XMLHttpRequest도 지원하지 않을 거라고 가정해선 안된다는 이야기이다.





함수 프로터피-메모이제이션(Memoization)

함수는 객체이기 때문에 프로퍼티를 가질 수 있다.
사실 함수는 생성될때부터 프로퍼티와 메서드를 가지고 있다.

함수에 프로퍼티를 추가하여 결과를 캐시하면 다음 호출 시점에 복잡한 연산을 반복하지 않을 수 있다.
이런 활용 방법을 메모이제이션 패턴이라고 한다.

//myFunc함수에 cache프로퍼티를 생성
//myfunc.cache로 접근 가능하고 cache프로퍼티는
//함수로 전달된 param 매개변수를 키로 사용하고 계산 결과를 값으로 가지는 객체(해시)다.
var myFunc=function(param){
if( !myFunc.cache[param]){
var result={};
//비용이 많이 드는 수행
myFunc.cache[param]=result;
}
return myFunc.cache[param];
}
//캐시 저장공간
myFunc.cache={};


위 코드에서 더 많은 매개변수와 복잡한 타입을 갖는다면 일반적으로 직렬화하여 해결할 수 있다.
//tip ===========================================================================================
   여기서 말하는 직렬화, 즉 객체 직렬화는 객체의 상태를 문자열로 변환하는 과정을 말한다.
   이때 생성된 문자열은 나중에 객체 복원에 사용할 수 있다.
   JSON.stringify()- 자바스크립트 객체를 직렬화
   JSON.parse() -직렬화한 문자열을 객체로 복원
//=============================================================================================


var myFunc=function(){
//JSON문자열로 직렬화하고 cache객체에 키로 사용
var cachekey=JSON.stringify( Array.prototype.slice.call( arguments ) ),
result;

if(!myFunc.cache[cachkey]){
result={};
//비용이 많이 드는 수행....
myFunc.cache[cachekey]=result;
}
return myFunc.cache[cachekey];
}

//캐시 저장공간
myFunc.cache={};

직렬화하면 객체를 식별할 수 없게 되는 것을 주의하라.
만약 같은 프로퍼티를 가지는 두개의 다른 객체를 직렬화하면, 
이 두 객체는 같은 캐시 항목을 공유하게 될 것이다.





설정 객체 패턴

설정객체 패턴은 좀 더 깨끗한 API를 제공하는 방법이다.
라이브러리나 다른 프로그램에서 사용할 코드를 만들 때 특히 유용하다.

addPerson()이라는 함수를 작성한다고 가정해보자. 이 함수는 이름과 성을 전달받아 목록에 사람을 추가한다.
function addPerson( first, last ){....}

실제로는 생일도 저장해야하고, 성별과 주소도 선택적으로 저장할 필요가 있다는 것을 나중에 알게 되었다.
따라서 함수를 변경하여 새로운 매개변수를 추가했다.
function addPerson( first, last, dob, gender, address ){....}

이 시점에서 이미 함수는 조금 길어지고 있다. 그런데 이때 username 또한 선택사항이 아닌, 반드시 필수로 
저장해야 한다는 사실을 알게 되었다.  이제 함수를 호출할 때는 선택적인 매개변수도 전달해야 하며, 매개변수의 
순서가 뒤섞이지 않게 주의해야 한다.
addPerson( "Bruce", "Wayne", new Date(), null, null, "batman" );

많은 수의 매개변수를 전달하기는 불편하다. 모든 매개변수를 하나의 객체로 만들어 대신 전달하는 방법이 더 낫다.
이 객체를 설정(configuration)을 뜻하는 conf라고 지정하자.
addPerson( conf );

그러면 함수의 사용자는 다음과 같이 conf를 선언할 수 있다.

var conf={
usename:"batman",
first: "Bruce",
last: "Wayne"
}

설정 객체의 장점
1. 매개변수와 순서를 기억할 필요가 없다.
2. 선택적인 매개변수를 안전하게 생략할 수 있다.
3. 읽기 쉽고 유지보수가 편하다.
4. 매개변수를 추가하거나 제거하기가 편하다.

설정 객체의 단점
1. 매개변수의 이름을 기억해야 한다.
2. 프로퍼티 이름은 압축되지 않는다.

이 패턴은 함수가 DOM 엘리먼트를 생성할 때나 엘리먼트의 CSS스타일을 지정할 때 유용하다.




커리( Curry )

함수 적용
순수한 함수형 프로그래밍 언어에서, 함수는 불려지거나 호출된다고 표현하기보다는 적용(apply)된다고 표현한다.
자바스크립트에서도  Function.prototype.apply()를 사용하면 함수를 적용할 수 있다.

//함수를 정의한다.
var sayHi=function(who){
//삼항연산자로 who가 존재하면 ","+who를 반환
return "Hello"+(who? ", "+who : "" )+"!";
};
sayHi(); //"Hello"
sayHi('world');//"Hello, world!"

//함수를 적용(apply)한다.
sayHi.apply( null, ["hello"]); //"Helllo, hello!"

호출 두 결과 모두 동일하다.
apply()는 두개의 매개변수를 받는다. 
apply(함수 내에 this와 바인딩할 객체, 배열 또는 인자(arguments객체));
1번째 매개변수가 null이면, this는 전역 객체를 가리킨다.
즉 객체 메서드가 아닌 일반적인 함수로 호출할 때와 같다.
함수가 객체 메서드일 때는 null을 전달하지 않는다.

//apply() 1번째 인자를 객체를 전달한다.
var alien={
sayHi:function(who){
return "Hello"+(who? ", "+who : "") +"!";
}
};
alien.sayHi( 'world' ); //"Hello, world!"
sayHi.apply( alien, ["humans"]); //Hello, humas!"


이 코드에서 sayHi()내부의 this는 alien을 가리킨다.
앞선 예제에서 this는 전역개체를 가리킨다.

두 예제에서 설명한 것처럼 함수 호출이라는 것은 사실상 함수 적용을 가리키는 문법 설탕(syntatic sugar)나 다름 없다.
=======================================================================================================
-tip 문법 설탕(syntatic sugar)
Syntactic Sugar를 적당한 우리말로 바꾸기가 좀 어렵다. 
설탕이라는 말처럼 사람이 쓰기 편리하게 만든 문법 정도로 이해하면 되겠다.
예를 들어 공백제거 함수 만들어 적용해 보자

utils={
trim:function( str ){
var regx=/\s+/g;
str=str.replace(regx, "");
return str;
}
}
var str="AB C D E";
str=utils.trim( str );
console.log( str );//ABCDE

이렇게 공백제거를 함수로 만들어 편하게 쓸 수가 있다. 
내부에서도 물론 정규표현식이라던지 string.replace() 등으로 문자 검사, 변환등을 쉽게 할 수 있었다.
이러한 모든 편리한 문법을 문법설탕과 연결하여 생각하면 되겠다.
========================================================================================================

다시 함수적용으로 돌아오자.
apply()와 더불어 Function.prototype객체에 call()메서드도 있다는 것을 알아두자.
call()메서드 역시 apply와 매우 비슷한 문법설탕이다.
함수의 매개변수가 단하나 일때는 굳이 배열을 만들지 않고 요소 하나만 지정하는 방법이 
더 편하기 때문에 call()을 쓰는게 더 나을 때도 있다.

//배열을 만들지 않는 2번째 방법이 더 효과적~
sayHi.apply( alien, ["human"]);//Hello, humas!"
sayHi.call( alien, "humans");//Hello, humas!"

부분적인 적용

함수의 호출이 실제로는 인자의 묶음을 함수에 적용하는 것임을 알게 되었다.
인자 전부가 아니라 일부 인자만 전달하는 것이 가능할까 ?
두개의 숫자 x와 y를 더하는 add() 함수가 있다고 해보자

function add(x, y){
return x+y;
}

//인자들을 알고 있다.
add(5, 4);

//1단계---하나의 인자를 대체
function add( 5, y ){
return 5+y;
}

//2단계 -- 나머지 인자를 대체
function add( 5, 4 ){
return 5+4
}

1단계와 2단계는 유효한 자바스크립트가 아니지만 이 문제를 직접 푸는 방법이다.
이 예제의 1단계에서 부분적인 적용이 수행되었다.
1번째 인자만을 적용한 상태이며 부분적인 적용을 실행하면 결과가 나오는 대신 또
다른 함수가 나온다. 아래 그 적절한 예시이다.

var add=function( x, y ){
return x+y;
}

add.apply( null, [5, 4] ); //9
var newadd=add.partialApply( null, [5] );
newadd.apply( null, [4] ); //9


부분적인 적용을 실행한 결과는 또다른 함수이며, 이 함수는 다른 인자값을 적용하여 호출할 수 있다.
이것은 사실 add(5)(4)와 같다.
add(5)가 (4)로 호출할 수 있는 함수를 반환하기 때문이다.
즉 add(5, 4)는 add(5)(4)를 대신하는 문법 설탕이라고 생각할 수 있다.
원래 이야기로 돌아오면 partialApply() 메서드는 존재하지 않고 자바스크립트 함수는 기본적으로
이렇게 동작하지 않는다. 하지만 자바스크립트는 굉장히 동적이기에 이렇게 동작하도록 만들 수 있다.
함수가 부분적인 적용을 이해하고 처리할 수 있도록 만드는 과정을 커링이라고 한다.




커링(Curring)

하스켈 커리로부터 유래되었다. 커링은 함수를 변형하는 과정이다.
다른 함수형 언어에서는 커링 기능이 언어 자체에 내장되어 있어 모든 함수가 기본적으로 커링된다.
add()함수를 수정하여 부분 적용을 처리하는 커링 함수로 적용해 보자.
//커링된 add
function add( x, y ){
var oldx=x,
oldy=y;
if( typeof oldy==="undefined"){
//부분적인 적용
return function( newy ){
return oldx+newy;
};
}
//전체 인자를 적용
return x+y;
}
//테스트
typeof add(5);//"function"
add(3)(4); //7

//새로운 함수를 만들어 저장
var add2000=add(2000);
add2000(10);//2010

처음 add()를 호출할 때 add가 반환하는 내부 함수에 클로저를 만든다.
클로저는 원래의 x와 y값을 비공개 변수인 oldx와 oldy에 저장한다.
첫번째 변수인 oldx는 내부 함수가 실행될 때 사용된다.
부분적인 적용이 없고 x,y 둘 다 전달 되었다면, 함수는 단순히 이 둘을 더한다.
add()는 설명을 위해 필요 이상으로 약간 장황하게 구현하였다.
더 간단한 버전은 다음 예제에서 볼수 있다.
여기선 oldx와 oldy가 없다. 원래의 x는 암묵적으로 클로저에 저장되어 있고, 이전
예제에서 newy라는 새로운 변수를 만들었던 것과는 달리 지역 변수 y를 재사용한다.
function add(x,y){
//부분적인 적용
if( typeof y==="undefined" ){
return function (y){
return x+y;
};
}
//전체 인자를 적용
return x+y;
}
이 예제에서 함수 add()자체가 부분적인 적용을 처리한다.


조금 더 범용적인 방식으로 처리할 수 있을까? 다시 말해 어떤 함수라도 부분적인 매개변수를 
받는 새로운 함수로 변형할 수 있을까? 다음 예제는 이를 수행하는 범용 함수를 보여준다.
function schonfinkelize(fn){
//schonfinkelize()함수가 조금 복잡해지는 이유는 단지 자바스크립트에서 arguments가 실제로는
//배열이 아니기 때문이다. Array.prototype으로부터 slice()메서드를 빌려오면 arguments를 배열로
//바꿔 사용하기 더 편리하게 만들 수 있다.
var slice=Array.prototype.slice,
//schonfinkelize()가 호출될 때 지역변수 slice에 slice()메서드에 대한 참조를 저장하고 stored_args에 인자를 저장한다.
//이때 1번째 인자는 커링될 함수이기 때문에 떼어낸다.
stored_args=slice.call( arguments, 1 );
//새로운 함수를 반환한다.
return function(){
//반환된 새로운 함수는 호출되었을 때 클로저를 통해 이전에 비공개로 저장해 둔
// stored_args와 slice참조에 접근할 수 있다.
var new_args=slice.call( arguments ),
//일부 적용된 인자인 stored_args와 새로운 인자 new_args를 합친다.
args=stored_args.concat(new_args);
//클로저에 저장되어 있는 원래의 함수fn에 적용
return fn.apply(null, args);
}
}

//일반 함수
function add( x, y){
return x+y;
}

//함수를 커링하여 새로운 함수를 얻는다
var newadd=schonfinkelize(add, 5);
newadd(4); //9

//반환되는 새로운 함수를 바로 호출할 수도 있다.
schonfinkelize(add, 6)(7); //13

schonfinkelize()에 매개변수를 한개만 쓸 수 있거나 커링을 한 단계만 할 수 있는 건 아니다.

//일반 함수
function add( a, b, c, d, e ){
return a+b+c+d+e;
}

//여러 개의 인자를 사용할 수도 있다.
schonfinkelize( add, 1, 2, 3)(5, 5); //16

//2단계의 커링
var addOne=schonfinkelize(add, 1);
addOne(10, 10, 10, 10); //41
var addSix=schonfinkelize(addOne, 2, 3);
addSix(5, 5);//16


커링을 사용해야 할 경우

어떤 함수를 호출할 때 대부분의 매개변수가 항상 비슷하다면, 커링의 적합한 후보라고 할 수 있다.
매개변수 일부를 적용하여 새로운 함수를 동적으로 생성하면 이 함수는 반복되는 매개변수를 내부적으로 저장하여 
매번 인자를 전달하지 않아도 원 함수가 기대하는 전체 목록을 미리 채워놓을 것이다.



종합 정리
1. 함수는 일급객체. 값으로 전달가능, 프로퍼티와 메서드 확장할 수 있다.
2. 함수는 지역 유효범위 제공. 중괄호 묶음은 그렇지 않다.
로컬 변수의 선언은 로컬 유효범위의 맨 윗부분으로 호이스팅된다.

문법
1. 기명함수표현식
2. 함수 표현식(익명함수)
3. 함수 선언문

패턴
1. API패턴-함수에 더 좋고 깔끔한 인터페이스 제공
   *콜백 패턴-함수를 인자로 전달
   *설정 객체- 함수에 많은 수의 매개변수 전달 때 
   *함수 반환- 함수의 반환값이 함수일 수 있다.
   *커링- 원본 함수와 매개변수 일부를 물려받는 새로운 함수 생성.
   
2. 초기화 패턴-초기화와 설정작업할 때, 전역네임스페이스를 말끔히 해줌. 임시변수를 사용해 좀 더 깨끗하고 구조화 수행.
   *즉시 실행 함수- 정의하자마자 실행
   *즉시 객체 초기화- 익명 객체 내부에서 초기화 작업을 구조화 후 즉시 호출가능한 메서드 제공.
   *초기화 시점의 분기- 최초 코드 실행 시점에 코드를 분기, 애플리케이션 생명 주기 동안 계속해서 분기가 발생 않도록 막는다.

3. 성능 패턴- 코드 실행 속도를 높이는 데 도움을 준다.
  *메모이제이션 패턴- 함수 프로퍼티를 사용해 계산된 값을 다시 계산되지 않도록 한다.
  *자기선언 함수- 자기 자신을 덮어씀으로써 2번째 호출 이후부터는 작업량이 줄어들게 한다.


728x90
반응형

'Programming language > javascript' 카테고리의 다른 글

javascript 유효범위체인  (0) 2013.02.07
자바스크립트 많이 쓰는 용어  (0) 2013.02.07
함수1  (0) 2013.02.06
바인딩이란?  (0) 2013.02.06
클로저(closure)란?  (0) 2013.02.06
댓글