IT_Programming/JavaScript

자바스크립트 완벽가이드 - 8.2 함수 전달인자

JJun ™ 2010. 7. 4. 22:14


자바스크립트 함수는 임의 개수의 전달인자와 함께 호출될 수 있는데 여기서 전달 받을 수 있는

인자의 개수는 함수가 정의될 당시에 이름 붙은 전달인자의 개수와는 상관이 없다.

 

자바스크립트는 데이터 타입의 제약이 느슨한 언어다.

따라서 함수는 자신이 받기를 기대하는 전달인자의 데이터 타입을 미리 선언하는 것이 불가능하다.

즉, 모든 함수에는 어떠한 데이터 타입의 값이라도 전달할 수 있다.

 

다음 절에서는 함수 전달인자에 대하여 더 자세히 알아보겠다.

 

 

1. 생략 가능한 전달 인자

 

함수가 정의 당시에 선언된 전달인자의 개수보다 적은 수의 전달인자와 함께 호출되면, 호출 시점에 지정되지 않고 남은 전달인자들의 값은 undefined가 된다. 때로는 함수 호출 시점에 전달인자를 생략할 수 있게 함수를 작성하는 것이 유용할 수도 있다. 이를 위해선 호출 당시에 생략될 수 있는 (또는 null로 지정된) 전달인자에 대해 적당한 기본값을 할당해 줄 수 있어야만 한다.

 

예를 들면 다음과 같다.

// 객체o의 열거 가능한 프로퍼티의 이름을 배열 a에 이어 붙이고
// 배열 a를 반환한다. 만약 전달인자 a가 생략되었거나 null이면
// 새로운 배열을 생성하고 이 배열을 반환한다.
function copyPropertyNamesToArray(o, /* optional */ a) {
if (!a) a = [];  // a가 정의되지 않았거나 혹은 null이라면 빈 배열을 사용한다.
for(var property in o) a.push(property);
return a;
}


함수가 위와 같이 작성되어 있다면 다음과 같이 유연한 방법으로 함수를 호출할 수 있다.

// 객체 o와 p의 프로퍼티 이름들을 얻는다.
var a = copyPropertyNamesToArray(o);  // o의 프로퍼티 이름들을 새로운 배열에 넣는다.
copyPropertyNamesToArray(p,a);  // p의 프로퍼티 이름들을 위에서 받은 배열에 넣는다.


위 함수의 첫 부분에서 사용한 if 문 대신에 || 연산자를 사용하여 다음과 같이 독특하게 작성할 수도 있다.

a = a || [];


5장에서 설명한 바와 같이 || 연산자는 연산자의 첫 번째 전달인자(좌측에 위치한 값)가 true거나 true로 변환되는 값이라면 이 값을 반환하며, 그렇지 않다면 두 번째 전달인자(우측에 위치한 값)를 반환한다.

 

위 예에서 a가 null이 아니라면 a가 비어있든 상관없이 이 배열을 반환하며 그렇지 않다면 새로운 빈 배열을 생성하여 반환한다. 생략 가능한 전달인자를 사용하여 함수를 작성할 때는 생략 가능한 것들을 함수 전달인자 목록의 끝에 위치하게 하여, 호출할 때에 임의로 생략할 수 있게 하는 것이 중요하다.

 

예를 들어 프로그래머가 여러분의 함수를 호출할 때는 함수의 첫 번째 전달인자를 생략하고 두 번째를 전달하는 것이 불가능하다. 굳이 이를 위해선 첫 번째 전달인자에 명시적으로 undefined 또는 null 값을 지정해야

한다.

 

 

 

2. 가변 길이 전달 인자 목록: 전달인자 객체

 

함수의 몸체에서 식별자 arguments는 특별한 의미를 지닌다. arguments는 Arguments 객체를 참조하는

특별한 프로퍼티다. Arguments 객체는 배열과 유사한 객체(설명은 7.8절을 참조하라)로서 함수에 전달된

전달인자의 값을 전달인자 이름이 아니라 숫자를 사용해 접근하기 위한 방법을 제공한다.

Arguments 객체는 callee 프로퍼티를 추가적으로 정의하고 있는데 이에 관해선 다음 절에서 설명한다.


자바스크립트 함수는 비록 고정된 개수의 이름 붙은 전달인자들로 정의되지만 호출 시점에서는 이 고정된

개수와는 상관없이 임의 개수의 전달인자들을 건네받을 수 있다. Arguments 객체는 이름이 붙어있든 없든

상관없이 건네받은 모든 전달인자의 값에 접근하기 위한 방법을 제공한다. 한 개의 전달인자 x를 받는

