Language/Javascript (Typescript)

[Javascript] Tutorial(3): 1급 객체 함수

ooeunz 2020. 3. 4. 05:09
반응형

※ 이 포스팅은 스터디 강의 자료로 사용되었습니다.

 

이번 포스팅에서는 자바스크립트 함수에 대해서 알아보도록 하겠습니다. 누군가 저에게 자바스크립트에서 가장 중요한 개념이 무엇이냐고 묻는다면 저는 단연 함수라고 이야기할 것 같습니다. 어떤 언어에서나 함수는 중요한 역할을 하지만 특히 자바스크립트에서는 함수의 역할이 남다르게 중요합니다. 그러한 이유에는 모듈화, 클로저, 객체와 같은 자바스크립트의 근간이 되는 기능들을 함수가 제공하기 때문입니다.

 

이전 포스팅에서도 가끔 언급했듯이 자바스크립트의 함수는 1급 객체라고 불립니다. 1급 객체는 아래와 같은 특성을 지닙니다.

  • 변수나 데이터 구조안에 담을 수 있습니다.
  • 파라미터로 전달할 수 있습니다.
  • 리턴 값으로 사용할 수 있습니다.
  • 리터럴로 생성할 수 있습니다.
  • 동적으로 프로퍼티를 생성 및 할당이 가능합니다.

 

쉽게 말해 함수 또한 일반 객체처럼 으로 취급되어 객체가 담당하는 부분을 모두 함수가 동일하게 당당할 수 있다는 말입니다. 지금부터 그러한 예시들과 함께 어떤 상황에 어떤 형태로 함수를 사용하는 것이 옳은지, 그와 관련된 미세한 차이들을 다뤄보도록 하겠습니다.

 


 

3-3. 함수 정의

자바스크립트에서는 함수를 정의하는 방법이 3가지가 있습니다.

  • 함수 선언문
  • 함수 표현식
  • Function() 생성자 함수

각각의 방법들은 모두 함수를 생성한다는 관점에서는 같지만, 동작 방식의 미묘한 차이가 있습니다. 특별히 이번 포스팅에서는 가장 많이 사용하고 혼동하는 함수 선언문과 함수 표현식에 관해서 살펴보도록 하겠습니다.

 

그럼 먼저 함수 리터럴에 대해서 알아보도록 하겠습니다. 함수 리터럴이란 함수를 선언하는 방법을 이야기 합니다. (이전 포스팅의 객체 리터럴에 대해서 언급했었습니다.) 지금부터 알아볼 함수 선언문과 표현식은 리터럴 방식으로 예제를 작성하도록 하겠습니다.

※ 화살표 함수와 같은 ES6 문법은 4번째 포스팅에서 다루도록 하겠습니다.

function add(x, y) {
  return x + y;
}

 

함수 선언문

먼저 함수 선언문입니다. 함수 선언문은 리터럴 방식과 거의 유사하다고 보면 됩니다. 다만 차이점은 함수 선언 방식은 반드시 함수명이 정의되어야 한다.라는 점입니다. 이에 관해서는 밑에서 추가적으로 설명하도록 하겠습니다.

function add(x, y) {
  return x + y;
}

console.log(add(1, 2)); // (출력값) 3

console.log(add('문자열', '합치기')); // (출력값) 문자열합치기

console.log(add(3, '합치기')); // (출력값) 3합치기

 

위의 예시는 두개의 매개변수 받아서 합을 리턴하는 간단한 함수입니다.

위의 특징을 살펴본다면 아래와 같습니다.

  • function이라는 키워드를 통해 함수를 선언하고 있습니다.
  • add라는 함수의 이름을 가지고 있습니다.
  • x, y라는 매개변수를 받아 값을 더해 리턴하고 있습니다.

그런데 특이점이라고 한다면 매개변수에 타입이 기술되어있지 않다는 점입니다. 예시의 출력 값에서도 보이듯 숫자와 숫자가 들어오든, 숫자와 문자가 들어오든, 모두 에러를 뱉지 않고 유연하게 출력해내는 모습을 볼 수 있습니다.

 

 

