티스토리 뷰

Programming language/javascript

함수1

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

함수-- Javascript Pattern 참고 및 정리

1. 자바스크립트에서 함수는 일급(first-class)객체다.
2. 함수는 유효범위(scope)를 제공한다.

함수는 다음과 같은 특징을 가지는 객체다
-런타임, 즉 프로그램 실행 중에 동적 생성 가능.
-변수에 할당, 다른 변수에 참조 복사 및 확장가능. 몇몇 경우 제외하고 삭제 가능.
-다른 함수의 인자로 전달 가능, 다른 함수의 반환값이 될 수 있다.
-자기 자신의 프로퍼티 및 메서드를 가질 수 있다.

일반적으로 말하자면 자바스크립트에서 함수는 하나의 객체라고 생각하면 된다.
다만 이 객체는 호출하여 실행할 수 있는 특별한 기능을 가지고 있다.
함수가 객체라는 사실은 다음과 같은 new Function()생성자의 동작을 보면 명확해진다.
//안티패턴
//데모목적으로만 테스트
var add=new Function('a, b', 'return a+b' );
add(1,2);

이 코드에서 add()는 생성자를 통해 만들었기에 객체라는 사실이 자명하다.
하지만 Function생성자의 사용은 코드가 문자열로 전달되어 평가되기에 eval() 만큼이나 좋지 않은 방법이다


두번째로 중요한 기능은 함수가 유효범위(scope)를 제공한다는 것이다.
자바스크립트에서는 중괄호( {} ) 지역 유효범위가 없다. 단지 함수 유효범위가 있을 뿐이다.
어떤 변수이건 함수내에서 var로 정의되면 지역변수고 함수 밖에서는 참조할 수 없다.
중괄호가 지역 유효범위를 제공하지 않는다는 말은 변수를 if조건문이나 for문, 
while문 내에서 var로 정의해도, 이 변수가 if나 for문의 지역 변수가 되지 않는다는 뜻이다.
이 변수는 해당 블럭을  감싸는 함수가 있을 때만 지역 변수가 된다. 감싸는 함수가 없으면 전역 변수가 된다.



용어 정리
패턴에 대해 이야기할때 합의된 정확한 이름을 사용하는 것은 코드자체만큼이나 중요하다.

1. 기명 함수 표현식( named function expression )
var add=function add( a, b ){
 return a+b;
}

2.무명 함수 표현식( unnamed function expression )
이름을 생략한 함수 표현식.
  간단하게 함수 표현식( function expression ) or 익명함수( anonymous function )라는 말로도 널리 쓰인다.
//함수 표현식( 익명 함수 )
var add=function( a, b ){
return a+b;
}
따라서 함수표현식이 더 넓은 의미, 기명함수표현식은 함수표현식 중 이름을 정의한 함수를 가리키는 구체적인 용어.

3.함수 선언문( function declaration )
function foo(){
}
함수 리터럴이라는 용어도 자주 사용된다. 이 용어는 함수 표현식을 뜻할 수도 있고, 기명함수표현식을 뜻할 수도 있다.
따라서 이런 애매한 표현은 사용하지 않는 편이 낫다.




선언문 vs 표현식: 이름과 호이스팅
함수 선언문과 함수 표현식 중 어떤 것을 사용해야 할까~? 
이 문제는 구문상 함수 선언물을 사용할 수 없는 경우를 생각하면 쉽게 풀린다.

//함수 표현식을 callMe 함수의 인자로 전달한다.
callMe( function(){
  //이 함수는 무명함수(익명함수)표현식이다.
} );

//기명 함수 표현식을 callMe함수의 인자로 전달한다.
callMe( function me(){
  //이 함수는 me라는 기명함수표현식이다.
});

//함수 표현식을 객체의 프로퍼티로 저장한다.
var myobject={
  say:function(){
    //이 함수는 함수표현식이다.
  }
};

함수 선언문은 전역 유효범위나 다른 함수의 본문 내부, 즉 '프로그램코드'에서 만 쓸 수 있다.
프로퍼티에 할당할 수 없고, 함수 호출시 인자로 함수를 넘길 때도 사용할 수 없다.
그렇다면 언제 함수 선언문을 사용할 수 있을까?
다음 예제에서 함수 foo(), local()은 모두 함수 선언문 패턴으로 정의되었다.

//전역 유효범위
function foo(){}

function local(){
   //지역 유효범위
  function bar(){
   return bar;
  }
}




함수의 name 프로퍼티

