함수 (function)

Javascript

기본적으로 함수란 특정 기능을 구현한 집합체, 특정 문제 또는 필요로 하는 요구사항를 해결하기 위해 수행하는 코드의 묶음 또는 블록으로, Javascript에서 함수는 한번 구현해놓으면 재호출해 코드의 재사용성을 높일 수 있으며 함수 내 특정 변수를 숨기기 위한 캡슐화와 기능을 세분화해 코드의 유지보수의 용이성과 확장성을 높힐 수 있습니다.

Javascript 함수(Function)의 구조는 아래와 같이 구성되어 있습니다.

  • 함수명(function name)
  • 함수블록 내에서 처리하기 위해 전달되는 매개변수들(Parameter)과 인자(Argument)
  • 함수의 기능을 구현하기 위한 몸체(function body) 또는 구문(statement)
1
2
3
4
5
6
7
// 함수 정의
function name(para, meter) {
// statement
}

// 함수 호출
name('argue', 'ment')

Javascript에서 함수(Function)은 원시값과 객체 데이터 타입 중 객체(Object)에 속하는 타입으로, 특히 Javascript의 함수는 일급 객체(First-class object) 로써 동적 생성과 리터럴 방식으로 함수를 생성할 수 있으며 변수(Variable), 배열(Array) 내 요소, 객체의 프로퍼티(Property) 등에 할당 가능하다는 특징을 가지고 있습니다.

함수 정의하기

Javascript에서 함수를 정의하기 위한 방법으로는 여러가지가 있으며 대표적으로는 함수 선언문(Declarations), 표현식(expressions) 그리고 ECMAScript 6에서 추가된 화살표 함수(Arrow function)이 있다.

비고 : Function 생성자(constructor)로 함수를 정의하는 것은 JS 엔진 최적화를 방해 또는 기타 다른 문제를 유발할 수 있는 이유로 권장되지 않는 방식입니다.

함수 선언문(Declarations)

Javascript에서 함수를 정의하기 위해 사용되는 가장 대표적인 방법으로, 서두에서 언급한 함수 구조를 띄고 있다.

1
2
3
4
5
6
7
// 함수 정의
function decFunction(para, meter) {
return para + meter
}

// 함수 호출
decFunction('argu', 'ment') // 'argument'
  • 함수명 : 함수 또는 구현된 기능을 대표하기 위한 함수의 이름으로 일반적 함수 호출, 재귀적 호출 그리고 실행 컨텍스트에서 memory 상에 함수를 올리기 위한 식별자(Identifier)의 목적으로 함수 선언문에서는 생략할 수 없는 필수요소이다.
  • 매개변수 : 함수 등에서 사용되는 전달된 값을 받는 변수로써, 함수의 기능상 외부 환경에서 데이터를 받아 가공 및 반환이 필요할 경우 함수 내부로 전달하는 역할을 하며 콤마(,) 로 구분하여 최대 255개까지 받을 수 있습니다.
  • 함수 구문 : 함수의 로직을 담당하는 부분으로 중괄호 {} 안에 매개변수를 받아 데이터를 처리하거나 DOM을 조작하는 등 필요에 따른 특정 기능을 담고 있는 중추적 부분입니다.
  • 인자: 함수에 전달되는 인수의 배열집합으로써, 정확히는 유사배열 객체로 arguments 객체라 칭하며 매개변수를 통해 전달받은 함수 외부환경의 값, 변수 또는 참조
  • 반환값 : 함수 내부에서 처리된 값으로 return 키워드를 통해 처리된 값을 반환할 수 있다. 또한 함수의 코드상 종료시점에 return 구문이 없다면 함수는 undefined 를 반환하도록 Javascript에서는 설계되어 있다.

함수 표현식(expressions)

함수 선언문(Declarations)과 다르게 함수를 변수에 할당한다는 점이 다른 점으로 함수의 일급 객체(First-Class Object) 의 특성을 이용한 함수식이다.

구조적 요소는 함수 표현식과 같으나 코드상 구조는 변수에 할당한다는 점이 다르다. 아래를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Anonymous function expression (익명 함수 표현)
var expFunction = function (para, meter) {
return para + meter
}

// Named function expression (기명 함수 표현)
var expNamedFunction = function namedFunction (para, meter) {
return para + meter
}

console.log(expFunction('argu', 'ment')); // 'argument'
console.log(expNamedFunction('argu', 'ment')); // 'argument'
console.log(namedFunction('argu', 'ment')); // Uncaught ReferenceError: expNamedFunction is not defined

