12장 함수
12.1 함수란?(12-01)
- 함수는 일련의 과정을 문(statement)으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
// f(x, y) = x + y
function add(x, y) { // 매개변수(parameter)
return x + y; // 반환값
}
// f(2, 5) = 7
add(2, 5); // 7 // 인수(argument)
12-02
- 다양한 정의 방법중 함수 선언문을 통한 예
// 함수 정의
function add(x, y) {
return x + y;
}
12-03
// 함수 호출
var result = add(2, 5);
// 함수 add에 인수 2, 5를 전달하면서 호출하면 반환값 7을 반환한다.
console.log(result); // 7
12.2 함수를 사용하는 이유
- 코드의 재사용, 유지보수의 편의성, 코드의 신뢰성, 코드의 가독성
12.3 함수 리터럴(12-04)
- 함수도 객체 리터럴처럼 함수 리터럴이 존재
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
12.4 함수 정의
- 함수 정의는 총 4가지 : 함수 선언문(이름생략 불가), 함수 표현식, Function 생성자 함수, 화살표 함수(ES6)
12.4.1 함수 선언문(12-05)
- 함수 선언문
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 참조
// console.dir은 console.log와는 달리 함수 객체의 프로퍼티까지 출력한다.
// 단, Node.js 환경에서는 console.log와 같은 결과가 출력된다.
console.dir(add); // ƒ add(x, y)
// 함수 호출
console.log(add(2, 5)); // 7
12-06
-
함수 선언문은 표현식이 아닌 문이다, 함수 리터럴은 표현식이다
-
단, 함수 리터럴은 함수 이름 생략가능
// 함수 선언문은 함수 이름을 생략할 수 없다.(아마도 암묵적으로 식별자 만들어야 하기 때문)
function (x, y) {
return x + y;
}
// SyntaxError: Function statements require a function name
12-07
- 이것이 함수 리터럴로 평가 되었기 때문에 할당되는 것처럼 보이는것
// 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없다.
// 하지만 함수 선언문이 변수에 할당되는 것처럼 보인다.
var add = function add(x, y) {
return x + y;
};
// 함수 호출
console.log(add(2, 5)); // 7
12-08
-
() 연산자 내부에 피연산자로 사용한것임
-
다만, 기명 함수 리터럴은 암묵적 식별자 생성을 해서 호출 가능했지만 bar()는 호출 불가 그 이유는 식별자가 없기 때문이다 즉, 함수는 함수 객체를 가리키는 식별자로 호출한다!!
// 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
// 함수 선언문에서는 함수 이름을 생략할 수 없다.
function foo() { console.log('foo'); }
foo(); // foo
// 함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석된다.
// 함수 리터럴에서는 함수 이름을 생략할 수 있다.
(function bar() { console.log('bar'); });
bar(); // ReferenceError: bar is not defined
12-09
- 즉, 함수 선언문으로 생성한 함수를 함수이름으로 호출한게 아니라 엔진이 암묵적으로 생성한 함수이름과 동일한 식별자로 호출하는 것!
var add = function add(x, y) {
return x + y;
};
console.log(add(2, 5)); // 7
12.4.2 함수 표현식(12-10)
-
일급 객체란 값의 성질을 갖는 객체를 의미
-
자바스크립트의 함수는 일급 객체다
// 함수 표현식(익명 함수)
var add = function (x, y) {
return x + y;
};
console.log(add(2, 5)); // 7
12-11
-
함수 선언문도 암묵적 식별자를 생성해서 식별자로 접근하니까 함수 표현식과 비슷해 보인다. 그러나 중요한 차이가 있다.
-
함수 선언문은 “표현식이 아닌 문”, 함수 표현식은 “표현식인 문”
// 기명 함수 표현식
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
12.4.3 함수 생성 시점과 함수 호이스팅(12-12)
-
함수 생성 시점이 다르기 때문에 발생한 에러이다
-
함수 선언문은 모든 선언문이 그렇듯 런타임 이전에 엔진에 의해 먼저 실행. 따라서 아래에 있던 함수 선언문 코드가 선두로 올려진 것처럼 동작하는것을 함수 호이스팅라 함
-
함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. 즉, 변수 선언문과 변수 할당문을 한 번에 기술한 축약 표현과 동일하게 동작. 이는 런타임에 평가되고, 함수 호이스팅이 아니라 변수 호이스팅이 발생
-
함수, 변수 호이스팅의 미묘한 차이점은 var키워드로 선언할시 undefined로 엔진이 선언당시 먼저 초기화 하는데, 함수는 식별자를 암묵적으로 선언후 함수 선언문을 바로 객체로 초기화 하는 차이가 있다. 따라서 함수 선언문은 호출이 정상적으로 나왔던 것이다
-
하지만 선언문은 보통 위에서 쓰는게 원칙인데 아래에서 써도 동작이 가능하다는건 원칙을 무시하기 때문에 함수 표현식을 권장한다
// 함수 참조
console.dir(add); // ƒ add(x, y)
console.dir(sub); // undefined
// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // TypeError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
var sub = function (x, y) {
return x - y;
};
12.4.4 Function 생성자 함수(12-13)
- 지금은 함수 선언문, 함수 표현식으로 생성한 함수와 다르게 동작한다는 점만 체크
var add = new Function('x', 'y', 'return x + y');
console.log(add(2, 5)); // 7
12-14
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
12.4.5 화살표 함수(12-15)
-
function키워드 대신 =>로 간략히 표현가능(항상 익명함수로 정의)
-
화살표 함수는 26.3절에서 확인
// 화살표 함수
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7
12.5 함수 호출
12.5.1 매개변수와 인수(12-16)
- 매개변수(인자)또한 함수 몸체에서 암묵적으로 생성후 undefined로 초기화
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 호출
// 인수 1과 2는 매개변수 x와 y에 순서대로 할당되고 함수 몸체의 문들이 실행된다.
var result = add(1, 2);
12-17
- 매개변수의 스코프(유효 범위)는 함수 내부이다
function add(x, y) {
console.log(x, y); // 2 5
return x + y;
}
add(2, 5);
// add 함수의 매개변수 x, y는 함수 몸체 내부에서만 참조할 수 있다.
console.log(x, y); // ReferenceError: x is not defined
12-18
- 인수 부족으로 인수가 할당되지 않은 매개변수의 값은 undefined
function add(x, y) {
return x + y;
}
console.log(add(2)); // NaN
12-19
- 매개변수보다 인수가 더 많은 경우 초과된 인수는 무시
function add(x, y) {
return x + y;
}
console.log(add(2, 5, 10)); // 7
12-20
- 정확히는 버려지는게 아니라 모든 인수는 암묵적으로 arguments객체의 프로퍼티로 보관
function add(x, y) {
console.log(arguments);
// Arguments(3) [2, 5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
return x + y;
}
add(2, 5, 10);
12.5.2 인수 확인(12-21)
function add(x, y) {
return x + y;
}
12-22
function add(x, y) {
return x + y;
}
console.log(add(2)); // NaN
console.log(add('a', 'b')); // 'ab'
12-23
-
자바스크립트는 인수가 잘 전달되었는지 확인할 필요가 있다
-
타입스크립트를 사용하는 이유 중 하나
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: 인수는 모두 숫자 값이어야 합니다.
12-24
-
기본값 할당 방식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
12-25
- 기본값 이용
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
12.5.3 매개변수의 최대 개수(12-26)
$.ajax({
method: 'POST',
url: '/user',
data: { id: 1, name: 'Lee' },
cache: false
});
12.5.4 반환문(12-27)
- 반환문
function multiply(x, y) {
return x * y; // 반환문
}
// 함수 호출은 반환값으로 평가된다.
var result = multiply(3, 5);
console.log(result); // 15
12-28
function multiply(x, y) {
return x * y; // 반환문
// 반환문 이후에 다른 문이 존재하면 그 문은 실행되지 않고 무시된다.
console.log('실행되지 않는다.');
}
console.log(multiply(3, 5)); // 15
12-29
function foo () {
return;
}
console.log(foo()); // undefined
12-30
function foo () {
// 반환문을 생략하면 암묵적으로 undefined가 반환된다.
}
console.log(foo()); // undefined
12-31
- 세미콜론 자동 삽입 기능에 의해 잘못된 결과가 발생할 수 있다
function multiply(x, y) {
// return 키워드와 반환값 사이에 줄바꿈이 있으면
return // 세미콜론 자동 삽입 기능(ASI)에 의해 세미콜론이 추가된다.
x * y; // 무시된다.
}
console.log(multiply(3, 5)); // undefined
12-32
- 반환문은 함수 몸체 내부에서만 사용가능. 아래는 전역에서 사용한 예
<!DOCTYPE html>
<html>
<body>
<script>
return; // SyntaxError: Illegal return statement
</script>
</body>
</html>
12.6 참조에 의한 전달과 외부 상태의 변경(12-33)
- 참조에 의한 전달과 외부 상태의 변경
// 매개변수 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"}
12.7. 다양한 함수의 형태
12.7.1 즉시 실행 함수(12-34)
- 즉시 실행 함수는 단 한 번만 호출되며 다시 호출할 수 없다
// 익명 즉시 실행 함수
(function () {
var a = 3;
var b = 5;
return a * b;
}()); // ()는 실행!!
12-35
- 그룹 연산자(..)내의 기명함수는 함수 리터럴로 평가되며, 함수 이름은 함수 몸체에서만 참조할 수 있는 식별자임
// 기명 즉시 실행 함수
(function foo() {
var a = 3;
var b = 5;
return a * b;
}());
foo(); // ReferenceError: foo is not defined
12-36
- 함수 선언문은 함수 이름 생략불가
function () { // SyntaxError: Function statements require a function name
// ...
}();
12-37
- 세미콜론 자동 삽입 기능때문
function foo() {
// ...
}(); // SyntaxError: Unexpected token ')'
12-38
function foo() {}(); // => function foo() {};();
12-39
(); // SyntaxError: Unexpected token )
12-40
console.log(typeof (function f(){})); // function
console.log(typeof (function (){})); // function
12-41
- 그룹 연산자로 함수를 묶은 이유는 먼저 함수 리터럴을 평가해서 함수 객체를 생성하기 위함이다. 따라서 다른 연산자를 이용해도 가능하다
(function () { // 가장 일반적인 방법
// ...
}());
(function () {
// ...
})();
!function () {
// ...
}();
+function () {
// ...
}();
12-42
// 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있다.
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
12.7.2 재귀 함수(12-43)
function countdown(n) {
for (var i = n; i >= 0; i--) console.log(i);
}
countdown(10);
12-44
function countdown(n) {
if (n < 0) return;
console.log(n);
countdown(n - 1); // 재귀 호출
}
countdown(10);
12-45
// 팩토리얼(계승)은 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 * 1 * 1 = 24
console.log(factorial(5)); // 5! = 5 * 4 * 3 * 2 * 1 = 120
12-46
// 함수 표현식
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
12-47
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 * 1 * 1 = 24
console.log(factorial(5)); // 5! = 5 * 4 * 3 * 2 * 1 = 120
12.7.3 중첩 함수(12-48)
- 함수 내부에 정의된 함수를 중첩함수 또는 내부함수라 함
function outer() {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조할 수 있다.
console.log(x + y); // 3
}
inner();
}
outer();
12.7.4 콜백 함수(12-49)
- 콜백함수
// n만큼 어떤 일을 반복한다
function repeat(n) {
// i를 출력한다.
for (var i = 0; i < n; i++) console.log(i);
}
repeat(5); // 0 1 2 3 4
12-50
// n만큼 어떤 일을 반복한다
function repeat1(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
12-51
-
콜백 함수 : 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
-
고차 함수 : 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수
// 외부에서 전달받은 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
12-52
- 콜백 함수를 하나의 고차 함수 내부에만 호출된다면 아래방식이 일반적
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
// 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성한다.
repeat(5, function (i) {
if (i % 2) console.log(i);
}); // 1 3
12-53
- 콜백 함수를 여러곳에서 호출해야 할 경우는 아래 방식
// logOdds 함수는 단 한 번만 생성된다.
var logOdds = function (i) {
if (i % 2) console.log(i);
};
// 고차 함수에 함수 참조를 전달한다.
repeat(5, logOdds); // 1 3
12-54
// 콜백 함수를 사용한 이벤트 처리
// myButton 버튼을 클릭하면 콜백 함수를 실행한다.
document.getElementById('myButton').addEventListener('click', function () {
console.log('button clicked!');
});
// 콜백 함수를 사용한 비동기 처리
// 1초 후에 메시지를 출력한다.
setTimeout(function () {
console.log('1초 경과');
}, 1000);
12-55
- forEach도 기억하길 바람
// 콜백 함수를 사용하는 고차 함수 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
12.7.5 순수 함수와 비순수 함수(12-56)
- 순수 함수 : 외부 상태 변경하는 등 부수효과 없는 함수
var count = 0; // 현재 카운트를 나타내는 상태
// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다.
function increase(n) {
return ++n;
}
// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1
count = increase(count);
console.log(count); // 2
12-57
- 비순수 함수 : 외부 상태 변경하는 등 부수효과 있는 함수
var count = 0; // 현재 카운트를 나타내는 상태: increase 함수에 의해 변화한다.
// 비순수 함수
function increase() {
return ++count; // 외부 상태에 의존하며 외부 상태를 변경한다.
}
// 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워진다.
increase();
console.log(count); // 1
increase();
console.log(count); // 2
댓글남기기