: 함수는 자바스크립트에서 가장 중요한 핵심 개념이며, 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다.
// f(x, y) = x + y
function add(x, y) {
return x + y;
}
// f(2, 5) = 7
add(2, 5); // 7
: 자바스크립트의 함수는 객체 타입의 값이다. 따라서 숫자 값을 숫자 리터럴로 생성하고 객체를 객체 리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성할 수 있다. 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
add(2, 5); // ReferenceError 발생
함수 리터럴의 구성
1) function 키워드
2) 함수 이름
3) 매개 변수 목록
4) 함수 몸체
함수 리터럴은 값을 생성하기 위한 표기법
-> 함수 리터럴도 평가되어 값을 생성하며, 이 값은 객체이다
-> 함수는 객체다
일반 객체는 호출할 수 없지만, 함수는 호출할 수
<이미지 출처: https://hong-p.github.io/javascript/javascript-deepdive-ch12/#1%ED%95%A8%EC%88%98%EB%9E%80>
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 참조
// console.dir은 console.log와는 달리 함수 객체의 프로퍼티까지 출력한다.
// 단, Node.js 환경에서는 console.log와 같은 결과가 출력된다.
console.dir(add); // ƒ add(x, y)
// 함수 호출
console.log(add(2, 5)); // 7
함수 선언문은 함수 리터럴과 형태가 동일
함수 리터럴은 함수 이름을 생략할 수 있으나 함수 선언문은 함수 이름을 생략할 수 없다
함수 선언문은 표현식이 아닌 문이다.
표현식이 아닌 문은 변수에 할당할 수 없는데... 아래 예시는?
// 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없다.
// 하지만 함수 선언문이 변수에 할당되는 것처럼 보인다.
var add = function add(x, y) {
return x + y;
};
// 함수 호출
console.log(add(2, 5)); // 7
---> 중의적 표현... 함수 선언문일 수도 있고, (기명)함수 리터럴일 수도 있고...
함수 선언문은 함수 이름을 생략할 수 없다.
자바스크립트 엔진은 함수 선언문을 해석해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.
반면 함수 표현식(함수 리터럴)은 “함수 이름은 함수 몸체 내에서만 참조 할수 있는 식별자”라는 특성이 있어, 별도로 변수에 할당받지 않은 이상 호출할수 있는 식별자가 없다.
// 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
// 함수 선언문에서는 함수 이름을 생략할 수 없다.
function foo() { console.log('foo'); }
foo(); // foo
// 함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석된다.
// 함수 리터럴에서는 함수 이름을 생략할 수 있다.
(function bar() { console.log('bar'); });
bar(); // ReferenceError: bar is not defined
// 아래와 같이 작성해야 호출 가능하다.
bar = (function bar(){ console.log('bar'); });
bar(); // bar
자바스크립트의 함수는 객체 타입의 값이다. 값의 성질을 갖는 객체를 일급 객체라 한다.
일급 객체는 값처럼 변수에 할당할 수 있고, 프로퍼티 값이 될수도 있으며, 배열의 요소도 될 수 있다. 함수는 일급 객체이므로 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다.
이러한 함수의 정의 방식을 함수 표현식이라고 한다.
// 함수 표현식 (함수 리터럴의 함수 이름은 생략 가능 --> 익명함수라 한다)
var add = function (x, y) {
return x + y;
};
console.log(add(2, 5)); //
**함수 표현식의 함수명(주로 생략)으로 해당 함수를 호출할 수 없다
1) 함수 선언문으로 정의한 함수: 함수 선언문 이전에 호출 가능
런타임 시점에는 이미 함수 객체가 생성 완료, 식별자에 할당까지 완료된 상태
함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작 --> 이를 함수 호이스팅이라 한다
2) 함수 표현식으로 정의한 함수: 표현식 이전에 호출 불가능
--> 함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 규칙을 무시하기 때문에 함수 선언문 보다는 함수 표현식의 사용을 권장한다.
// 함수 참조
console.dir(add); // ƒ add(x, y)
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x - y;
};
책 참고,
Function 생성자 함수로 함수를 생성하는 방식은 권장되지 않는다.
ES6 에서 도입된 화살표 함수는 function 키워드를 사용하지 않고 화살표(=>, fat arrow)를 사용해 좀 더 간략한 방법으로 함수를 선언할 수 있다
// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7
function add(x, y) {
if (typeof x !== 'number' || typeof y !== 'number') {
// 매개변수를 통해 전달된 인수의 타입이 부적절한 경우 에러를 발생시킨다.
throw new TypeError('인수는 모두 숫자 값이어야 합니다.');
}
return x + y;
}
console.log(add(2)); // TypeError: 인수는 모두 숫자 값이어야 합니다.
console.log(add('a', 'b')); // TypeError: 인수는 모두 숫자 값이어야 합니다.
원시 값과 객체의 비교’에서 살펴 봤듯이 원시값은 값에 의한 전달(pass by value), 객체는 참조에 의한 전달(pass by reference)방식으로 동작한다.
함수의 매개변수도 동일하다.
// 매개변수 primitive는 원시값을 전달받고, 매개변수 obj는 객체를 전달받는다.
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'Kim';
}
// 외부 상태
var num = 100;
var person = { name: 'Lee' };
console.log(num); // 100
console.log(person); // {name: "Lee"}
// 원시값은 값 자체가 복사되어 전달되고 객체는 참조값이 복사되어 전달된다.
changeVal(num, person);
// 원시값은 원본이 훼손되지 않는다.
console.log(num); // 100
// 객체는 원본이 훼손된다.
console.log(person); // {name: "Kim"}
※순수 함수
외부의 상태를 변경하지 않고, 외부 상태에 의존하지도 않는 함수.
함수가 외부 상태를 변경하게 되면 상태 변화를 추적하기 어려워진다.
이는 코드의 복잡성을 증가시키고 가독성을 해치는 원인이 된다. 순수 함수를 통해 부수효과를 최대한 억제하여 오류를 피하고 안정성을 높이는 패러다임을 함수형 프로그래밍이라 한다.
함수의 정의와 동시에 즉시 호출되는 함수. 단 한 번만 호출, 재호출 불가능
// 익명 즉시 실행 함수
(function () {
// statements
})()
즉시 실행 함수를 사용하는 이유?
예시)
var initText;
(function (number) {
var textList = ["is Odd Text", "is Even Text"];
if (number % 2 == 0) {
initText = textList[1];
} else {
initText = textList[0];
}
})(5);
console.log(initText);
console.log(textList);
함수 자기 자신을 호출하는 것을 재귀 호출(recursive call)이락 한다.
예시)
function countdown(n) {
if (n < 0) return;
console.log(n);
countdown(n - 1); // 재귀 호출
}
countdown(10);
함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 한다. 중첩 함수를 포함하는 함수는 외부함수이고, 중첩함수(내부함수)는 외부함수의 내부에서만 호출할 수 있다. 일반적으로 중첩함수는 자신을 포함하는 외부함수의 헬퍼 함수의 역할을 한다.
function outer() {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조할 수 있다.
console.log(x + y); // 3
}
inner();
}
outer();
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수(callback function)라고 한다.
매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(Higher-Order Function, HOF)라고 한다.
콜백함수는 익명함수 리터럴 형태로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다.
// 외부에서 전달받은 f를 n만큼 반복 호출한다
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i); // i를 전달하면서 f를 호출
}
}
var logAll = function (i) {
console.log(i);
};
// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logAll); // 0 1 2 3 4
var logOdds = function (i) {
if (i % 2) console.log(i);
};
// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logOdds); // 1 3
※고차 함수
-콜백 함수를 전달받은 함수.
-콜백 함수를 자신의 일부분으로 합성한다.
-전달받은 콜백함수의 호출 시점을 결정해서 호출한다.
-콜백 함수에 인수를 전달할 수 있다.
// 콜백 함수를 사용하는 고차 함수 map
var res = [1, 2, 3].map(function (item) {
return item * 2;
});
console.log(res); // [2, 4, 6]
// 콜백 함수를 사용하는 고차 함수 filter
res = [1, 2, 3].filter(function (item) {
return item % 2;
});
console.log(res); // [1, 3]
// 콜백 함수를 사용하는 고차 함수 reduce
res = [1, 2, 3].reduce(function (acc, cur) {
return acc + cur;
}, 0);
console.log(res); // 6
.
.
.
그래서 콜벡함수는 언제 사용되는데?
예시 1)
비동기적인 것을 동기적으로 구현할때... 콜백함수를 사용, 즉 일을 순차적으로 하고자 할 때 사용한다...
: 자바스크립트는 비동기적으로 동작하기 때문에, 먼저 실행된 작업이 끝나지 않더라도 다음 작업이 시작된다.
콜백 함수를 이용하면, 특정한 코드에 순서를 정하여 원하는 시점에 실행을 할 수 있다.
function a(callback) {
//First
// a lot of hard work...
callback()
}
function b() {
//Second
}
a(b)
a 함수의 일을 처리한 이후에 b 함수를 실행하고자 할때 사용할 수 있다.