여기서 잠깐 살펴보고 가고 싶은 것이 있습니다. 위의 함수 선언문을 이야기하며 반드시 함수명이 정의되어야 한다고 이야기 했습니다. 그렇다면 다르게 생각하면 함수명이 없는 함수도 존재한다는 말이 아닐까요?

 

이러한 함수를 자바스크립트에서는 익명 함수라고 부릅니다.

 

위의 예제 함수를 보면 add라는 함수를 선언하고, add라는 함수의 이름을 사용해서 함수를 호출하는 형태입니다. 하지만 자바스크립트에서는 익명 함수를 생성하고 그 함수를 즉시 실행할 수 있는 문법 또한 제공합니다.

 

(function() {
  console.log('즉시 실행 익명 합수입니다.');
})();

// (출력값) 즉시 실행 익명 합수입니다.

조금 전 함수 선언문 형태에서 함수의 이름이 빠지고 함수를 괄호로 감싼 다음 끝에 다시 한번 괄호가 있는 형태로

즉 (함수가 들어가는 부분)()와 같은 형태를 띄고 있습니다.

 

위의 익명 함수를 실행할 경우 함수의 이름이 없는 관계로 두 번 실행하는 것은 불가능하지만 선언과 호출의 순서를 생략하고 바로 실행할 수 있다는 장점이 있습니다.

 

 

함수 표현식

다음은 함수 표현식에 관해서 알아보겠습니다. 이 포스팅 첫머리에서 함수는 1급 객체이며, '변수나 데이터 구조안에 담을 수 있습니다.'라고 했던 부분을 기억하실 겁니다. 함수 표현식은 바로 이러한 함수의 특징을 담아낸 표현식으로 함수 리터럴로 하나의 함수를 만들고, 여기서 생성된 함수를 변수와 할당하여 함수를 생성하는 것을 이야기합니다.

 

const add = function(x, y) {
  return x + y;
};

console.log(add(1, 2)); // (출력값) 3

함수 표현식은 함수 선언문과 문법이 거의 유사합니다. 다만 차이점은 함수 선언문에서는 필수였던 함수의 이름이 함수 표현식에서는 필수가 아닙니다. (대개 익명 함수로 사용합니다만 때때로 재귀 함수와 같이 함수명이 필요한 경우 함수명을 선언하기도 합니다.)

 


3-1. 함수 호이스팅

방금까지 함수를 어떻게 선언하는지에 관해서 살펴보았습니다. 그렇다면 이번에는 위의 함수들의 동작 방식의 차이 중 하나인 함수 호이스팅에 대해서 알아보도록 하겠습니다.

 

먼저 함수 선언문의 예시를 보도록 하겠습니다.

console.log(add(1, 2)); // (출력값) 3

function add(x, y) {
  return x + y;
}

console.log(add(1, 2)); // (출력값) 3

 

절차지향이나 객체지향 언어에 익숙하신 분들이라면 위의 예제가 본인이 평소에 즐겨 쓰던 프로그래밍 언어와 조금 다르다고 느낄 수 있습니다. 왜냐하면 첫째 줄에서 호출된 add함수는 셋째 줄에 가서야 선언이 되는데, 자신의 위치와 상관없이 함수를 호출하는 것을 알 수 있습니다. 즉, 함수 선언문 형태로 정의한 함수의 유효 범위는 코드의 맨 처음부터 시작합니다. 이러한 현상을 함수 호이스팅이라고 부르며 자바스크립트의 변수 생성과 초기화 작업이 따로 분리되어 진행되기 때문에 일어나게 됩니다.

 

자바스크립트 언어 개발에 관여하고 있는 Douglas Crockford는 자신의 저서에 이러한 함수 호이스팅은 함수를 사용하기 이전에 함수를 선언해야 한다는 규칙을 무시하므로 코드의 구조를 망가뜨릴 수 있다고 지적하며 함수 표현식 사용을  권장하고 있습니다.

 

그 예로 다음의 함수 표현식 예시를 보면 첫째 줄의 add 함수는 아직 함수가 선언되기 이전에 호출하였기 때문에, Error를 발생시키고 있음을 알 수 있습니다.

