- 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것.
- 입력을 전달받는 변수를 매개변수, 입력을 인수, 출력을 반환값이라 한다.
- 함수가 여러 개 존재할 수 있으므로 식별자인 함수 이름을 사용할 수 있다.
- 함수는 함수 정의를 통해 생성한다.
- 인수를 매개변수를 통해 전달하여 함수의 실행을 명시적으로 지시해야 한다. 이를 함수 호출이라 한다.
var multi = function(x,y) {
return x * y;
}; //함수 리터럴을 변수 multi에 할당
<함수 리터럴의 구성 요소>
구성 요소 설명 함수 이름 - 함수 이름은 식별자다. 따라서 식별자 네이밍 규칙을 준수해야 한다.
- 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다.
- 함수 이름은 생략 가능하다. 이름이 있는 함수를(기명 함수), 없는 함수를(무명 함수/익명 함수)라 한다.매개변수 목록 - 0개 이상의 소괄호로 감싸고 쉼표로 구분한다.
- 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당된다. 즉 매개변수 목록은 순서에 의미가 있다.
- 매개변수는 함수 내에서 변수로 취급된다. 따라서 네이밍 규칙을 따라야 한다.함수 몸체 - 함수가 호출되었을 때 일괄적으로 실행할 문들을 하나의 실행 단위로 정의한 코드 블록이다.
- 함수 몸체는 함수 호출에 의해 실행된다.
- 함수 정의란 매개변수와 실행할 문들, 반환값을 지정하는 것을 말한다.
- 함수를 정의하는 방식은 4가지가 있다.
- 함수 선언문
function sum(x,y) { return x + y; }
- 함수 표현식
var sum = function(x,y) { return x + y; };
- Function 생성자 함수
var sum = new Function('x', 'y', return x + y');
- 화살표 함수
var sum = (x, y) => x + y;
function (x, y) {
return x + y;
} // SyntaxError
function foo() { console.log('foo'); }
//기명 함수 리터럴을 단독으로 사용했기 때문에 함수 선언문으로 해석됨.
(function tmp() { console.log('tmp'); })
//피연산자로 사용했기 때문에 함수 리터럴 표현식으로 해석된다.
//-> 피연산자는 *값*으로 평가될 수 있는 표현식이어야 한다.
//함수 선언식은 *문*이기 때문에 피연산자로 사용할 수 없다.
foo() -> ⭕
tmp() -> ❌
-> WHY?❗❗ tmp를 호출할 수 없는 이유
- 함수 리터럴에서, 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다 라고 했다. 이는 외부에서 함수 이름으로 함수를 참조할 수 없으므로 호출할 수 없다는 것을 의미한다.
❓❓ foo는 호출할 수 있는 이유
- 함수 선언문으로 정의된 함수 또한 식별자를 선언한 적도 없고 할당한 적도 없다. 그런데 어떻게 실행되는 걸까?
-> JS엔진이 암묵적으로 생성한 식별자이다.- JS엔진은 함수 선언문을 해석해 함수 객체를 생성한다. 이때 함수 객체를 참조할 수 있는 식별자가 필요하다.
- 따라서 JS엔진은 암묵적으로 함수 이름과 동일한 이름의 식별자를 생성하고, 함수 객체를 할당한다.
- 함수는 함수 이름이 아닌 함수 식별자로 호출한다.
var sum = function(x, y) {
return x + y;
};
var sum = function sumNumbers(x,y) {
return x + y;
};
sumNumbers(2,3) -> ❌ReferenceError
var res = square(5); //문제 없음
//선언문
function square(number) {
return number * number;
}
var res = square(5); // TypeError: square is not a function
//표현식
var square = function(number) {
return number * number;
}
var sum = (x, y) => x + y;
var sum = new Function('x', 'y', return x + y);
- 함수는 함수를 가리키는 식별자와 한 쌍의 소괄호인 함수 호출 연산자로 호출한다. 함수 호출 연산자 내에는 0개 이상의 인수를 쉼표로 구분해서 나열한다.
- 함수를 호출하면 현재의 흐름을 중단하고 호출된 함수로 실행 흐름을 옮긴다.
- 이때 매개변수에 인수가 순서대로 할당되고 함수 몸체의 문들이 실행되기 시작한다.
- 함수의 작업 실행을 위해 외부에서 함수 내부로 값을 전달할 필요가 있는 경우, 매개변수를 통해 인수를 전달한다.
- 인수는 값으로 평가될 수 있는 표현식이어야 한다.
var sum = function(x,y) { return x + y; }; //인수 1과 2가 매개변수 x, y에 순서대로 할당되고 함수 몸체의 문들이 실행된다. var result = sum(1, 2);
- 매개변수는 함수를 정의할 때 선언하며, 함수 몸체 내부에서 변수와 동일하게 취급된다.
- 매개변수도 식별자이다.
- 함수 몸체 외부에서 참조할 수 없다. -> 매개변수의 scope는 함수 내부이다.
- 함수가 호출되면 함수 몸체 내에서 undefined로 초기화된 이후 인수가 순서대로 할당된다.
- 매개변수의 개수와 인수의 개수가 달라도 에러를 발생시키지 않는다. -> 인수가 부족해서 할당되지 않은 값은 undefined다.
var sum = function(x,y) { return x + y; }; //매개변수 y에 전달할 인자가 없다. //따라서 2 + undefined와 같으므로 NaN이 반환된다. console.log(sum(2));
- 매개변수의 개수가 초과되면 무시한다. -> 그냥 버려지는 것은 아니고, 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
- JS는 동적 타입 언어이므로 매개변수의 타입을 사전에 지정할 수 없다.
- 인수는 함수를 호출할 때 생성된다.
var sum = function(x,y) { return x + y; }; console.log(sum(2)); //NaN console.log(sum('a', 'b')); //'ab'
- 위 코드의 경우 에러가 발생하지 않음을 확인할 수 있다.
- 따라서 적절한 인수가 전달되었는지 확인할 필요가 있다. -> typeOf, 단축평가
var sum = function(x = 0,y = 0) {
return x + y;
};
console.log(sum()); //기본값으로 인해 0이 반환된다.
// ajax 메소드에 객체를 인수로 전달하는 예
$.ajax ({
method: 'POST',
url: '/user',
data: { id: 1, name: 'Lee' },
cache: false
});
- 호출 표현식은 return 키워드가 반환한 표현식의 평가 결과로 평가된다. -> 반환값으로 평가된다.
- 반환문은 함수 내에서만 사용할 수 있다.
- 반환문은 두 가지 역할을 한다.
- 함수의 실행을 중단하고 함수를 빠져나간다.
- return 키워드 뒤에 오는 표현식을 평가해 반환한다. -> 지정해주지 않으면 undefined 반환
- 반환문은 생략할 수 있다. -> 마지막 문까지 실행한 후 undefined 반환
- return 키워드와 반환값 사이에 줄바꿈이 있으면 안된다.
var sum = function(x = 0,y = 0) { return //;이 자동 추가된다. x + y; //무시된다. }; console.log(sum()); //undefined
- 반환문은 함수 내부에서만 사용할 수 있다. -> 전역에서 사용하면 SyntaxError가 발생한다.
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'Kim';
obj.gender = 'female';
}
var num = 100;
var obj = {
name: 'Lee',
gender: 'male'
};
console.log(num); // 100
console.log(obj); // Object {name: 'Lee', gender: 'male'}
changeVal(num, obj);
console.log(num); // 100 -> 원시 값이므로 값 변경 불가
console.log(obj); // Object {name: 'Kim', gender: 'female'} -> 객체이므로 값 변경됨
// 기명 즉시 실행 함수(named immediately-invoked function expression)
(function myFunction() {
var a = 3;
var b = 5;
return a * b;
})(); ->괄호 밖에 빼주는 게 좋음.
// 익명 즉시 실행 함수(immediately-invoked function expression)
(function () {
var a = 3;
var b = 5;
return a * b;
})();
// 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있다.
res = (function (a, b) {
return a * b;
})(3, 5); //15
function fibonacci(n) {
if (n < 2) return n; //탈출 조건
//함수 내부에서의 fibobacci는 함수 이름이다.
//외부에서 호출할 때는 반드시 식별자로 호출해야 한다.
return fibonacci(n - 1) + fibonacci(n - 2);
}
- 함수 내부에 정의된 함수를 중첩 함수(nested function)또는 내부함수(Inner function) 라 한다.
- 일반적으로 중첩 함수는 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할을 한다.
- 중첩 함수를 포함하는 함수를 외부 함수 라 부른다.
- 중첩 함수는 외부 함수 내에서만 호출할 수 있다. (스코프 때문)
- 내부 함수는 외부 함수의 변수에 접근할 수 있다.
- 하지만 외부 함수는 내부 함수의 변수에 접근할 수 없다.
function parent(param) { var parentVar = param; //중첩 함수 function child() { var childVar = 'lee'; console.log(parentVar + ' ' + childVar); // Hello lee -> 외부 함수의 변수를 참조할 수 있다. } child(); console.log(parentVar + ' ' + childVar); // Uncaught ReferenceError: childVar is not defined -> 중첩 함수 변수 참조 불가❌ } parent('Hello');
- ES6부터 함수 정의는 문이 위치할 수 있는 문맥 어디든 가능하다. -> BUT 호이스팅으로 인해 혼란이 발생할 수도 있으므로 if문이나 for문 내부에 정의하는 것은 바람직하지 않다.
- 매개변수를 통해 다른 함수의 내부로 전달되는 함수.
- 콜백 함수를 전달받은 함수를 고차 함수라 한다.
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); //반복 호출할 함수를 인수로 전달한다.
- 고차 함수는 콜백 함수를 자신의 일부분으로 합성한다. -> 콜백 함수는 외부에서 주입되기 때문에 조금 더 자유롭게 교체가 가능하다.
- 고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출한다. -> 콜백 함수는 고차 함수에 의해 호출된다.(반드시 그렇지는 않다.)
- 고차 함수는 콜백 함수에 인수를 전달할 수 있다. -> 콜백 함수 자체를 고차 함수에 전달해야 한다.
repeat(5, function(i) {
if(i % 2) console.log(i);
}); //1 3
//logAll 함수는 단 한 번만 생성된다.
var logAll = function(i) {
console.log(i)
}
//고차 함수에 함수 참조를 전달한다.
repeat(5, logAll);
//버튼을 클릭하면 콜백 함수 실행
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log('button clicked!');
});
//1초 후에 메세지 출력
setTimeout(function () {
console.log('1초 후 출력된다.');
}, 1000);
- 부수 효과가 없는 함수
var cnt = 0;
function increase(n) {
return ++n;
}
cnt = increase(cnt); //결과값을 재할당해서 상태 변경
console.log(cnt) //1
- 외부 상태를 변경하는 부수 효과가 있는 함수
var cnt = 0;
function increase() {
return ++cnt; //외부 상태 변경
}
// 외부 상태(cnt)를 변경하므로 상태 변화 추적 어려움.
increase();
console.log(cnt) //1
📌 함수가 외부 상태를 변경하게 되면 상태 변화 추적이 어려워진다. -> 순수 함수를 지향하자.
📌 함수형 프로그래밍은 부수 효과를 최소화해서 불변성과 안정성을 지향하는 프로그래밍 패러다임이다.
<모던 자바스크립트 deepdive와, 추가 탐구한 내용을 정리한 포스트입니다.>