함수 표현식은 함수명을 생략하는 익명 함수 방식의 선언이 일반적이지만 기명 함수 표현으로 생성했을 경우, 스택 트레이스에 함수 이름이 포함되어 오류의 원인을 쉽게 찾을 수 있다는 이점 이 있습니다.

그리고 함수를 할당 받은 변수는 기명 함수라고 할지라도 함수명을 저장하는 것이 아니라 기명이든 익명이든 함수의 참조값을 저장하게 된다.

위 두개의 함수 정의문은 모두 함수 리터럴 방식 의 함수생성법으로 결국 Function Contstructor 함수로 함수 정의 방식을 단축화한 것으로 *일종의 축약법(short-way)*이라고 할 수 있다.

클로저

(Javascript의 클로저 개념은 다룰게 많기때문에 여기서는 함수와 클로저의 관계와 중심적 내용만 다루고 넘어가겠습니다. 자세한 내용은 포스팅할 예정입니다.)

Javascript의 함수를 언급할때 빠지지 않는 것이 있다면 바로 Closure 일 것이다. Closer 란 무엇인지 MDN에서 정의한 Closure 에 대한 내용을 보자.

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.


closure 는 주변 상태에 대한 참조와 함께 번들로 묶인 (포함된) 함수의 조합입니다. (lexical environment). 다른 말로, 클로저는 내부 함수에게 외부 변수의 스코프에 접근할 수 있도록 해주는 것이다. Javascript에서 클로저는 함수가 생성될 때마다 생성된다.

이전에 포스팅한 자바스크립트 실행 컨텍스트(Execution Context) 에 대해 알고 있다면 대략 무슨 뜻인지 이해할 수 있다. 즉, 함수와 함수의 참조 환경인 Lexical Environment의 조합으로 구성된 함수로써 자식함수가 부모함수의 Scope를 Outer Lexical Environment 로 참조(Reference)함으로써 Closer의 장점인 은닉화, 캡슐화 등의 장점을 통해 Public/Private 메서드 를 흉내낼 수 있다.

이해를 돕기 위해 예제를 보자.

1
2
3
4
5
6
7
8
9
function isUnder30 (age) {
let min = 30;
function checkAge (age) {
return age < min;
}
return checkAge(age)
}

isUnder30(29) // true

위 예제는 중첩 함수로써, 위에서 말한 클로저의 정의와 환경에 충족하고 있다. 실행 환경을 분석해보자.

  1. 실행 컨텍스트 스택에 전역 실행 컨텍스트 push
  2. isUnder30 가 호출 및 실행되면서 isUnder30 함수의 실행 컨텍스트가 push
    1. Lexical Environment에 변수 min, 내부함수 checkAge 그리고 Outer Lexical Environment로써 전역 실행 컨텍스트가 정의 및 참조되고
    2. 외부함수로 정의 가능한 isUnder30 함수가 checkAge 함수를 return 하면서
    3. checkAge 함수를 호출하고 isUnder30 함수의 함수 실행 컨텍스트는 종료된다.
  3. 그리고 내부함수인 checkAge 가 호출 및 실행되면서 checkAge 함수의 함수 실행 컨텍스트가 push
    1. Lexical Environment에 변수 min, 인자 age 그리고 Outer Lexical Environment로써 isUnder30 함수 실행 컨텍스트가 정의 및 참조되고
    2. 실행되는 동안 min 변수 값을 내부 스코프에서 검색하지 못함으로써 Outer Lexical Environment에서 검색해서 값을 가지고 와서
    3. return age < min; 결과값을 반환(return) 하면서
    4. checkAge 함수의 실행 컨텍스트 또한 종료된다.

위 순서를 보면 checkAge 가 isUnder30 보다 오래 유지되었고, checkAge가 실행되는 동안 isUnder30의 지역 스코프을 참조해 값을 반환하고 종료되었다. 이처럼 내부함수가 외부함수보다 오래 유지되었고, 내부함수가 외부함수의 지역 스코프(=Outer Lexical Environment)를 참조 및 접근할 수 있는 함수를 **클로저(Closure)**라고 한다.

함수 호이스팅

호이스팅이란 변수와 함수 선언을 코드가 실행되기 이전에 그들의 스코프의 최상위로 옮기는 Javascript 메커니즘으로 함수와 변수가 전역이든 지역 스코프든 그들의 스코프에 상관없이 최상위로 옮겨집니다. 그렇다면 함수는 호이스팅에 어떤 영향을 받으며 함수의 정의 방법에 따른 호이스팅의 차이는 어떻게 될까?

함수 선언문

1
2
3
4
5
6
7
8
9
10
function firstDecFn(para, meter) {
return para + meter
}

firstDecFn('argu', 'ment') // 'argument'
secondDecFn('argu', 'ment') // 'argument'

