[모던자바스크립트] 12.함수
- -
1.함수란 ?
일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행단위로 정의한 것이다.
함수는 스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 생성, 메서드, this, 프로토타입, 모듈화 등과 관련깊다.
2. 함수를 사용하는 이유
- 코드의 재사용성에 유용
- 유지보수의 편의성을 높인다.
- 코드의 신뢰성을 높인다 (실수를 줄일 수 있기에)
- 코드의 가독성을 향상한다.
3. 함수 리터럴
자바스크립트의 함수는 객체 타입의 값이다. 따라서 함수도 함수 리터럴로 생성할 수 있다.
구성요소 | 설명 |
함수 이름 | - 함수 이름은 식별자다 - 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다. - 함수 이름은 생략할 수 있다. |
매개변수 목록 | - 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분한다. - 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당된다. - 매개변수는 함수 몸체 내에서 변수와 동일하게 취급된다. |
함수 몸체 | - 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 블럭이다. - 함수 몸체는 함수 호출에 의해 실행된다. |
함수는 객체다.
일반 객체는 호출할 수 없지만 함수는 호출할 수 있다. 또한 함수 객체만의 고유한 프로퍼티를 갖는다.
함수가 객체라는 사실은 다른 프로그래밍 언어와 구별되는 자바스크립트의 중요한 특징이다.
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;
4-1. 함수 선언문
함수선언문은 함수 리터럴과 형태가 동일하다.
하지만 함수 리터럴은 함수 이름을 생략할 수 있지만 함수 선언문은 함수 이름을 생략할 수 없다.
함수 선언문은 표현식이 아닌 문이다.
표현식이 아닌 문은 변수에 할당 할 수 없으므로 함수 선언문도 변수에 할당 할 수 없다.
var add = function add(x,y){
return x + y;
}
console.log(add(2,5));
위 예제의 경우 함수선언문이 변수에 할당되는 것 처럼 보인다. 이렇게 동작하는 이유는 다음과 같다
- 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우
- 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우
자바스크립트 엔진은 함수 이름이 있는 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석하고, (1)
함수 리터럴이 값으로 평가되어야 하는 문맥이면 함수리터럴 표현식으로 해석한다(2)
//기명함수 리터럴은 단독으로 사용하면 함수 선언문으로 해석된다.
//함수 선언문에서는 함수 이름을 생략할 수 없다.
function foo(){ console.log("foo"); }
foo() //foo
//함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석된다.
//함수 리터럴에서는 함수 이름을 생략할 수 있다.
(function bar() { console.log('bar');});
bar() // ReferenceError
위 처럼 기명함수 리터럴은 코드 문맥에 따라 함수 선언문 또는 함수 리터럴 표현식으로 해석이 된다.
이는 호출의 차이가 있다.
함수 리터럴에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자 인데, 함수외부에서는 이를 참조 할 수 없기 때문에 bar 함수는 함수 외부에서 함수를 호출 할 수 없다.
함수 선언문의 경우 foo라는 이름으로 함수를 호출 할 수 있었는데, 이는 자바스크립트 엔진이 암묵적으로 foo라는 식별자를 생성한 것이다.
자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다. 즉 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.
결론적으로 자바스크립트 엔진은 함수 선언문을 함수 표현식으로 변환해 함수 객체를 생성한다고 생각할 수 있다.
4-2. 함수 표현식
자바스크립트의 함수는 값의 성질을 갖는 객체, 즉 일급 객체이다. (= 함수를 값처럼 자유롭게 사용할 수 있음)
함수가 일급 객체이기 때문에 함수 리터럴로 생성한 함수 객체를 변수에 할 당 할 수 있는데, 이러한 방식을 함수 표현식이라 한다.
var add = function (x, y) {
return x + y;
}
console.log(add(2,5)) //7
함수 선언문은 "표현식이 아닌 문"이고, 함수 표현식은 "표현식인 문" 이다.
4-3. 함수 생성시점과 함수 호이스팅
함수 선언문
함수 선언문은 선언 이전에 함수를 참조 할 수 있고, 호출도 가능하다 = 함수 호이스팅
함수 표현식
함수 표현식은 변수 선언문과 변수 할당문을 한 번에 기술 축약 표현과 동일하게 동작하는데,
변수 할당문의 값은 런타임에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
함수표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다.
✅ 함수 호이스팅 & 변수 호이스팅
런타임 이전에 자바스크립트 엔진에 의해 먼저 실행되어 식별자를 생성한다는 점에서 동일하나,
var 사용시 -> 변수는 undefined로 초기화,
함수 선언문 -> 함수 객체로 초기화 됨.
4-4. Function 생성자 함수
클로저를 생성하지 않고, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작하기에 위 방법은 일반적이지 않고 바람직하지도 않다.
4-5. 화살표 함수
화살표함수는 항상 익명함수로 정의하며,
이는 기존의 함수보다 표현만 간략한 것이 아니라 내부동작 또한 간략화 되어있다.
화살표함수는 생성자 함수로 사용할 수 없으며, 기존함수들과는 this 바인딩 방식이 다르고, prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않는다.
(화살표 함수는 this를 바인딩하지 않고 상위스코프의 this를 사용한다. ref. 코어자바스크립트-함수를 호출할때 함수 내부에서 this)
5. 함수 호출
5-1. 매개변수와 인수
인수는 값으로 평가될 수 있는 표현식이어야 하며 인수는 함수를 호출할때 지정한다. 개수와 타입에는 제한이 없다.
매개변수는 함수 정의시 선언하며, 함수 몸체 내부에서 변수와 동일하게 취급된다.매개 변수의 스코프는 함수 내부이다.
매개변수의 개수와 인수의 개수가 일치 하지 않아도 된다.
- 인수가 부족하면 할당되지 않는 매개변수의 값은 undefined다.
- 초과 되는 경우 버려지는 것이 아니라, arguments객체의 프로퍼티로 보관된다.
5-2. 인수 확인
- 자바스크립트 함수는 매개변수와 인수의 개수가 일치하지 않는지 확인하지 않는다.
- 자바스크립트는 동적 타입 언어다. 따라서 자바스크립트 함수는 매개변수의 타입을 사전에 지정할 수 없다.
따라서 함수를 정의 할때 적절한 인수가 전달되었는지 확인 할 필요가 있다.
1)typeof 로 확인하기
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: 인수는 모두 숫자 값이어야 합니다.
2) 단축평가를 활용해 기본값 할당하기
function add(a, b, c) {
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
3) ES6에 도입된 매개변수 기본값을 사용하기
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
5-3. 매개변수의 최대 개수
이상적인 매개변수의 개수는 0개 이며, 적을 수록 좋다.
많은 매개변수가 필요하다면 객체를 활용하는 것이 유리하다.
5-4. 반환문
함수 호출은 표현식이다. 함수 호출 표현식은 return 키워드가 반환한 표현식의 평가 결과, 즉 반환값으로 평가된다.
- 함수의 실행을 중단하고 함수 몸체를 빠져나간다.
- return 키워드 뒤에 오는 표현식을 평가해 반환한다. 없다면 undefined가 반환된다.
- 반환문은 생략할 수 있으나, 마지막 문 까지 실행 후 암묵적으로 undefined를 반환한다.
- 반환문은 함수 몸체 내부에서만 사용할 수 있다. (전역에서 사용하면 문법 에러 발생 )
하지만 node.js는 모듈 시스템에 의해 파일별로 독립적인 파일 스코프를 갖기에, node.js 환경에서는 파일의 가장 바깥 영역에 반환문을 사용해도 에러가 발생하지 않는다.
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);
// 원시값은 원본이 훼손되지 않는다.
console.log(num); // 100
// 객체는 원본이 훼손된다.
console.log(person); // {name: "Kim"}
person객체를 매개변수로 전달하면 함수 내부에서 이를 수정할 때 원본 객체가 변경되는 부수효과가 발생한다.
원시값은 값을 복사하여 전달하기에 영향이 없다.
부수효과를 해결하기 위한 방법 중 하나는 객체를 불변 객체로 만들어 사용하는 것이다.
객체를 원시 값처럼 변경 불가능한 값으로 동작하게 만들어 상태변경을 원천 봉쇄하고 변경이 필요한 경우에는 객체의 방어적 복사(깊은 복사)를 통해 새로운 객체를 생성하고 재할당하여 교체한다.
✅순수함수란
외부 상태를 변경하지 않고 외부 상태에 의존하지도 않는 함수를 말한다.
함수가 외부상태를 변경하게 되면 상태변화를 추적하기 어려워지는데, 이는 코드의 복잡성을 증가시키고 가독성을 해치는 원인이 된다.
순수함수를 통해 부수효과를 최대한 억제하여 오류를 피하고 안정성을 높이는 패러다임을 함수형 프로그래밍이라 한다.
7. 다양한 함수의 형태
7-1. 즉시 실행 항수
함수의 정의와 동시에 즉시 호출되는 함수를 말하며, 이는 단 한번만 호출 되며 다시 호출 할 수 없다.
// 익명 즉시 실행 함수
(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 () {
// ...
}());
(function () {
// ...
})();
!function () {
// ...
}();
+function () {
// ...
}();
7-2. 재귀함수
함수가 자기자신을 호출하는 것을 말한다.
// 함수 표현식
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
함수 표현식으로 정의한 함수 내부에서는 함수 이름은 물론 함수를 가리키는 식별자로도 자기 자신을 재귀호출 할 수 있다.
재귀 함수는 자기자신을 무한 재귀 호출하기에 이를 멈출 수 있는 탈출 조건을 반드시 만들어야한다.
탈출 조건이 없다면 함수가 무한 호출 되어 스택 오버플로에러가 발생한다.
7-3. 중첩함수
중첩함수 또는 내부 함수라 한다. 그리고 중첩함수를 포함하는 함수는 외부함수라 한다.
function outer() {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조할 수 있다.
console.log(x + y); // 3
}
inner();
}
outer();
ES6부터 함수 정의는 문이 위치 할 수 있는 문맥이라면 어디든지 가능하다(if, for문 등의 코드블록 내에서도 정의 가능)
단 호이스팅 발생할 수 있으므로 바람직하지 않다.
중첩함수는 스코프와 클로저에 깊은 관련이 있다.
7-4. 콜백 함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백함수 라고 하고,
변수를 통해 함수의 외부에서 콜백 함수를 전달 받은 함수를 고차 함수라고 한다.
고차함수는 콜백함수를 자신의 일부분으로 합성한다.
고차함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출한다.
콜백함수는 고차함수에 의해 호출 되며 이떄 고차함수는 필요에 따라 콜백함수에 인수를 전달 할 수 있다.
익명함수 리터럴로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다.
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
// 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성한다.
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
함수형 프로그래밍 패러다임뿐만 아니라 비동기처리(이벤트, Ajax통신, 타이머함수 등)에 활용되는 중요한 패턴이다.
// 콜백 함수를 사용한 이벤트 처리
// myButton 버튼을 클릭하면 콜백 함수를 실행한다.
document.getElementById('myButton').addEventListener('click', function () {
console.log('button clicked!');
});
// 콜백 함수를 사용한 비동기 처리
// 1초 후에 메시지를 출력한다.
setTimeout(function () {
console.log('1초 경과');
}, 1000);
7-5. 순수함수와 비순수함수
- 순수함수
- 외부상태에 의존하지 않고 변경하지도 않는 부수효과가 없는 함수
- 동일한 인수가 전달되면 언제나 동일한 값을 반환
- 전달된 인수에게만 의존해 반환값을 만든다. - 비순수함수
- 외부 상태에 의존하거나 외부 상태를 변경하는 부수효과가 있는 함수
- 회부상태에 따라 반환값이 달라진다.
순수함수
var count = 0; // 현재 카운트를 나타내는 상태
// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다.
function increase(n) {
return ++n;
}
// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1
count = increase(count);
console.log(count); // 2
비순수함수
var count = 0; // 현재 카운트를 나타내는 상태: increase 함수에 의해 변화한다.
// 비순수 함수
function increase() {
return ++count; // 외부 상태에 의존하며 외부 상태를 변경한다.
}
// 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워진다.
increase();
console.log(count); // 1
increase();
console.log(count); // 2
매개변수를 통해 객체를 전달받으면 비순수함수가 된다.
순수 함수를 사용하는 것이 좋다
'JavaScript' 카테고리의 다른 글
[모던자바스크립트] 14.전역 변수의 문제점 (0) | 2023.05.08 |
---|---|
[모던자바스크립트] 13. 스코프 (0) | 2023.05.08 |
[모던자바스크립트] 11.원시 값과 객체의 비교 (0) | 2023.05.02 |
[모던자바스크립트] 10. 객체 리터럴 (0) | 2023.05.02 |
[모던자바스크립트] 9.타입 변환과 단축 평가 (0) | 2023.05.01 |
소중한 공감 감사합니다