학습 자료

함수 — 코드를 이름으로 묶는다


Article

같은 코드를 세 군데에 복사해 붙였다고 하자. 나중에 고칠 일이 생기면 세 군데를 다 고쳐야 하고, 한 군데를 빠뜨리면 버그가 된다.

코드 덩어리에 이름을 붙여 한 곳에 두고, 필요할 때마다 그 이름을 부른다. 그게 함수다.

만들고 부른다

function greet() {
  console.log("안녕하세요");
}

greet();
greet();
콘솔
  • 안녕하세요
  • 안녕하세요

function 키워드로 정의하고, 이름 뒤에 괄호를 붙여 호출한다. 정의만 해두면 아무 일도 일어나지 않는다. 호출해야 실행된다.

값을 건네준다 — 매개변수와 인자

늘 같은 인사만 하면 쓸모가 적다. 바깥에서 값을 넣어줄 수 있다.

function greet(name) {
  console.log(`안녕하세요, ${name}님`);
}

greet("지원");
greet("민수");
콘솔
  • 안녕하세요, 지원님
  • 안녕하세요, 민수님

여기서 name"지원"은 이름이 다르다. 자주 섞어 쓰지만 구분해두면 좋다.

매개변수와 인자

매개변수 (parameter)

정의할 때 쓰는 이름

  • function greet(name)name
  • 아직 값이 없는 빈 상자
  • 함수 안에서만 쓰인다

인자 (argument)

호출할 때 넣는 값

  • greet("지원")"지원"
  • 상자에 담기는 실제 값
  • 호출할 때마다 달라진다

매개변수는 여러 개 둘 수 있고, 순서대로 짝지어진다.

function introduce(name, age) {
  console.log(`${name}, ${age}살`);
}

introduce("지원", 27);
introduce(27, "지원");
콘솔
  • 지원, 27살
  • 27, 지원살

두 번째 호출은 순서를 바꿔 넣었더니 뜻이 무너졌다. 에러는 나지 않는다. 자바스크립트는 순서만 볼 뿐 의미를 모른다.

개수가 안 맞아도 에러가 아니다

다른 언어와 크게 다른 점이다.

function introduce(name, age) {
  console.log(name, age);
}

introduce("지원");
introduce("지원", 27, "서울");
콘솔
  • '지원' undefined
  • '지원' 27

모자라면 undefined가 들어가고, 남으면 그냥 버려진다. 4강에서 본 "아직 값이 정해지지 않았다"는 그 undefined다.

모자랄 때를 대비해 기본값을 정해둘 수 있다.

function greet(name = "손님") {
  console.log(`안녕하세요, ${name}님`);
}

greet("지원");
greet();
greet(undefined);
greet(null);
콘솔
  • 안녕하세요, 지원님
  • 안녕하세요, 손님님
  • 안녕하세요, 손님님
  • 안녕하세요, null님

기본값은 인자가 undefined일 때만 쓰인다. null을 넣으면 그대로 null이 들어간다. 6강의 ??와 정확히 같은 규칙이다.

return — 값을 돌려준다

지금까지의 함수는 출력만 했지 결과를 남기지 않았다. 계산 결과를 밖에서 쓰려면 돌려줘야 한다.

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

const result = add(3, 4);
console.log(result);
console.log(add(1, 2) * 10);
콘솔
  • 7
  • 30

return이 있으면 호출한 자리가 그 값으로 바뀐다. add(3, 4)라고 쓴 자리에 7이 놓이는 셈이다.

console.log와 헷갈리기 쉽다. 둘은 완전히 다르다.

출력과 반환

console.log()

사람에게 보여준다

  • 콘솔 화면에 글자를 찍는다
  • 코드는 그 값을 쓸 수 없다
  • 디버깅용

return

코드에게 건네준다

  • 호출한 자리에 값을 놓는다
  • 변수에 담거나 계산에 쓴다
  • 화면에는 아무것도 안 나온다

