2022. 10. 7. 00:13ㆍFE/JavaScript
함수
함수는 입력과 출력이 있고 코드 블록으로 일련의 과정을 감싸 하나의 실행 단위로 정의한 것이다.
function add(x, y){
return x + y;
}
console.log(add(2, 5)); // 7
함수 사용하는 이유
동일한 작업을 반복적으로 수행해야 할 때 재사용이 가능하고 코드 중복을 피할 수 있기 때문이다.
코드를 수정할 때도 비교적 쉽기 때문에 유지 보수성을 높이고 실수를 줄일 수 있어 코드의 신뢰성 또한 높일 수 있다.
함수 리터럴
자바스크립트에서 함수는 객체 타입의 값이다.
일반 객체와는 다른 호출할 수 있는 일급 객체이다.
숫자 값을 숫자 리터럴로 생성하듯이 함수도 함수 리터럴로 생성할 수 있다.
함수 리터럴은 다음과 같다.
함수 리터럴과 함수 선언문은 형태가 동일하다.
{ }를 블록문 또는 객체 리터럴로 해석하듯이 JS 엔진은 함수 선언문과 함수 리터럴을 문맥에 따라 해석한다.
함수 리터럴이 단독으로 사용되면 JS엔진은 함수 선언문으로 해석한다.
함수 리터럴을 피연산자로 사용하면 함수 리터럴로 해석한다.
function 함수명 (매개변수) {
실행문
return 반환값
}
함수 정의
함수 선언문 사용
function add(x, y){
return x + y;
}
함수 표현식 사용 (함수 리터럴)
const add = function(x, y){
return x + y;
}
함수 리터럴로 생성한 함수 객체를 변수에 할당하는 표현식이다.
함수 리터럴일 때 함수명은 생략 할 수 있다.
함수를 호출할 때 함수 이름이 아니라 함수를 가리키는 식별자를 사용해야 한다는 것을 알 수 있다.
Function 생성자 함수 사용
const add = new Function('x', 'y', 'return x + y');
Function 객체의 생성자 함수에 매개변수와 함수 바디를 문자열로 전달하면서 함수 객체를 생성한다.
매개변수 앞에서부터 함수 입력을, 맨 뒤에 함수 바디 부분을 인자로 받는다.
new 연산자를 생략해도 함수 객체가 만들어진다.
※ 생성자 함수는 객체를 생성하는 함수를 의미한다.
생성자 함수로 함수를 생성하는 것은 권장하는 방법이 아니다.
생성자 함수로 생성된 함수는 클로저를 생성하지 않는 등, 함수 선언문이나 함수 표현식으로 생성한 함수와는 다르게 동작한다.
var add1 = function(){
var a = 10;
return function(x, y){
return x + y - a;
};
}();
console.log(add1(1,2)); // a 참조 가능
var add2 = function(){
var a = 10;
return new Function('x', 'y', 'return x + y - a;');
}();
console.log(add2(1,2)); // a 참조 불가능
화살표 함수 사용
const add = (x, y) => {return x + y;};
const add = (x, y) => x + y;
화살표 함수는 항상 익명 함수로 정의한다.
화살표 함수는 기존의 함수 선언문이나 함수 표현식을 대체하기 위한 것이 아니다.
※ 화살표 함수는 생성자 함수로 사용할 수 없고 기존 함수와 this 바인딩이 다르고 prototype 프로퍼티가 없고 arguments 객체를 생성하지 않는다.
함수 호출
함수 이름은 함수 바디부분에서만 참조할 수 있는 식별자다.
function foo() {
console.log(foo);
}
// [Function: foo]
function foo(){
console.log(foo + "");
}
foo();
// 함수명은 함수 리터럴 자체를 의미한다.
JS엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름으로 식별자를 암묵적으로 만들고 함수 객체를 할당한다.
따라서 함수는 함수 이름으로 호출하는 것이 아니고 함수 객체를 가리키는 식별자로 호출한다.
단지 함수 객체를 가리키는 식별자도 함수 이름과 같아서 함수 이름으로 호출하는 것처럼 보인다.
그렇지만 함수 선언문과 함수 표현식은 정확히 동일하게 동작하는 것은 아니다.
함수를 호출하면 현재 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮긴다.
매개변수와 인자
function foo(a){
return a;
}
console.log(foo()); // undefined
매개변수는 함수를 정의할 때 선언하는데 함수 바디 내부에서 변수와 동일하게 취급된다.
일반 변수처럼 undefined로 초기화된 이후 인자가 순서대로 할당된다.
매개변수는 함수 내부에서만 참조할 수 있다.
function add(x, y){
return x + y;
}
console.log(add(2)); // y는 undefined로 덧셈 결과는 NaN
함수에 인자 개수가 부족하더라도 에러가 발생하지 않고 할당되지 않은 매개변수는 undefined 값을 갖는다.
function add(x, y){
return x + y;
}
console.log(add(1,2,3,4,5,6)); // 3,4,5,6 인자는 무시
함수에 인자 개수가 매개변수보다 많으면 초과된 인자는 무시되는 것처럼 보이나
모든 인자는 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
※ 함수에 전달받은 인자들은 인덱스를 키를 갖는 arguments 객체가 만들어진다.
인자 확인
함수는 매개변수와 인자의 개수가 일치하는지 확인하지 않고 동적 타입 언어로 매개변수의 타입을 사전에 지정할 수 없다.
function add(x, y){
if(typeof x !== 'number' || typeof y !== 'number'){
throw new TypeError('인수는 모두 숫자 값이어야 한다.');
}
return x + y;
}
따라서 자바스크립트에서 함수를 정의할 때 적절한 인자가 전달되었는지 확인해야 한다.
타입스크립트와 같은 정적 타입 언어를 사용하는 것도 하나의 방법이다.
arguments 객체를 통해 전달 받은 인자 개수를 체크할 수 있고 인자가 전달되지 않는 경우 단축 평가를 사용해 매개변수에 기본값을 할당할 수도 있다.
function add(a, b, c){
a = a || 0;
b = b || 0;
c = c || 0; // a, b, c 값이 할당되지 않으면 0으로 할당됨
return a + b + c;
}
console.log(add()); // 0
매개변수 기본값을 사용하여 함수를 정의할 수도 있다.
인자가 전달되지 않은 경우나 undefined를 전달한 경우에만 유효하다.
function add (a=0, b=0, c=0){
return a + b + c;
} // undefined가 전달될 때 매개변수 디폴트 값이 할당된다.
매개변수의 최대 개수
ECMAScript 사양에서 매개변수 최대 개수에 대해 명시적으로 제한하고 있지 않다.
매개변수는 순서에 의미가 있어 순서를 고려해야 한다.
이상적인 함수는 한 가지 일만 해야하고 작게 만들어야 한다.
따라서 매개변수는 최대 3개 이상 넘지 않는 것을 권장한다.
반환문
return 키워드와 표현식으로 이뤄진 반환문을 사용해서 함수 외부로 값을 전달할 수 있다.
함수 호출은 return 키워드가 반환한 표현식으로 평가되고 return이 없으면 undefined로 평가된다.
return 키워드 사이에 줄바꿈이 있는 경우 JS엔진이 세미콜론을 자동으로 삽입하여 의도치 않은 결과가 발생할 수 있다.
function multiply(x, y){
return
x * y; // 무시
}
함수 생성 시점과 함수 호이스팅
함수 호이스팅은 함수 선언문이 코드 선두로 올려진 것처럼 동작하는 JS 고유의 특징을 말한다.
모든 선언문은 런타임 이전에 JS엔진에 의해 실행되기 때문에 함수 선언문이 아래 있어도 먼저 실행되는 호이스팅이 발생한다.
console.log(add); //
console.log(sub); // undefined
function add(x, y){
return x + y;
}
var sub = function(x, y){
return x-y;
} // 변수 선언이 초기화보다 먼저 일어난다.
함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출할 수 있다.
함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없다.
함수 선언문으로 함수를 정의하면 런타임 이전에 함수명과 동일한 이름의 식별자를 생성하고 함수 객체가 먼저 생성된다.
var 키워드를 사용한 변수는 호이스팅 시 undefined로 초기화된다.
따라서 sub 변수에는 function 객체를 참조하지 않기 때문에 undefined가 출력된다.
변수 할당문 값은 할당문이 실행되는 런타임에 평가되기 때문에 함수 표현식 함수 리터럴도 런타임에 함수 객체가 된다.
따라서 함수 표현시긍로 함수를 정의하면 함수 호이스팅 대신에 변수 호이스팅이 발생한다.
함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 규칙을 무시한다.
→ 함수 선언문 대신 함수 표현식을 사용할 것을 권장한다.
참조에 의한 전달과 외부 상태의 변경
function changeVal(primitive, obj){
primitive+= 100;
obj.name = 'Kim';
}
let num = 100;
const person = {name : "Lee"};
console.log(num); // 100
console.log(person.name); // "Lee"
changeVal(num, person);
console.log(num); // 100
console.log(person.name); // "Kim"
함수 인자로 원시 값을 갖는 변수와 참조 값을 갖는 변수가 전달되었다.
둘 다 값을 복사하여 매개변수에 전달된다.
원시 값은 변경 불가하기 때문에 primitive에 새로운 원시 값으로 교체된다.
참조한 객체는 변경 가능하기 때문에 재할당 없이 기존 객체의 데이터가 변경된다.
원시 타입 값을 전달한 경우 원본이 훼손될 우려는 없지만 객체 타입 값을 전달하는 경우 원본이 훼손될 우려가 있다.
함수가 외부 상태를 변경하면 상태 변화 추적하기 어렵고 복잡성과 가독성을 해치는 원인이 되기 때문에
매개변수가 참조 값을 받는다면 객체의 변경을 추적하기 위한 옵저버 패턴, 불변 객체를 사용한다.
※ 옵저버 패턴
객체의 상태 변화를 감시하는 패턴이다.
예를 들어 어떤 이벤트가 발생하면 특정 동작을 수행하도록 하는 것이다.
※ 불변 객체
원시 값처럼 변경 불가능한 값으로 취급하여 객체의 변화를 기존의 객체를 수정하는 대신 새로운 객체로 대체하는 것이다.
이를 통해 객체 상태 변경을 원천봉쇄할 수 있다.
함수 형태
즉시 실행 함수
함수 정의와 동시에 호출되는 함수를 즉시 실행 함수라고 한다.
정의와 함께 호출되기 때문에 재호출이 불가능하다.
(function add(){
const a = 10;
const b = 10;
console.log(a + b); // 20 출력
}());
add(); // 재호출이 불가능
즉시 실행 함수는 익명 함수를 사용하는 것이 일반적이다.
즉시 실행 함수는 ( ) 그룹 연산자로 반드시 감싸야한다. 그렇지 않으면 Error가 발생한다.
( ) 안에 함수 선언문이 들어가면 JS엔진은 함수 리터럴로 인식하기 때문에 add 함수가 호출된 것이다.
참고로 JS엔진은 함수 선언문이 끝나는 위치 코드 블록 닫는 } 뒤에 ; 암묵적으로 추가한다.
function foo() {} // 암묵적으로 } 뒤에 ; 추가된다.
그룹 연산자로 함수를 묶으면 함수 선언문을 함수 리터럴로 인식하기 때문인데 그룹 연산자 이외 방법도 있다.
!function () {
console.log('실행됨');
}();
+function(){
console.log('실행됨');
}();
즉시 실행 함수를 활용하면 변수나 함수 이름 충돌을 방지할 수 있다.
재귀 함수
자기 자신을 호출하는 함수를 말한다.
재귀 함수를 정의할 때는 무한 루프에 빠질 수 있기 때문에 종료되는 조건을 잘 고려해야 한다.
function countdown(n){
for(let i=n;i>=0;i--) console.log(i);
}
countdown(10);
---------------------------------------------------------------------------
function countdown(n){
if (n<0) return;
console.log(n);
countdown(n-1); // 재귀 호출
}
countdown(10);
재귀 함수는 반복문을 사용하는 것보다 재귀함수를 사용하는 편이 더 직관적으로 이해하기 쉬울 때만 사용하는 것이 바람직하다.
콜백 함수
함수의 매개변수를 통해 다른 함수 내부로 전달되는 함수를 콜백 함수라고 한다.
매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수는 고차 함수라고 한다.
function repeat(n){
for (let i=0;i<n;i++) console.log(i);
}
repeat(5);
----------------------------------------------------------
function repeat2(n){
for (let i=0;i<n;i++) if(i%2==0) console.log(i);
}
repeat2(5);
repeat 함수는 for문 내에 다른 작업을 하고 싶을 때 새롭게 함수를 정의해야 한다.
함수의 일부분만 다를 때 함수를 합성하여 해결할 수 있는데 함수의 변하지 않는 부분은 미리 정의하고
변경되는 로직은 추상화해서 외부에서 함수 내부로 전달하는 것이다.
function repeat(n, f){
for(let i=0;i<n;i++) f(i);
}
const f1 = function (i) {
console.log(i);
}
const f2 = function (i) {
if(i % 2 === 0) console.log(i);
}
repeat(5, f1);
repeat(5, f2);
f1, f2 함수는 한 번만 생성된다.
콜백 함수는 고차 함수에 의해 호출되고 콜백 함수에 인자도 전달할 수 있다.
따라서 고차 함수에 콜백 함수를 전달할 때 함수 자체를 전달해야 한다.
function repeat(n, f){
for(let i=0;i<n;i++) f(i);
}
repeat(5, function (x) { console.log(x);})
// 고차 함수 내부에서만 호출되는 콜백 함수를 익명 함수 리터럴로 정의
repeat(5, (x) => console.log(x))
// 익명 함수 리터럴을 화살표 함수로 더 간단한게 표현 할 수 있음
콜백 함수를 익명 함수 리터럴로 정의하면 고차 함수가 호출할 때마다 콜백 함수가 생성된다.
※ 콜백 함수는 함수형 프로그래밍 패러다임뿐만 아니라 비동기 처리, 배열 고차 함수에서도 활용되는 중요한 패턴이다.
중첩 함수
함수 내부에 정의된 함수를 의미한다.
중첩 함수를 포함하는 함수는 outer 함수라 한다.
중첩 함수는 외부 함수 내부에서만 호출할 수 있고 외부 함수를 돕는 헬퍼 함수로 사용된다.
function outer(){
let x = 5;
function inner(){
let y = 5;
console.log(x + y); // 10
}
inner();
}
outer();
ES6부터 함수 정의는 문이 위치할 수 있는 곳 어디에서나 가능하다.
단, 호이스팅으로 혼란을 야기할 수 있으니 if문, for문 등 코드 블록에서 함수 선언문을 통해 정의하는 것은 바람직하지 않다.
순수 함수와 비순수 함수
함수형 프로그래밍에서 외부 상태에 의존하지 않고 변경하지도 않는 함수를 순수 함수라고 한다.
외부 상태에 의존하거나 변경하는 함수를 비순수 함수라고 한다.
외부 상태에는 전연 변수, 서버 데이터, 파일, 콘솔, DOM 등이 있다.
순수함수
순수 함수는 동일한 인자가 오면 항상 같은 값을 반환하는 함수다.
내부 상태에만 의존해 호출될 때마다 값이 변하면 순수 함수가 아니다.
순수 함수는 인자를 변경하지 않는 것이 기본이다.
let count = 0; // 현재 카운트를 나타내는 상태
function increase(n){ // 동일한 인자를 전달 받으면 동일한 값을 반환하므로 순수 함수
return ++n;
}
count = increase(count); // 순수 함수 반환 결과로 상태를 변경
console.log(count);
비순수 함수
외부 상태에 의존하거나 변경하는 함수다.
let count = 0;
function increase(){
return ++count; // 외부 상태에 의존하여 외부 상태를 변경
}
increase() // 외부 상태를 변경하여 상태 변화 추적이 어려워진다.
console.log(count);
외부 상태 변경을 지양하는 순수 함수를 사용하는 것이 코드의 복잡성을 감소하고 부수 효과를 줄일 수 있다.
함수형 프로그래밍
함수형 프로그래밍은 순수 함수와 보조 함수 조합을 통해 외부 상태 변경을 최소화해 불변성을 지향하는 프로그래밍 패러다임이다. 로직 내 존재하는 조건문, 반복문을 제거해 복잡성을 해결하고 변수 사용을 억제하고 생명주기를 최소화해 상태 변경을 피해 오류를 최소화하는 것을 목표로 한다.
자바스크립트는 멀티 패러다임 언어로 함수형 프로그래밍뿐만 아니라 객체지향 프로그래밍도 활용하고 있다.
'FE > JavaScript' 카테고리의 다른 글
[Javascript] 전역 변수 문제점 (0) | 2022.10.07 |
---|---|
[Javascript] 스코프 (0) | 2022.10.07 |
[Javascript] 원시 값과 객체 비교 (0) | 2022.10.04 |
[Javascript] 객체 리터럴 (0) | 2022.09.29 |
[JavaScript] 타입 변환과 단축 평가 (0) | 2022.09.11 |