- 함수의 선언 및 정의, 호출
-
함수는
function 함수명([매개변수]) { 함수객체 }
로 구성되는데, 함수명 은 그 자체 변수이며 뒤따라 정의되는 { 함수객체 }의 작업수행 결과가 이 함수 '변수'의 값 이 된다. 따라서 함수는 선언과 동시에 렉시컬 스코프의 맨 위로 끌어올려지고(= '함수 호이스팅'), 호출될 때마다 { .. } 안의 내용을 실행하며, 함수의 매개변수 는 { .. } 안에서 지역변수처럼 사용된다:
const jc= "kjc"
function fnc(name) { // 일반함수 fnc 선언 및 정의
// 인자로 받은 name(매개변수)을 갖고 작업하여 호출자에게 반환한다(return 문)
return name[0].toUpperCase() + name.slice(1); // 인자로 받은 'kjc'의 첫자를 대문자로 바꾸고, 'kjc'의 1번 인덱스부터 끝까지(= 'jc') 가져와 문자열로 연결한다
}
console.log(fnc(jc)) // Kjc ← 변수(jc)를 인자로 넘기면서 함수명(fnc)으로 함수를 호출하고, 그 결과값을 돌려받는다
const jc= "kjc"
const fnc2= function(name) { // 변수에 할당된 함수 표현식 ← 함수에 이름이 없다!
return name[0].toUpperCase() + name.slice(1);
}
fnc2(jc) // Kjc ← 변수(jc)를 인자로 넘기면서 변수 fnc2에 할당된 함수를 호출한다 ← 변수명(fnc2)으로 함수를 호출한다!
const fnc3= (name) => { // 화살표 함수로 작성한 익명함수
return name[0].toUpperCase() + name.slice(1);
}
fnc3(jc) // Kjc ← fnc3에 할당된 화살표 함수를 호출한다
☞ 변수에 할당한 (익명)함수 표현식은 해당 변수명 으로 호출하는데, 이름 없는 '익명함수'는 화살표 함수로 작성하면 좀 더 간결하고, 알아보기 쉽다!
➥ 함수 이름 뒤에 ()를 붙이면; 스크립트는 함수를 호출한다고 이해하여 함수객체 내부 코드를 실행하고(괄호가 없으면; 함수를 참조 하기만 하는 것이며, 그 함수를 실행 하지는 않는다!), 함수를 호출한 표현식은 return 문에 의해 반환되는 값 (리턴값이 없는 경우에는 undefined)을 돌려받게 된다 - 곧, 함수를 호출한 표현식은 리턴값이 된다!
일반적으로, 함수는 호출 시마다 값이 달라지는 '부수 효과'가 발생하며, 리턴값 또한 계속 달라질 수 있다. 반면, 오직 함수로서의 함수, 곧 순수 함수는 입력이 같으면 결과 또한 언제나 같고, '부수 효과'를 발생시키지 않는 함수를 말한다!
- 값으로서의 함수
- 함수는 객체이며, 따라서 함수도 일반 객체처럼 값으로 취급될 수 있다: 함수는 단순히 입력한 값을 받아 처리한 다음 그 결과를 반환하는 것만이 아니라 일반 객체처럼 값 으로 취급될 수 있다 - 곧, 함수를 변수, 객체의 프로퍼티, 배열의 요소 등에 할당할 수 있는 것이다!
hello() // Hi, Kjc! ← 1. 함수명으로 함수를 호출한다
function hello() { // 일반함수 선언 및 정의 ← 함수 '호이스팅'이 일어나 맨 위로 끌어올려진다!
return "Hi, Kjc!" // 함수의 반환 값 ← 함수 내 실행 코드는 함수가 호출된 이후에 작동된다!
} // 여기서 함수의 이름 'hello'는 그저 { 함수객체 }를 참조하는 변수일 뿐이다!
const fnc= hello // 함수명을 변수에 할당하여 참조한다 ← 함수명은 그 자체로 변수다!
fnc() // Hi, Kjc! ← 2. 함수를 할당한 참조 변수 이름으로 함수를 호출한다
const obj= {} // 빈 객체 생성
obj.property= hello // 함수를 객체의 프로퍼티에 할당하여 참조한다 ← 괄호가 없으면; 함수를 '참조'하기만 하는 것이며, 그 함수를 '실행'하지는 않는다!
obj.property() // Hi, Kjc! ← 3. 객체의 프로퍼티로 함수를 호출한다
const arr= [] // 빈 배열 생성
arr[0]= hello // 함수를 배열의 요소에 할당하여 참조한다 ← 괄호가 없으면; 함수를 '참조'하기만 하는 것이며, 그 함수를 '실행'하지는 않는다!
arr[0]() // Hi, Kjc! ← 4. 배열의 요소로 함수를 호출한다
➥ 함수 또한 객체인데, 매개변수로 받은 변수 f 가 함수인지 여부를 명확히 확인하고자 한다면; typeof 연산자를 사용할 수 있다:
function fnc(ary, f) {
// 함수가 인자로 전달되지 않았다면; (undefined 대신)매개변수를 그대로 반환하는 널함수
if(typeof f != 'function') f= z => z; // 'f'가 함수가 아니라면; 그냥 건너뛴다!
// 매개변수 f가 함수가 맞다면;
.. 작업 수행
}
- 함수 표현식과 즉시실행 함수
- 1. 함수명 뒤에 ()를 붙여 '호출'하면; 함수를 '실행'한 뒤 그 결과값 을 돌려받게 된다. 반면, 단순히 함수명 을 변수 에 할당하는 경우, (그 함수가 바로 실행되지는 않고)단지 해당 변수가 그 함수를 가리키고 있을 뿐이다. 한편, 함수를 () 안에 넣어 바로 '실행'되도록 할 수도 있다(= 즉시실행 함수):
- 2. 함수 표현식은 일반적인 함수 선언과 비슷하지만, 더 큰 표현식이나 문의 일부로서 존재하고 이름을 붙이지 않아도 된다. 일반 함수 선언은 실제로 변수를 선언하며 그 변수에 함수 객체를 할당하지만('호이스팅'된다!), 함수 표현식은 곧바로 변수에 할당되지 않는다 - 나중에 실제 사용 시 변수나 상수에 '할당'하여 '호출'하게 된다:
(function(a, b) { // 즉시실행 함수 ← 함수 전체를 괄호로 둘러싸준다!
console.log(a + b) // 3
}) (1, 2); // 함수 선언과 동시에 인자를 전달하면서 즉시 함수를 호출한다!
const add= function(x, y) { // 익명함수 함수 표현식: 익명함수를 정의하고 변수에 할당한다
return x + y;
} // 함수 표현식에서는 함수 호이스팅이 일어나지 않는다! 따라서, 호출 전에 작성되어야 한다!
add // ƒ (x) { return x+x } ← 그냥 그 곳을 바라보고 있을 뿐, 아직 내부 코드는 실행되지 않았다!
add(1, 2) // 3 ← 변수명 뒤에 ()를 붙여 함수를 호출한다!
const add2= add // 또 다른 변수 add2로 (add 변수에 할당된)함수를 참조한다!
add2(2, 3) // 5 ← 함수 호출
➥ 일반 함수 선언으로 함수 f() 를 정의할 때 함수 객체는 실행되기 전에 존재하며 정의하기 전에 사용할 수 있다(= 함수 '호이스팅'). 반면, 함수 표현식에서의 함수 객체는 변수에 할당하기 전에는 '참조'할 수 없고, 따라서 '호출'할 수도 없다!
ES2020에서는 () 대신 ?.()로 함수를 호출할 수 있는데,
앞쪽의 표현식이 null 이나 undefined 일 때도 타입 에러를 일으키지 않고
호출된 함수 전체를 undefined 로 평가한다(= 단축 평가)
- 곧, (부수 효과를 고려하지 않는다면;)
f?.(x)
는 (f !== null && f !== undefined) ? f(x) : undefined
와 같다!
let f= null, x= 0
f?.(x++) // undefined ← 뒤는 평가하지 않는다(단축 평가!)
console.log(x) // 0 ← 단축 평가되어 x 값은 변동이 없다!
✓ 함수호출 표현식 맨 앞에 있는 표현식이 프로퍼티 접근 표현식이라면; 이 호출은 메서드 호출이 되는데, 메서드 호출에서는 프로퍼티 접근 대상인 객체는 메서드가 실행되는 동안 this 값이 된다:
-
obj.m();
프로퍼티 접근 및 메서드 호출 ← obj는 반드시 객체여야 하고,m()
메서드가 있어야 하며, 그 값도 함수여야 한다! -
obj?.m();
조건부 프로퍼티 접근 & 기본 메서드 호출 ← obj 가 null 및 undefined 라면; 표현식 전체가 undefined 로 된다! -
obj.m?.();
기본 프로퍼티 접근 & 조건부 메서드 호출 ← obj는 반드시 객체여야 하고, m 프로퍼티가 없거나 그 값이 null 이라면; 표현식 전체가 undefined 로 된다!