console.log(add(1, 2)); // Error 발생!

const add = function(x, y) {
  return x + y;
};

console.log(add(1, 2)); // (출력값) 3

 


3-4. 값으로 취급되는 함수

앞서 살펴보았듯이 자바스크립트의 함수도 다른 프로그래밍 언어의 함수와 다름없이 입력한 값을 받아 처리한 다음 그 결과를 반환하는 구조를 가지고 있습니다. 하지만 이러한 기본적인 기능 외에도 자바스크립트에서 함수를 제대로 이해하려면 함수가 일급 객체이며 이는 곧 함수가 일반 객체처럼 값(value)으로 쓰일 수 있다는 것을 이해해야 합니다.  따라서 함수를 변수나 객체, 배열 등에 값으로 저장할 수 있으며, 다른 함수의 인자로 전달하거나 함수의 리턴 값으로도 사용할 수 있습니다.

 

객체의 프로퍼티로 함수를 할당

먼저 객체 프로퍼티로 함수를 할당하는 예시를 살펴보도록 하겠습니다. 아래의 예시는 obj라는 이름의 객체를 객체 리터럴로 생성하고, func이라는 프로퍼티 안에 익명 함수를 할당하는 예제입니다.

let obj = {};

obj.func = function() {
  console.log('객체 안에 함수를 프로퍼티로 할당해보기');
};

obj.func(); // (출력값) 객체 안에 함수를 프로퍼티로 할당해보기

 

 

함수의 매개변수로 함수를 받기

이번에는 함수의 매개변수로 함수를 받는 형태의 예시를 살펴보도록 하겠습니다. 아래의 예시에서는 exFunc이라는 함수를 매개변수로 받는 함수를 함수 표현식으로 생성하고 exFunc함수 안에서 그 함수를 실행시키는 함수입니다. 아래의 실행코드를 보면 실제로 익명 함수를 즉석에서 만들어 인자로 넣어주는 형태로, exFunc이 파라미터로 받은 함수가 정상적으로 출력되는 것을 알 수 있습니다.

 

이와 같이 함수의 매개변수로 함수를 받는 형태는 실제로 콜백 함수와 같은 형태로 많이 사용하기 때문에, 확실히 이해하고 넘어가는 것이 좋습니다.

 

※ 콜백 함수는 3-5 파트에서 다루겠습니다.

// 함수 표현식으로 exFunc함수 생성
const exFunc = function(parameterFunc) {
  parameterFunc();
};


// exFunc 함수 실행
exFunc(function() {
  console.log('이 출력값은 exFunc의 매개변수로 들어온 익명함수에서 실행됩니다.');
});

// (출력값) 이 출력값은 exFunc의 매개변수로 들어온 익명함수에서 실행됩니다.

 

 

리턴 값으로 함수 사용하기

마지막으로 함수를 리턴값으로 사용해보도록 하겠습니다. 이번에는 익명 함수가 아니라 파라미터로 들어온 값의 제곱을 리턴하는 square 함수를 미리 생성하고 sumAndSquare 함수에서 x, y를 파라미터로 받아 x + y를 더한 값을 square 함수에 인자로 넘겨 (x+y)^2의 형태의 연산을 만들어냈습니다.

 

마지막 예제라 이러한 형태로도 가능하다는 것을 보여드리기 위해 조금 난이도를 올려보았는데 만약 이해가 안 되신다면 그냥 '함수 안에 리턴 값으로도 함수를 사용할 수도 있구나' 정도로만 이해하셔도 괜찮습니다. :)

const square = function(num) {
  return num ** 2;
};

const sumAndSquare = function(x, y) {
  return square(x + y);
};

console.log(sumAndSquare(1, 2));

 

 

헷갈릴 수 있는 부분

마지막으로 3-4 파트를 끝내기 전에 한 가지 헷갈릴 수 있는 부분을 언급하고 넘어갈까 합니다. 먼저 예시를 보겠습니다.

const luckySeven = function() {
  return 7;
};

const test1 = luckySeven;

const test2 = luckySeven();