함수를 정의하는 패턴을 선택할 때 읽기 전용인 name프로퍼티를 쓸 일이 있는지도 고려해봐야한다.
표준은 아니지만 많은 실행 환경에서 사용가능하다.
함수 선언문과 기명함수표현식을 사용하면 name프로퍼티가 정의된다.
반면 무명함수 표현식의 name프로퍼티 값은 경우에 따라 다르다.
IE에서는 undefined가 되고, 파이어폭스와 웹킷에서는 빈 문자열로 정의된다.
function foo(){}// 함수 선언문
var bar=function(){};// 함수 표현식
var baz=function baz(){} //기명함수표현식

foo.name; // "foo"
bar.name; //""
baz.name; //"baz"

name 프로퍼티는 파이어버그나 다른 디버거에서 코드를 디버깅할때 유용하다.
함수내에서 발생한 에러를 보여주어야 할때, 디버거가 name프로퍼티 값을 확인하여 이름표로 쓸 수 있기 때문이다.
name프로퍼티는 함수 내부에서 자신을 재귀적으로 호출할때 사용하기도 한다.
이 두가지 경우 해당하지않으면 무명함수 표현식이 더 쓰기 쉽고 간결하다.

함수선언문보다 함수표현식을 선호하는 이유
-함수가 객체의 일종이며 어떤 특별한 언어 구성요소가 아니라는 사실이 좀 더 드러나기 때문이다.

엄밀히 말하면, 기명 함수 표현식을 그와 다른 이름의 변수에 할당할 수 있다.
var foo=function bar(){};
하지만 어떤 브라우저(IE)에서는 이 사용법이 제대로 구현되어 있지 않기 때문에 추천하지 않는다.







함수 호이스팅
함수 선언문과 기명함수표현식의 동작 방식은 호이스팅 동작에 차이점이 있다.
모든 변수는 함수 본문 어느 부분에서 선언되더라도 내부적으로 함수의 맨 윗부분으로 끌어올려(hoist)진다.
함수 또한 결국 변수에 할당되는 객체이기 때문에 동일한 방식이 적용된다.
함수 선언문을 사용하면 변수 선언뿐 아니라 함수 정의자체도 호이스팅되기 때문에 자칫 오류를 만들어내기 쉽다.

//안티패턴
//설명을 위해 사용
//전역 함수
function foo(){
  alert( 'global foo' );
}
function bar(){
  alert( 'global bar' );
}

function hoistMe(){
    console.log( typeof foo );//"function"
    console.log( typeof bar );//"undefined"
  
    foo(); //"local foo"
    bar(); //TypeError : bar is not a function
  
   //함수 선언문
   //변수 'foo'와 정의된 함수 모두 호이스팅된다.
    function foo(){
       alert('local foo');
    }
  
   //함수 표현식
    //변수 'bar'는 호이스팅되지만 정의된 함수는 호이스팅되지 않는다.
    var bar=function(){
       alert('local bar');
    };
}
hoistMe();

hoistMe()함수 내에서 foo와 bar를 정의하면 실제 변수를 정의한 위치와 상관없이 
끌어올려져 전역 변수인 foo와 bar를 덮어쓰게 된다. 
그런데 지역변수foo()는 나중에 정의되어도 상단으로 호이스팅되어 정상 동작하는 반면, 
bar()의 정의는 호이스팅되지 않고 선언문만 호이스팅된다. 
때문에 bar()의 정의가 나오기 전까지는 undefined 상태이고, 따라서 함수로 사용할 수도 없다.
또한 선언문 자체는 호이스팅되었기 때문에 유효범위 체인 내에서 전역bar()도 보이지 않는다.






콜백패턴

함수는 객체다. 즉 함수를 다른 함수에 인자로 전달할 수 있다.

function writeCode( callback ){
 //어떤 작업수행
 callback();
}
function introduceBugs(){
  //버그를 만든다.
}  
writeCode( introduceBugs );

introduceBugs()함수를 writeCode()함수의 인자로 전달하면, 
아마도 writeCode()는  어느 시점에 introduceBugs()를 실행할 것이다.
이때 introduceBugs()를 콜백 함수 또는 간단하게 콜백이라고 부른다.
writeCode()의 인자로 괄호없이 전달되었다. 괄호를 붙이면 함수가 실행되는데 위 경우는 
함수 참조만 하고 실행은 추후에 적절한 시점에 writeCode()가 해주기에 괄호가 없다.






콜백과 유효범위
콜백이 일회성의 익명함수나 전역 함수가 아니고 객체의 메서드인 경우도 많다. 
만약 콜백메서드가 자신이 속해있는 객체를 참조하기 위해 this를 사용하면 예상치 않게 동작할 수도 있다.
myapp이라는 객체의 메서드인 paint()함수를 콜백으로 사용한다고 가정해보자.

