모바일/Javascript

함수에 대하여

박종명 2013. 7. 31. 10:33
728x90

자바스크립트를 공부하다 보면 함수의 독특함에 많이 놀라곤 한다

이 언어가 가지는 유연함의 중심에 함수가 있으며 과히 함수기반 언어라 할 수 있다

 

먼저 모든 프로그래밍 언어에 존재하는 함수는 수학의 그것과 개념적으로 완전히 동일하다

즉 입력 값(x)에 대응하는 출력 값(y)이라는 개념 즉, f(x) = y라는 수학적 개념이 프로그래밍 언어에도 그대로 계승되었다는 것이다

 

 

 

먼저 가장 일반적인 함수 형태를 살펴 보자

//함수 정의
function Size(width,height){
    var size = width * height;
    return size;
}
       
//함수 호출
var size = Size(10,10);   
console.log(size); //100

 

function 이라는 키워드를 사용하여 함수를 정의할 수 있는데 위의 예에서는 너비와 높이를 입력 값으로 받아서 두 값의 곱 즉 사이즈를 출력(반환)하는 함수를 정의하고 호출하는 코드를 작성했다

 

return 문은 없어도 된다(이 경우 undefined가 반환된다)

대부분의 프로그래밍 언어처럼 자바스크립트 함수에서도 반드시 return으로 반환해야 하는 것은 아니다

대체로 void 함수로 정의하는데 자바스크립트에서는 반환 값의 타입을 지정하지 않기 때문에 다음 코드와 같이 return 반환이 없는 함수를 정의할 수 있다

 

function writeLog(message){
   console.log(message);
}

 

이 경우, 한가지 참고할 사항은 return 없는 함수의 호출 결과는 자동으로 undefined가 반환된다는 점이다.

return문을 통한 명시적 반환이 없는 함수가 자동으로 뱉는 undefined는 실제 프로그램에서 사용할 일은 거의 없다고 봐도 개념적으로는 기억해 두는 것이 좋다

//return문이 없는 함수 정의
function writeLog(message){
    console.log(message);
}
            
var returnValue = writeLog('Hello'); //Hello
console.log(returnValue);               //undefined

 

 

매개변수의 데이터 타입을 명시하지 않아도 된다.

심지어 매개변수의 개수가 달라도 된다

C#과 같은 언어에서는 타입 안정성을 위해 데이터 타입 제약을 강하게 둔다. 즉 사용하고자 하는 데이터의 타입이 사전에 정확히 명시되어야만 컴파일을 허용한다(C#의 var 선언도 결국 컴파일 시점에 타입이 결정된다)

 

반면, 자바스크립트는 타입 제약이 느슨한 언어이다. 앞서 코드들에서 봤겠지만 함수의 출력값은 물론 입력 매개변수에 대해서도 타입을 지정하지 않는다

 

또한 더욱 심오한(?) 것은 입력 매개변수의 개수도 개의치 않는다는 점이다. 앞서 작성했던 Size() 함수를 호출할 때 함수에 정의된 입력 매개변수 보다 더 많은 매개변수를 전달해 보자

function Size(width,height){
   var size = width * height;
   return size;
}

 

//더 많은 매개변수 전달
var size = Size(10,10,10);
console.log(size); //100

 

위 코드는 여전히 잘 동작하며 원하던 100이라는 결과를 내뱉는다.

자바스크립트에서는 함수에 정의된 것 보다 더 많은 매개변수가 전달될 경우 그것들은 무시하게 된다

 

그럼 더 적은 매개변수를 전달하면 어떻게 될까?

var size = Size(10);
console.log(size); //NaN

 

결과는 NaN이다. 오류가 아니라 정상적으로(?) 결과를 반환하는 것이다.

자바스크립트에서는 함수에 정의된 것 보다 더 적은 매개변수가 전달될 경우 나머지 값은 undefined로 할당된다.

 

즉 위의 예에서는 width = 10, height = undefined 가 되어 다음 식으로 계산된 결과를 반환한 것이다.

 

10 * undefined = NaN

 

NaN이 반환된 것은 undefined가 숫자 문맥에서는 NaN으로 평가되고 NaN과 숫자의 연산은 모두 NaN이기 때문이다

 

함수 리터럴

자바스크립트엣는 이름 없는 함수를 정의할 수 있다. 이를 함수 리터릴이라 한다. 앞서 Size 함수를 함수 리터럴로 정의하면 다음과 같다

var f = function(width,height){
       var size = width * height;
       return size;
}
            
var size = f(10,10);
console.log(size); //100

 

