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