var myapp={};
myapp.color="greein";
myapp.paint=function(node){
  node.style.color=this.color;
}

var findNodes=function( callback ){
  //....
  if( typeof callback==="function"){
    callback( found );
  }
}

findNodes( myapp.paint )를 호출하면 this.color가 정의되지 않아 예상대로 동작하지 않는다.
findNodes()가 전역함수이기 때문에 객체는 this는 전역 객체를 참조한다.
findeNodes()가 (dom.findeNodes()처럼) dom이라는 객체의 메서드라면, 콜백 내부의 this는 예상과 달리
myapp이 아닌 dom을 참조하게 된다.

이 문제를 해결하기 위해서는 콜백함수와 함께 콜백이 속해 있는 객체를 전달하면 된다.

findNodes(myapp.paint, myapp); var findNodes=function(callback, callback_obj){ if( typeof callback==="function"){ callback.call( callback_obj, found ); } } //위 예제를 보면 myapp객체를 두번 반복하는데 다음과 같이 하면 객체를 두번 반복하지 않아도 된다. findNodes( "paint", myapp ); // callback 매개변수로 전달되는 인자값은 문자열과 함수 2가지 대응되게끔 설정하였다. var findNodes=function ( callback, callback_obj ){ if( typeof callback==="string" ){ callback=callback_obj[callback]; } //call()을 써서 전달받은 객체를 바인딩한다. if( typeof callback==="function"){ callback.call( callback_obj, found); } }



함수는 객체이기 때문에 반환 값으로 사용될 수 있다. 
즉 함수의 실행 결과로 꼭 어떤 데이터 값이나 배열을 반환할 필요는 없다는 뜻이다.
보다 특화된 함수를 반환할 수도 있고, 입력 값에 따라 필요한 함수를 새로 만들어낼 수 있다.

var setup=function(){
 console.log(1);
 return function(){
   console.log(2);
 }
}

var my=setup();// 1출력
my();//2 출력

setup()은 반환된 함수를 감싸고 있기 때문에 클로저를 생성한다.
클로저는 반환되는 함수에서는 접근할 수 있지만 코드 외부에서는 접근불가이기에 
비공개 데이터 저장을 위해 사용할 수 있다.

var setup=function(){
  var count=0;
  return function(){
     return (count+=1);
  }
}
var next=setup();
next();//1을 반환
next();//2를 반환
next();//3을 반환






자기 자신을 정의하는 함수

함수는 동적으로 정의할 수 있고 변수에 할당할 수 있다.
새로운 함수를 만들어 이미 다른 함수를 가지고 있는 변수에 할당한다면, 
새로운 함수가 이전 함수를 덮어 쓰게 된다.
어떤 면에서는 이전 함수 포인터가 새로운 함수를 가리키도록 재사용하는 것이다.
이런 일을 이전 함수의 본문 내에서 할 수도 있다.

var scareMe=function(){
 console.log("Boo!");
 scareMe=function(){
    console.log("Double boo!")
 }
}
scareMe();
scareMe();
이 패턴은 함수가 어떤 초기화 준비 작업을 단 한번만 수행할 경우에 유용하다.
정의된 함수의 작업량이 적기 때문에 이 패턴은 애플리케이션 성능에 확실히 도움이 된다.
단점은 자기자신을 재정의한 후에는 이전 원본 함수에 추가했던 프로퍼티들은 모두 찾을 수 없게 된다는 점이다
또한 함수가 다른 이름으로 사용된다면, 예를 들어 다른 변수에 할당하거나, 객체의 메서드로써 사용되면 
재정의된 부분이 아니라 원본 함수의 본문이 실행된다.


//1. 새로운 프로퍼티를 추가한다.
scareMe.property="properly";

//2. 다른 이름으로 할당한다.
var prank=scareMe;

//3. 메서드로 사용한다.
var spooky={
 boo:scareMe
}

//새로운 이름으로 호출한다.
prank();//"Boo!"
prank();//"Boo!"
console.log(prank.property);//"properly"

//메서드로 호출한다.
spooky.boo();//"Boo!";
spooky.boo();//"Boo!";
console.log(spooky.boo.property);//"properly"

//자기자신을 재정의한 함수를 사용한다.
scareMe();//Double boo!
scareMe();//Double boo!
console.log( scareMe.property ); //undefined

