아래 내용은 학원 수업과 "모던자바스크립트 Deep Dive : 이웅모 저"를 읽고 정리한 내용입니다.

1. 함수

프로그래밍 언어에서 얘기하는 함수는 작성된 프로그래밍의 과정을 코드블록{}으로 감싼 실행단위이며, 이렇게 코드블록으로 묶인 실행단위는 수학의 함수와 비슷하게 input을 넣으면 outfut이 나오게 된다.

함수는 함수 정의(function definition)를 통해서 생성하는데, 함수의 내부에서 필요한 값을 전달받는 변수를 매개변수(parameter)라고 하며, 함수의 코드 블록 내부에 작성된 문들을 실행한 뒤 외부로 출력되는 값을 반환값(return value)이라고 한다. 이렇게 생성된 함수를 사용하기 위해서는 함수를 호출(function call/invoke) 해야 하는데, 이 때 함수 내부에서 필요한 값을 매개변수를 통해 전달하는 값을 인수(argument)로 작성해야 한다.

다시 정리하면 함수는 미리 정의하고 필요할 때 호출을 하는데, 이 때 함께 작성한 인수(argument)를 함수 정의 부분의 매개변수로 받아서 코드 블록 안으로 전달하고, 코드 블록 안에서는 매개변수(parameter)들을 사용하여 문을 실행한다. 최종적으로 실행한 결과 값을 외부에서 사용하기 위해서 실행결과를 반환(return value)하는 것이다.

함수의 사용이유

프로그래밍을 하다보면, 같은 코드를 사용해야 하는 경우가 생긴다. 이 때 코드를 재사용하지 않고, 여러번 작성하여 사용하게 된다면 유지보수 편의성이 나쁘고 실수할 가능성이 높아진다. 이 때 같은 코드를 사용해야 하는 부분에 함수를 작성하고, 필요할 때마다 호출하여 재사용하게 되면 유지보수가 쉬워지고, 실수를 줄여준다.

함수 리터럴

함수 이름 (식별자)

  • 함수 이름은 함수 몸체 내부에서만 참조할 수 있는 식별자이다. 따라서 식별자 네이밍 규칙을 준수해야 한다.
  • 함수 이름은 생략가능하다. 함수 이름이 있는 함수는 기명함수, 없는 함수는 무명/익명 함수라고 한다.

매개변수 목록

  • 매개변수는 없을 수도 있다. 만약 매개변수가 여러개인 경우에는 소괄호로 감싸고 쉼표로 구분한다.
  • 매개변수는 함수 몸체 내부에서 일반 변수처럼 사용되므로 식별자 네이밍 규칙을 준수해야 한다.

함수 몸체

  • 함수가 호출 되었을 때 실행될 문들을 코드 블록으로 묶은 하나의 실행단위이다.
  • 함수 몸체는 함수 호출에 의해서 실행된다.

함수 정의

함수를 정의하는 방법은 4가지가 있고, 각각의 차이점을 가지고 있다. 여기서는 함수 선언문과 함수 표현식에 대해서만 알아보고자 한다.

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

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

// 함수 호출

