함수란?


프로그래밍 언어의 함수는 일련의 과정을 문(statement)으로 구현하고 코드 불록으로 감싸서 하나의 실행 단위로 정의한 것이다.

함수 내부로 입력을 전달받는 변수를 매개변수(parameter), 입력을 인수(argument), 출력을 반환값(return value)이라 한다.

함수는 인수를 매개변수로 하여 함수에 전달하면서 함수의 실행을 명시적으로 지시해야 하며 이 과정을 함수 호출이라 한다.

함수 사용 이유


함수는 몇번이든 호출할 수 있으므로 코드의 재사용 측면에서 매우 유용하다.

코드의 중복을 억제하고 재사용성을 높이는 함수는 유지보수 편의성, 코드의 신뢰성을 높이는 효과가 있다.

적절한 함수명은 함수 내부 코드를 이해하지 않고도 함수의 역할을 파악할 수 있게 하여 코드 가독성을 향상시킨다.

코드는 개발자를 위한 문서이기도 하다. 따라서 사람이 이해할 수 있는 코드, 즉 가독성이 좋은 코드가 좋은 코드다.

함수 리터럴


const f = function add(x, y) {
	return x + y;
};

함수 리터럴의 구성 요소는 다음과 같다.

함수는 객체다. (다른 프로그래밍 언어와 구별되는 자바스크립트의 중요한 특징이다.)

하지만 일반 객체와는 다르다. 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다. 그리고 일반 객체에는 없는 함수 객체만의 고유한 프로퍼티를 갖는다.

함수 정의


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

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

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

함수 선언문은 표현식이 아닌 문이다. 크롬 개발자 도구에서 함수 선언문을 실행하면 완료 값 undefined가 출력된다.

함수 선언문이 만약 표현식인 문이라면 undefined 대신 표현식이 평가되어 생성된 함수가 출력되어야 한다.

표현식이 아닌 문은 변수에 할당할 수 없다.

const add = function add(x, y) {
	return x + y;
};

이는 변수에 할당되는 것처럼 보인다.

이렇게 동작하는 이유는 자바스크립트 엔진이 코드 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문이다.

자바스크립트 엔진은 함수 이름이 있는 리터럴을 피연산자로 사용하지 않는 경우에는 선언문으로 해석하고, 피연산자로 사용하면 함수 리터럴 표현식으로 해석한다.

function foo() { console.log('foo'); }
foo(); // foo

(function bar() { console.log('bar'); });
bar(); // ReferenceError: bar is not defined

예제에서 단독으로 사용된 함수 리터럴(foo)는 함수 선언문으로 해석된다. 하지만 그룹 연산자 ()에 있는 함수 리터럴(bar)는 함수 리터럴 표현식으로 해석된다.

“함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다.”라고 했다.

이는 함수는 메모리에 생성되지만 함수 몸체 외부에서는 함수 이름으로 함수를 참조할 수 없으므로 함수를 가리키는 식별자가 없는 bar의 함수는 호출할 수 없다.

foo는 함수 몸체 내부에서만 유효한 식별자인 함수 이름이므로 이론적으론 foo 함수를 호출할 수 없어야 한다.

foo라는 이름으로 함수를 호출하려면 foo는 함수 이름이 아니라 함수 객체를 가리키는 식별자여야한다.

foo는 자바스크립트 엔진이 암묵적으로 생성한 식별자다.

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

함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.

  • 함수 표현식
const add = function (x, y) {
	return x + y;
}

함수 리터럴의 함수 이름은 생략할 수 있다.

함수 표현식의 함수 리터럴은 함수 이름을 생략하는 것이 일반적이다.

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

console.dir(add); // *f add(x, y)*
console.dir(sub); // *undefined

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

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

함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출할 수 있다. 그러나 함수 표현식으로 정의한 함수는 표현식 이전에 호출할 수 없다.

이는 함수 선언문으로 정의한 함수와 표현식으로 정의한 함수의 생성 시점이 다르기 때문이다.

모든 선언문이 그렇듯 함수 선언문도 코드가 실행되는 시점인 runtime 이전에 JS 엔진에 의해 먼저 실행된다. 즉, 함수 선언문으로 함수를 정의하면 runtime 이전에 객체가 먼저 생성된다. 그리고 JS 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 함수 객체를 할당한다.

코드가 실행되기 전까지 함수 객체가 생성되어 있고 함수 이름과 동일한 식별자에 할당까지 완료된 상태다. 따라서 함수 선언문 이전에 함수를 참조할 수 있고 호출할 수도 있다.

이는 함수 선언문이 끌어 올려진 것처럼 동작하는 호이스팅이라고 한다.

