함수는 자바스크립트에서 가장 중요한 핵심 개념 중 하나이다.
자바스크립트의 핵심 개념들인 스코프,실행컨텍스트,클로저,생성자 함수에 의한 객체 새성 ,메서드 this , 프로토타입,모듈화 등등 모두 함수와 직간접적으로 관련이 있기 때문에 자바스크립트를 정확히 이해하고 사용하기 위해서는 함수에 대한 이해가 필수이다.
사실 프로그래밍 언어의 함수의 기본개념은 수학과 크게 다르지 않다.
function add(x,y) {
return x + y;
}
add(2,5)
함수에 대한 문법은 크게 설명하고 넘어가지는 않을 것 같다!
함수는 필요할 때 여러번 호출할 수 있고 몇번이고 호출할 수 있으므로 코드의 재사용 측면에서 아주 유용하다.
함수를 사용하지 않고 같은 코드를 중복해서 사용하면 그 코드를 반복해서 적어야 할 뿐 아니라 수정해야 할때 실수할 가능성이나 시간을 줄여준다.
또한 적절한 함수이름을 가진 함수는 내부 코드를 이해하지 않고도 함수의 역할을 파악할 수 있으므로 코드의 가독성도 향상시켜준다.
함수 리터럴
자바스크립트 함수는 객체 타입의 값이다. 따라서 숫자 값을 숫자 리터럴로 생성하고 객체를 객체 리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성할 수 있다.
var f = function add(x,y) {
return x + y;
}
위 예제를 보면 타 언어와는 다르게 함수 리터럴을 변수에 할당하고 있다. 이전에 알아보았듯이 리터럴이란 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식을 말한다.
즉 리터럴은 값을 생성하기 위한 표기법이다. 즉 함수 리터럴도 값을 생성하며 함수는 객체란 말이된다.
함수는 객체다
함수를 호출하기 위해서는 함수를 정의해야 하는데, JS에는 다양한 방식의 함수 선언 방식이 존재한다.
//함수 선언문
function add(x,y){
return x+y;
}
//함수 표현식
var add = function (x,y) {
return x+y;
};
//function 생성자 함수
var add = new Function('x','y','return x+y');
//화살표 함수
var add = (x,y) => x+y;
모든 함수 정의 방식은 함수를 정의하지만 미묘하게 다른 부분들이 있다.
잠깐 정의와 선언에 대해 이야기하자면 함수를 선언하면 식별자가 암묵적으로 생성되어 함수 객체가 할당되므로 변수는 선언하고, 함수는 정의한다는 표현이 옳다.
함수 선언문은 함수 리터럴과 형태가 동일하다. 단 함수 리터럴은 함수 이름을 생략할 수 있지만 함수 선언문은 이름을 생략할 수 없다.
함수선언문은 표현식이 아닌 문이다.
function add(x,y) {
reutnr x+y;
{
//undefined
따라서 다음과 같이 함수를 선언해도 콘솔창에는 undefined가 찍히게 된다.
그런데 이상한 점이 있다. 분명 이전에 표현식이 아닌 문은 변수에 할당할 수 없다고 했는데 함수 선언문은 변수에 할당하는 것처럼 보인다.
var add = function add(x,y) {
return x+y;
}
console.log(add(2,5)); //7
이렇게 동작하는 이유는 JS엔진이 문맥에 따라서 동일한 함수 리터럴을 표현식이 아닌 함수 선언문으로 해석하는 경우와 표현식인 리터럴 표현식으로 해석하는 경우가 있기 때문이다.
조금 쉽게 설명하자면 { } 은 블록문일수도 있고 객체 리터럴일수도 있다. 즉 중의적 표현이다. 따라서 JS는 코드의 문맥을 파악해서 이를 파악한다.
아래 예시를 봐보자.
function foo() {console.log('foo')}
foo() // foo
(function bar() {console.log('bar')})
bar() // bar is not defined
함수 리터럴을 그룹 연산자 안에서 썼기 때문에 함수 리터럴 표현식으로 해석되어 표현식이 아닌 문인 함수 선언문은 피연산자로 사용할 수 없다.
위 예시에서 이름이 있는 함수 리터럴은 코드의 문맥에 따라 함수 선언문 혹은 함수 리터럴 표현식으로 해석되어 함수 선언문으로 생성된 foo는 호출되나 함수 리터럴 표현식으로 생성된 bar는 호출되지 않는 것이다.
그런데 foo라는 함수는 어떻게 사용할 수 있는 것일까 ? 우리는 분명 foo라는 변수에 함수를 할당한 적 없다. 이것이 가능한 이유는 JS 엔진이 암묵적으로 식별자를 생성해주기 때문이다.
JS엔진은 함수 선언문을 해석하여 함수 객체를 생성해준다.
여기서 중요한 포인트가 나온다.
함수는 함수 이름으로 호출하는 것이 아닌 함수 객체를 가리키는 식별자로 호출한다.
우리는 함수를 정의하고 사용할때 함수 이름으로 호출하는것 같지만 사실은 식별자로 호출하는 것이다.
JS에서 함수는 객체 타입의 값이다. 이처럼 값의 성질을 갖는 객체를 일급 객체라고 하며 자바스크립트의 함수는 일급 객체이다.
함수가 일급 객체라는 것은 함수를 값처럼 자유롭게 사용할 수 있다는 의미이다.
함수 생성 시점과 함수 호이스팅
console.dir(add); // fadd(x,y)
console.dir(sub); // undefined
//함수 호출
console.log(add(2,5)) // 7
console.log(sub(2,5)) // sub is not a function
functions add(x,y) {
return x+y
}
var sub = function (x,y) {
return x-y
}
여기서 재미있는 점을 발견할 수 있다. 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다르기 때문에 나오는 현상이 있다.
함수 선언문은 런타임 이전에 JS엔진에 의해 먼저 실행되기 때문에 함수 선언문이 코드의 선두로 끌어 올려진것처럼 동작하는 JS고유 특징인 함수 호이스팅현상이 있다.
함수 호이스팅과 변수호이스팅은 미묘한 차이가 있다.
var키워드를 사용한 변수 선언문과 함수선언문은 런타임 이전에 JS엔진에 의해 먼저 실행된다는 공통점이 있지만 var 키워드로 선언된 변수는 undefined로 초기화되고 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화된다.
따라서 var키워드를 사용한 변수는 선언 이전에 참조할대 undefined로 평가되지만 함수 선언문으로 정의한 함수는 호출 자체가 가능하다.
즉, 함수 표현식으로 함수를 정의하게 되면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생하게 되는 것이다.
Function 생성자 함수
사실 Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않고 바람직하지도 않다. 가장큰 특징은 클로저를 생성하지 않는다는 점으로 함수 선언문과 함수 표현식으로 만든 함수와는 조금 다르게 생성한다는 점을 알아두자.
클로저에 대해서는 추후에 자세히 다뤄보자.
화살표 함수
화살함수는 function 키워드 대신 화살표를 활용해 좀 더 간략한 방법으로 함수를 선언할 수 있는 문법으로 기존의 함수보다 표현도 간략하지만 내부동작 또한 간략화 되어있다.
화살표 함수는 생성자 함수로 사용할 수 없으며 this 바인딩 방식이 다르고 prototype프로퍼티가 없는등 차이가 있다. 화살표 함수 또한 추후에 자세히 다뤄보자.
함수 호출
함수를 호출할때는 아래와 같이 호출한다.
function add(x,y){
return x+y
}
var result = add(1,2);
함수에 값을 전달해야할 때는 매개변수로 넘겨주는데, 매개변보다 인수가 더많은 경우 초과된 인수는 무시된다.
정확하게 따지면 인수가 그냥 버려지는 것은 아니며 argumetns객체의 프로퍼티로 보관되긴 한다. 해당 내용역시 추후에 알아보자.
매개변수의 최대 개수
ECMAScript 사양에서 매개변수의 최대 개수에 명시적으로 제한하고 있지는 않다. 그럼 매개변수를 무제한 사용하는것이 좋을까? 그렇지는 않다.
우선 이상적인 함수의 정의를 알아보자.
이상적인 함수는 한가지 일만 해야하며 가급적 작게 만들어야 한다.
따라서 매개변수가 3개 이상 넘어가는 것은 별로 권장하지 않는 방법이다. 만약 그 이상의 매개변수가 필요한 경우 객체로 인수로 전달하는것이 유리할 수 있다.
이 경우 프로퍼티 키를 통해서 매개변수를 넘기므로 코드의 가독성도 좋아지고 실수도 줄어들게 된다. 단 함수 외부에서 전달한 객체가 함수 내부에서 바뀌는 경우에 함수 외부 객체의 값도 변동되는 부수효과는 알아둬야할 필요가 있다.
이런 문제의 해결 방법중 하나는 객체를 불변 객체로 만들어 사용하는 것이다. 객체를 마치 원시 값처럼 변경할 수 없게 만들어서 객체의 변화가 필요한 경우에는 깊은 복사를 활용하여 새로운 객체를 만드는 방법이 있다.
외부 상태를 변경하지 않고 외부 상태에 의존하지 않는 함수를 순수 함수라 하며 순수 함수를 통해서 부수 효과를 최대한 억제하고 프로그램의 안정석을 높이는 프로그래밍 패러다임을 함수형 프로그래밍이라 한다.
즉시 실행 함수
즉시 실행 함수는 함수 정의와 동시에 즉시 호출되는 함수로 한번만 호출되고 다시 호출 할 수 없다.
(function() {
var a = 3;
var b = 5;
return a * b
}());
이런 함수는 왜 사용될까? 즉시 실행 함수 내에 코드를 모아두면 혹시 있을 수도 있는 변수나 함수 이름의 충돌을 방지할 수 있다.
이러한 충돌방지 내용에 대해서도 또 다뤄보겠다!
중첩 함수
함수 내부에 정의된 함수를 중첩함수로 부르며 ES6부터 함수 정의는 문이 위치할 수 있는 문맥이라면 어디든 가능하다. 단 호이스팅으로 인해 혼란이 발생할 수 있어 if문이나 for 문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직하지 않다.
function outer() {
var x = 1;
// 중첩 함수
function inner() {
var y = 2;
// 외부 함수의 변수를 참조할 수 있다.
console.log(x + y); // 3
}
inner();
}
outer();
콜백 함수
함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백함수라고 하며 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 고차함수라고 한다.
// 외부에서 전달받은 f를 n만큼 반복 호출한다
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i); // i를 전달하면서 f를 호출
}
}
var logAll = function (i) {
console.log(i);
};
// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logAll); // 0 1 2 3 4
var logOdds = function (i) {
if (i % 2) console.log(i);
};
// 반복 호출할 함수를 인수로 전달한다.
repeat(5, logOdds); // 1 3
고차함수는 콜백 함수를 자신의 일부분으로 합성하게 된다.
자바스크립트의 함수에 대해 간략하게(?) 알아보았다.
내가 생각보다 함수에 대해 무지했다는 생각이 들기도 했고 자바스크립트란 언어에 대해 조금 더 매력을 느끼게 된 장이었던 것 같다.
'FrontEnd > Deep Dive' 카테고리의 다른 글
[JS] DeepDive(14) 전역변수의 문제점 (0) | 2023.08.04 |
---|---|
[JS] DeepDive(13) 스코프 (0) | 2023.08.01 |
[JS] DeepDive(11) 원시 값과 객체의 비교 (0) | 2023.07.28 |
[JS] DeepDive(10) 객체 리터럴 (0) | 2023.07.27 |
[JS] DeepDive(9) 타입 변환과 단축 평가 (0) | 2023.07.26 |