console.log(sum(2, 5); // 7
함수 선언문함수 표현식
함수 이름생략 불가능생략 가능
표현식표현식이 아닌 문표현식인 문 (값으로 평가 됨)
변수 할당불가능가능
호이스팅함수 호이스팅변수 호이스팅

함수 선언문과 함수 표현식은 형태가 동일하다. 이 때 자바스크립트 엔진은 문맥을 파악하여 함수 선언문 혹은 함수 리터럴 표현식으로 해석하게 된다.

함수 선언문

함수 선언문으로 정의한 함수를 호출할 때에는 함수를 이름으로 호출하는 것처럼 보이지만 사실은 아니다. 함수 선언문을 정의하면 자바스크립트 엔진은 함수 선언문의 이름과 동일한 식별자를 암묵적으로 생성하여 함수 객체를 할당하고 이렇게 함수가 할당된 식별자로 함수를 호출한다. 때문에 함수 이름으로 함수를 호출하는 것처럼 보이게 되는 것이다.

함수 표현식

자바스크립트의 함수는 일급 객체이기 때문에 변수에 할당할 수 있다.(함수를 값처럼 사용할 수 있기 때문에) 이러한 정의 방식을 함수 표현식이라고 한다. 이 때 함수 리터럴의 함수 이름은 생략할 수 있고, 익명 함수라고 한다. 함수 표현식에서는 익명 함수를 사용하는 것이 일반적이다.

함수 생성 시점과 함수 호이스팅

위에서 함수 선언문에 대한 설명을 할 때에 자바스크립트 엔진이 함수 이름과 동일한 식별자를 암묵적으로 생성하여 함수를 할당한다고 했다. 그리고 함수 표현식을 살펴보면 자바스크립트 엔진이 암묵적으로 실행하는 식별자를 생성하고 함수를 할당하는 과정을 직접 진행하는 것처럼 보인다. 때문에 함수 선언문과 함수 표현식이 동일하게 동작하는 것처럼 보일 수 있다. 하지만, 함수 선언문과 함수 표현식은 함수의 생성 시점이 다르고, 때문에 서로 다른 호이스팅이 일어나게 된다.

함수 선언문은 말 그대로 선언문이다. 때문에 런타임 이전에 자바스크립트 엔진이 함수 객체를 먼저 생성하고, 함수 이름과 동일한 이름의 식별자를 만들어서 함수 객체를 할당한다. 결국 순차적으로 코드가 실행되는 시점에는 함수가 이미 생성되어 있고, 함수 선언문 이전에 함수를 호출할 수 있는 함수 호이스팅이 일어난다.

반면 함수 표현식은 변수에 함수 객체를 할당한 것이다. 때문에 변수 선언문과 같은 방식으로 동작하게 된다. 런타임 이전에 변수 선언이 일어나고 undefined로 초기화 된다. 이후 순차적으로 코드가 실행되는 시점에 와서야 함수 리터럴이 함수 객체가 되는 것이다. 따라서 함수 표현식은 변수 호이스팅이 일어나게 되는 것이며, 함수 표현식 이전에 함수를 호출하게 되면 (undefined를 호출하려고 하는 상황이 되기 때문에) TypeError가 발생한다.

함수 호출

함수를 사용하기 위해서는 함수를 가르키는 식별자(함수 이름)와 함수 호출 연산자()로 호출한다.

매개변수와 인수

함수를 실행하기 위해 필요한 값은 함수를 호출할 때 인수로 작성하고, 매개변수를 통해 함수에 전달된다. 인수는 개수와 타입에 제한이 없고, 작성한 순서대로 매개변수에 할당된다. 이 때 자바스크립트는 매개변수와 인수의 개수가 같은지 확인하지 않으며, 매개변수보다 인수가 더 많을 때는 초과된 인수가 무시된다. 반대로 모자랄 경우 값이 할당되지 않은 매개변수는 undefined가 된다. 이 매개변수는 함수 몸체 안에서만 참조할 수 있다.

반환문

함수는 실행 결과를 return 키워드를 사용하여 함수 외부로 반환할 수 있다. 반환문은 생략이 가능하지만, 이럴경우 undefined가 반환된다. 반환문을 작성한 경우 반환문 이후에 작성된 문은 실행되지 않는다. 여기서 유의해야 할 점은 return키워드 이후에 줄 바꿈을 하고, 표현식을 작성하면 안된다는 것이다. 만약 줄바꿈을 하고 표현식을 작성하면, 자바스크립트 엔진은 줄바꿈을 문이 끝났다고 판단하여 자동으로 세미콜론을 삽입하고, 표현식은 무시하기 때문이다.

다양한 함수의 형태

즉시 실행 함수

즉시 실행 함수는 함수를 정의함과 동시에 바로 호출해서 단 한번 실행되는 함수이다. 즉시 실행 함수는 반드시 그룹 연산자로 감싸야 하며, 그룹 연산자로 감싸지 않으면 SyntaxError가 발생한다. 또한 즉시 실행 함수는 보통 익명 함수를 사용한다.

(function () {
  // ...
}());

(function () {
  // ...
})();

!function () {
  // ...
}();

+function () {
  // ...
}();

재귀 함수

재귀 함수는 자기 자신을 호출하는 함수를 말한다. 재귀 함수는 반복문이 없이도 반복되는 처리를 위해서 사용할 수 있다. 단, 재귀 함수는 탈출 조건을 반드시 작성해줘야 한다. 만약 탈출 조건을 작성하지 않는다면 함수가 무한 호출되고 스택 오버플로 에러가 발생하게 된다. 이처럼 재귀 함수는 위험성을 내포하고 있다. 때문에 반복문보다 재귀함수를 사용할 때 가독성이 좋아지는 경우에만 사용하는 것이 좋다.

중첩 함수

함수 내부에 정의된 함수를 중첩함수(또는 내부함수)라고 한다. 이 때 중첩 함수를 포함하는 함수는 외부 함수라고 한다. 이런 경우 중첩 함수는 보통 외부 함수를 돕는 역할을 한다.

콜백 함수

자바스크립트의 함수는 일급 객체이므로 매개변수를 통해 함수를 전달할 수 있다. 이 때 함수의 내부로 전달된 함수를 콜백 함수(Call back function)라고 하고, 콜백 함수를 전달받은 함수를 고차 함수(Higher-Order function)라고 한다. 이 고차 함수는 전달 받은 콜백 함수를 자신의 일부분으로 만들게 되는데, 호출 시점은 고차 함수가 결정한다. 이 말은 고차 함수를 호출하며 작성하는 인수에콜백 함수를 작성하게 되는데, 이 때 콜백 함수를 호출하는 것이 아니라는 것이다. 때문에 고차 함수를 호출할 때 인수에 작성하는 콜백 함수는 호출 연산자를 제외한, 함수의 식별자 이름만 작성해야 한다.

순수 함수와 비순수 함수

외부 상태에 의존하거나 변경하지 않는 함수를 순수 함수라고 하고, 동일한 인수를 전달 받았을 때는 항상 같은 값을 반환한다. 반대로 외부 상태에 의존하거나 변경하는 함수는 비순수 함수라고 하고, 당연히 외부 상태가 변화한다면 다른 값을 반환하기도 한다. 또한 이 비순수 함수는 외부 상태를 변화시키기도 하는데 이런 경우 상태 변화를 추적하기 어렵기 때문에 가능하면 순수 함수를 사용하는 것이 좋다.

2. 함수와 일급객체

  1. 무명의 리터럴로 생성할 수 있다. (함수 표현식)
  2. 변수나 자료구조에 저장할 수 있다. (함수 표현식, 객체의 메소드)
  3. 함수의 매개변수에 전달할 수 있다. (콜백함수)
  4. 함수의 반환값으로 사용할 수 있다. (함수 내부에서 외부로 return)

3. ES6 함수의 추가 기능

ES6 이전에는 하나의 함수가 다양한 기능을 했다. 당연하지만 일반 함수로 사용했고, new 연산자와 함께 호출해서 생성자 함수로서, 그리고 객체의 프로퍼티 값에 할당을 해서 메서드로서 사용했다. 이 것은 편리하다는 장점과 함께 실수를 유발하거나 성능이 떨어지는 단점을 가지고 있다.

때문에 이러한 단점을 보완하기 위한 새로운 함수들이 등장했다. 바로 메서드와 화살표 함수이다. 단, 여기서 말하는 메서드는 단순히 객체의 프로퍼티 값에 할당한 함수를 구별하기 위해서 말하는 메서드가 아닌, 메서드 축약 표현으로 정의된 함수를 말한다.

메서드 축약표현

메서드 축약 표현으로 정의한 메서드는 생성자 함수로서 동작하지 않는다. 때문에 prototypeconstructor가 없다.

const obj = {
    a: 2,
    b: 1,
    sum() {
        return this.a + this.b;
    }
}

화살표 함수

화살표 함수는 function 키워드 대신 =>를 사용하여 간단하게 함수를 정의할 수 있다. 단, function 키워드를 사용하지 않기 때문에 함수 표현식으로만 정의할 수 있으며, 호출 방식은 기존의 함수와 동일하다. 화살표 함수 또한 자바스크립트의 다른 함수와 마찬가지로 일급 객체이고, 때문에 고차 함수에 인수로 전달할 수 있다.

const sum = (a, b) => a + b;
sum(1, 2);

유의점

1. 매개변수

화살표 함수의 매개변수는 일반 함수와 동일하게 ()안에 선언할 수 있다. 단, 일반함수와 다르게 매개변수가 하나일 경우에는 ()를 생략할 수 있다. (거꾸로 말하면 매개변수가 없거나, 여러개일 경우에는 ()를 생략할 수 없다는 뜻이다.)

// 매개변수가 0개
const arrow = () => {...};

// 매개변수가 1개
const arrow = x => {...};

// 매개변수가 여러개
const arrow = (a, b) => {...};

2. 함수 몸체 정의

화살표 함수의 경우 함수 몸체에 작성되는 문이 하나인 경우 함수 몸체를 의미하는 {}를 생략할 수 있다. 이 때 중괄호를 생략하는 것은 화살표 함수의 몸체에 작성된 하나의 문을 함수 외부로 반환하겠다는 의미가 된다. 때문에 작성된 하나의 문이 표현식이 아니라면 에러가 발생하게 되므로 {}를 생략할 수 없다.

여기서 더 주의해야 할 것은 중괄호를 생략하고 작성을 할 때 반환하는 값이 객체인 경우이다. 값으로 평가되는 모든 문은 표현식이기 때문에 객체를 작성하여 반환할 수 있게 되는 것이다. 그런데 문제는 객체를 표현할 때에도 {}를 사용한다는 것이다. 때문에 이런 경우에는 객체를 ()로 감싸주어야 한다. 이렇게 해야 자바스크립트 엔진이 객체에 사용된 중괄호가 함수 몸체의 중괄호라고 잘못 해석하는 일을 방지하게 되는 것이다.

만약 함수 몸체에 작성되는 문이 하나 이상이 경우에는 {}를 생략할 수 없고, return문을 작성하여 명시적으로 값을 반환해야 한다.

// 함수 몸체 안에 표현식인 문이 1개
const sum = (a, b) => a + b;

// 함수 몸체 안에 표현식이 아닌 문이 1개
const arrow = () => {const a = 1;};

// 함수 몸체 안에 문이 2개 이상
const sum = (a, b) => {
    const result = a + b;
    return result;
}

// 함수 몸에 안에 객체 1개를 반환
const create = (id, contents) => ({id, contents});
create(1, 'Javascript');

화살표 함수의 this

함수는 어떻게 호출했는지에 따라서 this가 달라지게 된다. 이 때문에 콜백 함수의 경우 this가 꼬이는 경우가 생기게 된다. (이후 this에서 다루게 될 예정) 이럴 경우에는 function.prototype.bind를 사용해서 해결할 수 있다.

사실 이것보다 편한 방법은 화살표 함수를 사용하는 것이다. 왜냐하면 화살표 함수는 자체의 this 바인딩을 갖지 않기 때문에 상위 스코프의 this를 참조하게 된다. 화살표 함수 내부에 화살표 함수가 중첩되어 있고, 중첩된 화살표 함수에서 this를 참조하는 경우 스코프 체인을 따라서 위쪽으로 this를 검색해 나가게 된다.

Rest 파라미터

매개변수의 이름 앞에 ...을 붙여서 작성하면 함수를 호출할 때 작성했던 인수들을 하나의 배열로 전달 받을 수 있다.

// Rest 파라미터
function sum(...rest) {
    console.log(rest);
}
foo(1, 2, 3, 4, 5);

만약 인수목록중 일부만 배열로 전달받고 싶다면, 배열이 아닌 값으로 전달 받을 인수는 기존과 같이 매개변수를 작성하는 것과 마찬가지로 매개변수 이름을 작성하고 쉼표로 구분을 한 뒤 배열로 전달받고 싶은 인수가 작성된 순서에 맞춰서 Rest 파라미터를 작성하면 된다.

자바스크립트의 함수가 지원하는 arguments는 함수를 호출할 때 작성한 인수를 값으로 가지고 있는 유사 배열 객체이다. 때문에 배열 메서드를 사용하려면 배열로 변환해야 한다. 하지만 Rest 파라미터는 인수를 직접 배열로 전달받기 때문에 이런 번거로움을 생략할 수 있다.

또한 화살표 함수에서는 arguments가 없기 때문에 화살표 함수에서 가변 인자 함수를 구현해야 하는 경우에는 Rest 파라미터를 사용해야만 한다.

유의점

  1. Rest 파라미터는 하나만 선언할 수 있다. 만약 두개의 Rest 파라미터를 선언한다면, 문법 에러가 발생하게 된다.
// Rest 파라미터가 두개인 경우 오류가 발생한다. 
function sum(...rest1, ...rest2) {...}
foo(1, 2, 3, 4, 5);
  1. Rest 파라미터는 매개변수의 마지막 파라미터이어야 한다는 것이다. 첫번째로 Rest 파라미터를 작성하게 되면, 이 또한 문법 에러가 발생하게 된다.
// Rest 파라미터는 마지막 매개변수이어야 한다.
function sum(...rest, 4, 5) {...}
foo(1, 2, 3, 4, 5);

매개변수 기본값

자바스크립트는 함수를 호출할 때 작성하는 인수가 매개변수의 숫자보다 적거나 많더라도 에러를 발생시키지 않는다. 물론 인수는 매개변수의 개수만큼 작성하는 것이 옳바르다.

이때 인수가 전달되지 않는 매개변수는 undefined의 값을 가지게 된다. 문제는 이렇게 될 경우 의도치 않은 결과가나올 수 있다는 것이다.

const sum = (a, b) => a + b;

예를 들어서 위와 같이 매개변수의 값을 모두 더한 값을 반환하는 함수가 있다고 하자. 이 경우 인수의 개수가 매개변수와 같거나 매개변수보다 많을때는 문제가 되지 않는다.
하지만 만약 인수의 개수가 매개변수보다 모자르게 작성되어 있다면? 이 경우 개발자는 전달된 인수만 더해진 값이 반환되기를 바라겠지만, 실제는 그렇지 않다. 인자를 전달받지 못한 매개변수는 undefined값이 할당되고, 자바스크립트 엔진은 인수로 전달된 숫자값과 undefined를 더하려고 하지만 숫자와 숫자가 아닌 값은 더할수가 없기 때문에 숫자가 아닌 값이다. 라는 의미를 가진 NaN이 반환되게 된다. 이 경우 매개변수에 기본 값이 할당되어 있었다면, 문제가 발생하지 않았을 것이다.

// 매개변수 기본값
const sum = (a = 0, b = 0) => a + b;

이 매개변수의 기본값이 적용되는 것은 매개변수에 인수가 전달되지 않았거나, undefined를 전달한 경우에만 유효한다. 때문에 매개변수에 어떤 값을 넣어두더라도, 인수로 전달되는 값이 있다면(전달되는 값이 undefined가 아니라는 전제하에..) 더 우선되는 것이다.

함수 몸체 정의, Rest 파라미터 및 매개변수 기본값 예시 코드 출처
모던 자바스크립트 Deep Dive : 이웅모 저

profile
디자인하는 Frontend Developer.

0개의 댓글