위의 코드를 보면 함수의 이름이 생략되었다. 여기소 var f 는 함수의 이름이 아니라 변수이며 함수 리터럴을 변수에 할당한 것이다.

 

함수 정의와 호출을 동시에

다음 코드와 같이 자바스크립트는 함수 정의와 호출을 동시에 할 수 있도록 지원한다. 아래 코드는 함수 리터럴을 선언하고 바로 호출했지만 이름을 가진 함수도 동일하게 작성할 수 있다

var result = (function(width,height){
       var size = width * height;
       return size;
})(10,10);
            
console.log(result); //100

 

 

데이터로써의 함수

앞서 함수 리터럴에서 본 것 처럼, 함수를 변수에 할당할 수 있다는 것은 함수 자체가 데이터로써의 역할을 할 수 있다는 예기가 된다.  이번 예에서는 두 개의 함수를 정의하고 이를 배열에 담아 배열로 함수를 호출하는 예를 살펴보자.

function Size(width,height){
      var size = width * height;
      return size;

   
function Sum(value1,value2){
      return value1 + value2;      
}   
  
var functionArray = new Array();
functionArray[0] = Size;
functionArray[1] = Sum;

  
for(var i = 0; i < functionArray.length; i++){
   console.log(functionArray[i](i,i));

 

위 코드는, 함수 자체를 배열로 담을 수 있다는 것으로 함수가 데이터로써의 역할을 한다는 것을 다시 한번 확인 가능하며 더불어 자바스크립트의 배열 유연성을 덤으로 눈치 챌 수 있게 하는 예이다.

 

데이터로써의 함수 특징을 볼 수 있는 마지막 예는 함수의 매개변수로 사용되는 경우이다.

function Size(width,height){
      var size = width * height;
      return size;

      
function DoubleSize(value1,value2,func){
   return func(value1,value2) * func(value1,value2);
}  
   
//함수의 매개변수로 또 다른 함수를 전달
var result = DoubleSize(10,10,Size);
console.log(result); //10000

 

C#과 같은 언어에서도 delegate라는 개념을 기반으로 한 함수(메서드)의 데이터화를 지원하지만 자바스크립트의 그것에 비하면 역사적으로 늦은 편에 속한다.

 

객체로써의 함수

C#과 Java와 같이 자바스크립트에서도 객체 기반의 프로그래밍이 가능하다.

언어적인 특징으로만 보면 자바스크립트는 생성자 함수와 프로토타입을 기반으로 한 객체 지향 프로그래밍을 지원하며 클로저를 이용한 private 멤버 구현하는 등 C#과 Java의 그것과는 다르다 할 수 있지만 상속/오버라이딩, 캡슐화와 같은 객체의 특징을 비롯해 객체에 기반한 구조적인 프로그래밍이 가능하기 때문에 객체 기반 장점을 누릴 수 있다.

 

자바스크립트에서는 클래스 생성 구문이 없다. 다만 생성자 함수를 통해 클래스와 같은 원형을 만들 수 있다

다음의 코드는 Rect라는 생성자 함수를 정의해서 객체로 사용되는 예를 보여준다.

 

이렇게 객체로 사용하는 함수를 메서드라고 부르며 메서드에는 this라는 키워드로 자신의 객체에 접근할 수 있게 된다. 내부적으로 보면 메서드는 그 메서드가 속해 있는 객체를 묵시적으로 전달받게 되는데 이 객체를 this로 접근할 수 있다는 것이다.

 

//생성자 함수 정의
function Rect(width, height){
    this.width = width;
    this.height = height;
    
    this.Size = function(){
      return this.width * this.height;
    }
}
     
//객체 생성 및 활용
var rect = new Rect(10,10);
var size = rect.Size();
   
console.log(size); //100

 

여기서 한가지 주의할 점은, 생성자 함수를 객체로 생성해서 호출(메서드 호출)하는 것이 아닌 일반 함수로 호출할 경우에는 함수 안의 this는 전역 객체를 가리키게 된다는 것이다. 다음의 예를 살펴 보자.

//생성자 함수 정의
function Rect(width){
    this.width = width;    //this는 메서드와 일반함수로 호출될 경우 대상이 달라진다
}
   
//객체(메서드)로 호출(Rect객체의 멤버 변수 width 값을 수정한다)
var rectObj = new Rect(10);
       
//일반 함수로 호출(전역 객체의 width 값을 수정한다)
var rectFun = Rect(20)
   
console.log(rectObj.width); //10          
console.log(width);            //20