함수 f를 정의한다고 가정하자.

 

만약 이 함수를 두 개의 전달인자와 함께 호출한다면 첫 번째 전달인자는 매개변수 이름 x나 arguments[0]으로 접근할 수 있다. 두 번째 전달인자는 이름이 없으므로 arguments[1]로만 접근할 수 있다. 또한 arguments에는 실제 배열처럼, 원소들의 수를 명시하는 length 프로퍼티가 있다. 두 개의 전달인자와 함께 호출된

함수 f의 몸체 안에서 arguments.length의 값은 2이다.


arguments 객체는 여러모로 유용하다. 다음 예는 함수가 올바른 개수의 전달인자들과 함께 호출되었는지

검사하기 위해 arguments 객체를 사용하는 방법을 보여 준다. 자바스크립트는 이런 검사를 해주지 않기

때문에 직접 작성해야 한다.

function f(x, y, x)
{
// 먼저 올바른 개수의 전달인자를 받았는지 검사한다.
if (arguments.length != 3) {
throw new Error("function f called with" + arguments.length + 
                        "arguments, but it expects 3 arguments.");
}
// 이제 함수의 작업을 시작한다.
}


또한 arguments 객체는 자바스크립트 함수의 중요한 가능성을 열어준다. 자바스크립트 함수는 임의 개수의 전달인자와도 작동할 수 있게 작성될 수 있다. 다음 예는 임의 개수의 전달인자를 건네받아서 가장 큰 값의

전달인자를 반환하는 max() 함수를 작성하는 방법을 보여 준다(내장 함수는 Math.max() 함수도 동일한

방식으로 작동된다).

function max(/* ... */)
{
var m = Number.NEGATIVE_INFINITY;
// 모든 전달인자를 순회하며 가장 큰 값을 찾아 기억해둔다.
for(var i = 0; i < arguments.length; i++)
if (arguments[i] > m) m = arguments[i];
//  최대 값을 반환한다.
returm m;
}

var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6);


이와 같이 임의 개수의 전달인자를 받을 수 있는 함수들은 variadic 함수나 variable arity 함수, varargs 함수 등으로 부른다. 이 책에서는 C 프로그램 언어의 초창기부터 전해져 온 구어적 용어인 varargs[각주:1]을 사용한다. 이전 예에서 정의한 max 함수의 전달인자 목록은 비어있다. 하지만 varargs 함수의 전달인자 목록을 항상 비워두어야 하는 것은 아니다. arguments[]는 고정된 개수의 이름 붙은 전달인자들에 이어서 임의 개수의 전달인자를 건네받는 함수를 작성할 때도 매우 적합하게 사용할 수 있다.


 

arguments가 실제로는 배열이 아니라 Arguments 객체라는 것을 기억하라. 각 Arguments 객체는 숫자 인덱스가 붙은 배열 원소들과 length 프로퍼티를 정의한다. 하지만 기술적으로 이것을 배열이라 할 수 없다. 그냥 우연히 번호가 붙은 몇 개의 프로퍼티를 소유한 객체라고 생각하는 편이 낫다. ECMAScript 명세서에 따르면 Arguments 객체는 배열의 특징을 구현하지 않아도 된다. 비록 arguments.length 프로퍼티에 새로운 값을 할당할 수 있지만, ECMAScript는 이에 따라 객체가 가진 배열 원소들의 개수를 실제로 변경하는 작업을 수행하지는 않는다(실제 Array 객체가 가진 length 프로퍼티의 특별한 성질에 대해서는 7.6.3항을 참고하라).


Arguments 객체에는 별난 특징이 하나 있다. 만약 함수에 이름 붙은 전달인자가 있으면 Arguments 객체의 배열 원소는 함수 전달인자를 담은 지역 변수의 별칭이 된다. arguments[] 배열과 이름 붙은 전달인자는

동일한 변수를 참조하는 두 가지 다른 방법이다. 전달인자 이름을 사용하여 전달인자의 값을 변경하면 arguments[] 배열을 통해 읽어 들이는 값도 변한다. 거꾸로 말하면, arguments[] 배열을 사용하여

전달인자의 값을 변경하면 전달인자 이름을 통해 읽어 들이는 값도 역시 변한다.

 

예를 들면 다음과 같다.

function f(x) {
print(x);                      // 전달인자 x의 처음 값을 출력한다.
arguments[0] = null;    // 배열 원소의 값을 변경하면 x의 값도 변한다!
print(x);                      // 이제 "null"을 출력한다.

}


이것은 Arguments 객체가 평범한 배열이었더라면 절대로 볼 수 없는 특성이다.