function secondDecFn(para, meter) {
return para + meter
}

기본적으로 함수 선언문은 Javascript 호이스팅의 기본 타겟 중 하나로 위 예제에서 firstDecFn 함수처럼 먼저 선언하고 후에 호출하든 secondDecFn 함수처럼 먼저 호출하고 나중에 선언하든 함수 선언문은 Javascipt의 호이스팅으로 모두 최상위로 끌어올려져 정상작동합니다.

함수 표현문

1
2
3
4
5
6
7
8
9
10
var firstDecFn = function (para, meter) {
return para + meter
}

firstDecFn('argu', 'ment') // 'argument'
secondDecFn('argu', 'ment') // Uncaught TypeError: secondDecFn is not a function

var secondDecFn = function (para, meter) {
return para + meter
}

결론부터 말하면 함수 표현문은 hoisting(끌어올림) 되지 않는다. 왜냐하면 Javascript Engine의 Parser 부분을 이해하면 쉬운데 Parser는 스크립트 내 변수를 분석할때 변수의 선언과 할당이 분리해 실행한다. 설명을 위해 간단한 변수 호이스팅을 예를 들어보자.

1
2
3
4
5
6
7
8
// 우리가 작성한 코드
console.log(x);
var x = 100;
-----------------------------------------------------------------------------------
// JavaScript Parser가 해석한 코드
var x;
console.log(x);
x = 100;

위 예제에서 “우리가 작성한 코드” 부분을 보면 console.log(x) 에서 변수 x를 호출하고, 다음줄에 var x = 100 이라고 선언했다. 그러나 “JavaScript Parser가 해석한 코드” 를 보면 선언과 할당 이 분리되어 있음을 볼 수 있다. 이러한 원리가 바로 Hoisting이고 함수 표현식으로 작성된 함수 또한 이 원리를 따르며 함수표현식의 예제를 Javascript가 해석한 코드로 변환하면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
var firstDecFn;
var secondDecFn;
firstDecFn = function (para, meter) {
return para + meter
}

firstDecFn('argu', 'ment') // 'argument'
secondDecFn('argu', 'ment') // Uncaught TypeError: secondDecFn is not a function

secondDecFn = function (para, meter) {
return para + meter
}

그렇다면 함수 표현식은 hoisting에 적용받지 못해 TypeError도 나는데 쓰지 말아야할까? 그것은 아니다. 함수 표현식이 가지고 있는 장점 또한 있다.

  • 클로저 사용
  • 콜백 함수 (함수의 인자로서 함수표현식으로 선언된 함수를 전달)
  • 즉시 실행 함수 (IIFE)

매개변수(Parameter)와 인자(Argument)

설명을 위해 예제 함수를 만들어보자.

1
2
3
4
5
function example(pa,ra,me,ter) {
return pa + ra + me + ter
}

example('ar', 'gu', 'me', 'nt')

위 예제를 보면 example 함수는 pa,ra,me,ter 라는 데이터를 받아 반환(return)하고, example 함수가 호출될때 'ar', 'gu', 'me', 'nt' 이라는 데이터를 넘겨줬다.

쉽게 정의하자면, 함수내에서 활용하느냐함수안으로 넘겨주느냐 의 차이이다. 편의상 이렇게 정의 가능하며 정확한 정의는 함수 정의하기 섹션의 함수 선언문 에서 정의한 내용이다.

  • 매개변수 : 함수 등에서 사용되는 전달된 값을 받는 변수로써, 함수의 기능상 외부 환경에서 데이터를 받아 가공 및 반환이 필요할 경우 함수 내부로 전달하는 역할을 하며 콤마(,) 로 구분하여 최대 255개까지 받을 수 있습니다.
  • 인자: 함수에 전달되는 인수의 배열집합으로써, 정확히는 유사배열 객체로 arguments 객체라 칭하며 매개변수를 통해 전달받은 함수 외부환경의 값, 변수 또는 참조이다.

매개변수 (Parameter)

매개변수란 ‘함수 등에서 사용되는 전달된 값을 받는 변수’ 로, 함수에서 정의한 매개변수가 충분히 오지 않으면 undefined 로 정의되며 ECMAScript 6 부터는 Default ParameterRest Parameter 가 추가 되었다.

매개변수 기본 처리방식
1
2
3
4
5
6
function example(pa,ra,me,ter) {
console.log(`me => ${me}, ter => ${ter}`) // me => undefined, ter => undefined
return pa + ra + me + ter
}

example('argu', 'ment') // argumentundefinedundefined
Default Parameter (기본 매개변수)