예제에서 보는 것처럼 함수가 새로운 변수에 할당되면 예상과 달리 자기 자신을 정의하지 않는다.
prank()가 호출될 때마다 알림으로 "Boo!"가 출력된다. 또한 전역 scareMe()함수를 덮어썼는데도 prank() 자신은 
여전히 property프로퍼티를 포함한 이전의 정의를 참조한다.
spooky객체의 boo()메서드로 함수가 사용될 때에도 똑같은 일이 일어난다.
이 모든 호출들은 계속해서 전역 scareMe() 포인터를 덮어 쓴다.
따라서 마지막에 전역 scareMe()가 호출되었을때 비로소 "Double boo"를 출력하도록 갱신된 본문이
처음으로 실행된다. 또한 scareMe.property도 더이상 참조할 수 없게 된다.






즉시 실행 함수

문법
(function(){
console.log("watch out");
}());

이 패턴은 사실상 (기명이든 무명이든) 함수 표현식을 생성한 직후 실행시킨다.
즉시 실행 함수라는 용어는 ECMAScript표준에서 정의된 용어가 아니지만, 짧고 간단하다.
*함수를 함수 표현식으로 선언한다. (함수 선언문으로 동작하지 않는다.)
*함수가 즉시 실행될 수 있도록 마지막에 괄호쌍을 추가한다.
*전체 함수를 괄호로 감싼다. ( 함수를 변수에 할당하지 않을 경우에만 필요하다.)


다음의 대체 문법 또한 일반적으로 사용되지만 JSList는 처음의 패턴을 선호한다.

(function(){
 console.log( "watch out!");
})();

페이지 로드완료후 이벤트 핸들러 등록등의 초기 설정 작업시 단 한번만 되기에 이름을 지정한 함수를 
생성할 필요가 없다. 그러기에 초기화 단계 완료할때까지만 사용할 임시변수들이 필요하다.
한번만 사용할거기에 이 모든 변수를 전역으로 생성하는 것은 좋지 않은 생각이다.
이럴때 즉시 실행 함수가 필요하다. 즉시 실행 함수는 모든 코드를 지역 유효범위로 감싸고
어떤 변수도 전역 유효범위로 새어나가지 않게 한다.

(function(){
 var days=['Sun', 'Mon', 'Tue', 'Wed', 'Fri', 'Sat'],
 today=new Date(),
 msg='Today is'+days[today.getDay()]+', '+today.getDate()
 console.log( msg );

}());//"Today is Fri, 13"
//만약 이코드가 즉시 실행 함수로 감싸져 있지 않았다면 days, today, msg변수는 
//전역 변수가 되어 초기화 코드 이후에도 남아 있게 될 것이다.


즉시 실행 함수의 매개변수
즉시 실행 함수에 인자를 전달할 수도 있다. 

(function(who, when){
 console.log("I met"+who+" on "+when);
}( "Joe Black", new Date()));
//I met Joe Black on Sun Jan 27 2013 00:08:08 GMT+0900 (KST) 

일반적으로 전역객체가 즉시 실행 함수의 인자로 전달 된다.
따라서 즉시 실행 함수 내에서 window를 사용하지 않고도 전역 객체에 접근할 수 있다.
이러한 방법을 통해 브라우저 외의 실행 환경에서도 코드를 공통으로 사용할 수 있다.

(function (global){
//전역객체를 'global'로 참조
}(this));

하지만 즉시 실행 함수에 대해 인자를 너무 많이 전달하지 않는 것이 좋다.
코드 동작을 이해하려고 계속해서 코드의 맨 윗부분과 아랫부분 사이를 오가며 스크롤하기가 부담스럽기 때문이다.






