javascript-함수

함수

함수란?

함수는 자바스크립트에서 중요한 핵심중 하나이다. 프로그래밍 언어의 함수는 일련의 과정을 문으로 구현하고 코드블록으로 감싸서 하나의 실행 단위로 정의 한것이다.
함수는 입력을 받아 출력을 내보낸다. 이때 함수 내부로 입력을 전달받는 변수를 매개변수 입력을 인수 출력을 반환값이라 한다. 또한 함수는 값이고 여러개 존재할 수 있기 때문에 특정 함수를 구별하기 위해 식별자인 함수 이름을 사용할 수 있다.

1
2
3
4
5
// 함수 정의
function 함수이름(매개변수1, 매개변수2) {
return 매개변수1 + 매개변수2 // 반환값
}
함수이름(값1, 값2); // 함수 호출

함수는 함수 정의로 생성하며 다양한 방법으로 정의할 수 있다.

1
2
3
4
// 함수 정의
function add (x, y) {
return x + y;
}

함수를 정의하고 나서 실행을 하기 위해서는 인수를 매개변수를 통해 함수에 전달하며 함수의 실행을 지시해야한다. 이 과정을 함수 호출이라고 한다.

1
2
3
4
5
// 함수 호출
var result = add(2, 5);

// 인수를 전달하면 반환값이 반환된다.
console.log(result); // 7

함수는 왜 사용할까?

함수는 필요할 때마다 호출하여 실행이 가능하다. 실행 시점은 개발자가 결정하기도 하고 재사용이 가능하다. 동일한 작업을 반복적으로 실행해야 한다면 같은 코드를 중복하여 작성하는 것이 아닌 함수를 재사용하는 것이 효율적이다. 함수는 코드의 재사용 측면에서 매우 유용한 기능이다.

함수는 재사용성을 높여주고 유지보수 측면에서 편의성을 높이고 실수를 줄여 코드의 신뢰성을 높이는 효과가 있다. 이러한 함수를 정의할때 함수이름 즉 식별자를 붙일 수 있다. 함수이름은 함수의 역할을 이해하기 쉽게 네이밍해주는 것이 좋다.

함수 리터럴?

자바스크립트의 함수는 객체 타입의 값이다. 따라서 함수도 함수 리터럴로 생성할 수 있다. 함수 리터럴은 function키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성된다.

함수 리터럴의 구성요소

구성요소 설명
함수이름 함수이름은 식별자다.
함수이름은 함수 몸체 내에서만 참조할수 있는 식별자이다.
함수이름은 생략이 가능하다 이를 무명함수라 한다.
매개변수 목록 0개이상의 매개변수를 소괄호 내에 정의한다.
각 매개변수는 함수 호출시 전달한 인자의 순서대로 할당된다.
함수 몸체 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행단위로 정의한 것이다.
함수 몸체는 함수의 호출로 실행된다.

함수 정의

함수 정의란 함수를 호출하기 전에 전달받을 매개변수들 실행할 문들 반환값을 정의하는 것을 말한다. 정의된 함수는 자바스크립트 엔진에 의해 함수 객체가 된다.

함수 정의 방식

정의 방식 예시
함수 선언문 function add(x, y) { return x + y }
함수 표현식 var add = function (x, y) { return x + y }
Function 생성자함수 var add = new Function('x', 'y', 'return x + y');
화살표 함수 var add = (x, y) => x + y;

함수 선언문

1
2
3
4
5
function add(x, y) {
return x + y;
}

console.log(add(1, 2)); // 3

함수 선언문은 함수 리터럴과 형태가 동일하다. 단 함수 선언문은 함수 이름을 생략할 수 없다.

함수 선언문은 표현식이 아닌 문이다.
자바스크립트 엔진은 함수 선언문을 해석하여 함수 객체를 생성한다. 이때 함수이름과 동일한 이름의 식별자를 임의로 생성하고 거기에 함수 객체를 할당한다.
즉 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출하는 것이다.

함수 생성 시점과 호이스팅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 함수 참조
console.log(add);
console.log(sub);

// 함수 호출
console.log(add(2, 5));
console.log(sub(2, 5));

// 함수 선언문
function add(x, y) {
return x + y;
}

//함수 표현식
var sub = function (x, y) {
return x - y;
};

위의 코드처럼 함수 선언문으로 선언한 함수는 함수 선언이전에 호출이 가능하다. 하지만 함수 표현식으로 정의한 함수는 정의 이전에 호출이 불가능하다. 이는 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다르기 때문이다.

