⛅ 프로그래밍 언어의 함수란?
- 일련을 과정을 문으로 구현하고 코드블록으로 감싸서 하나의 실행단위로 정의한 것
- 용어
- 매개변수: 함수 내부로 입력을 전달 받는 변수
- 인수: 입력
- 반환값: 출력
- 함수의 실행은 함수의 정의와 함수의 호출을 통해 이루어진다.
- 자바스크립트의 함수는 객체 타입의 값이다.
⛅ 함수를 사용하는 이유
- 함수는 코드의 재사용이라는 측면에서 매우 유용함
- 유지보수의 편의성 증가
- 코드의 신뢰성 증가
- 코드의 가독성 향상
⛅ 함수 리터럴
- 함수 리터럴의 구성요소
- 함수 이름
- 매개변수 목록
- 함수 몸체
- 함수는 객체지만 일반 객체와는 다르다.(호출 가능, 고유한 프로퍼티를 가짐)
⛅ 함수 정의
- 정의 방식
- 함수 선언문:
function add(x,y){...}
- 함수 표현식:
var add = function (x,y){...}
- Function 생성자 함수:
var add = new Function(...);
- 화살표 함수:
var add = (x,y) => x+y;
- 함수 선언문:
❓함수는 선언이 아닌 정의인 이유❓
함수 선언문이 평가되면 식별자가 암묵적으로 생성되기 때문!
function add(){...} 라는 선언문은 add라는 식별자가 암묵적으로 생성되며, add 라는 식별자는 함수 객체의 참조 값을 가짐!!
함수 선언문
- 함수 선언문은 함수 리터럴과 형태가 동일하다.
- 하지만 함수 리터럴은 함수이름 생략 O, 함수 선언은 함수이름 생략 X
- 함수 선언문은 표현식이 아닌 문이다! => 따라서 크롬 개발자도구에서 선언문을 실행하면 완료값은
undefined
가 출력됨 - 이름이 있는 기명함수의 리터럴은 코드의 문맥에 따라 함수 선언문 또는 함수 리터럴 표현식으로 해석될 수 있다.
- 표현식이 아닌 문
❓표현식이 아닌 문은 변수에 할당할수 없는데 함수는 된다❓
표현식이 아닌 문은 변수에 할당할 수 없다. 하지만 아래 예제는 함수 선언문이 변수에 할당되는 것처럼 보인다.
ex) var add = functon add(x,y) {...};
💡 자바스크립트 엔진은 코드의 문맥에 따라 다르게 평가한다!! 💡
예를들어 { }는 블록문 또는 객체 리터럴일 수 있다. 따라서 { }는 중의적 표현인데, 이처럼 중의적인 코드는 코드의 문맥에 따라 해석이 달라진다!
기명 함수 리터럴도 중의적인 코드이므로 코드의 문맥에 따라 해석이 달라질 수 있다. 위의 예제에서는 함수 리터럴을 변수에 할당하면서 함수 리터럴이 값으로 평가되어야 하는 문맥이므로 자바스크립트 엔진은 함수 리터럴 표현식으로 해석한다.
- 함수 선언문과 함수 리터럴의 내부 동작 차이
- 함수 객체를 생성한다는 점에서는 동일하다.
- 함수 선언문 => 호출 가능
- 함수 리터럴 => 호출 불가능
- 함수 리터럴에서 함수이름은 함수 몸체 내에서만 참조할 수 있는 식별자 이다. 이 말은 즉, 함수 함수를 가리키는 식별자가 없다는 것과 마찬가지임!
- 함수 선언문은 위 "함수는 선언이 아닌 정의인 이유" 에서 설명한 것과 같이 식별자가 암묵적으로 생성되고 함수객체가 런타임 이전에 할당됨
( + 함수는 함수 이름으로 호출하는 것이 아닌 함수 객체를 가리키는 식별자로 호출함!)
함수 표현식
- 함수는 객체타입의 값이다.
- 값의 성질을 갖는 객체를 일급 객체 라고 한다.
- 자바스크립트의 함수는 일급 객체이다! (함수를 값처럼 자유롭게 사용 가능)
- 함수 리터럴의 함수 이름은 생략 가능(익명 함수)
- 표현식인 문
// 함수 표현식 예제
var add = functon foo(x,y){...};
// 함수 객체를 가리키는 식별자로 호출
console.log(add(2,5));
// 함수 이름으로 호출하면 ReferenceError
// 함수 이름은 함수 몸체 내부에서만 유효!
console.log(foo(2,5)); // ReferenceError
함수 생성 시점과 함수 호이스팅
// 아래의 함수 선언문과 함수 표현식은 생성 시점이 다르다!
// 1️⃣ 함수 선언문 (함수 호이스팅 적용)
// 선언문은 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행됨!
// 런타임 이전에 함수 객체가 먼저 생성됨
// 함수 선언문 이전에 함수를 참조 가능, 호출 가능 => 함수 호이스팅
// 함수 선언문 이전에 함수 참조 가능
function add(x,y){...}
// 2️⃣ 함수 표현식 (변수 호이스팅 적용)
// 변수 호이스팅과 동일하게 동작함
// 함수 표현식 이전에 함수를 참조하면 undefined로 평가됨
var sub = function(x,y){...};
🚨 함수 호이스팅 vs 변수 호이스팅 🚨
변수 호이스팅
- 런타임 이전에 식별자를 생성
- 선언된 식별자(변수)를 undefined로 초기화
함수 호이스팅
- 런타임 이전에 식별자를 생성
- 선언된 식별자(변수)를 함수객체로 초기화
🔥 따라서 함수 표현식으로 함수를 정의하면 함수 호이스팅이 아닌 변수 호이스팅이 발생 하는 것이다!! 🔥
Function 생성자 함수
- Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않고, 바람직하지 않음
- 클로저 생성X, 함수 선언문이나 표현식으로 생성한 함수와 다르게 동작함
화살표 함수
function
키워드 대신 화살표(=>
) 를 사용해 좀 더 간략한 방법으로 함수 선언 가능- 항상 익명 함수로 정의해야함
- 내부 동작 또한 기존의 함수보다 간략화 되어있음에 유의!
- ex) this 바인딩 방식, prototype 프로퍼티 X, arguments 객체 X
⛅️ 함수 호출
매개변수와 인수
- 함수의 실행을 위해 함수 외부의 값을 함수 내부로 전달할때, 매개변수를 통해 인수를 전달한다.
- 인수는 값으로 평가될 수 있는 표현식 이어야 함
- 매개변수는 일반 변수와 동일하게 함수가 호출되면 변수 생성 후 undefined로 초기화 된다. 그 다음에 인수가 순서대로 할당 된다.
- 매개변수의 스코프는 함수 내부이다. (함수 몸체 외부에서 참조 불가)
- 인수가 할당되지 않은 매개변수의 값은 undefined 이다.
- 초과된 인수는 무시된다. (암묵적으로 arguments 객체의 프로퍼티로 보관됨)
// 예제 1 => 매개변수의 스코프는 함수 내부
function add(x, y) {
console.log(x, y); // 2,5
}
add(2, 5);
console.log(x, y); // Reference Error
// 예제 2 => 인수가 할당되지 않은 매개변수의 값은 undefined
function add(x, y) {
return x + y; // 2 + undefined
}
console.log(add(2)); // NaN
// 예제 3 => 초과된 인수는 무시되고 arguments 객체의 프로퍼티로 보관
// arguments 객체는 매개변수 개수를 확정할 수 없는 가변 인자 함수를 구현할때 유용함
function add(x, y) {
console.log(arguments); // Arguments(3) [2,5,10,calle:f,Symbol...]
return x + y;
}
add(2, 5, 10);
인수 확인
- 적절한 인수가 전달되었는지 확인하기 위해서 다음과같은 방법을 사용할 수 있음
typeof
연산자를 이용하여 매개변수의 타입 체킹arguments
객체를 통해 인수의 개수 확인- 인수가 전달되지 않은 경우 단축 평가를 사용하여 기본값 할당
- ES6의 매개변수 기본값 사용
function add(x,y){
if(typeof x !== 'number' || typeof y !== 'number')
throw new TypeError('인수는 모두 숫자 값이어야 합니다.');
if(arguments.length !== 2)
throw new TypeError('인수는 2개를 할당해야 합니다.');
return x+y;
}
console.log(add(2)); // TypeError
console.log(add('a','b'); // TypeError
console.log(add(2,3,4); // TypeError
매개변수의 최대 개수
- 매개변수는 순서에 의미가 있음
- 매개변수의 개수가 많다는 것은 함수가 여러가지 일을 한다는 증거
- 이상적인 함수는 한 가지 일만 해야하며 가급적 작게 만들어야 한다!
- 매개변수는 최대 3개 이상을 넘지 않는 것을 권장 => 그 이상은 하나의 매개변수에 객체 형태로 인수를 전달
🚨 매개변수를 객체로 전달할때 주의할점 🚨
함수 내부로 전달한 객체가 함수 내부에서 변경된다면, 함수 외부에서 전달한 객체도 같이 변경된다!
반환문
- 함수 호출은 반환값으로 평가된다.
- 반환문의 역할
- 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나간다. => 반환문 이후에 다른 문이 존재하면 그 문은 실행 X
- 반환문은 return 키워드 뒤에 오는 표현식을 평가해 반환한다. => 반환값이 명시적으로 지정되지 않으면 undefined 반환
- 반환문은 생략 가능 => 마지막 문까지 실행한 뒤 암묵적으로 undefined 반환
function multiple(x, y) {
return x * y;
console.log('실행 X');
}
console.log(multiple(3, 5)); // 15
function foo() {
return;
}
console.log(foo()); // undefined
function foo() {}
console.log(foo()); // undefined
⛅️ 참조에 의한 전달과 외부 상태의 변경
- 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급
- 매개변수 또한 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식이 그대로 적용
- 원시 타입 인수: 값 자체가 복사되어 매개변수에 전달 => 원본 훼손 X
- 객체 타입 인수: 참조 값이 복사되어 매개변수에 전달 => 원본 훼손 O
- 원본이 훼손되지 않게 불변 객체로 만들어 사용하면 의도치 않은 변경을 피할수 있음
- 깊은복사를 이용해 새로운 객체를 생성하고 재할당을 통해 교체함
( + 외부 상태를 변경하지않고 외부 상태에 의존하지도 않는 함수를 순수 함수 라고함!) - 순수 함수를 통해 부수 효과를 최대한 억제하고 프로그램의 안정성을 높이는 프로그래밍 패러다임 => 함수형 프로그래밍
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'KIM';
}
var num = 100;
var persom = { name: 'LEE' };
changeVal(num, person);
console.log(num, person); // 100, {name:'KIM'}
다양한 함수의 형태
즉시 실행 함수
- 함수 정의와 동시에 즉시 호출되는 함수
- 단 한번만 호출, 다시 호출 X
- 보통 익명 함수를 사용함
()
내의 기명 함수는 함수 선언문이 아니라 함수 리터럴로 평가되어 함수 이름은 함수 몸체에서만 사용 가능하기 때문에 다시 호출할 수 없음- 그룹 연산자
()
로 함수를 묶은 이유는 먼저 함수 리터럴을 평가해서 함수 객체를 생성하기 위해서!
// 익명 즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a * b;
})();
// 일반 함수 처럼 값을 반환하는것을 이용하여 변수에 할당
var res = (function () {
var a = 3;
var b = 5;
return a * b;
})();
// 인수 전달 가능
var res = (function (a, b) {
return a * b;
})(3, 5);
재귀 함수
- 함수가 자기 자신을 호출하는 것을 재귀 호출 이라고 함
- 재귀 호출을 수행하는 함수 === 재귀 함수
- 반복되는 처리를 위해 사용
- 재귀 함수는 탈출조건이 무조건 필요함!!
- 반복되는 처리를 반복문 없이 구현할 수 있지만 무한루프에 빠질 위험이 있고, stack overflow 에러를 발생 시킬 수 있으므로 주의해야함
// 10~0 까지 출력하는 함수
function cutdoown(n) {
if (n < 0) return;
console.log(n);
cutdown(n - 1); // 재귀 호출
}
cutdown(10);
// 팩토리얼
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(4)); // 4! = 4 * 3 * 2 * 1 = 24
중첩 함수
- 함수 내부에 정의된 함수 === 중첩 함수(내부 함수)
- 중첩 함수를 포함하는 함수 === 외부 함수
- ES6 부터는 함수 선언이 if문이나 for문 등의 코드 블럭에서도 정의 가능 하지만 호이스팅으로 인해 혼란이 발생할 수 있으므로 지양해야함
- 중첩 함수는 스코프와 클로저에 깊은 관련이 있음
❓스코프란❓
- 스코프는 변수에 접근할 수 있는 범위를 의미
- 스코프의 종류: 지역 스코프(local scope), 전역 스코프(global scope)
❓클로저란❓
- 클로저는 함수와 그 함수가 선언된 렉시컬 환경(Lexical Environment)의 조합
- 함수가 외부 함수의 스코프에 접근할 수 있는 능력으로, 외부 함수가 종료된 후에도 내부 함수는 그 변수를 기억한다.
- 클로저의 특징
- 외부 함수의 변수에 접근가능 => 외부 함수가 종료된 후에도 접근한 변수를 기억
- 외부 함수가 실행을 마친 후에도 해당 스코프의 변수에 접근 가능
function outer() {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조 가능
console.log(x + y); // 3
}
inner();
}
outer();
콜백 함수
- 함수의 변하지 않는 공통 로직은 미리 정의해두고 경우에 따라 로직을 바꿀 수 있음
- 바꿀 수 있는 로직은 추상화하여 함수 외부에서 함수 내부로 전달
- 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수 === 콜백 함수
- 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수 === 고차함수(HOF)
- 배열 메소드도 고차함수다! (
map()
,filter()
,reduce()
) - 🚨 모든 콜백 함수가 고차함수에 의해 출력되는 것은 아님!
ex)setTimeout
💡 고차함수 간단 정리
- 함수를 전달 받거나 리턴값으로 함수를 반환하는 함수
- 콜백 함수를 자신의 일부분으로 합성
- 전달 받은 콜백 함수의 호출 시점을 결정할 수 있음
- 콜백함수는 고차함수에 의해 호출됨
- 필요에 따라 콜백 함수에 인수 전달 가능
- 고차 함수에 콜백 함수를 전달할 때 콜백 함수를 호출하지 않고 함수 자체를 넘겨야 함!
- 콜백 함수가 고차함수 내부에서만 호출될때 콜백함수를 익명함수 리터럴로 정의할 수 있음
// func를 n만큼 호출하는 함수
function repeat(n, func) {
for (var i = 0; i < n; i++) {
func(i);
}
}
// 1. 익명 함수 리터럴을 콜백함수로 고차함수 repeat에 전달
// 콜백 함수는 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성함
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
// 2. 고차함수 repeat에 함수의 참조를 전달
// 여기서는 odd 함수 객체는 한번만 생성됨
// 위의 1번 예제에서는 고차함수가 호출될때마다 콜백 함수가 생성됨
var odd = function (i) {
if (i % 2) console.log(i);
};
repeat(5, odd); // 1 3
순수 함수와 비순수 함수
- 순수 함수: 어떤 외부 상태에 의존하지도 않고 변경하지도 않는 함수 (부수 효과 X)
- 비순수 함수: 외부 상태에 의존하거나 외부 상태를 변경하는 함수 (부수 효과 O)
1️⃣ 순수 함수의 특징
- 동일한 인수가 전달되면 언제나 동일한 값을 반환
- 오직 매개변수를 통해 내부로 전달된 인수에게만 의존해 값을 생성하고 반환함
- 일반적으로 최소 하나 이상의 인수를 전달 받는다.
=> 인수를 전달받지 않는 순수 함수는 언제나 동일한 값을 반환하므로 상수와 같다.
=> 따라서 하나 이상의 인수를 전달받지 않는 순수 함수는 의미가 없음!
- 인수를 변경하지 않는 것이 기본 (인수의 불변성 유지)
- 외부 상태 의존 X, 외부 상태 변경 X
2️⃣ 비순수 함수의 특징
- 외부 상태를 참조하지 않아도 객체를 매개변수로 받으면 비순수 함수가 된다.
- 반환값이 일정하지 않고 외부 상태도 변경할 수 있어 부수효과가 일어난다. (코드의 복잡성 증가)
🔥 함수형 프로그래밍의 지향점
- 순수함수와 보조함수의 조합을 통해 외부 상태를 변경하는 부수효과를 최소화해서 불변성을 지키자
- 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성 해결
- 변수 사용 억제 및 생명주기 최소화 => 상태 변경을 피해 오류를 최소화
📚 REFERENCE
- 모던 자바스크립트 Deep Dive 12장 함수
'FE > Javascript' 카테고리의 다른 글
13. 모던 자바스크립트 Deep Dive(스코프) (1) | 2024.12.08 |
---|---|
11. 모던 자바스크립트 Deep Dive(원시 값과 객체의 비교) (0) | 2024.12.06 |
10. 모던 자바스크립트 Deep Dive (객체 리터럴) (0) | 2024.12.05 |
09. 모던 자바스크립트 Deep Dive (타입 변환과 단축 평가) (0) | 2024.12.03 |
정규식 예제 (2) | 2024.11.20 |