함수란?
- 일련의 과정을 **문**으로 구현하고 **코드 블록**으로 감싸서 **하나의 실행 단위**로 정의한 것
- 입력을 전달받는 변수 : 매개변수
- 입력 : 인수
- 출력 : 반환값
- 함수는 함수 정의를 통해 생성됨
- 정의만으로 실행되는 것은 아님 → **함수 호출**을 해야됨
함수를 사용하는 이유
- **재사용** 가능
- 함수 재사용 시 유지보수의 편의성을 높이고 코드의 신뢰성을 높임
- 함수는 객체 타입의 값이라 이름(식별자)을 붙일 수 있음 ⇒ 코드의 가독성 형상
함수 리터럴
- 함수는 객체 타입의 값이라 함수 리터럴을 통해 생성 가능
- 함수 리터럴 : **function** 키워드, **함수 이름**, **매개변수** 목록, **함수** 몸체로 구성
- 함수 이름
- 식별자 (식별자 네이밍 규칙에 준수)
- 이름 생략 가능 (기명 함수/익명, 무명 함수로 나뉨)
- 매개변수 목록
- 소괄호로 감싸고 쉼표로 구분
- 함수 호출 시 지정한 인수가 **순서대로** 할당됨
- 식별자 네이밍 규칙 준수
- 함수 몸체
- 함수 호출 시 일괄적으로 실행될 문들을 **하나의 실행 단위**로 정의한 코드 블록
- 함수 몸체는 함수 호출에 의해 실행됨
- 함수 이름
- 함수는 객체지만 일반 객체와 다른 점 : 호출 가능하고, 함수 객체만의 고유한 프로퍼티를 갖고 있음
함수 정의
- 함수 정의 : 함수 호출 전 **매개변수**와 **실행할 문**들, **반환 값**을 지정하는 것
- 함수를 정의하는 방법
- 함수 선언문 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');
- 화살표 함수 (ES6) var add = (x,y) => x+y;
함수 선언문
- 함수 선언문은 함수 리터럴과 형태가 동일 but **함수 이름 생략 X** (함수 리터럴은 함수 이름 생략 가능)
- SyntaxError 발생 function (x,y) { return x+y; }
- 함수 선언문은 표현식이 아닌 문 ⇒ 함수 실행 시 크롬 콘솔에 완료 값 undefined
- 함수 선언문은 표현식이 아닌 문이라 변수에 할당 불가능 but 코드 문맥에 따라 기명 함수 리터럴이 함수 선언문이나 함수 리터럴 표현식으로 해석될 수 있음 ⇒ 함수 선언문이 변수에 할당되는 것처럼 보이기도 함
- 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성 후 함수 객체 할당
- 함수는 함수 이름으로 호출하는 것이 아니라 **함수 객체를 가리키는 식별자로 호출** ⇒ 자바스크립트 엔진이 암묵적으로 생성한 식별자를 호출하는 것
⇒ 함수 선언문 : 표현식이 아닌 문 (변수에 할당 불가)
함수 표현식
- 자바스크립트 함수는 일급 객체 ⇒ 함수를 **값처럼 자유롭게 사용** 가능
- 일급 객체 : 값처럼 변수에 할당할 수 있고, 프로퍼티 값이 될 수 있으며 배열의 요소가 될 수 있는 값의 성질
- 함수 리터럴로 생성한 **함수 객체를 변수에 할당** 가능 ⇒ 이러한 함수 정의 방식 : **함수 표현식**
// 함수 리터럴은 이름 생략 가능하여 아래같은 함수를 익명 함수라 함
var add = function(x, y) {
return x+y;
}
- 함수 표현식의 함수 리터럴은 함수 이름을 생략하는 것이 일반적
- **기명 함수**를 사용해도 함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 **함수 이름으로 호출 X** (함수 객체를 가리키는 **식별자를 사용해 호출**해야 됨)
var add = function foo(x, y) {
return x+y;
};
console.log(add(2,5)) // 7
console.log(foo(2,5)) // ReferenceError (함수 이름은 함수 내부에서만 호출 가능하여 참조 에러)
⇒ 함수 표현식 : 표현식인 문 (변수에 할당 가능)
함수 생성 시점과 함수 호이스팅
- **함수 선언문**으로 정의한 함수는 함수 선언문 **이전에 호출 가능**
- **함수 표현식**으로 정의한 함수는 함수 표현식 **이전에 호출 X**
⇒ 함수 선언문과 표현식의 생성 시점이 다름
- 함수 선언문이 코드 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징 : 함수 호이스팅
- 변수 호이스팅은 undefined가 할당되어 변수 선언 전에 참조하면 undefined를 출력하지만, 함수 호이스팅은 함수 객체로 초기화되어 함수 선언 이전에 호출해도 사용 가능
// 함수 참조
console.dir(add); // f add(x,y)
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError
// 함수 선언문
function add(x, y) {
return x+y;
};
// 함수 표현식
var sub = function(x, y) {
return x-y;
};
- 함수 표현식으로 함수 정의 시 함수 호이스팅이 아닌 변수 호이스팅이 발생 ⇒ 함수 표현식 이전에 함수 참조 시 undefined로 평가 (이때 함수 호출하면 TypeError)
Function 생성자 함수
- 자바스크립트가 기본 제공하는 빌트인 함수
- 매개변수 목록과 함수 몸체를 **문자열**로 전달하면서 **new 연산자**와 함께 호출하면 함수 객체를 생성하여 반환 (new 연산자 없어도 결과는 동일)
var add = new Function('x', 'y', 'return x+y');
- Function 생성자 함수로 생성한 함수는 **클로저를 생성하지 X** ⇒ 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작하여 일반적이지 않고 바람직하지 않음
화살표 함수
- ES6에서 도입
- function 키워드 대신 화살표를 사용해 좀 더 간략하게 함수 선언 가능
- **항상 익명 함수**로 정의
const add = (x,y) => x+y;
- 화살표 함수는 생성자 함수로 사용 X
- 기존 함수와 this 바인딩 방식 다름
- prototype 프로퍼티가 없으며 arguments 객체 생성 X
함수 호출
- 함수는 함수를 가리키는 **식별자**와 소괄호인 **함수 호출 연산자**로 호출
- 함수 호출 연산자 내에는 0개 이상의 인수를 쉼표로 구분해서 나열
매개변수와 인수
- 함수 실행 시 필요한 값을 함수 **외부에서 내부로** 전달할 경우, 매개변수(인자)를 통해 인수 전달
- 인수는 값으로 평가될 수 있는 표현식이어야 하며, 함수 호출 시 지정하고 개수와 타입에 제한 X
- 매개변수는 함수 정의 시 선언하며, 함수 몸체 내부에서 **변수와 동일하게 취급**
- 함수 호출 시 함수 몸체 내에서 암묵적으로 매개변수가 생성되고 undefined로 초기화된 이후 인수가 순서대로 할당됨
- 매개변수의 스코프는 함수 내부여서 함수 외부에서 사용 X
- 함수는 매개변수 개수와 인수의 개수가 일치하는지 체크 X ⇒ 인수가 부족해 할당되지 않은 매개변수는 undefined가 되어 에러 발생 X
- 만약 매개변수보다 인수가 더 많을 경우 초과된 인수는 무시됨 (arguments 객체의 프로퍼티로 보관됨)
인수 확인
- 자바스크립트는 함수의 매개변수와 인수의 개수가 일치하는지 확인 X
- 자바스크립트는 동적 타입 언어이므로, 자바스크립트 함수는 매개변수 타입을 사전에 지정할 수 X
⇒ 함수 정의 시 적절한 인수가 전달되었는지 확인할 필요가 있음
- 인수가 전달되지 않은 경우 **단축 평가**를 통해 매개변수에 기본값 할당 가능
function add(a, b, c) {
a = a || 0;
b = b || 0;
c = c || 0;
return a+b+c;
}
- ES6의 경우 **매개변수 기본값**을 사용하면 함수 내에서 인수 체크 및 초기화를 간소화할 수 있음
- 매개변수 기본값은 매개변수에 인수를 전달하지 않았을 경우와 undefined를 전달할 경우에만 유효
function add(a=0, b=0, c=0) {
return a+b+c;
}
매개변수의 최대 개수
- 매개변수의 최대 개수는 정해져있지 않지만 **매개변수의 순서에는 의미가 있어** 개수가 많아진다면 그만큼 **유지보수성**이 나빠짐
- 매개변수는 최대 3개 이상을 넘지 않는 것을 권장
- 그 이상의 매개변수가 필요하다면 **객체를 인수로 전달**
- 객체를 인수로 전달 시 프로퍼티 키만 정확히 지정하면 **매개변수 순서 신경 X**
- but 함수 외부에서 내부로 전달한 객체를 함수 내부에서 변경하면 함수 외부 객체가 변경되는 **부수 효과 (side effect) 발생**
- 그 이상의 매개변수가 필요하다면 **객체를 인수로 전달**
반환문
- **return 키워드**와 **반환값**으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환 가능
function mul(x, y) {
return x*y; // 반환문
}
// 함수 호출은 반환값으로 평가
const result = mul(3, 5)
⇒ 함수 호출은 표현식임
- 반환문 역할
- 함수의 실행을 중단하고 함수 몸체를 빠져나감
- return 키워드 뒤에 오는 표현식을 평가해 반환 (명시적으로 지정하지 않으면 undefined 반환 ⇒ 즉, 생략 가능)
- 반환문은 함수 몸체 내부에서만 사용 가능
참조에 의한 전달과 외부 상태의 변경
- 원시 값은 값에 의한 전달 / 객체 값은 참조에 의한 전달 방식으로 동작함
- 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급하므로 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식을 따름 ⇒ 값에 의한 호출, 참조에의한 호출
function changeVal(primitive, obj) {
primitive += 10;
obj.name = 'Kim';
}
var num = 100;
var person = {name: 'Lee'}
console.log(num) // 100
console.log(person) // {name: 'Lee'}
// primitive는 원시 타입이므로 변경 X (재할당 필요)
// obj는 객체 타입이므로 재할당 없이 직접 변경 가능
changeVal(num, person)
console.log(num) // 100 원본 훼손 X
console.log(person) // {name: 'Kim'} 원본 훼손
- **객체 타입** 인수는 **참조 값이 복사되어 매개변수에 전달**되므로 함수 몸체에서 참조 값을 통해 객체 변경할 경우 **원본 훼손** ⇒ 함수 외부에서 함수 몸체 내부로 전달한 참조 값에 의해 원본 객체가 변경되는 부수 효과 발생
- 객체를 **불변 객체**로 만들어 사용하면 부수 효과 발생 X
- **객체의 복사본을 생성**하여 객체를 마치 원시 값처럼 변경 불가능한 값으로 동작하게 함 ⇒ **깊은 복사**를 통해 새로운 객체를 생성하고 재할당을 통해 교체
다양한 함수의 형태
즉시 실행 함수
- 함수 정의와 동시에 즉시 호출되는 함수
- 단 한 번만 호출되며 다시 호출 불가능
// 익명 즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a*b;
}());
- 즉시 실행 함수는 익명 함수가 일반적
- 그룹 연산자 (…) 내의 기명 함수는 함수 리터럴로 평가되며 함수 이름은 함수 몸체에서만 참조할 수 있는 식별자이므로 다시 호출 불가능
- 즉시 실행 함수는 반드시 그룹 연산자 (…)로 감싸야 함
- 그룹 연산자로 감싸지 않고 아래처럼 작성하면 자바스크립트 엔진이 중괄호 뒤에 ‘;’을 자동으로 추가해 에러 발생
function foo() { var a = 3; var b = 5; return a*b; }();- 함수 선언문 뒤의 ()는 함수 호출 연산자가 아닌 그룹 연산자로 해석되고, 그룹 연산자에 피연산자가 없으므로 에러 발생하는 것
⇒ 그룹 연산자로 묶는 이유 : 먼저 함수 리터럴을 평가하여 함수 객체를 생성하기 위해
재귀 함수
- 재귀 호출 : 함수가 자기 자신을 호출하는 것
- 재귀 함수 : 자기 자신을 호출하는 행위 (재귀 호출)을 하는 함수
- 재귀 함수는 반복 처리를 위해 사용
- 함수 이름은 함수 몸체 내부에서 유효하므로 함수 내부에서 함수 이름을 사용해 자기 자신 호출 가능
function countdown(n) { if (n<0) return; console.log(n) countdown(n-1); // 재귀 호출 } countdown(10) - 재귀 함수는 자신을 무한 재귀 호출함 ⇒ 탈출 조건 필요
- 재귀 함수는 반복되는 처리를 **반복문 없이 구현**할 수 있다는 **장점**이 있지만, **무한 반복**에 빠질 **위험**이 있음
중첩 함수
- 중첩 함수 (내부 함수) : 함수 내부에 정의된 함수
- 자신을 포함하는 외부 함수를 돕는 **헬퍼 함수 역할**
- 외부 함수 : 중첩 함수를 포함하는 함수
- 중첩 함수는 외부 함수 내부에서만 호출 가능
콜백 함수
- 콜백 함수 : 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
- 고차 함수 : 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수
⇒ 고차 함수는 콜백 함수를 자신의 일부분으로 합성
- 중첩 함수가 외부 함수 내부에서 할퍼 함수 역할을 하는 것처럼, 콜백 함수도 고차 함수 내부에서 헬퍼 함수 역할을 함 but **콜백 함수는 자유롭게 교체**할 수 있다는 장점이 있음
- 함수들이 반복하는 일은 변하지 않아 공통적으로 수행해야 하지만, 하는 일의 내용은 다를 때 공통 로직 미리 정의해두고 **변경 로직은 추상화**하여 함수 외부에서 내부로 전달
function repeat(n, f) {
for (var i=0; i<n; i++) {
f(i); // 콜백 함수
}
}
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
- 콜백 함수는 고차 함수에 의해 호출되며, 이때 콜백 함수에 인수를 전달할 수 있음 ⇒ 고차 함수에 **콜백 함수** 전달할 때 함수를 호출하지 않고 **함수 자체를 전달**해야 됨
- 콜백 함수는 비동기 처리 뿐만 아니라 배열 고차 함수에서도 사용 (ex. map, filter, reduce,..)
순수 함수와 비순수 함수
- **순수 함수** : 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, **부수 효과가 없는 함수**
- 동일한 인수가 전달되면 언제나 동일한 값 반환
- 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값 생성해 반환
- **최소 하나 이상의 인수를 전달**받음 (인수가 없다면 언제나 동일한 값을 반환하므로 상수와 마찬가지)
- 인수의 불변성 유지
var count = 0; // 순수 함수 function increase(n) { return ++n; } count = increase(count); // 1 count = increase(count); // 2 - **비순수 함수** : 외부 상태에 의존하거나 외부 상태 변경하는, **부수 효과가 있는 함수**
- 외부 상태에 따라 반환값 달라짐
- 외부 상태 : 전역 변수, 서버 데이터, 파일, Console, DOM,…
- 상태 변화 추적 어려움
var count = 0; // 비순수 함수 function increase() { return ++count; } // 외부 상태(count)를 변경하므로 상태 변화 추적이 어려워짐 increase(); // 1 increase(); // 2
'Web > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
| [모던 자바스크립트 Deep Dive] 14. 전역 변수의 문제점 (0) | 2023.08.30 |
|---|---|
| [모던 자바스크립트 Deep Dive] 13. 스코프 (0) | 2023.08.29 |
| [모던 자바스크립트 Deep Dive] 11. 원시 값과 객체의 비교 (0) | 2023.08.28 |
| [모던 자바스크립트 Deep Dive] 10. 객체 리터럴 (1) | 2023.08.28 |
| [모던 자바스크립트 Deep Dive] 09. 타입 변환과 단축 평가 (0) | 2023.08.14 |