먼저 luckySeven이라는 단순히 숫자 7을 리턴하는 함수를 생성하고 각각 test1에는 luckySeven과 test2에는 luckySeven()이라는 코드로 변수에 각각의 값을 할당하였습니다.

 

혹시 이 차이가 느껴지시나요?

만약 test1과 test2를 console.log로 출력했을 경우 출력값을 예측할 수 있다면 위의 예제들을 잘 이해하셨다고 할 수 있을 것 같습니다.

위의 예제 출력 시 아래와 같은 결괏값을 얻을 수 있습니다.

 

console.log(test1); // (출력값) [Function: luckySeven]

console.log(test2); // (출력값) 7

 

결론부터 말씀드리자면 test1은 luckySeven에는 luckySeven 함수의 참조 값을 할당했고, test2는 luckySeven의 리턴 값을 할당했기 때문에 위와 같은 출력 값이 나타나게 됩니다.

 

※ 만약 참조 값을 할당했다는 말이 이해가 되지 않으신다면 call by value, call by reference를 공부해 보시는 것을 추천드립니다!

 


3-4 콜백 함수

자바스크립트 함수 표현식에서 함수 이름은 꼭 붙이지 않아도 되는 선택사항이라고 이미 언급했었습니다. 이러한 익명 함수의 대표적인 용도가 바로 콜백 함수입니다. 콜백 함수는 명시적으로 호출하는 함수가 아니라, 특정 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출되는 함수를 말합니다. 또한 특정 함수를 특정 함수의 인자로 넘겨서, 코드 내부에서 호출되는 함수 또한 콜백 함수라고 부릅니다. 눈치채셨을지 모르겠지만 '함수의 매개변수로 함수를 받기' 부분에서 우리는 이미 한번 콜백 함수의 형태를 사용하였습니다.

 

이러한 콜백 함수의 대표적인 예가 이벤트 헨들러입니다. 웹 페이지에서 마우스 클릭 이벤트가 발생했을 때 특정 이벤트가 발생하는 것과 같은 것들이 콜백 함수를 정의하고 실행하는 것입니다.

 

 

[JavaScript] 이벤트(Event)에 관하여

이벤트란? 이벤트(event)는 어떤 사건을 의미한다. 브라우저에서의 사건이란 사용자가 클릭을 했을 '때', 스크롤을 했을 '때', 필드의 내용을 바꾸었을 '때'와 같은 것을 의미한다. 먼저 이러한 이벤트들에 관한..

ooeunz.tistory.com

 

또 다른 예로는 비동기 처리를 할 때 콜백 함수를 사용합니다. 하지만 이 내용은 이번 포스팅에서 다루는 범위를 넘어가기 때문에 다음 포스팅으로 미루도록 하겠습니다.

 


마치며

이번 포스팅에서는 함수의 대략적인 부분들에 대해 다뤄보았습니다. 지금까지 포스팅을 꾸준히 읽어주신 분이라면 왜 화살표 함수나 ES6 같은 문법은 안 알려주고 이렇게 개념적인 이야기만 계속 지루하게 이어가냐는 생각이 들 수도 있을 것 같습니다. 다음 포스팅에서 ES6와 같은 편리하고 강력한 문법들은 뒷부분에서 포스팅할 예정이지만, 저는 배움에 있어서 늘 근간이 되는 기본에 대한 이해가 가장 중요하다고 생각합니다. 실제로 문법을 아는 것보다, 어떤 프레임워크를 아는 것보다 그 언어의 동작원리나 네트워크와 관련된 기본 개념에 대한 이해가 좀 더 저를 한층 성장시킴을 느낄 때가 많았기 때문입니다.

 

다음 포스팅에서는 방금 말씀드렸다 싶이 ES6 문법을 배워보도록 하겠습니다. ES6 문법은 2015년에 나온 자바스크립트의 최신 문법들이고, 다른 언어의 장점을 많이 가져와 보다 편리하고 강력한 기능을 제공합니다. 그럼 다음 포스팅에서 뵙겠습니다. :)

 

※ 더 공부해볼만한 부분: 클로저

반응형