2019. 06. 02 수정


JS에서 함수는 꽤 여러가지 특징이 있습니다.

  • 일급 객체( 링크 )
  • 익명 함수 권장
  • 파라미터 선언에 대해 자유로움
  • 비동기로 처리되는 JS 함수에서는 콜백(callback) 함수 존재

때문에 JS 에서는 함수를 조금 특별하게 바라 볼 필요가 있습니다. 





1. 함수 정의

JS에서 함수를 선언할 때는 다음과 같이 3가지 방식이 있습니다.


1) 함수 선언문 사용

function 키워드와 함수명이 정의된 방식입니다.

function add(a,b){
return a + b;
}

console.log(add(2,5)); // 7




2) 함수 표현식

함수 리터럴로 익명 함수를 만들고 변수에 할당하는 방식입니다.

익명 함수가 무엇인지는 밑에서 살펴볼 것이며, 간단하게 이름이 없는 함수라고 생각하시면 됩니다.

또한 JS의 함수는 일급 객체라서 함수를 하나의 값으로 취급할 수 있기 때문에 변수에 할당이 가능합니다.

let add = function(a,b){
return a + b;
}

console.log(add(2,5)); // 7

함수 선언문 방식과의 차이점은 변수 add가 함수 이름이 아니며, 함수의 참조값을 가진다는 것입니다.


let add = function plus(a,b){
return a + b;
}

console.log(add(2,5)); // 7
console.log(plus(2,5)); // error : plus is not defined

물론 위의 코드처럼 이름이 있는 함수를 작성하고 변수에 할당해도 되지만 주의할 점이 있습니다.


plus라는 이름으로 함수를 호출하면 정의되지 않았다는 에러메시지를 출력합니다.

그 이유는 함수 표현식에서 사용된 함수 이름( plus )은 외부에서 접근이 불가능하기 때문인데 자세한 원인은 다음과 같습니다.


사실 앞에서 살펴본 함수 선언문은 함수 표현식으로 동작합니다.

// 1번 코드
function add(a,b){
return a + b;
}

// 2번 코드
let add = function add(a,b){
return a + b;
}

위의 두 코드는 정확히 일치하며, JS 엔진에서는 1번 코드를 2번코드로 변환합니다.

즉, 함수 선언문에서 함수 이름으로 함수가 호출되는 것처럼 보이지만 실제로는 함수 변수로 함수를 호출한 것입니다.




3) Function() 생성자 함수를 통한 함수 생성

JS의 함수는 Function()이라는 내장 생성자 함수로부터 생성된 객체입니다.

즉, 앞에서 살펴본 함수 선언문, 함수 표현식 모두 내부적으로 Function() 생성자 함수로부터 함수가 생성된다는 것입니다.

( 생성자 함수는 간단하게 객체를 생성하는 함수라고 생각하시면 되는데, 자세한 내용은 여기를 참고해주세요. )

그럼에도 함수 선언문, 표현식을 사용하는 이유는 함수 작성이 편리하기 때문입니다.


let add = new Function('a', 'b', 'return a + b');
console.log(add(2,5));

위의 코드는 생성자 함수를 통해 함수를 선언한 방식을 보여줍니다.

별로 이렇게 함수를 생성하고 싶지 않다는 생각이 들죠?

이 방식은 주로 사용하는 방식이 아니므로 간단하게 참고만 하시면 될 것 같습니다.



지금까지 함수를 선언하는 3가지 방법에 대해 알아보았습니다.

3가지 방법 중 가장 권고하는 방식은 함수 표현식인데, 그 이유는 호이스팅과 관련이 있습니다.

함수 선언문으로 함수를 정의하면 함수 호이스팅에 의해 코드의 구조가 꼬일 수 있으므로, 함수 표현식을 이용해서 함수를 정의할 것을 권고하고 있습니다.

( 호이스팅에 대해서는 여기를 참고해주세요 ! )





2. 익명 함수 ( Anonymous Function )

익명 함수란 함수에 이름이 없는 함수를 의미합니다.

JS에서는 익명함수를 무척이나 자주 사용하는데요, 그 이유는 다음과 같습니다.

  • 익명함수를 사용하면 함수에 대한 이름을 작명 할 필요가 없습니다.
    • 일회성으로 사용되는 함수에 이름을 일일이 작성한다는 것은 피곤한 일이죠.
  • 또한 함수명이 없으므로 namespace가 혼란스러워 지는 것을 막을 수 있습니다.


지금부터 간단한 예제를 통해 익명함수가 무엇인지, 어떻게 사용하는지 살펴보도록 하겠습니다.



var foo = function(){
console.log("변수 foo에 익명 함수를 할당했습니다. ")
}

이 예제는 function(){ ... } 이라는 익명 함수를 변수에 foo에 할당하는 코드입니다.

이 익명 함수를 호출하기 위해서는 foo()로 호출하면 됩니다.