return이 없는 함수를 호출하면 무엇이 나올까.

function greet() {
  console.log("안녕");
}

const value = greet();
console.log(value);
콘솔
  • 안녕
  • undefined

undefined다. 돌려줄 값을 정해주지 않았으니 자바스크립트가 대신 넣어준 것이다.

return 뒤의 코드는 실행되지 않는다

return을 만나면 함수는 그 자리에서 즉시 끝난다.

function check(age) {
  if (age < 0) {
    return "나이가 이상하다";
  }

  if (age < 19) {
    return "미성년자";
  }

  return "성인";
}

console.log(check(-5));
console.log(check(15));
console.log(check(30));
콘솔
  • '나이가 이상하다'
  • '미성년자'
  • '성인'

else가 하나도 없는데 잘 동작한다. return이 함수를 끝내버리니 아래로 내려갈 일이 없기 때문이다.

이 방식을 **조기 반환(early return)**이라 부른다. 예외 상황을 위에서 먼저 걸러내면, 중첩된 if보다 훨씬 읽기 좋다.

호출하면 무슨 일이 벌어지나

함수가 함수를 부르면 어떻게 될까. 자바스크립트는 콜 스택이라는 곳에 지금 실행 중인 함수를 차곡차곡 쌓는다.

function third() {
  console.log("가장 안쪽");
}

function second() {
  third();
}

function first() {
  second();
}

first();

third()가 실행되는 순간, 스택은 이런 모습이다.

third() 실행 중일 때의 콜 스택
  • third()실행 중
  • second()
  • first()가장 먼저 호출

Call Stack

third() 가 끝나면 위에서부터 하나씩 사라지고, 마지막에 first() 가 사라진다

접시를 쌓듯 위로 쌓이고, 가장 위에 있는 것부터 치워진다. 함수가 끝나면 스택에서 빠지고, 자기를 부른 함수로 돌아간다.

여기서 스택이 무한정 쌓이면 어떻게 될까.

function forever() {
  forever();
}

forever();
콘솔
  • RangeError: Maximum call stack size exceeded

"콜 스택 크기를 초과했다"는 에러다. 자기 자신을 끝없이 부르면 스택이 넘친다. 스택 오버플로(stack overflow)라 부른다.

함수를 잘 만드는 기준

이름과 크기
  1. 01

    하나의 일만 시킨다

    이름에 "그리고"가 들어가면 나눠야 한다는 신호다. saveAndSendEmail 은 둘로 쪼갠다.

  2. 02

    이름은 동사로 시작한다

    무엇을 하는 함수인지 이름만 보고 알 수 있어야 한다.

    getUser(), calculateTotal(), isValid()
  3. 03

    참/거짓을 돌려주면 is · has 를 붙인다

    조건문에 넣었을 때 문장처럼 읽힌다.

    if (isAdult(age)) { ... }

정리하면

  • 함수는 코드에 이름을 붙여 재사용하는 장치다. 정의만으로는 실행되지 않는다.
  • 매개변수는 정의부의 빈 상자, 인자는 호출부의 실제 값이다. 순서대로 짝지어진다.
  • 인자 개수가 안 맞아도 에러가 아니다. 모자라면 undefined가 들어간다.
  • 기본값은 인자가 undefined일 때만 쓰인다. null은 그대로 들어간다.
  • console.log는 보여주고, return은 값을 건네준다. return이 없으면 undefined가 나온다.
  • return을 만나면 함수는 즉시 끝난다. 이 성질로 조기 반환을 쓴다.
  • 함수 호출은 콜 스택에 쌓이고, 끝나면 위에서부터 사라진다.

다음 강의에서는 함수가 사실 이라는 사실을 본다. 변수에 담을 수도, 다른 함수에 넘길 수도 있다. 화살표 함수도 여기서 나온다.

함수 — 코드를 이름으로 묶는다 — 디코드랩(DCODELAB)