함수 선언문은 코드가 실행되는 시점인 런타임 이전에 실행이 된다. 따라서 런타임 이전에 함수가 생성이 되는 것이다. 이처럼 함수선언문이 코드의 위로 끌어 올려져 먼저 생성된 이후 실행되는 것처럼 보이는 것을 함수 호이스팅이라고 한다.
var 키워드를 사용한 변수는 선언문 이전에 참조시 undefined로 평가되지만 함수는 호이스팅에 의해 호출이 가능한 차이점이 있다.

이에 반해 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. 변수 선언은 런타임 이전에 undefined로 초기화 되지만 할당문은 값이 할당되는 시점에 평가되므로 런타임에서 함수 객체가 된다. 따라서 함수 표현식은 함수 정의 이전에 참조하게 되면 undefined로 평가되는 것이다.

화살표 함수

ES6에서 도입된 화살표 함수는 function 키워드 대신 화살표 => 를 사용하여 좀더 간략한 방법으로 함수를 정의할 수 있다.

함수 호출

매개변수와 인수

함수를 실행하기 위해 필요한 값을 함수 외부에서 전달해야 할경우 매개변수를 통해 인자를 전달한다. 이때 인수는 값으로 평가될 수 있는 표현식이여야 한다. 인수는 함수를 호출할때 지정하고 개수와 타입에 제한이 없다.

매개변수는 함수를 정의할때 선언하며 함수 몸체 내부에서 변수와 동일하게 취급된다. 따라서 함수가 호출되면 암묵적으로 매개변수가 생성되고 undefined로 초기화된 이후 인수가 순서대로 할당된다.

함수의 호출시 실행 단계
함수정의 -> 호출 (호출시 인수 전달) -> 정의된 함수에서 매개변수 생성 후 인자 할당 -> 정의된 문 실행 후 반환 -> 호출시 지정한 변수에 값 할당

반환문

함수는 return 키워드와 함께 표현식으로 이뤄진 반환문을 사용해 실행 결과를 외부로 반환 할 수 있다.

1
2
3
function add(x, y) {
return x + y; // 반환문
}

함수 호출은 표현식이다. 따라서 return 키워드가 반환한 평가결과를 값으로 평가한다.

1
2
3
4
function add(x, y) {
return x + y; // 반환문
cosole.log('hi'); // 무시됨
}

return 키워드 이후의 실행문들은 실행되지 않고 무시된다.

참조에 의한 전달과 외부 변경 상태의 변경

원시값은 값에 의한 전달 객체는 참조에 의한 전달방식으로 동작한다.
매개변수 또한 함수 몸체 내부에서는 변수와 동일하게 취급되므로 값에 의한 전달 참조에 의한 전달 방식을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
function changeVal (primitive, obj) {
primitive += 100;
obj.name = 'Kim';
}

var num = 100;
var person = { name: 'Lee' };

changeVal(num , person);

console.log(num); // 100 원시값은 원본이 바뀌지 않는다.
console.log(person); // { name: 'Kim' } 객체는 원본이 훼손된다.

위 경우 함수는 원시타입 인수와 객체 타입 인수를 전달받아 값을 변경하는데 원시값은 값의 변경이 불가능 하므로 원시값을 재할당해 새로운 값으로 변경하고 객체타입은 변경이 가능하므로 재할당없이 직접 값을 변경해준다.

이처럼 함수가 외부상태의 어떤 값을 변경하게 되면 상태변화를 추적하기 어려움이 있다. 이러한 문제점을 해결하기 위한 방법 중 한가지는 객체를 불변객체로 만들어 사용하는 것이다.
객체를 깊은 복사를 통해 완전히 새로운 객체를 생성하여 재할당을 통해 교체하면 된다.

다양한 함수의 형태

즉시 실행 함수

함수는 정의와 동시에 실행되는 함수를 의미한다. 이 함수는 한번만 호출되며 다시 실행할 수 없다.

  1. 익명 즉시 실행 함수
    1
    2
    3
    4
    5
    (function () {
    var a = 3;
    var b = 4;
    return a * b;
    }());
    즉시 실행 함수는 일반적으로 함수이름이 없는 익명 함수를 사용하는 것이 일반적이다. 함수이름이 있는 기명 함수도 실행은 가능하지만 그룹연산자 **(…)**로 선언해야하기 때문에 함수 선언문이 아닌 리터럴로 평가되어 다시 호출이 불가능하다.
1
2
3
4
5
6
7
(function foo () {
var a = 3;
var b = 4;
return a * b;
}());

foo(); // Error

즉시 실행 함수는 반드시 그룹연산자로 감싸야 한다는 특징을 가지고 있다. 그룹연산자로 함수를 묶는 이유는 함수 리터럴을 평가하여 함수 객체를 생성하기 위해서이다. 이렇게 즉시 실행 함수에 코드를 모아두게 되면 혹시 있을 변수의 충돌이나 함수이름의 충돌을 방지 할 수 있다.