var foo = function(value){
console.log(value)
}
foo(10);

이 예제는 익명 함수에도 매개변수를 선언할 수 있다는 것을 보여줍니다.

익명 함수는 함수의 이름이 없을 뿐 일반 함수와 똑같습니다.



setTimeout(function(){
console.log("익명 함수를 인자로 전달할 수 있습니다.");
}, 2000);

이 예제는 setTimeout() 함수에 첫 번째 인자로 익명 함수를 전달하는 예제입니다.

이를 콜백함수라고 하는데, 콜백함수에 주로 익명함수를 사용하곤 합니다.





3. 자유로운 파라미터

JS는 함수가 선언한 매개변수 수보다 인자가 많이 전달되든 적게 전달되든 에러가 발생하지 않습니다.

즉, 파라미터 선언이 자유롭습니다.


var foo = function(var1, var2, var3){
return var1 + var2 + var3;
}

var result = foo(3,5,7,9,10,12);
console.log(result);

변수 foo에 할당된 익명 함수의 매개변수는 3개 만을 정의하고 있습니다.

그런데 foo() 함수를 호출할 때는 6개의 인자를 전달했습니다.


이 코드는 에러를 발생할까요?

그렇지 않습니다.

결과를 보시면 3,5,7만 인자로 받고, 나머지 인자는 무시되어 3+5+7 = 15의 결과를 출력합니다.



이번에는 인자를 적게 전달해보겠습니다.

var foo = function(var1, var2, var3){
console.log(var3)
return var1 + var2 + var3;
}

var result = foo(3,5);
console.log(result);

결과를 보시면 NaN(Not a Number)이 출력됩니다.

숫자가 아니라는 결과를 정상적으로 출력해줍니다.

결코 에러가 아닙니다.


매개변수 var3에 대한 값을 출력해보면 undefined입니다.

즉, 3 + 5 + undefined = NaN이 되기 때문에 정상적으로 출력이 된 것입니다.

연산의 근거는 아래의 표를 확인하시길 바랍니다.


출처 : http://delapouite.com/ramblings/javascript-coercion-rules.html


이상으로 자유로운 파라미터 선언에 대해 알아보았습니다.

JS에서 함수의 파라미터를 몇 개를 정의했든 상관 없이 인자를 마음대로 전달할 수 있습니다.

단, 자유로운 파라미터 때문에 원하는 결과를 얻지 못할 수 있으니 인자 전달 순서를 잘 지켜줘야 합니다.





4. arguments

함수를 호출할 때 정의된 매개변수 개수보다 인자를 많이 전달한 경우, 초과된 인자들은 버리지 않고 arguments라는 객체에 할당합니다.

arguments는 모든 함수에서 사용 가능한 지역변수이며, arguments객체를 통하여 초과로 전달된 인자를 참조 할 수 있습니다.


var foo = function(var1, var2, var3){
for(let i = 0; i < arguments.length; i++){
console.log(arguments[i])
}
}
foo(3,5,2,1,2,3,4);

arguments 객체는 함수에 전달된 모든 인수를 요소로 갖는 Array "같은" 객체입니다.

arguments 객체는 인자의 개수를 프로퍼티 length로 갖고 있습니다.

그러나 slice, push, pop과 같은 Array 객체의 메서드가 존재하지 않습니다.

때문에 arguments를 "Array 같다"고 표현한 것입니다.

아래의 예제를 살펴보겠습니다.

var foo = function(var1, var2, var3){
arguments.push(100);

for(let i = 0; i < arguments.length; i++){
console.log(arguments[i]);
}
}
foo(3,5,2,1,2,3,4);


arguments는 배열이 아니기 때문에 push() 메서드를 갖고 있지 않습니다.


그런데 call() 또는 apply() 메서드를 사용하면 arguments 객체를 Array로 만들 수 있습니다.

var foo = function(var1, var2, var3){
var args = Array.prototype.slice.call(arguments);
args.push(100);

for(let i = 0; i < args.length; i++){
console.log(args[i]);
}
}
foo(3,5,2,1,2,3,4);

마찬가지로 push() 메서드를 호출했지만 이번에는 에러가 발생하지 않고, args 배열의 마지막 요소 뒤에 100이라는 값을 추가했습니다.

slice() 메서드는 문자열을 잘라서 복사한 배열을 반환하는 함수인데, 인자를 넘겨주지 않을 경우 전체 배열이 복사됩니다.

즉, 예제에서는 arguments 객체의 원소들을 모두 복사해서 새로운 배열로 만들어 변수 args에 할당한 것입니다.





이상으로 JS function과 arguments 개념에 대해 마치도록 하겠습니다.

함수 관련하여 생성자 함수라는 개념이 있는데, 이 부분은 객체를 생성하는 부분과 관련이 있다고 생각하여 여기에서 다루도록 하겠습니다.