본문으로 건너뛰기

모던 javascript - 함수

· 약 8분
Seung hyo, Lee
Front End Engineer @ Assistfit

TIL : Today I Learned

함수는 마치 재료를 투입받아 제품을 생산하는 기계와 같다.

함수​

노트

자바스크립트에서 가장 중요한 핵심 개념

스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, 메서드, this, 프로토타입, 모듈화 등이 모두 함수와 깊은 관련이 있다.

함수란​

함수는 일련의 과정을 문(statement)으로 구현하고, 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다.

// 함수 정의: 함수 이름 add, 매개변수 x, y, 함수 몸체, 반환값
function add(x, y) {
return x + y;
}

// 함수 호출: 인수 2, 5
add(2, 5);

여기서 자주 헷갈리는 내용인 매개변수와 인수가 있다.

매개변수(parameter)는 함수를 선언(정의) 할 때 사용하는 변수를 의미한다. 인수(argument)는 함수를 호출할 때, 즉 입력될 때 넣는 값을 인수라고 한다.

함수 정의

함수를 정의하는 방법은 다양하지만, 아래는 함수 선언문을 사용하여 정의한 코드이다.

function introduce(name, age) {
return `Hello, I'm ${name}!, ${age} old`;
}

function이라는 함수 선언 키워드 뒤에 함수명(=식별자)을 적고, 중괄호 { 코드 }로 감싸 작성한다.

단 함수의 정의만으로 함수가 실행되는 것은 아니다.

함수 호출

함수 호출이란 미리 정의된 함수를 실행시키기 위해 필요한 입력을 해주는 것이다. 입력한 인수는 매개변수를 통해 함수에 전달되어 함수를 실행시키는 명령어이다.

var intro = introduce('승효', 27);

console.log(intro); // Hello, I'm 승효!, 27 old

함수를 호출하면 코드 블록에 담긴 문들이 일괄적으로 실행되고, 실행 결과로 반환값을 반환한다.

참고로 매개변수와 인수는 생략이 가능하다.

함수를 사용하는 이유​

함수를 통해 중복을 제거하고 코드를 재사용할 수 있다.

// 함수 없이
var x = 0;
var y = 0;
var result = 0;

x = 1;
y = 2;
result = x + y; // 3

x = 3;
y = 4;
result = x + y; // 7

x = 5;
y = 6;
result = x + y; // 11
// 함수로 중복 제거
function add(x, y) {
return x + y;
}

var result = 0;

result = add(1, 2); // 3
result = add(3, 4); // 7
result = add(5, 6); // 11

가장 중요한 핵심은 함수는 몇 번이든 호출할 수 있어 코드의 재사용이라는 측면에서 매우 유용하다는 점이다.

함수 리터럴​

함수는 객체 타입의 값이다. 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성된다.

// 변수에 함수 리터럴을 할당
var add = function (x, y) {
return x + y;
};
  1. 함수 이름
    • 네이밍 규칙을 준수한 식별자
    • 함수 몸체 내에서만 참조할 수 있는 식별자
    • 함수 이름은 생략 가능
      • 기명 함수(named function): 이름이 있는 함수
      • 익명 함수(anonymous function): 이름이 없는 함수
  2. 매개변수 목록
    • 0개 이상의 매개변수를 괄호로 감싸고 쉼표로 구분 (a, b)
    • 매개변수에 인수 할당
    • 매개변수는 함수 몸체 내에서 변수와 동일하게 취급되기 때문에 식별자 네이밍 규칙 준수
  3. 함수 몸체
    • 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 블록
    • 함수 호출에 의해 실행됨

함수 리터럴과 함수 선언문의 차이​

// 함수 선언문
// - 표현식이 아닌 문이다.
// - 코드 블록이라 ; 을 안 쓴다.
// - 표현식이 아닌 문은 실행되면 undefined를 반환한다.
// - 추후 foo() 라고 호출이 가능하다.
function foo() {}

// 함수 리터럴 (그룹 연산자 안)
// - 괄호를 치면 함수 선언문(표현식이 아닌 문)이 아니라 함수 리터럴(표현식)로 해석한다.
// - 따라서 해당 구문은 결국 '값'이 된다.
// - 추후 호출이 불가능하다.
(function foo() {});

그룹 연산자 ( ) 안에 들어오는 것은 피연산자이다. 피연산자로 들어오는 것은 리터럴, 값이어야만 한다. 결국 ( function foo() { } )는 리터럴 및 값이 되는 것이다.