전달값이 없거나 undefined 인 경우 파라미터에 기본값으로 초기화 될 값을 할당

1
2
3
4
5
6
function example(pa,ra,me = 'me',ter = 'nt') {
console.log(`me => ${me}, ter => ${ter}`) // me => me, ter => nt
return pa + ra + me + ter
}

example('ar', 'gu') // argument
Rest Parameter (나머지 매개변수)

파라미터의 갯수가 불확실해 Array (배열) 로 인수를 넘길때 사용할 수 있다.

1
2
3
4
5
6
function double(...theArgs) {
return theArgs.map(x => x + x);
}

double(2, 1, 2, 3); // [4, 2, 4, 6]
double(2, 1, 2, 3, 9, 5); // [4, 2, 4, 6, 18, 10]

인자 (Argument)

인자(Argument)는 함수에 전달되는 인수의 배열집합 또는 모든 함수 내에서 이용 가능한 지역변수 로서 함수 호출시 전달되는 값을 할당 또는 참조해 함수 내에서 사용할 수 있습니다.

함수 내에서는 arguments 로 호출할 수 있으며 정확시는 유사객체 배열(array-like object) 에 속한다. 따라서 Javascript의 built-in function 중 Array 관련 메서드를 사용할 수 없으며 length 속성은 사용 가능합니다.

argument 객체는 배열과 같이 인덱스로 접근가능합니다.

1
2
3
4
5
6
7
8
9
10
function arg () {
console.log(arguments.length)
for(let i=0; i<arguments.length; i++) {
console.log(arguments[i])
}
}
arg(1, 2, 3)
// 1
// 2
// 3
argumentsArray 로 변환하기
1
2
var args = Array.from(arguments);
var args = [...arguments];

Function 프로토타입 객체와 프로퍼티

JavaScript에 함수는 Data Type에서 객체(Object)에 속하며, 모든 JavaScript 함수는 사실 Function 객체입니다.

1
2
3
4
5
6
7
8
9
10
// 함수 정의
function square(number) {
return number * number;
}

// 함수 프로퍼티 추가 Case
square.x = 10;
square.y = 20;

console.log(square.x, square.y); // 10, 20

아래의 함수 생성법들은 모두 true 를 반환하며 전역 Function 객체는 자신만의 메서드 또는 속성이 없으나 그 자체로 함수이기에 Function.prototype에서 프로토타입 체인을 통해 일부 메서드 및 속성을 상속 받습니다.

1
2
3
4
5
6
7
8
9
10
// 즉시 실행 함수 (IIFE)
(function(){}).constructor === Function // true

// 함수 선언식
function deFn () { return 'body' }
deFn.constructor === Function // true

// 함수 표현식
var exFn = function () { return 'body' }
exFn.constructor === Function // true

또한, 함수는 일반 객체와 다른 함수만의 프로퍼티와 메서드를 갖습니다.

Function 프로토타입 객체
메서드

화살표 함수 (Arrow Function)

ES6 부터 함수를 생성하는 방법으로 함수 표현식보다 단순하고 간결한 화살표 함수 => 가 추가되었다.

특징

  • 함수 표현식보다 구문이 짧고,
  • 자신의 this, arguments, super 또는 new.target을 바인딩 하지 않는다
  • 항상 익명 함수이다.
  • 메소드 함수가 아닌 곳에 즉, 인자로서 부모 함수가 넘겨줄 callback 함수에 적합하다.
  • 함수 생성자 (function constructor)로서 사용할 수 없다.
  • 반환해야 할 값이 있을때 return{} 를 생략할 수 있다.
1
2
3
4
function double (n) {
return n * 2;
}
let double = n => n * 2
1
2
3
4
5
const numbers = [0, 2, 4, 6, 8];
let double = n => n * 2
console.log(numbers.map(double));
console.log(numbers.map(number => number * 2));
// expected output: Array [0, 4, 8, 12, 16]

기본 구문

1
2
3
4
5
6
7
8
9
10
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// 다음과 동일함: => { return expression; }

// 매개변수가 하나뿐인 경우 괄호는 선택사항:
(singleParam) => { statements }
singleParam => { statements }

// 매개변수가 없는 함수는 괄호가 필요:
() => { statements }

고급 구문

1
2
3
4
5
6
7
8
9
10
// 객체 리터럴 표현을 반환하기 위해서는 함수 본문(body)을 괄호 속에 넣음:
params => ({foo: bar})

// 나머지 매개변수 및 기본 매개변수를 지원함
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements }

// 매개변수 목록 내 구조분해할당도 지원됨
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6

(화살표 함수의 자세한 내용에 대해서는 추후 포스팅할 예정입니다.)


참조