재귀함수

함수가 자기 자신을 호출하는것을 재귀함수라 한다. 재귀함수는 반복되는 처리를 위해 사용한다.

  1. 반복문을 사용하여 10부터 0까지 출력하는 함수
    1
    2
    3
    4
    5
    function count (a) {
    for (var i = a; i >= 0; i--) {
    console.log(i);
    }
    }
  2. 재귀함수를 사용하여 10부터 0까지 출력하는 방법
    1
    2
    3
    4
    5
    6
    7
    // 재귀함수
    function fac (n) {
    if (n < 0) return;
    console.log(n);
    fac(n -1);
    }
    fac(10);
    동일한 실행결과를 반복문 없이도 구현이 가능하다. 하지만 재귀함수는 반복되는 처리를 무한 반복하기 때문에 탈출조건을 반드시 정의해야하며 무한 반복에 빠질경우 스택 오버플로를 일으킬수 있으므로 주의하여 사용해야한다. 또한 재귀함수는 반복문을 사용하는 것보다 직관적으로 이해하기 편하기 때문에 사용하는 것이므로 한정적으로 사용하는 것이 좋다.

중첩 함수

함수 내부에 정의된 함수를 중첩 함수, 내부 함수라고 한다. 중첩 함수를 가지고 있는 함수를 외부 함수라고 부른다. 중첩 함수의 호출은 외부 함수에서만 가능하며 중첩 함수는 자신을 호출하는 외부 함수를 돕는 헬퍼 함수 역할을 하는 특징이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 중첩 함수
function outer () {
var x = 1;

// 중첩 함수
function inner () {
var y = 2;

// 중첩 함수는 외부 함수의 변수를 참조할 수 있다.
console.log(x + y);
}

inner();
}
outer();

콜백 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function re (n , f) {
for (var i = 0; i < n; i ++) {
f(i);
}
}
var logAll = function (i) {
console.log(i);
}
re(5, logAll);

var logOdds = function (i) {
if(i%2) console.log(i);
};

re (5, logOdds);

re함수의 경우에 따라 바뀌는 실행 결과를 f로 추상화하여 이를 외부에서 전달한다. js의 함수는 일급 객체이므로 함수의 매개변수를 통해 함수가 전달이 된다.
re 함수는 실행할 횟수인 인수를 전달받고 실행결과를 출력할 함수로 전달받은 함수를 호출한다. 따라서 위 실행결과는 하나의 함수에서 여러가지의 실행결과를 출력할 수 있다.
이러한 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백함수라고 하며 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 고차 함수라고 한다.

이전의 중첩 함수가 외부 함수의 헬퍼 함수역할을 하는 것처럼 콜백 함수도 고차 함수의 헬퍼 함수역할을 한다. 고차 함수는 매개변수를 통해 전달받은 콜백함수의 호출 시점을 결정해서 호출한다. 따라서 콜백함수는 고차 함수에 의해 호출되며 고차 함수는 필요에 따라 콜백 함수에 인수를 전달 할 수 있다.

  1. 콜백 함수를 사용하는 예제
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var res = [1, 2, 3].map((item) => {
    return item * 2;
    });
    console.log(res); // [2, 4, 6]

    var fil = [1, 2, 3].filter((item) => {
    return item % 2;
    });
    console.log(fil); // [1, 3]

순수 함수와 비순수 함수

함수형 프로그래밍에서 어떠한 외부 상태에도 의존하지 않고 변경하지도 않는 함수를 순수 함수라고 하고 외부 상태에 의존하거나 외부 상태를 변경하는 함수를 비순수 함수라고 한다.
순수 함수는 동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수이다. 즉 순수 함수는 오직 매개변수를 통해 함수 내부에 전달된 인수에게 의존하여 반환값을 만드는 함수이다.

  1. 순수 함수
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var count = 0;

    function increase(n) {
    return ++n;
    }

    // 순수 함수가 반환한 값을 변수에 재할당하여 상태를 변경
    count = increase(count);
    console.log(count); // 1
    count = increase(count);
    console.log(count); // 2

순수 함수의 의미와 반대된 개념으로 비순수 함수는 외부 상태에 의존하는 함수이다.

  1. 비순수 함수
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var count = 0;

    function increase() {
    return ++count; // 외부 상태에 의존하여 외부 상태를 변경한다.
    }

    increse();
    console.log(count); // 1
    increse();
    console.log(count); // 2

함수형 프로그래밍은 순수 함수와 보조 함수의 적절한 조합으로 외부 상태를 변경하는 부수효과를 최소화하여 불변성을 지향하는 프로그래밍이다.

Author

han Ju Ryeon

Posted on

2021-10-11

Updated on

2021-12-05

Licensed under

댓글