함수 리터럴은 이름을 가질 수도 있고, 안 가질 수도 있다. 함수 리터럴의 식별자는 자신의 몸체 내부에서만 유효하다.

그룹 연산자 ( ) 내에 있는 것은 함수 선언문이 아니다. 다시 말해, 자바스크립트 엔진은 그룹 연산자 () 내에 있는 문을 함수 선언문이 아닌 함수 리터럴로 해석한다.

그룹 연산자의 피연산자는 값으로 평가할 수 있는 표현식이다. 따라서 표현식이 아닌 문인 함수 선언문은 피연산자로 사용할 수 없다.

함수 정의​

함수를 정의하는 방법에는 4가지가 있다.

1. 함수 선언문

function introduce(name, age) {
return `Hello, I'm ${name}!, ${age} old`;
}

함수 선언문은 함수 이름을 생략할 수 없다. 표현식이 아닌 문이다.

2. 함수 표현식

// 함수 표현식
var add = function plus(x, y) {
return x + y;
};

위 함수 리터럴에서 설명한 것과 같은 맥락으로 plus()를 직접 호출할 수 없고, add()로 호출해야 실행이 가능하다. 왜냐하면 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출하기 때문이다.

여기서 무명 함수라고 한다면, 자바스크립트 엔진은 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.

3. Function 생성자 함수

생성자 함수란? 객체를 생성하는 함수. 따라서 Function 생성자 함수는 함수라는 객체를 생성하는 함수다.

// 첫 번째 인수, 두 번째 인수, 마지막 인수는 반환값
var add = new Function('x', 'y', 'return x + y');

console.log(add(2, 5)); // 7
  • 생성된 함수는 클로저를 생성하지 않음
  • 함수 선언문이나 함수 표현식으로 생성한 함수와 생성자 함수는 동일하게 동작하지 않음

4. 화살표 함수 (ES6)

항상 익명 함수이며, 축약형으로 간략하게 사용하기에 유리하다.

// 화살표 함수
const add = (x, y) => x + y;

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

화살표 함수는 대부분 고차 함수의 콜백 함수에 이용한다.

  • 기존의 함수와 this 바인딩 방식이 다름
  • prototype 프로퍼티 없음
  • arguments 객체를 생성하지 않음

위 부분에서는 생성자 함수, this, 프로토타입, arguments 객체의 배경지식이 필요하기에 26장 3절 '화살표 함수'에서 자세히 다룬다.

함수 호출 시 유의 사항​

함수를 실행하기 위해 필요한 값을 함수 외부에서 내부로 전달할 필요가 있는 경우, 매개변수를 통해 인수를 전달한다.

인수는 값으로 평가될 수 있는 표현식이어야 한다. 인수는 함수를 호출할 때 지정하며, 개수와 타입에 제한이 없다. 또한 배열처럼 순서에 의존하기 때문에 호출 시점의 인수의 순서대로 매개변수에 대입된다.

또한 제한이 없다고 해도, 이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 한다.

다양한 함수의 형태​

  1. 즉시 실행 함수
  2. 재귀 함수
  3. 중첩 함수
  4. 콜백 함수 (중요)
  5. 순수 함수와 비순수 함수

즉시 실행 함수 (IIFE)​

단 한 번만 호출되며 다시는 호출할 수 없다. (리액트 useState 개념과 비슷하다)

즉시 실행 함수는 익명 즉시 실행 함수를 사용하는 것이 일반적이다. 클로저를 쓸 때 사용된다. 의외로 많이 사용되고, 내부 변수는 지역 변수가 되어 충돌을 방지할 수 있다.

// 익명 즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a * b;
})();

재귀 함수​

자기 자신을 호출하는 함수

재귀 함수는 무한 호출되며 stack overflow 에러를 유발할 위험이 다분하다. 따라서 최대한 적게 이용하고, 대부분의 재귀 함수는 while, for(+ if) 등으로 대체가 가능하다.

직접 코드에 사용하는 일은 드물지만 알고리즘 문제에 간혹 등장한다.

// 팩토리얼(계승)은 1부터 자신까지의 모든 양의 정수의 곱이다.
// n! = 1 * 2 * ... * (n-1) * n
function factorial(n) {
// 탈출 조건: n이 1 이하일 때 재귀 호출을 멈춘다.
if (n <= 1) return 1;
return factorial(n - 1) * n;
}