arguments[0]과 x가 처음에는 같은 값을 가리킬 수 있지만, 이러한 경우 하나의 참조를 변경하는 것이

다른 참조에는 영향을 미치지 않는다.


끝으로 arguments는 예약어라 아니라 평범한 자바스크립트 식별자라는 사실을 명심하라. 만약 함수에 arguments 이름의 전달인자나 지역 변수가 있으면 이 변수가 Arguments 객체에 대한 참조를 가리는 결과를 낳고 만다. 이러한 이류로 arguments가 마치 예약어인 것처럼 간주하여 이것을 변수 이름을 사용하지 않는 것이 좋은 습관이다.

  1. (역자 주) 가변 길이 전달인자(variable length arguments)를 의미한다. [본문으로]

 

 

 

3. callee 프로퍼티

 

Arguments 객체는 배열 원소뿐만 아니라 현재 실행되고 있는 함수를 가리키는 callee 프로퍼티를 추가로

정의한다. 이 프로퍼티는 거의 사용되지 않는다. 하지만 이 프로퍼티를 사용하면 이름 없는함수도 자기 자신을 재귀적으로 호출할 수 있다. 다음은 계승(factorial)을 계산하기 위하여 이름 없는 함수 리터럴을 사용하는 예를 보여준다.

function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x-1);
}

 

 

 

 

4. 객체 프로퍼티를 전달인자로 사용하기

 

만약 함수에 전달인자가 세 개 이상 필요하다면, 함수를 호출할 때 전달할 전달인자들의 올바른 순서를

기억하는 일이 어려워진다. 함수를 호출할 때마다 함수에 대한 문서를 참조해야 하는 곤란에서 벗어나려면, 

전달인자를 순서에 관계없이 이름과 값의 쌍으로 전달하여 주는 방법을 제공하라.

 

이러한 방식의 함수 호출을 구현하려면 우선 하나의 객체를 전달인자로 받는 함수를 정의하고

함수의 사용자로 하여금 함수에서 필요로 하는 이름과 값의 쌍들을 객체 리터럴로 정의하여 전달하게 하면

된다.

 

다음 코드는 이러한 방식의 구현 예이며 또한 이렇게 객체 프로퍼티를 전달인자로 받는 함수를 정의할 경우, 

빠뜨릴 수 있는 전달인자들에 대해 기본값을 지정해줄 수도 있음을 보여 준다.

// 배열 from에서 배열 to로  length개만큼의 원소들을 복사한다.
// from 배열의 from_start 위치의 원소부터 복사를 시작하여
// to 배열의 to_start 위치부터 저장한다.
// 전달인자들의 순서를 기억하기란 매우 어렵다.
function arraycopy(/* array */ from, /* index */ from_start,
                            /* array */ to, /* index */ to_start,
                            /* integer */ length)
{
// 이곳에 코드가 위치한다.
}

// 새로운 버전은 약간 덜 효율적이다. 그러나 전달인자들의 순서를
// 기억할 필요는 없다. 또한 from_start와 to_start의 기본값은 0이다.
function easycopy(args) {
arraycopy(args.form,
                args.from_start || 0,  // 기본값이 제공됨을 확인하라.
                args.to,
                args.to_start || 0,
                args.length);
}

// 이제 easycopy()를 호출하는 방법은 다음과 같다.
var a = [1,2,3,4];
var b = new Array(4);
easycopy({from:a, to:b, length:4});

 

 

 

5. 전달 인자 데이터 타입

 

자바스크립트는 데이터 타입 제약이 느슨하기 때문에 함수의 전달인자들은 데이터 타입이 선언되지 않고,

함수에 값을 전달할 때도 데이터 타입 검사를 수행하지 않는다.

 

함수 전달인자들에 자신을 잘 묘사하는 이름을 붙이거나 방금 전에 본 arraycopy() 함수에서처럼

전달인자의 데이터 타입을 주석으로 적어 넣음으로써 코드 자체를 문서화하면 도움이 될 수 있다.

 

생략 가능한 전달인자에는 'optional'('생략 가능') 같은 주석을 적어 넣을 수 있다.

또한 만약 함수가 임의 개수의 전달인자를 받는다면 말줄임표 기호(...)를 주석으로 사용할 수 있다.

function max(/* number... */) { /* 이곳에 코드가 위치한다. */ }


3장에서 설명한 바와 같이 자바스크립트는 필요에 따라 자유롭게 데이터 타입을 변환한다.

따라서 만약 문자열 전달인자를 기대하는 함수를 작성하고 이 함수를 다른 데이터 타입의 값과 함께 호출하면 함수가 그 값을 문자열로 쓰려는 시점에 문자열로의 변환 작업이 수행된다.

 

