모던 자바 스크립트 Deep Dive 를 읽고 정리한 글 입니다.
12. 1 함수란?
함수
계산 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의
- 함수 이름 : 함수를 구별하기 위해 붙인 식별자
- 매개 변수 (parameter) : 함수 내부로 입력을 전달 받는 변수
- 반환값 (return value) : 출력
- 인수 (argument) : 입력
- 함수 정의 (function definition)
- 함수 생성
- 함수 정의만으로 함수가 실행되지 않음.
- 함수 호출 (function call/invoke)
- 인수를 매개 변수를 통해 함수에 전달 및 함수의 실행 명시적 지시
- 함수를 호출 시 코드 블록에 담긴 문들 일괄적으로 실행 및 반환값 반환
12. 2 함수를 사용하는 이유
코드의 재사용
함수는 몇 번이든 호출 가능
유지보수의 편의성 높임
코드의 신뢰성 높임
코드의 가독성 향상
함수는 객체 타입의 값이므로 식별자를 붙일 수 있음
적절한 함수 이름은 함수의 내부코드 이해 없이 함수의 역할 파악 도움
12. 3 함수 리터럴
함수 리터럴
자바스크립트에서 함수는 객체 타입의 값이므로 함수 리터럴 생성 가능
function 키워드, 함수 이름, 매게 변수 목록, 함수 몸체
리터럴 : 사람이 이해할 수 있는 문자, 약속된 기호를 사용하여 값을 생성하는 표기 방식
함수 리터럴 구성 요소
구성 요소 | 설명 |
함수 이름 | 함수 이름은 식별자 -> 식별자 네이밍 규칙 준수 함수 이름은 함수 몸체 내에서만 참소 할 수 있는 식별자 함수 이름 생략 가능 - 기명 함수(named function) : 이름이 있는 함수 - 무명/익명 함수(anonymous function) : 이름이 없는 함수 |
매개변수 목록 | 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분 각 매개변수에는 함수를 호출할 때 지정한 인수 순서대로 할당 (매개변수 목록은 순서에 의미) 매개변수는 함수 몸체 내에서 변수와 동일 취급 -> 식별자 네이밍 규칙 준수 |
함수 몸체 | 함수가 호출되었을 때 일괄적으로 실행된 문들을 하나의 실행 단위로 정의한 코드 블록 함수 몸체는 함수 호출에 의해 실행 |
함수는 객체이다
일반 객체는 호출할 수 없지만 함수는 호출 가능
일반 객체에는 없는 함수 객체만의 고유한 프로퍼티를 가짐
12. 4 함수 정의
함수 정의 방식 | 예시 |
함수 선언문 | function add(x,y) { return x+y; } |
함수 표현식 | var add = function (x,y) { return x+y; }; |
Function 생성자 함수 | var add = new Function('x', 'y', 'return x+y'); |
화살표 함수 | var add = (x,y) => x+y; |
변수 선언
변수는 선언(declaration)
함수 정의
함수는 정의(definition)
함수 선언문
함수 선언문이 평가되면 식별자가 암묵적으로 생상되고 함수 객체가 할당
함수 선언문은 함수 리터럴과 동일 형태
- 함수 리터럴은 함수 이름 생략 가능
- 함수 선언문은 함수 이름 생략 불가능
함수 선언문은 표현식이 아닌 문
크롬 개발자 도구의 콘솔에서 함수 선언문을 실행하면 완료 값(undefined) 출력
표현식이 아닌 문은 변수에 할당 불가능
그러나 함수 선언문이 변수에 할당되는 것 처럼 보임
-> 코드의 문맥에 따라 함수 선언문으로 해석(표현식X) / 함수 리터럴 표현식으로 해석(표현식O)
var add = function add(x, y) {
return x+y;
};
// 함수 호출
console.log(add(2,5)); // => 7
ex) {}
블록문 : 단독으로 존재
객체 리터럴 : 값으로 평가될 문맥에서 피연산자로 사용
=> 동일한 코드도 코드의 문맥에 따라 해석이 달라짐
기명 함수 리터럴 - 중의적 코드
- 함수 선언문 : 단독 사용
함수 리터럴을 피연산자로 사용하지 않는 경우
값으로 평가되어야 하는 문맥에서 함수 리터럴 사용하지 않은 경우
- 함수 리터럴 표현식 : 함수 리터럴이 값으로 평가 되어야 하는 문맥
함수 리터럴 변수에 할당
피연산자로 사용
=> 함수가 생성된 것은 동일
=> 호출 방법의 차이
함수 몸체 내 -> 함수 이름으로 참조할 수 있는 식별자
함수 몸체 외부 -> 함수 이름으로 함수 호출 불가능
// 기명 함수 리터럴을 단독 사용 -> 함수 선언문으로 해석
// 함수 선언문에서는 함수 이름 생략 불가
function foo() { console.log('foo'); }
foo(); // => foo
// 함수 리터럴 피연산자로 사용 -> 함수 리터럴 표현식으로 해석
// 함수 리터럴에서는 함수 이름 생략 가능
(function bar() { console.log('bar'); });
bar(); // => ReferenceError: bar is not defined
// 그룹 연산자의 피연산자는 값으로 평가될 수 있는 표현식
// 표현식이 아닌 함수 선언문은 피연산자로 사용 불가
원칙적으로 foo와 bar는 호출 불가능
그러나 식별자 foo를 선언/할당 한적 없지만, 자바스크립트 엔진이 암묵적으로 식별자를 생성해서 가능
결과적으로
자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일 이름의 식별자를 암묵적 생성 및 함수 객체 할당
함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출
함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름으로 호출 불가능
자바스크립트 엔진은 함수 선언문을 함수 표현식으로 변환하여 함수 객체 생성 (동일 동작은 아님)
함수 표현식
일급 객체 : 값의 성질을 갖는 객체
자바스크립트의 함수는 일급 객체
함수를 값처럼 자유롭게 사용 가능
- 함수 선언문 - 표현식이 아닌 문
- 함수 표현식 - 표현식인 문, 함수 리터럴로 생성한 함수 객체를 변수에 할당 가능한 함수 정의 방식
함수 표현식으로 정의한 함수는 그 이후 참조, 호출 가능
이전에 함수 참조 -> undefined
이전에 이전 함수 호출 -> 타입 에러 발색
// 함수 표현식
var add = function (x,y) {
return x + y;
};
console.log(add(2,5)); // => 7
익명 함수 : 함수 리터럴이 함수 이름을 생략함
// 기명 함수 표현식
var add = function foo (x, y) {
return x + y;
};
// 함수 객체를 가리키는 식별자로 호출
console.log(add(2, 5)); // => 7
// 함수 이름으로 호출하면 ReferenceError 발생
// 함수 이름은 함수 몸체 내무에서만 유효한 식별자
console.log(foo(2, 5)); // => ReferenceError: foo is not defined
자바스크립트 엔진은 함수 선언문의 이름으로 식별자를 암묵적 생성, 생성된 함수 객체를 할당하므로 함수 표현식과 유사하게 동작되는 것으로 보이나 동일하지 않음.
함수 생성 시점과 함수 호이스팅
함수 선언문으로 정의한 함수
- 함수 선언문 이전에 호출 가능
- 런타임 이전에 함수 객체 생성
- 함수 이름과 동일한 이름의 식별자 암묵적 생성 후 생성된 함수 객체 할당
- 런타임에는 함수 객체 생성, 함수 이름과 동일한 식별자에 할당까지 완료
- 따라서 함수 선언문 이전에 함수를 참조, 호출 가능
함수 표현식으로 정의한 함수
- 함수 표현식 이전에 호출 불가능
// 함수 참조
console.dir(add); // => f add(x, y)
console.dir(sub); // => undefined
// 함수 호출
console.log(add(2, 5)); // => 7
console.log(sub(2, 5)); // => TyepError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x - y;
};
함수 호이스팅
함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징
함수 호출 전 함수를 선언해야 한다는 규칙 무시
함수 표현식으로 함수 정의시 함수 호이스팅이 아닌 변수 호이스팅이 발생
- 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가 되어 함수 객체가 됌
- 함수 표현식은 변수 선언문과 변수 할당문을 한 번에 기술한 축약 표현과 동일 동작
- 변수 선언은 런타임 이전에 실행되어 undefined로 초기화
- 변수 할당문의 값은 할당문이 실행되는 시점(런타임)에 평가
함수 호이스팅 | 변수 호이스팅 |
함수 선언문으로 암묵적 생성 식별자 => 함수 객체로 초기화 |
var 키워드로 선언된 변수 => undefined로 초기화 |
함수 선언문 이전에 호출 => 함수 호이스팅에 의해 호출 가능 |
변수 선언문 이전에 변수 참조 => 변수 호이스팅에 의해 undefined로 평가 |
런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되어 식별자 생성 |
Function 생성자 함수
Function 생성자 함수를 사용 하여 함수 생성
- function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출 -> 함수 객체를 생성해서 반환
- 클로저 생성 하지 않음
- 함수 선언문, 표현식으로 생성한 함수와 다르게 동작
생성자 함수 : 객체를 생성하는 함수
var add = new Function('x', 'y', 'return x + y');
conso.log(add(2, 5)); / /=> 7
var add1 = (function () {
var a = 10;
return function (x, y) {
return x + y + a;
};
}()):
console.log(add1(1, 2)); // => 13
-------------------------------------------------
var add2 = (function () {
var a = 10;
return new Function('x', 'y', 'return x + y + a:');
}()):
console.log(add2(1, 2)); // => ReferenceError: a is not defined
화살표 함수
function 키워드 대신 화살표 ' => ' 를 사용하여 더 간략한 방법으로 함수 선언
- 항상 익명 함수로 정의
- 기존 함수보다 간략한 표현 및 내부 동작
- 생성자 함수로 사용 불가능
- 기존 함수와 this 바인딩 방식 다름
- prototype 프로퍼티 없음
- arguments 객체 생성하지 않음
const add = (x, y) => x + y;
consloe.log(add(2, 5); // => 7
12. 5 함수 호출
식별자와 함수 호출 연산자( () )로 함수 호출
함수 호출 연산자 내에는 0개 이상의 인수를 쉼표로 구분해서 나열
함수를 호출하면 현재의 실행 흐름 중단, 호출된 함수로 변화
매개변수에 순서대로 인수 할당, 함수 몸체의 문들 실행
매개변수와 인수
함수를 실행하기 위해 필요한 값을 함수 외부에서 내부로 전달할 필요가 있을 경우
-> 매개 변수(인자)를 통해 인수 전달
- 매개변수
- 함수를 정의할 때 선언
- 함수 몸체 내부에서 변수와 동일 취급, 참조 가능
- 함수 몸체 외부에서 참조 불가능
- 인수
- 값으로 평가 될수 있는 표현식
- 함수를 호출할 때 지정, 개수/타입에 제한 없음
함수가 호출 -> 함수 몸체 내 암묵적 매개변수 생성 -> undefined로 초기화 -> 순서대로 인수 할당
매개변수의 유효 범위는 함수 내부
function add(x, y) {
console.log(x, y); // => 2 5
return x + y;
}
add(2, 5)l
console.log(x, y); // => RefrernceError: x is not defined
인수가 부족해 할당되지 않은 매개변수의 값은 undefined
function add(x, y) {
return x + y;
}
console.log(add(2)); // => NaN (2 + undefined)
매개변수보다 인수가 더 많은 경우 초과된 인수는 무시되나 암묵적으로 arguments 객체의 프로퍼티로 보관
argumensts 개체 : 함수 정의할 때 매개변수 개수를 확정할 수 없는 가변 인자 함수 구현에 사용
function add(x, y) {
return x + y;
}
console.log(add(2, 5, 10)); // => 7
-------------------------------------------------------
function add(x, y) {
console.log(arguments);
// Arguments(3) [2, 5, 10, callee: f, symbol(symblo.iterator): f]
return x + y;
}
add(2, 5, 10);
인수 확인
function add(x, y) {
return x + y;
}
console.log(add(2, 5) // => 7
console.log(add(2)); // => NaN
console.log(add('a', 'b')); // => 'ad'
코드상으로 어떤 타입의 인수를 전달해야 하는지와 어떤 타입의 값을 반환하는지 명확하지 않음
- 자바스크립트 함수는 매개변수와 인수의 개수의 일치 여부를 확인하지 않음
- 자바스크립트는 동적타입 언어이므로 매개변수의 타입을 사전에 지정 불가
따라서 자바스크립트는 함수를 정의할 때 적절 인수가 전달되었는지 확인 필요
(그러나 함수 내부에 적절 인수가 전달되었는지 확인해도 부적절한 호출 사전에 방지는 불가)
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: 인수는 모두 숫자 값이어야 한다.
arguments 객체로 인수 개수 확인 가능
function add (a, b, x) {
a = a || 0;
b = b || 0;
c = c || 0;
return a + b + c;
}
console.log(add(1, 2, 3)); // => 6
console.log(add(1, 2)); // => 3
console.log(add(1)); // => 1
console.log(add()); // => 0
매개변수 기본값으로 함수 내 수행 인수 체크 및 초기화 간소화
(매개변수에 인수를 전달하지 않은 경우, undefined를 전달한 경우에만 유효)
function add(a = 0, b = 0, c = 0) {
return a + b + c;
}
console.log(add(1, 2, 3)); // => 6
console.log(add(1, 2)); // => 3
console.log(add(1)); // => 1
console.log(add()); // => 0
매개변수의 최대 개수
매개변수의 순서 중요
매개변수의 개수는 적을 수록 좋음
이상적인 함수는 한가지 일만 해야 하며 가급적 적게 해야함.
최대 3개 이상을 넘지 않는 것 권장
다수의 매개변수가 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것이 유리
객체를 인수로 사용하는 경우 정확한 프로퍼티 키가 지정된다면 매개변수의 순서를 신경쓰지 않아도 됌
그러나 부수효과 주의
부수 효과 : 함수 외부에서 함수 내부로 전달한 객체를 함수 내부에서 변경하면 함수 외부의 객체가 변경 됌
반환문
함수는 return 키워드와 표현식(반환값)으로 이루어지 반환문을 사용하여 실행 결과를 함수 외부로 반환 가능
// multiply 함수 : 두 개의 인수를 전달 받아 곱한 값을 return 을 통해 반환
function multiply(x, y) {
return x * y; // 반환문 = 함수 호출 표현식
}
// 함수 호출은 반환값으로 평가
var rewult = multiply(3, 5);
console.log(result); // => 15
반환문의 역할
1. 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나감
- 반환문 이후에 다른 문이 존재하면 그 문은 실행되지 않고 무시
function multiply(x, y) {
return x * y; // 반환문
// 반환문 이후에 다른 문이 존재한다면 그 문은 실행되지 않고 무시
console.log('실행되지 않는다');
}
console.log(multiply(3, 5)); // => 15
2. 반환문은 return 키워드 뒤에 오는 표현식을 평가하여 반환
- return 키워드 뒤에 반환값으로 사용할 표현식을 명시적으로 지정하지 않으면 undefined 반환
function foo () {
return;
}
console.log(foo()); // => undefined
반환문은 생략 가능
함수는 함수 몸체의 마지막 문까지 실행한 후 암묵적으로 undefined 반환
function foo () {
}
console.log(foo()); // => undefined
return 키워드와 반환 값으로 사용할 표현식 사이 줄바꿈이 있으면
-> 세미콜론 자동 삽입 기능이 작동하여 의도치 않은 결과 발생할 수도
function multiply(x, y) {
return // 세미콜론 추가!
x * y; // 무시
}
console.log(multiply(3, 5)); // => undefined
반환문은 함수 몸체 내부에서만 사용 가능
(전역에서 반환문 사용시 문법 에러 발생)
12. 6 참조에 의한 전달과 외부 상태의 변경
- 원시 값 - 값에 의한 전달
- 객체 - 참조에 의한 전달
- 매개변수 - 타입에 따라 다름
- 값에 의한 호출
- 참조에 의한 호출
// 매개변수 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);
--------------------------------------------------------
// 원시 값은 원본 훼손 X
console.log(num); // => 100
// 객체는 원본 훼손
console.log(person); // => {name: "Kim"}
- changeVal 함수 : 매개변수를 통해 전달받은 원시 타입 인수와 객체 타입 인수를 함수 몸체에서 변경
- 매개변수 primitive : 원시 타입 인수 전달 받음, 할당된 원시 값을 새로운 원시 값으로 교체
- 매개변수 obj : 객체 타입 인수 전달 받음, 재할당 없이 직접 객체 변경
따라서 가독성을 해치는 객체의 변경을 추적하려면
- 옵저버 패턴 등을 통해 객체를 참조하는 이들에게 변경 사실 통지 및 대응
- 객체를 불변객체로 만들어 사용하여 객체의 방어적 복사(깊은 복사)를 통해 새로운 객체 생성 및 재할당 교체
- 순수 함수 : 외부 상태를 변경하지도 않고 외부 상태에 의존하지도 않는 함수
- 함수형 프로그래밍 : 순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 줄이고 안정성을 높임
12. 7 다양한 함수의 형태
즉시 실행 함수
함수 정의와 동시에 즉히 호출되는 함수
단 한번만 호출되며 다시 호출할 수 없다
(function () {
var a = 3;
var b = 5;
return a * b;
}());
익명 함수를 사용하는 것이 일반적이나 기명 즉시 실행 함수도 가능
그룹 연산자 (...) 내의 기명함수는 함수 리터럴로 평가되어 즉시 실행 함수를 다시 호출 할 수 없음
(function foo() {
var a = 3;
var b = 5;
return a * b;
}());
foo(); // => ReferenceError: foo is not defined
즉시 실행 함수는 반드시 그룹 연산자 (...)로 감싸야 에러가 안남
함수 선언문은 함수 이름을 생략 할 수 없음
function () { // => SyntaxError: Function statements require a function name
// ...
}());
기명 함수를 정의하여 그룹 연산자 없이 즉시 호출
세미콜론이 자동 삽입되어 에러
-> 그룹 연산자가 함수 호출 연산자가 아니라 그룹 연산자로 해석됌
function foo() {
// ...
}()); // => SyntaxError: Unexpected token ')'
기명 / 무명 함수를 그룹 연산자로 감싸 함수 리터럴로 평가 => 함수 객체
console.log(typeof (function f(){})); // => function
console.log(typeof (function (){})); // => function
즉 그룹 연산자로 함수를 묶은 이유는 먼저 함수 리터럴을 평가해서 함수 객체 생성하기 위함
따라서 함수 리터럴을 평가해서 함수 객체를 생성할 수 있다면 그룹 연산자 이외도 가능
(function () {
// ...
}());
(function () {
// ...
})();
!function () {
// ...
}();
+function () {
// ...
}();
즉시 실행 함수도 일반 함수처럼 값 반환 가능, 인수 전달 가능
- 즉시 실행함수 내에 코드를 모아두면 변수, 함수 이름의 충돌 방지 가능
// 값 반환
var res = (function () {
var a = 3;
var b = 5;
return a * b
}());
console.log(res); // => 15
---------------------------------------
// 인수 전달
res = (function (a, b) {
return a * b;
}(3, 5));
console.log(res); // => 15
재귀 함수
- 재귀 호출 : 함수가 자기 자신을 호출
- 재귀 함수 : 재귀 호출을 수행하는 함수
function countdown(n) {
for (var i = n; i >+ 0; i--)
console.log(i);
}
countdown(10); // => 10 9 8 7 6 5 4 3 2 1 0
반복 처리를 (반복문 없이) 사용가능
// 펙토리얼(계승)은 1부터 자기 자신까지 모든 양의 정수의 곱
// n! = 1 * 2 * ... * (n-1) * n
function factorial(n) {
// 탈출 조건: n이 1 이하일 때 재귀 호출을 멈춤
if (n <= 1) return 1;
// 재귀호출
return n * factorial(n-1); // 함수 이름은 함수 몸체 내부에서만 유효
}
console.log(factorial(0)); // => 0! = 1
console.log(factorial(1)); // => 1! = 1
console.log(factorial(2)); // => 2! = 2 * 1 = 2
console.log(factorial(3)); // => 3! = 3 * 2 * 1 = 6
console.log(factorial(4)); // => 4! = 4 * 3 * 2 * 1 = 24
console.log(factorial(5)); // => 5! = 5 * 4 * 3 * 2 * 1 = 120
함수 몸체 내부에서 함수 이름으로 재귀 호출 가능
함수 몸체 외부에서 함수 호출 시 함수 식별자로 해야함, 함수 이름 불가
재귀 함수는 자신을 무한 재귀 호출
따라서 탈출조건을 반드시 만드렁야 함
대부분의 재귀 함수는 for문 while문으로 구현가능
재귀 함수는 한정적 사용이 바람직
// 함수 표현식
var factorial = function foo(n) {
// 탈출 조건: n이 1 이하일 때 재귀 호출을 멈춤
if (n <= 1) return 1;
// 함수를 가리키는 식별자로 자기 자신을 재귀 호출
return n * factorial(n - 1);
// 함수 이름으로 자기 자신을 재귀 호출할 수도 있음
// console.log(factorial === foo); // => true
// return n * foo(n - 1);
};
console.log(factorial(5)); // => 5! = 5 * 4 * 3 * 2 * 1 = 120
-----------------------------------------------------------------------
function factorial(n) {
if (n <= 1) return 1;
var res = n;
while (--n) res *= n;
return res;
}
console.log(factorial(0)); // => 0! = 1
console.log(factorial(1)); // => 1! = 1
console.log(factorial(2)); // => 2! = 2 * 1 = 2
console.log(factorial(3)); // => 3! = 3 * 2 * 1 = 6
console.log(factorial(4)); // => 4! = 4 * 3 * 2 * 1 = 24
console.log(factorial(5)); // => 5! = 5 * 4 * 3 * 2 * 1 = 120
중첩 함수
- 외부 함수 : 중첩 함수 포함 함수
- 중첩 함수 (내부 함수) : 함수 내부에 정의된 함수
- 외부 함수 내부에서만 호출 가능
- 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할
function outer () {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조
console.log(x + y); // => 3
}
inner();
}
outer();
콜백 함수
repeat 함수
매개변수를 통해 전달받은 숫자만큼 반복하며 console.log(i) 호출
repeat 함수의 반복문 내부에서 다른 일을 하고 싶다면 함수를 새로 정의해야 함
// n만큼 어떤 일을 반복한다
function repeazat1(n) {
// i를 출력
for (var i = 0; i < n; i++ )
console.log(i);
}
repeat1(5); // => 0 1 2 3 4
----------------------------------------
// n만큼 어떤 일을 반복한다
function repeat2(n) {
for (var i = 0; i < n; i++) {
// i가 홀수 일때만 출력
if (i % 2) console.log(i);
}
}
repeat2(5); // => 1 3
함수를 합성하는 것으로 함수의 매번 새롭게 정의하지 않아도 됌
함수의 변하지 않는 공통 로직을 정의하고 경우에따라 변경되는 로직은 추상화 하여 함수 외부에서 내부로 전달
// 외부에서 전달받은 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
콜백함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
고차 함수
매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수
매개변수를 통해 함수를 전달 받거나 반환값으로 함수를 반환하는 함수
-> 고차 함수는 콜백 함수를 자신의 일부분으로 합성하며 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정
-> 콜백 함수는 고차 함수에 의해 호출됨. 이때 고차함수는 필요에 따라 콜백 함수에 인수 전달
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
logOdds 함수는 단 한번만 생성
콜백 함수를 익명 함수 리터럴로 정의하면서 고차 함수에 전달
-> 고차 함수가 호출될 때마다 콜백 함수 생성
var logOdds = function (i) {
if (i % 2) console.log(i);
};
// 고차 함수에 함수 참조 전달
repeat(5, logOdds); // 1 3
콜백 함수는 비동기 처리(이벤트 처리, Ajax 통신, 타이머 함수 등)에 활용
// 콜백 함수를 사용한 이벤트 처리
// myButton 버튼을 클릭하면 콜백 함수 실행
document.getElementById('myButton').addEventLIstener('click',function () {
console.log('button clicked!');
});
// 콜백 함수를 사용한 비동기 처리
// 1초 후에 메시지를 출력
setTimeout(function () {
console.log('1초 경과')
}, 1000);
콜백 함수는 배열 고차 함수 사용 가능
// 콜백 함수를 사용하는 고차 함수 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
순수 함수와 비순수 함수
순수 함수 | 비순수 함수 |
외부 상태에 의존 및 변경하지 않음 | 외부 상태에 의존 및 변경 |
부수 효과가 없는 함수 | 부수 효과가 있는 함수 |
순수 함수의 특징
- 동일한 인수가 전달되면 항상 동일한 값 반환
- 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존하여 값을 생성, 반환
- 외부 상태( 전역 변수, 서버 데이터, 파일, Consloe, DOM 등)에 의존하지 않음
-> 함수 내부 상태에만 의존해도 그 내부 상태가 호출 될때마다 값이 변화한다면 순수 함수가 아님
- 최소 하나 이상의 인수를 전달
-> 인수를 전달 받지 않는 함수는 항상 동일한 값을 반환하므로 상수와 같기 때문
- 인수의 불변성 유지
- 함수의 외부 상태를 변경하지 않음
var count = 0; // 현재 카운트를 나타내는 상태
function increase(n) {
return ++n;
}
// 순수 함수가 반환한 결과 값을 변수에 재할당해서 상태 변경
count = increase(count);
console.log(count); // => 1
count = increase(count);
console.log(count); // => 2
비순수 함수의 특징
- 부수효과 : 함수의 외부 상태를 변경
- 함수 내부에서 외부 상태를 직접 참조하지 않더라도 매개변수를 통해 객체를 전달받음
var count = 0; // 현재 카운트를 나타내는 상태
// 비순수 함수
function increase() {
return ++count; // 외부 상태에 읜존하며 외부 상태 변경
}
// 비순수 함수는 외부 상태를 변경하므로 상태 변화를 추적하기 어려움
increase();
console.log(count); // => 1
increase();
console.log(count); // => 2
결론
함수 외부 상태의 변경을 지양하는 순수 함수를 사용하는 것이 좋음
-> 상태 변화 추적 용이
- 함수형 프로그램
- 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임
- 변수 사용 억제, 생명주기 최소화로 상태 변경을 피해 오류 감소
- 자바스크립트는 멀티 패러다임언어로 객체지향 프로그래밍과 함수형 프로그래밍을 적극적 활용함.
- 순수 함수를 통해 부수 효과를 최대한 억제하여 안정성을 높이려고함
- 로직 내 조건문과 반복문 제거로 복잡성 해결