console.log(factorial(0)); // 0! = 1
console.log(factorial(1)); // 1! = 1
console.log(factorial(2)); // 2! = 1 * 2 = 2
console.log(factorial(3)); // 3! = 1 * 2 * 3 = 6
console.log(factorial(4)); // 4! = 1 * 2 * 3 * 4 = 24
console.log(factorial(5)); // 5! = 1 * 2 * 3 * 4 * 5 = 120

중첩 함수​

함수 내부에 함수가 있을 수 있다. 이를 중첩 함수(nested function) 또는 내부 함수(inner function)라 한다.

(리액트를 사용해 보았다면, 함수형 컴포넌트 내부에 함수를 만드는 것과 비슷하다)

function outer() {
// 1. outer의 네이밍 값을 변수 선언문에 변수로 저장하여 호이스팅됨 (함수가 다 저장됨)
var x = 1; // 3. x = undefined 값으로 초기화됨
// 5. x에 1을 할당

function inner() {
// 3-1. 호이스팅되어 런타임 전에 메모리 값이 잡힘, 7. inner 함수 들어감
var y = 2; // 8. y 값 undefined 할당 -> 9. 런타임 실행 -> 10. y에 2 할당

console.log(x + y); // 11. x+y 값 평가 후 출력
// 12. return문이 없으면 undefined가 할당되어 함수를 빠져나옴
} // 4. undefined로 잡힘 -> 런타임 실행

inner(); // 6. inner 함수 실행

console.log(x + y); // 13. 실행
// 변수는 하위 변수를 참조할 수 없어서 y 값에 할당될 값이 없다.
// 1 + y 가 되지만 y가 선언조차 안 되어 ReferenceError가 뜸
// return undefined 생략되어 있음
}

outer(); // 2. 실행

콜백 함수 (중요)​

콜백 함수는 고차 함수에 인수(argument)로써 사용되는 함수를 뜻한다. 고차 함수는 매개변수를 함수로 받는 함수를 말한다.

// outer는 고차 함수다. 이유는 매개변수를 함수로 받고 있다.
function outer(fn) {
var x = 1;
var y = 2;
return fn(x, y); // 3
}

// 일반 함수
function inner(x, y) {
var dd = x + y;
return dd;
}

console.log(
// outer라는 고차 함수에 inner를 넣음으로 inner는 콜백 함수가 되었다.
outer(inner),
);

기본적으로 콜백 함수는 고차 함수를 사용할 때 사용하며, 가능한 이 함수들은 루프를 도는 형식이기에 사용을 하지 않도록 최대한 노력을 해야 한다.

고차 함수들로는 map, filter, reduce, find 등등이 있다. map, filter의 예는 다음과 같다.

// 콜백 함수를 사용하는 고차 함수 map
var res = [1, 2, 3].map(function (item) {
return item * 2;
}); // 내부에서는 for문이 돈다
// 배열 [1,2,3] 객체가 가지고 있는 메서드(map)

console.log(res); // [ 2, 4, 6 ]

// 콜백 함수를 사용하는 고차 함수 filter
res = [1, 2, 3].filter(function (item) {
return item % 2;
});

console.log(res); // [ 1, 3 ]

순수 함수와 비순수 함수​

아주 간단하다.

순수 함수 = 외부 상태에 의존하지 않고, 외부 상태를 변경하지도 않는 함수 비순수 함수 = 외부 상태에 의존하거나, 외부 상태를 변경하는 함수

// 순수 함수
var count = 0;

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

위 순수 함수에서는 count에 연결된 어떤 것도 없다.

// 비순수 함수
var count = 0;

function increase() {
return ++count;
}

위 비순수 함수는 count 변수가 없다면 에러가 발생할 것이고, count라는 변수를 증가 연산하므로 호출 시 매번 변수의 상태를 변화시키기 때문에 비순수 함수라고 한다.

최종 정리
  1. 함수를 정의하고, 호출함으로써 정의한 코드 블록을 실행할 수 있다.
  2. 함수를 사용하는 이유 중 가장 핵심은 코드의 재사용이다. (반복을 줄임)
  3. 함수는 객체 타입의 값이다. 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성된다.
  4. 함수는 함수 선언문, 함수 표현식, Function 생성자 함수, 화살표 함수 4가지로 정의한다.
  5. 이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 한다.
  6. 함수의 형태는 다음과 같다.
    1. 즉시 실행 함수 (function(){})()
    2. 재귀 함수 function oo(){oo()}
    3. 중첩 함수 function (){function(){}}
    4. 콜백 함수 (중요) function(function(){}){}
    5. 순수 함수와 비순수 함수