JavaScript
[모던 자바스크립트] 함수
jjin502
2022. 7. 5. 16:10
함수란
- 수학에서의 함수 : 입력을 받아 출력을 내보내는 일련의 과정을 정의한 것.
- 프로그래밍에서의 함수 : 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행단위로 정의한 것.
// 12-1
// 함수 : 일련의 과정을 문으로 구현하고 코드블록으로 감싸서 하나의 실행단뒤로 정의한 것.
// f(x, y) = x+y
function add(x, y) {
return x + y;
}
console.log(add(2, 5)); // 7
관련 용어
- 매개 변수 : 함수 내부로 전달 받는 변수
- 인수 : 넘겨주는 값
- 반환값 : 출력하는 값
특징
- 함수 = 값
- 따라서 여러개 존재 가능 → 구별하기 위해 식별자인 함수 이름을 사용
함수 생성 방법
- 함수 정의를 통해 생성함
어떻게 정의해요 .. ?
- 다양한 방법으로 !
// 12-2
// 함수 정의
function add(x, y) {
return x + y;
}
// 주의 : 정의만으론 실행 안됨.
주의
- 인수를 매개변수를 통해 함수에 전달 하면서 함수의 실행을 명시적으로 지시해야한다.
- 이를 함수 호출이라고 한다 !!
// 12-3
// 함수 정의
function add(x, y) {
return x + y;
}
// 정의만으론 실행 안됨.
// 호출이 필요함 !!!
var res = add(2, 5);
console.log(res);
함수를 사용하는 이유
- 필요할 때마다 호출 가능 !! → 코드의 재사용이라는 측면에서 매우 유용하다.
- 유지보수의 편의성을 높임
- 실수를 줄임으로써 코드의 신뢰성을 높임
- 코드의 가독성을 향상시킴
함수 리터럴
- 객체를 리터럴로 생성하는 것 같이 함수도 가능함
- 구성 : function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성됨
// 12-4
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
- 함수 = 객체
그럼 함수를 객체 처럼, 객체를 함수 처럼 쓸 수 있나요 .. ?
- 노놉. 일반 객체와 함수는 다른 점이 있다
- 바로 호!출!의 차이
- 함수 객체만의 고유의 프로퍼티를 가짐.
함수 정의
- 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것.
함수 선언문
// 12-5
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 참조
// console.dir은 console.log와 달리 함수 객체의 프로퍼티까지 출력한다.
// 단, node.js환경에서는 console.log와 같은 결과가 출력된다.
console.dir(add); // f add(x,y)
// 함수 호출
console.log(add(2, 5)); // 7
// 12-6
// 함수 선언문
function (x, y) {
return x + y;
}
// 함수 이름 생략 불가능 함.
// Uncaught SyntaxError: Function statements require a function name
// 12-7
// 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없음.
// 하지만 함수 선언문이 변수에 할당되는 것처럼 보임.
var add = function add(x, y) {
return x + y;
};
// 함수 호출
console.log(add(2, 5));
이렇게 동작하는 이유?
- 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문 ...
// 12-8
// 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
// 함수 선언문에서는 함수 이름을 생략할 수 없다.
function foo() {
console.log("foo");
}
foo();
// 함수 리터럴을 피연산자로 사용 -> 함수 선언문 뿐만 아니라 함수 리터럴 표현식으로 해석된다.
// 함수 리터럴에서는 함수 이름을 생략할 수 있다.
(function bar() {
console.log("bar");
});
bar();
// Uncaught ReferenceError: bar is not defined
- 자바 스크립트엔진은생성된 함수를 호출하기 위해 함수 리흠과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.
// 12-9
var f = function add(a, b) {
return a + b;
};
// var f(->식별자) = function add(->함수이름)(a, b) {
// return a + b;
// };
console.log(f(2, 3));
// console.log(f(->식별자)(2, 3));
- 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.
함수 표현식
- 일급 객체 : 값의 성질을 갖는 객체
- 자바스크립트 함수 = 일급 객체.
// 12-10
// 따라서, 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있음 = 함수 표현식
var add = function (a, b) {
return a + b;
};
// 함수리터럴의 함수 이름 => 생략 ㄱㄴ == 익명함수
console.log(add(2, 3));
- 함수 호출 시 함수 객체를 가리키는 식별자를 사용해야한다.
// 12-11
// 기명 함수 표현식
var add = function foo(a, b) {
return a + b;
};
// 함수 객체를 가리키는 식별자로 호출
console.log(add(3, 5));
// Uncaught ReferenceError: foo is not defined
// 함수 이름은 함수 몸체 내부에서만 유효한 식별자다.
console.log(foo(3, 5));
함수 생성 시점과 함수 호이스팅
// 12-12
// 함수 생성 시점과 함수 호이스팅
// 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성시점이 다름.
// 함수 참조
console.dir(add);
console.dir(sub);
// 함수 호출
console.log(add(2, 5));
console.log(sub(2, 5));
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 표현식
// 변수 호이스팅이 발생함
var sub = function (x, y) {
return x - y;
};
// 함수 호이스팅
// 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징
Function 생성자 함수
// 12-13
// Function 생성자 함수
// 생성자 함수 : 객체를 생성하는 함수
var add = new Function("x", "y", "return x+y");
console.log(add(2, 5));
// 일반적이지 않으며, 바람직하지도 않음.
// 클로저를 생성하지 앟는 등 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작함.
// 12-14
// Function 생성자 함수
// 생성자 함수 : 객체를 생성하는 함수
var add1 = (function () {
var a = 10;
return function (x, y) {
return x + y + a;
};
})();
console.log(add1(2, 5));
var add2 = (function () {
var a = 10;
return new Function("x", "y", "return x+y+a");
})();
console.log(add2(2, 5));
// Uncaught ReferenceError: a is not defined
화살표 함수
// 12-15
// 화살표 함수
// function 키워드 대신 사용
// 생성자 함수로 사용할 수 없다.
const add = (x, y) => x + y;
console.log(add(2, 5));
함수 호출
매개변수와 인수
- 매개변수와 인수에는 개수와 타입의 제한이 없다.
// 12-16
// 함수 호출
// 함수 선언문
function add(x, y) {
return x + y;
}
// 함수 호출
// 인수 1과 2가 매개변수 x와 y에 순서대로 할당, 함수 몸체의 문들이 실행
var res = add(1, 2);
// 12-17
// 함수 호출
// 함수 선언문
function add(x, y) {
console.log(x, y);
return x + y;
}
add(1, 2);
// add함수의 매개변수 x,y는 함수 몸체 내부에서만 참조할 수 있다.
console.log(x, y);
// Uncaught ReferenceError: x is not defined
// 12-18
// 함수 호출
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));
// 인수가 초과된 경우 -> 무시
// 12-20
// argument 객체 - 함수 정의 시 매개변수 개수를 확정할 수 없는 가변 인자 함수 구현시 유요하게 사용.
function add(x, y) {
console.log(arguments);
// Arguments(3) [2, 5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
return x + y;
}
console.log(add(2, 5, 10));
인수 확인
// 12-21
// 인수 확인
function add(x, y) {
return x + y;
}
// 어떤 타입의 인수 전달, 반환하는지 명확하지 않음.
// 따라서 이렇게 될수도 있음
// 12-22
console.log(add(2));
// 매개변수 인수 개수 일치 확인
console.log(add("a", "b"));
// 동적타입 언어이기 때문에 매개변수 타입을 사전에 지정할 수 없음.
- 문법상 이상 없기 때문에 아무런 이의제기 없이 코드를 실행 할 것.
이러한 현상이 발생한 이유?
- 자바스크립트 함수는 매개변수와 인수가 일치하는지 확인하지 않는다.
- 자바스크립트는 동적 타입 언어다. 따라서 자바스크립트 함수는 매개변수의 타입을 사전에 지정할 수 없다.
- 따라서 자바스크립트의 경우 함수를 정의할 때 적절한 인수가 전달됐는지 확인할 필요가 있음
// 12-23
// 인수 확인
function add(x, y) {
if (typeof x !== "number" || typeof y !== "number") {
throw new TypeError("인수는 모두 숫자 값이어야합니다.");
}
return x + y;
}
console.log(add(2));
console.log(add("a", "b"));
// TypeError: 인수는 모두 숫자 값이어야합니다.
- 인수가 전달되지 않은 경우 단축 평가를 사용해 매개변수에 기본값을 할당하는 방법도 있다.
// 12-24
function add(x, y, z) {
x = x || 0;
y = y || 0;
z = z || 0;
return x + y + z;
}
console.log(add());
console.log(add(1));
console.log(add(1, 2));
console.log(add(1, 2, 3));
- 매개 변수 기본값은 매개변수에 인수를 전달하지 않았을 경우와 undefined 를 전달한 경우에만 유효함
// 12-25
// 간소화 가능
function add(x = 0, y = 0, z = 0) {
return x + y + z;
}
console.log(add());
console.log(add(1));
console.log(add(1, 2));
console.log(add(1, 2, 3));
매개변수의 최대 개수
- 매개 변수의 개수가 많다 → 함수가 여러 가지 일을 한다는 증거
- 이상적 함수 → 한 가지 일만해야하며, 가급적 작게 만들어야함!
// 12-26
// 매개변수의 최대 개수
// 이상적 함수 : 한 가지 일만 해야함, 가급적 작게 만들어야함.
// jQuery의 ajax메서드에 객체를 인수로 전달하는 예
$.ajax({
method: "POST",
url: "./user",
data: { id: 1, name: "Sim" },
cache: false
});
반환문
// 12-27
// 반환문
function mul(x, y) {
return x * y;
// 반환문
}
// 함수 호출은 반환 값으로 평가됨
var res = mul(3, 5);
console.log(res);
// 함수 호출 = 표현식
// 반환문의 역할
// 1. 함수의 실행을 중단하고 함수 몸체를 빠져나감.
// 2. (다음장 ㄱㄱ)
// 12-28
// 반환문
function mul(x, y) {
return x * y;
// 반환문
console.log("실행X");
// 반환문 이후로는 무시
}
var res = mul(3, 5);
console.log(res);
// 반환문의 역할
// 1. 함수의 실행을 중단하고 함수 몸체를 빠져나감.
// 2. return 키워드 뒤에 오는 표현식을 평가해 반환함.
// 12-29
// 반환문 -> 생략 가능
function mul(x, y) {
return;
}
console.log(mul());
// undefined
// 12-30
// 반환문 -> 생략 가능
function mul(x, y) {}
console.log(mul());
// undefined
// 주의 : 의도치 않은 결과 발생할 수도 있음
// 12-31
function mul(x, y) {
// return 키워드와 반환값 사이에 줄바꿈이 있으면
return;
// ASI에 의해 자동으로 ; 추가
x * y;
// ignore
}
console.log(mul(3, 5));
// undefined
// 12-32
return;
// Uncaught SyntaxError: Illegal return statement
참조에 의한 전달과 외부 상태의 변경
- 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되므로 타입에 따라 값, 참조에 의한 방식을 따른다.
// 12-33
// primitive - 원시값, obj - 객체 전달 받음
function changeVal(primitive, obj) {
primitive += 100;
obj.name = "Sim";
}
// 외부 상태
var n = 100;
var person = { name: "Lee" };
console.log(n);
console.log(person);
// 원시값 : 값 자체 복사되어 전달
// 객체 : 참조 값이 복사되어 전달
changeVal(n, person);
// 원시 값은 원본이 훼손되지 않는다.
console.log(n);
// 객체는 원본이 훼손된다.
console.log(person);
다양한 함수의 형태
즉시 실행 함수
- 함수의 정의와 동시에 즉시 호출되는 함수
- 단 한 번 호출
- 다시 호출 할 수 없음
// 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();
// Uncaught ReferenceError: foo is not defined
// 그룹 연산자 내의 기명함수는 함수 선언문이 아니라 함수 리터럴로 평가 되며
// 함수 이름은 함수 몸체에서만 참조할 수 있는 식별자이므호 즉시 실행함수를 다시 호출할 수는 없다.
// 12-36
// 즉시 실행 함수
// 반드시 그룸 연산자 (...)로 감싸야함.
// 안그러면 에러 발생
function () {
// ...
}();
// Uncaught SyntaxError: Function statements require a function name
// 이유 ?
// 함수 정의가 함수 선언문의 형식에 맞지 않기 때문.
// 12-37
// 기명함수에서도 마찬가지
function foo() {
// ...
}();
// Uncaught SyntaxError: Function statements require a function name
// 이유 ?
// 자바스크립트 엔진이 암묵적으로 수행하는 세미콜론 자동 삽입기능에 의해 함수 선언문이 끝나는 위치에 세미콜론이 삽입되기 때문.
// 12-38
// 즉시 실행 함수
// function foo() {}();
// Uncaught SyntaxError: Unexpected token ')'
// 그룹 연산자에 피연산자가 없어서 에러 발생
// 12-39
// ();
// Uncaught SyntaxError: Unexpected token ')'
// 12-40
console.log(typeof function f() {});
// function
console.log(typeof function () {});
// function
- 즉, 그룹 연산자로 함수를 묶은 이유는 먼저 함수 리터럴을 평가해 함수 객체를 생성하기 위함이다.
- 따라서, 먼저 함수 리터럴을 평가해서 함수 객체를 생성할 수 있다면 다음과 같이 그룹연산자 이외의 연산자를 사용해도 좋음
// 12-41
(function foo() {
// ...
})();
(function foo() {
// ...
})();
!(function foo() {
// ...
})();
+(function foo() {
// ...
})();
- 즉시 실행함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있음
// 12-42
// 즉시 실행함수도 일반 함수 처럼 값을 반환할 수 있음
var res = (function () {
var a = 3;
var b = 4;
return a * b;
})();
console.log(res);
// 즉시 실행함수도 일반 함수 처럼 값을 전달할 수 있음
res = (function (a, b) {
return a * b;
})(3, 4);
console.log(res);
재귀함수
- 재귀호출 : 함수가 자기 자신을 호출 하는 것.
- 재귀함수 : 재귀호출을 수행하는 함수
// 12-43
// 재귀함수
// 반복되는 처리를 위해 사용
function cntdwn(n) {
for (var i = n; i >= 0; i--) console.log(i);
}
console.log(cntdwn(10));
// 12-44
// 재귀함수
// 반복되는 처리를 위해 사용
function cntdwn(n) {
if (n < 0) return;
console.log(n);
cntdwn(n - 1);
}
console.log(cntdwn(10));
// 12-45
// 재귀함수
// 반복되는 처리를 위해 사용
// 팩토리얼(계승)은 1부터 자신까지의 모든 양의 정수의 곱
// n! = 1 * 2 * .. * (n-1) * n
function ftl(n) {
// 탈출 조건 : n이 1이하일 때 호출을 멈춤
if (n <= 1) return 1;
// 재귀호출
return n * ftl(n - 1);
}
console.log(ftl(1));
console.log(ftl(2));
console.log(ftl(3));
console.log(ftl(4));
console.log(ftl(5));
// 12-47
// 재귀함수
// 반복되는 처리를 위해 사용
// 팩토리얼(계승)은 1부터 자신까지의 모든 양의 정수의 곱
// n! = 1 * 2 * .. * (n-1) * n
function ftl(n) {
// 탈출 조건 : n이 1이하일 때 호출을 멈춤
if (n <= 1) return 1;
// 재귀호출
var res = n;
while (--n) res *= n;
return res;
}
console.log(ftl(1));
console.log(ftl(2));
console.log(ftl(3));
console.log(ftl(4));
console.log(ftl(5));
// 반복문으로 구현할 수 있음
// 그러나, 무한 반복 위험 -> 스택 오버플로 에러 발생 위험 있음.
중첩함수(내부함수)
- 함수 내부에 정의된 함수
// 12-48
// 중첩함수
// 외부함수 : 중첩함수를 포함하는 함수
// 중첩함수는 외부함수 내에서만 호출 가능
// 역할 : 자신을 포함하는 헬퍼함수 역할을 함.
function outer() {
var x = 1;
// 중첩함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조할 수 있음.
console.log(x + y);
}
inner();
}
outer();
콜백함수
- 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
// 12-49
// 콜백함수
// n만큼 어떤 일을 반복한다.
function repeat(n) {
// i를 출력
for (let i = 0; i < n; i++) console.log(i);
}
repeat(5);
// 12-50
// 콜백함수
// n만큼 어떤 일을 반복한다.
function repeat1(n) {
// i를 출력
for (let i = 0; i < n; i++) console.log(i);
}
repeat1(5);
function repeat2(n) {
for (let i = 0; i < n; i++) {
if (i % 2) {
console.log(i);
}
}
}
repeat2(5);
// 12-50
// 콜백함수
// n만큼 어떤 일을 반복한다.
function repeat1(n) {
// i를 출력
for (let i = 0; i < n; i++) console.log(i);
}
repeat1(5);
function repeat2(n) {
for (let i = 0; i < n; i++) {
if (i % 2) {
console.log(i);
}
}
}
repeat2(5);
// 12-52
// 콜백함수
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
// 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성함.
repeat(5, function (i) {
if (i % 2) {
console.log(i);
}
});
// 12-53
// 콜백함수
// 함수는 단 한 번만 생성됨
var logOdds = function (i) {
if (i % 2) {
console.log(i);
}
};
// 고차함수에 함수 참조를 전달한다.
repeat(5, logOdds);
// 12-54
// 콜백함수
// 비동기 처리에 활용되는 중요한 패턴
// 콜백함수를 사용한 이벤트 처리
// myButton 클릭 시 콜백함수 실행
document.getElementById("myButton").addEventListener("click", function () {
console.log("button clicked");
});
// 콜백함수를 사용한 비동기 처리
// 1초 후에 메세지 출력
setTimeout(function () {
console.log("1초 경과");
}, 1000);
// 12-55
// 콜백함수
// 비동기 처리 뿐만 아니라 배열 고차 함수에서도 사용.
// 콜백 함수를 사용하는 고차함수 map
var res = [1, 2, 3].map(function (item) {
return item * 2;
});
console.log(res);
// 콜백 함수를 사용하는 고차함수 filter
res = [1, 2, 3].filter(function (item) {
return item % 2;
});
console.log(res);
// 콜백 함수를 사용하는 고차함수 reduce
res = [1, 2, 3].reduce(function (acc, cur) {
return acc + cur;
}, 0);
console.log(res);
순수 함수와 비순수 함수
// 12-56
// 순수 함수 : 어떤 외부 상태에 의존하지 않고 변경하짇도 않는,부수효과가 없는 함수
// 비순수 함수 : 어떤 외부 상태에 의존하거나 외부 상태를 변경하는, 부수효과가 있는 함수
// 외부 상태 : 전역 변수, 서버 데이터, 파일, Console, DOM
var cnt = 0;
// 순수함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환함.
function increase(n) {
return ++n;
}
// 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
cnt = increase(cnt);
console.log(cnt);
cnt = increase(cnt);
console.log(cnt);
// 12-57
var cnt = 0;
// 비순수함수 increase는 외부 상태에 의존하며 외부 상태를 변경.
function increase() {
return ++cnt;
}
// 비순수 함수는 외부 상태(cnt)를 변경하므로 상태 추적이 어려워짐.
increase();
console.log(cnt);
increase();
console.log(cnt);
마치며 ...
- 함수는 프로그래밍이든 .. 수학이든 .. 많은곳에 쓰인다는걸 다시 깨달았고, 내가 알던 것 보다 훨씬 더 많은 종류의 함수가 존재한다는 것을 알았다 ..