본 포스팅은 '모던 자바스크립트 Deep Dive'를 기반으로 공부한 내용을 정리한 것입니다.
함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다. 함수 내부로 입력을 전달받는 변수를 매개변수, 입력을 인수, 출력을 반환값이라 한다.
function add(x, y) {
return x + y;
}
add(2, 5)
function add(){~}
: 함수 정의add(2, 5)
: 함수 호출add
: 함수 이름x
, y
: 매개변수x + y
: 반환값2
, 5
: 인수: 인수를 매개변수를 통해 함수에 전달하면서 함수의 실행을 명시적으로 지시하는 것
함수는 몇 번이든 호출할 수 있으므로 코드의 재사용
측면에서 유용하다.
함수가 객체인 것은 자바스크립트의 중요한 특징이다. 일반 객체는 호출할 수 없지만, 함수는 호출할 수 있다.
// 변수에 함수 리터럴을 할당
var f = function add(x, y) {
return x + y;
};
리터럴은 값을 생성하기 위한 표기법이다. 따라서 함수 리터럴도 평가되어 값을 생성하며, 이 값은 객체다. 즉, 함수는 객체다.
: 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것
function add(x, y) {
return x + y;
}
var add = function (x, y) {
return x + y;
};
var add = new Function('x', 'y', 'return x + y');
var add = (x ,y) => x + y;
함수 선언문은 함수 리터럴과 형태가 동일하지만, 함수리터럴과 달리 함수 선언문은 함수 이름을 생략할 수 없다.
// 함수 선언문
function add(x, y){
return x + y;
}
표현식이 아닌 문은 변수에 할당할 수 없지만, 함수 선언문은 변수에 할당되는 것처럼 보인다.
var add = function add(x, y) {
return x + y;
};
이러한 이유는 기명 함수 리터럴은 코드의 문맥에 따라 함수 선언문 또는 함수 리터럴 표현식으로 해석되기 때문이다.
function foo() { console.log('foo'); }
foo(); // foo
(function bar() { console.log('bar'); });
bar(); // ReferenceError: bar is not defined
()
그룹 연산자 내에는 값으로 평가될 수 있는 표현식이어야하므로, 함수 리터럴은 함수 리터럴 표현식으로 해석된다.
함수 선언문과 함수 리터럴 표현식은 함수 객체를 생성한다는 점에서 동일하지만 호출에 차이가 있다. 함수 선언문으로 생성된 함수(foo
)는 호출할 수 있으나, 함수 리터럴 표현식으로 생성된 함수(bar
)는 호출할 수 없다.
함수 리터럴에서 "함수 이름"은 "함수 몸체 내에서만 참조할 수 있는 식별자"이기 때문에, 함수 이름으로 몸체 외부에서 함수를 호출할 수 없다. 즉, 함수를 가리키는 식별자가 없다는 말이다.
함수 선언문 사용시, 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자가 암묵적으로 생성하고, 거기에 함수 객체를 할당한다. "함수 이름"으로는 호출 못 할 지언정, 새로 생성된 "식별자"로는 호출이 가능한 것이다.
사실상 함수 이름으로 호출한 것이 아니라 함수 객체를 가리키는
식별자
로 호출된 것!
//
var add = function add(x, y) {
return = x + y;
};
add(2,5);
함수 표현식은 함수 리터럴로 생성한 함수 객체를 변수에 할당한 것이다. 함수는 값의 성질을 갖는 객체인 일급 객체이다. 함수가 일급 객체여서 값처럼 자유롭게 사용할 수 있어서 변수에 할당할 수 있다.
// 함수 표현식
var add = function (x, y) {
return x + y;
};
함수를 호출할 때는 함수 이름이 아니라 함수 객체를 가리키는 식별자를 사용해야한다.
// 기명 함수 표현식
var add = function foo (x, y) { // add는 식별자, foo는 함수이름
return x + y;
};
console.log(add(2,5)); // 7
console.log(foo(2,5)); // ReferenceError: foo is not defined
위와 같이 함수 이름이 아니라 식별자를 호출해야한다. 함수 이름으로 호출하면 ReferenceError
가 발생한다.
함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성시점이 다르다.
함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 생성된다.
함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성되고, 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하여 함수 객체를 할당한다. 모든 선언문이 그러한 것처럼 런타임 이전에 먼저 실행되는 것이다.
함수 표현식으로 함수를 정의하면 런타임에 함수 객체가 할당된다.
함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다. 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. 따라서 변수 할당문처럼 런타임에 값이 평가되어 함수 객체가 된다.
var add = new Function('x', 'y', 'return x + y');
Function
생성자 함수로 함수를 생성하는 방식은 일반적이지 않으며 바람직하지도 않다.
ES6에서 도입된 개념이다. 화살표를 사용해 간략한 방법으로 함수를 선언하며 항상 익명 함수로 정의한다. 표현만 간략한 것이 아니라 내부 동작 또한 간략화되어 있다.
const add = (x, y) => x + y;
함수를 실행하기 위해 필요한 값을 전달할 때, 매개변수를 통해 인수를 전달한다. 인수는 값으로 평가될 수 있는 표현식이어야 한다. 매개변수는 일반 변수와 마찬가지로 undefined
로 초기화된 이후 인수가 순서대로 할당된다. 함수가 호출될 때마다 이 단계를 거친다.
함수는 매개변수의 개수와 인수의 개수를 체크하지 않는다. 인수가 부족해도 에러가 발생하지 않으며 할당되지 않은 매개변수의 값은 undefined
이다.
function add(x, y) {
return x + y;
}
console.log(add(2)); // NaN
인수가 할당되지 않은 매개변수 y
는 초기화된 상태 그대로 undefined
가 되는 것이다.
초과된 인수는 무시된다.
function add(x, y) {
return x + y;
}
console.log(add(2, 5, 10)); // 7
Q : 매개변수는 최대 몇 개까지 사용하는 것이 좋을까?
A : 이상적인 매개변수 개수는 0개이며 적을 수록 좋다! 최대 3개를 권장한다.
매개변수의 최대 개수는 명시적으로 제한되지 않지만, 매개변수는 순서에 의미가 있기 때문에 그 수가 많으면 많을수록 유지보수성이 나빠진다.
return
키워드와 표현식(반환값)으로 이루어진 문. 실행결과를 함수 외부로 반환할 수 있다.반환문 이후에 다른 문이 존재하더라도 실행되지 않는다.
function multiply(x, y) {
return x * y;
console.log('실행되지 않는다.');
}
console.log(multiply(3,5)); // 15
return
키워드 뒤에 오는 표현식을 평가해 반환한다.return
키워드 뒤에 표현식을 명시하지 않으면 undefined
가 반환된다.
function foo () {
return;
}
console.log(foo()); // undefined
반환문은 생략가능하지만, 생략되면 마지막 문까지 실행한 후 암묵적으로 undefined
를 반환한다.
function foo(){
}
console.log(foo()); // undefined
반환문은 함수 몸체 내에서만 사용할 수 있다. 전역에서 반환문을 사용하면 문법 에러(SyntaxError: Illegal return statement
)가 발생한다.
함수에서도 마찬가지로 원시값은 값에 의한 전달, 객체는 참조에 의한 전달 방식으로 동작한다.
function changeVal(primitive, obj) {
primitive += 100;
obj.name = 'Kim';
}
// 외부 상태
var num = 100;
var person = { name: 'Lee' };
changeVal(num, person);
console.log(num); // 100
console.log(person); // {name: "Kim"}
위의 예제에서 원시 값인 num
은 원본이 훼손되지 않고, 객체인 person
은 원본이 훼손되어 변경되었다.
부수효과가 없다
.부수효과가 있다
.객체 타입의 인수는 언제든지 변경가능한 값이기 때문에 코드가 복잡해지면 변경을 추적하기 어려워진다.
객체를 마치 원시 값처러 변경 불가능한 값으로 동작하게 만드는 것.
(1)의 방법으로 객체의 상태 변경을 막은 뒤, 객체의 상태 변경이 필요할때 원본 객체를 완전히 복제하는 깊은 복사를 통해 새로운 객체를 생성하고 재할당을 통해 교체한다.