모든 종류의 기본 데이터 타입은 문자열로 변환될 수 있고, 또한 모든 객체에는 toString() 함수(꼭 유용한 형태로 재정의된 것일 필요는 없다)가 있으므로 이러한 경우엔 어떠한 에러도 발생하지 않는다.

 

하지만 항상 그런것은 아니다. 앞에서 살펴본 arraycopy() 함수를 다시 떠올려 보자.

 

이 함수는 첫 번째 전달인자로 배열을 기대한다. 이 함수를 적절하게 구현했다면 첫 번째 전달인자가 배열(또는 배열과 유사한 배열)이 아닐 때 호출이 실패해야 한다. 만약 한 두번만 쓰고 사용하지 않을 '일회용' 함수를 작성할 것이 아니라면 전달인자의 데이터 타입을 검사하는 코드를 추가하는 것이 좋다. 만약 잘못된 데이터 타입의 값을 건네받으면 이 사실을 알리는 예외를 발생시킨다.

 

다음의 코드처럼 잘못된 데이터를 받는다면, 굳이 실행을 하려고 숫자를 배열처럼 접근하는 바람에

결국 실패하느니, 예측할 수 있는 형태로 즉시 함수를 종료하는 것이 좋다.

// 배열(또는 배열과 유사한 객체) a가 가진 원소들의 합을 반환한다.
// 원소들은 모두가 반드시 숫자여야 한다. null, undefined 원소들은 무시한다.
function sum(a) {
if ((a instanceof Array) ||                             // 만약 배열이거나
(a && typeof a == "object" && "length" in a)) { // 또는 배열과 유사한 객체라면
var total = 0;
for(var i = 0; i < a.length; i++) {
var element = a[i];
if (!element) continue;  // 모든 null, undefined 원소를 무시한다.
if (typeof element == "number") total += element;
else throw new Error("sum(): 모든 배열 원소는 반드시 숫자여야 함.");
}
return total;
}
else throw new Error("sum(): 전달인자는 반드시 배열이어야 함.");
}


이 sum() 함수는 허용하는 전달인자들에 대해 충분히 엄격하며, 만약 잘못된 값을 건네받으면 적절한 정보를 담은 에러를 발생시킨다. 또한 이 함수는 진짜 배열외에 배열과 유사한 객체와도 작동하고 null과 undefined 배열 원소들은 무시하며 계산하는 유연성도 보여준다.


자바스크립트는 매우 유연하고 또 데이터 타입 제약이 느슨한 언어라서 때로는 함수가 건네받은 전달인자들의 개수와 데이터 타입에 대해 유연하게 대처할 수 있는 함수를 작성하는 것이 적절할 수 있다. 이 단락의 뒤에 보여 주는 flexisum() 메서드는 이러한 접근 방식을 (거의 틀립없이 극단적으로) 따르는 유연한 함수의 예다. 예를 들어 이 함수는 임의 개수의 전달인자를 허용하며 배열인 전달인자들을 재귀적으로 처리한다.

 

임의 개수의 전달인자를 받거나 배열을 전달인자로 받는 메서드로 이 함수를 사용할 수 있다.

게다가 이 함수는 숫자가 아닌 값들에 대해 에러를 발생시키기 전에 이를 숫자로 변환하기 위해 최선을

다한다.

function flexisum(a) {
var total = 0;
for(var i = 0; i < elements.length; i++){
var element = arguments[i];
if (!element) continue;  // 모든 null, undefined 전달인자를 무시한다.

// 전달인자의 데이터 타입에 따라 숫자 n으로의 변환 작업을 시도한다.
var n;
switch(typeof element) {
case "number":
n = element; // 변환이 필요 없다.
break;
case "object":
if (element instance of Array)  // 배열을 처리하기 위해 재귀 호출을 한다.
n = flexisum.apply(this, element);
else n = element.valueOf()  // 다른 객체들에 대해선 valueOf 메서드를 호출한다.?
break;
case "function":
n = element();  // 함수 호출을 시도한다.
break;
case "string":
n = parseFloat(element);  // 문자열 파싱을 시도한다.
break;
case "boolean":
n = NaN;  // 불리언 값을 변환할 수는 없다.
break;
}

// 만약 유효한 숫자를 얻으면 이를 total에 더한다.
if (typeof n == "number" && !isNaN(n)) total += n;
// 그렇지 않다면 에러를 보고한다.
else throw new Error("sum(): " + element + " 을 숫자로 변환하는데 실패했음.");
}
return total;
}