즉시 실행 함수의 반환 값
다른 함수와 비슷하게 즉시 실행 함수도 값을 반환할 수 있고 반환된 값은 변수에 할당될 수 있다.
(function(){
var result=(function(){
 return 2+2;
}());

감싸고 있는 괄호를 생략해서 같은 동작을 구현할 수 있다.
즉시 실행 함수의 반환 값을 변수에 할당할때는 괄호가 필요 없기 때문이다.
첫 번째 괄호쌍을 생략하면 다음과 같다.

(function(){
var result=function(){
 return 2+2;
}();

이 문법이 더 간단하지만 오해의 소지가 있다.
누군가 코드를 읽을 때 마지막의 ()를 눈여겨보지 못한다면 이 구문의 결과가 함수를 참조한다고 
생각할 수 있다.  동일한 결과를 갖는 다른 문법은 아래와 같다.

(function(){
var result=( function(){
 return 2+2;
})();

즉시 실행 함수의 실행 결과로 원시 데이터 타입인 정수 값을 반환한다.
원시 데이터값 외에도 모든 타입의 값이 가능하고 새로운 함수를 반환할 수도 있다.
이 경우 즉시 실행 함수의 유효범위를 사용해 특정 데이터를 비공개 상태(private)로 
저장하고, 반환되는 내부 함수에서만 접근하도록 할 수도 있다.
//즉시 실행 함수가 함수를 반환하고 이 반환 값이 getResult라는 변수에 할당된다.
var getResult=(function(){
 var res=2+2;

 return function(){
    //미리 계산하여 클로저에 저장해둔 res라는 값을 반환한다.
    return res;
 }
}());
즉시 실행 함수는 객체 프로퍼티를 정의할 때에도 사용할 수 있다.
어떤 객체의 프로퍼티가 객체의 생명주기 동안에는 값이 변하지 않고, 처음에 값을 정의할 때는 적절한
계산을 위한 작업이 필요하다고 가정해보자. 그렇다면 이 작업을 즉시 실행 함수로 감싼 후,
즉시 실행 함수의 반환 값을 프로퍼티값으로 할당하면 된다.
var o={
 //스크립트가 로딩될때 실행되어 프로퍼티를 정의한다.
 message:(function(){
        var who="me",
        what="call";
        return what+" "+who;
 }()),
 getMsg:function(){
       return this.message;
 }
};
//사용법
o.getMsg();//"call me"
o.message; //"call me"




즉시 실행 함수 장점과 사용법

전역 변수를 남기지 않고 상당량의 작업을 할 수 있게 해준다.
이 패턴은 북마클릿에서도 자주 쓰인다.
즉시 실행 함수패턴을 사용해 개별 기능을 독자적인 모듈로 감쌀 수도 있다.
페이지 작업에 개선사항 추가, 제거, 개별 테스트, 사용자가 비활성화할 수 있게 하는 등의 작업을 할 수 있다.

//module1.js에서 정의한 module1
(function(){
//모든 module1 코드
}());
이 템플릿을 활용하면 기능을 단위별로 정의할 수 있다.






즉시 객체 초기화
전역 유효범위가 난잡해지지 않도록 보호하는 또 다른 방법이 즉시 객체 초기화패턴이다.
이 패턴은 객체가 생성된 즉시 init()메서드를 실행해 객체를 사용한다.
init()함수는 모든 초기화 작업을 처리하게 한다.
({ 
   //여기에 설정 값(설정 상수)들을 정의할 수 있다.
   maxwidth:600,
   maxheight:400,
   //유틸리티 메서드 또한 정의할 수 있다.
   gimmeMax:function(){
    return this.maxwidth+"x"+this.maxheight;
   },
   //초기화
   init:function(){
   console.log(this.gimmeMax());
   //더 많은 초기화 작업들.....
   }
}).init();
문법적으로 객체 리터럴 사용법처럼 객체 생성과 똑같이 생각하면 된다.
아래처럼 두가지로 표현된다.
({....}).init();
({....}.init());

이 패턴의 장점은 즉시 실행함수패턴의 장점과 동일하다.
단한번 초기화 작업으로 전역 네임스페이스 보호가 가능하다.
도우미 함수들을 임시객체의 프로퍼티로 정의하면 즉시실행함수를 여기저기 흩어놓고 쓰는 것보다 훨씬 구분하기 쉽다.


이 패턴의 단점은 자바스크립트 압축도구가 즉시 실행 함수 패턴에 비해 압축 못할 수도 있다.
비공개 프로퍼티와 메서드의 이름은 더 짧게 변경되지 않는데 압축 도구의 관점에서는 그런 방식이 안전하기 때문이다. 
구글의 클로저 컴파일러의 고급모드만이 즉시 초기화되는 객체의 프로퍼티명을 단축시켜준다.
압축되면 아래와 같이 된다.
({d:600,c:400,a:function(){return this.d+"x"+this.c},b:function(){console.log(this.a())}}).b();
이 패턴은 주로 일회성 작업에 적합하다. init()가 완료되고 나면 객체에 접근할 수 없다.
init()가 완료된 이후에도 객체의 참조를 유지하고 싶다면 init()의 마지막에 return this;를 추가하면 된다.


728x90
반응형

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

자바스크립트 많이 쓰는 용어  (0) 2013.02.07
함수2  (0) 2013.02.06
바인딩이란?  (0) 2013.02.06
클로저(closure)란?  (0) 2013.02.06
가변길이 전달 인자 목록 : Arguments객체  (0) 2013.02.06
댓글