함수 호이스팅과 변수 호이스팅에는 미묘한 차이가 있다.

var 키워드를 사용한 변수 선언문과 함수 선언문은 런타임 이전에 JS 엔진에 의해 먼저 실행되어 식별자를 생성한다는 점에서 동일하다. 하지만 변수 초기화는 undefined로 되고, 함수는 객체로 초기화 된다.

따라서 변수 선언문 이전에 변수를 참조하면 undefined로 평가되지만, 함수는 호출이 가능한 상태인 것이다.

연관되어서 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다.

즉, 변수 선언문과 같은 동작을 하게 된다는 것이다. 변수 호이스팅이 발생되어 undefined로 초기화 되는 것이다.

따라서 이 때 함수를 호출하면 undefined를 호출하는 것과 마찬가지이므로 TypError가 발생한다.

호출 전 함수를 선언해야 한다는 당연한 규칙을 무시한다는 문제 때문에

JSON를 창안한 더글라스 크락포드는 함수 선언문 대신 함수 표현식을 사용할 것을 권장한다.

  • Function 생성자 함수
const add = new Function('x', 'y', 'return x + y');

일반적이지 않으며 바람직하지도 않은 방법이다.

클로저를 생성하지 않는다는 것 정도만 알아두자. 이것도 알기 싫으면 알 필요 없다.

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

화살표 함수는 기존의 함수 선언문 또는 함수 표현식을 대체하기 위해 디자인 된 것이 아니다.

화살표 함수는 기존의 함수보다 표현만 간략한 것이 아니라 내부 동작 또한 간략화 되어 있다. 알고 쓰자 나녀석아!!

화살표 함수는 생성자 함수로 사용할 수 없으며, 기존 함수와 this 바인딩 방식이 다르고(기존 함수의 this는 호출된 곳, 화살표 함수에서는 이 함수의 context), prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않는다.

나중에 자세히 알아보자…

매개변수와 인수


  • 인수
    • 값으로 평가될 수 있는 표현식이어야 한다.
    • 인수는 함수를 호출할 때 지정하며, 개수와 타입에 제한이 없다.
  • 매개변수
    • 함수 몸체 내부에서만 참조할 수 있다.
    • 스코프는 함수 내부이다.
    • 인수가 부족해서 할당되지 않은 매개변수의 값은 undefined이다.
    • 인수가 초과해서 할당되지 않은 인수는 무시된다.
      (진짜 버려지는 것은 아니고 arguments 객체의 프로퍼티로 보관됨.)
      ⇒ 화살표 함수에서는 버려지겠네…?
    • 3개 이상 넘지 말자…(권장)

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


11장에 나왔던 내용처럼 인수로 원시 값을 전달하는 경우 값이 복사되어 전달되고

객체가 전달되면 참조를 전달하게 된다.

따라서 함수 내부에서 참조값이 전달된 객체를 변경하게 되면 당연 기존 객체의 모양도 변형이 될 것이다.

이러한 문제의 해결법 중 하나는 immutable object로 만들어 사용하는 것이다. (feat. lodash)

깊은 복사를 통해 새로운 객체를 생성하고 재할당을 통해 교체한다.

다양한 함수의 형태


  • 즉시 실행 함수
(function () {
	var a = 3;
	var b = 5;
	return a * b;
}());
  • 재귀 함수
const factorial = function foo(n) {
	if (n <= 1) return 1;
	return n * factorial(n - 1);
};

대부분의 재귀함수는 반복문으로 표현이 가능하다.

재귀 함수는 반복되는 처리를 반복문 없이 구현할 수 있지만 infinity loop에 빠질 수 있고, stack overflow를 발생시킬 수 있으므로 재귀함수를 사용했을 때, 직관적으로 이해하기 쉬울 때만 한정적으로 사용하자.

  • 중첩 함수
function outer() {
	function inner() {
	};
};

호이스팅으로 인한 혼란을 방지하기 위해 if문이나 for문 등의 코드 블록에서 함수를 정의하지 않도록만 하자.

  • 콜백 함수

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 하며 콜백 함수를 전달받은 함수를 고차 함수(Higher-Order Function)이라고 한다.

  • 순수 함수(pure function)와 비순수 함수(impure function)

어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 side effect가 없는 함수를 순수 함수라고 하고 외부 상태에 의존하거나 변경하는 함수를 비순수 함수라고 한다.

함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임이다. 객체지향 프로그래밍 패러다임과 같이 적절히 조화하여 사용하자. (생각보다 함수형 언어들의 폐해가 많다.)

profile
프론트엔드 신입 육아과정🍼

0개의 댓글