티스토리 뷰

00. TL;DR

  • JavaScript의 함수는 일급 객체(first-class object)로서 변수에 할당하거나, 다른 함수의 인수로 전달하거나, 함수의 반환 값으로 사용될 수 있습니다.
  • 클로저(closure)를 사용하여 유연한 프로그래밍이 가능하게 합니다.
  • 함수를 다양한 방법으로 정의할 수 있습니다.

01. 함수란? (What is a Function?)

함수(Function)는 프로그래밍 언어에서 특정한 작업을 수행하기 위해 설계된 독립적인 코드 블록입니다.

마치 수학의 함수처럼 입력을 받아(매개변수) 특정 연산을 수행한 후 결과를 반환할 수 있습니다. JavaScript에서 함수는 단순히 코드의 묶음 이상의 의미를 가지며, 다음과 같은 중요한 역할을 합니다.

  • 코드 재사용성 증진: 동일한 코드를 여러 번 작성하는 대신 함수를 한 번 정의하고 필요할 때마다 호출함으로써 코드의 중복을 줄이고 효율성을 높입니다.
  • 모듈화 및 코드 조직화: 코드를 논리적인 기능 단위로 분리하여 관리하기 쉽도록 만들고, 전체 프로그램의 구조를 명확하게 합니다.
  • 추상화: 복잡한 내부 구현을 숨기고, 간단한 인터페이스를 통해 기능을 사용할 수 있도록 하여 코드의 이해도를 높입니다.
  • 유지보수 용이성: 코드가 함수 단위로 분리되어 있으면 특정 기능의 변경이나 오류 수정 시 해당 함수만 집중적으로 관리할 수 있어 유지보수가 용이해집니다.
  • 테스트 용이성: 각 함수는 독립적인 단위로 테스트할 수 있어 코드의 신뢰성을 높이는 데 도움이 됩니다.

JavaScript에서 함수는 객체(Object)의 한 종류이며, 특별한 기능을 가진 객체로 취급됩니다. 이러한 특징 덕분에 함수를 변수에 할당하거나, 다른 함수의 인수로 전달하거나, 함수의 반환 값으로 사용할 수 있는 일급 객체(First-Class Object)의 성질을 가집니다.


02. 함수 리터럴 (Function Literal)

함수 리터럴(Function Literal)은 함수를 정의하는 표현식입니다.

함수 리터럴은 객체 리터럴({})이나 배열 리터럴([])을 사용하여 객체나 배열을 생성하는 것과 유사합니다.

함수 리터럴은 주로 익명 함수를 생성할 때 사용되며, 변수에 할당하거나 다른 함수의 인수로 전달하는 등 다양한 방식으로 활용됩니다.

정의 방법:

var myFunction = function(매개변수1, 매개변수2, ...) {
  // 함수 몸체 (실행될 코드)
  return 반환값;
};

설명:

  • function: 함수 리터럴을 시작하는 키워드입니다.
  • (매개변수1, 매개변수2, ...): 함수가 받을 수 있는 매개변수 목록을 괄호 안에 쉼표로 구분하여 나열합니다. 매개변수가 없으면 빈 괄호 ()를 사용합니다.
  • { ... }: 중괄호 안에는 함수의 실제 동작을 정의하는 코드 블록인 함수 몸체가 위치합니다.
  • return 반환값;: 함수 실행의 결과를 호출자에게 반환하는 구문입니다. return 문이 없거나 값 없이 사용되면 함수는 undefined를 반환합니다.
  • var myFunction = ...;: 함수 리터럴로 생성된 익명 함수는 변수 myFunction에 할당됩니다. 이제 myFunction 변수를 통해 함수를 호출할 수 있습니다.

예제:

// 함수 리터럴을 사용하여 익명 함수를 변수에 할당
var add = function(a, b) {
  return a + b;
};

console.log(add(5, 3)); // 출력: 8

// 함수 리터럴을 다른 함수의 인수로 전달 (콜백 함수)
function operate(num1, num2, operation) {
  return operation(num1, num2);
}

var multiply = function(x, y) {
  return x * y;
};

var result = operate(10, 2, multiply);
console.log(result); // 출력: 20

03. 함수 정의 방법 (How to Define Functions)

JavaScript에서는 다양한 방식으로 함수를 정의할 수 있습니다.

각 방법은 약간의 차이점을 가지며, 상황에 따라 적절한 방법을 선택하여 사용할 수 있습니다.

03.01. 함수 선언 (Function Declaration)

정의 방법:

function 함수이름(매개변수1, 매개변수2, ...) {
  // 함수 몸체 (실행될 코드)
  return 반환값;
}

설명:

  • 함수 선언은 function 키워드로 시작하고, 반드시 함수 이름이 있어야 합니다. 매개변수 목록은 괄호 안에 쉼표로 구분하여 작성하며, 함수 몸체는 중괄호 {}로 둘러싸입니다.
  • 함수 선언의 가장 큰 특징은 함수 호이스팅(Function Hoisting)이 발생한다는 점입니다.
  • 따라서 함수를 선언하기 전에 해당 함수를 호출하는 코드를 작성해도 에러가 발생하지 않습니다.

예제:

console.log(subtract(10, 4)); // 함수 선언 전에 호출해도 작동 (호이스팅)

function subtract(a, b) {
  return a - b;
}

// 함수 호출
var result = subtract(15, 7);
console.log(result); // 출력: 8

03.02. 함수 표현식 (Function Expression)

정의 방법:

var 변수이름 = function(매개변수1, 매개변수2, ...) {
  // 함수 몸체
  return 반환값;
};

설명:

  • 함수 표현식은 함수 리터럴을 변수에 할당하는 방식으로 함수를 정의합니다.
  • 함수 표현식에서는 함수 이름은 선택 사항이며, 이름을 생략하면 익명 함수가 됩니다.
  • 함수 표현식은 해당 코드가 실제로 실행되는 시점에 함수 객체가 생성됩니다.
  • 따라서 함수 표현식으로 정의된 함수는 변수가 선언되기 전에 호출할 수 없습니다.
  • 이는 변수 호이스팅 규칙(변수 선언은 호이스팅되지만, 값의 할당은 해당 코드가 실행될 때 이루어짐)과 동일하게 적용되기 때문입니다.
  • 함수 표현식은 콜백 함수를 정의하거나, 클로저를 생성하거나, 모듈 패턴을 구현하는 등 다양한 상황에서 유용하게 사용됩니다.
  • 또한, 필요에 따라 이름을 가진 함수 표현식(Named Function Expression)을 사용할 수도 있으며, 이는 주로 디버깅 시 함수 이름을 명확하게 표시하거나, 함수 내부에서 자신을 재귀적으로 호출할 때 사용됩니다.

예제:

// 익명 함수 표현식
var multiply = function(a, b) {
  return a * b;
};

console.log(multiply(3, 9)); // 출력: 27

// 명명된 함수 표현식
var factorial = function recursiveFactorial(n) {
  if (n <= 1) {
    return 1;
  } else {
    return n * recursiveFactorial(n - 1); // 함수 내부에서 자신을 호출할 때 유용
  }
};

console.log(factorial(5)); // 출력: 120

// 함수 표현식은 변수 호이스팅 규칙을 따르므로 선언 전에 호출하면 에러 발생
// console.log(divide(10, 2)); // Error: Cannot read property 'call' of undefined
var divide = function(a, b) {
  return a / b;
};

03.03. Function 생성자 함수 (Function Constructor)

정의 방법:

var 함수이름 = new Function('매개변수1', '매개변수2', ..., '함수 몸체');

설명:

  • Function 생성자 함수는 문자열 형태로 매개변수 목록과 함수 몸체를 전달하여 동적으로 함수를 생성하는 방법입니다.
  • 첫 번째부터 마지막 직전의 인수는 함수의 매개변수 이름으로 취급되며, 마지막 인수는 함수의 몸체를 나타내는 문자열입니다.

주의사항: Function 생성자 함수를 사용하는 것은 일반적으로 권장되지 않습니다. 다음과 같은 이유 때문입니다.

  • 보안 위험: 문자열로 코드를 전달하므로 악의적인 코드가 주입될 가능성이 있어 보안에 취약할 수 있습니다.
  • 성능 저하: Function 생성자 함수로 생성된 함수는 생성될 때마다 코드를 파싱하고 컴파일해야 하므로 일반적인 함수 정의 방식에 비해 성능이 떨어집니다.
  • 스코프 문제: Function 생성자 함수로 생성된 함수는 전역 스코프에서 실행됩니다. 이는 예상치 못한 변수 충돌이나 접근 문제를 일으킬 수 있습니다.
  • 가독성 저하: 함수 몸체가 문자열로 표현되어 코드의 가독성이 떨어집니다.

예제:

var power = new Function('base', 'exponent', 'return Math.pow(base, exponent);');

console.log(power(2, 3)); // 출력: 8

var greet = new Function('name', 'console.log("Hello, " + name + "!");');
greet('Alice'); // 출력: Hello, Alice!

이러한 단점들로 인해 Function 생성자 함수는 특별한 경우가 아니면 사용하지 않는 것이 좋습니다.

03.04. 화살표 함수 (Arrow Function)

정의 방법:

// 기본 형태
(매개변수1, 매개변수2, ...) => {
  // 함수 몸체
  return 반환값;
};

// 매개변수가 하나인 경우 괄호 생략 가능
매개변수 => {
  // 함수 몸체
  return 반환값;
};

// 함수 몸체가 하나의 표현식인 경우 중괄호와 return 키워드 생략 가능
(매개변수1, 매개변수2, ...) => 반환값;

// 매개변수가 없는 경우 빈 괄호 사용
() => {
  // 함수 몸체
  return 반환값;
};

 

설명:

  • 화살표 함수는 ES6(ECMAScript 2015)에서 도입된 간결한 함수 표현식입니다.
  • function 키워드 대신 화살표(=>)를 사용하여 함수를 정의합니다. 화살표 함수는 익명 함수로만 사용할 수 있으며, 일반적인 함수 표현식에 비해 문법이 더 간결하고 this 바인딩 방식에 차이가 있습니다.

주요 특징:

  • 간결한 문법: 특히 함수 몸체가 하나의 표현식으로 이루어진 경우 매우 간결하게 함수를 작성할 수 있습니다.
  • 렉시컬 this 바인딩: 화살표 함수 내부의 this는 함수가 정의된 주변 환경(정적 스코프)의 this 값을 상속받습니다. 이는 일반 함수의 this가 함수가 호출되는 방식에 따라 동적으로 결정되는 것과 대조적입니다. 이러한 특징은 콜백 함수 내에서 this 컨텍스트를 유지하는 데 유용합니다.
  • arguments 객체 부재: 화살표 함수 내부에서는 arguments 객체를 사용할 수 없습니다. 대신 Rest 파라미터(...args)를 사용하여 모든 인수를 배열 형태로 받을 수 있습니다.
  • 생성자 함수로 사용 불가: 화살표 함수는 new 키워드와 함께 사용하여 생성자 함수로 호출할 수 없습니다.

예제:

// 기본 형태
var sum = (a, b) => {
  return a + b;
};
console.log(sum(1, 8)); // 출력: 9

// 매개변수가 하나인 경우 괄호 생략
var square = x => x * x;
console.log(square(5)); // 출력: 25

// 함수 몸체가 하나의 표현식인 경우 중괄호와 return 생략
var isEven = num => num % 2 === 0;
console.log(isEven(10)); // 출력: true

// 매개변수가 없는 경우
var sayHi = () => console.log("Hi!");
sayHi(); // 출력: Hi!

// 렉시컬 this 바인딩 예제
function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++; // this는 Person 객체를 가리킴
    console.log(this.age);
  }, 1000);
}

var person = new Person();

// arguments 객체 대신 Rest 파라미터 사용
var printArgs = (...args) => {
  console.log(args);
};
printArgs(1, 2, 3); // 출력: [1, 2, 3]

04. 함수 생성 시점 및 함수 호이스팅 (Function Creation Timing and Hoisting)

JavaScript에서 함수가 언제 생성되고, 호이스팅은 어떻게 발생하는지 이해하는 것은 코드를 예측 가능하게 작성하는 데 중요합니다.

04.01. 함수 생성 시점 (Function Creation Timing)

  • 함수 선언 (Function Declaration):
    • JavaScript 엔진은 코드를 실행하기 전에 파싱(parsing) 단계에서 함수 선언을 발견하고 함수 객체를 생성하여 메모리에 저장합니다.
    • 따라서 함수 선언은 코드의 어느 위치에서든 호출할 수 있습니다.
  • 함수 표현식 (Function Expression):
    • 함수 표현식은 코드가 실제로 실행되는 런타임(runtime) 시점에 함수 리터럴이 평가되어 함수 객체가 생성되고 변수에 할당됩니다.
  • Function 생성자 함수 (Function Constructor):
    • Function 생성자 함수는 코드가 실행될 때마다 동적으로 새로운 함수 객체를 생성합니다.
    • 이는 다른 함수 정의 방식에 비해 성능상의 부담이 있을 수 있습니다.
  • 화살표 함수 (Arrow Function):
    • 화살표 함수는 함수 표현식과 마찬가지로 코드가 실행되는 런타임 시점에 함수 객체가 생성되어 변수에 할당됩니다.

04.02. 함수 호이스팅 (Function Hoisting)

호이스팅(Hoisting)은 JavaScript 엔진이 코드를 실행하기 전에 모든 변수 선언과 함수 선언을 해당 스코프의 최상단으로 끌어올리는 것처럼 보이는 동작입니다.

  • 함수 선언 호이스팅:
    • 함수 선언은 함수 전체가 호이스팅됩니다.
    • 즉, 함수를 선언하기 전에 해당 함수를 호출하는 코드를 작성해도 JavaScript 엔진은 오류 없이 코드를 실행합니다.
  • 함수 표현식 호이스팅:
    • 함수 표현식의 경우, 변수 선언 자체는 호이스팅되지만, 변수에 함수가 할당되는 시점은 런타임입니다.
    • 따라서 함수 표현식을 사용하여 함수를 정의하기 전에 해당 변수를 통해 함수를 호출하려고 하면 undefined 에러가 발생하거나, 변수가 아직 초기화되지 않았다는 에러가 발생할 수 있습니다.

05. 다양한 함수 형태

05.01. 즉시 실행 함수 표현식 (Immediately Invoked Function Expression, IIFE)

실무 예제:

const module = (() => {
    let count = 0;
    return {
        increase() {
            count++;
            return count;
        },
        get() { 
            return count;
        }
    }
})();

 

설명:

  • IIFE를 사용하여 private과 public 인터페이스를 제공합니다.
  • 즉시 함수를 실행하여 그 결과를 반환하면서 외부 스코프를 오염시키지 않기 위해 사용되는 패턴입니다.

05.02. 재귀 함수 (Recursive Function)

실무 예제:

const tree = [
  {
    name: 'a1',
    children: [
      { name: 'a1-1', children: [] },
      { 
        name: 'a1-2', children: [
            { name: 'a1-2-1', children: [] },
            { name: 'a1-2-2', children: [] },
            { name: 'a1-2-3', children: [] },
        ]
      },
      { name: 'a1-3', children: [] },
    ],
  }
]

const recursive = (nodeList) => {
    nodeList.forEach((node) => {
        console.log('node --->', node.name);
        recursive(node.children)
    });
}

recursive(tree);

 

설명:

  • 재귀 함수를 이용하여 계층 구조를 갖는 자료구조를 DFS(깊이 우선 탐색)하는 예제입니다.

05.03. 중첩 함수 (Nested Function)

실무 예제:

const sumBit = (values) => {
    const sum = (bitArray) => {
      let result = 0
      for(let item of bitArray) {
        result |= item;
      }
      return result;
    };

    return sum(values);
}

sumBit([1, 2, 4, 8, 16]);

 

설명:

  • 함수 안에 함수(중첩 함수)를 정의하여 함수 호출시 내부 함수가 생성되어 결과를 계산하는 예시입니다.

05.04. 고차 함수 (Higher-Order Function)

실무 예제:

const currencyFormatter = (value) => {
    const currencyMapper = {
        ['ko-KR']: 'KRW',
        ['ja-JP']: 'JPY',
        ['en-US']: 'USD',
        ['zh-TW']: 'TWD',
        ['zh-CN']: 'CNY',
    };
    return (locale) => {
        // https://ko.wikipedia.org/wiki/ISO_4217
        const currency = currencyMapper[locale] ?? currencyMapper['ko-KR'];
        console.log(currency);
        return new Intl.NumberFormat(locale, { style: "currency", currency }).format(value);
    }
}

currencyFormatter(1123123.125)('en-US');

 

설명:

  • 중첩 함수를 이용하여 특정 로케일에 따라 숫자를 통화 형식으로 포맷팅하는 함수를 생성하고 실행하는 예시입니다.

05.05. 콜백 함수 (Callback Function)

실무 예제:

const p = (resolver) => {
    const thenable = {
        then: (thenFn) => {
            resolver((resolvedValue) => {
                thenFn(resolvedValue)
            });
        }
    };

    return thenable;
}

p((resolve) => {
    setTimeout(() => {
        resolve('1111')
    }, 1000);
}).then((v) => {
    console.log('>>>>>>>>>>', v);
});

 

설명:

  • 콜백을 이용해 Promise와 비슷한 인터페이스를 제공하는 예시입니다.

05.06. 순수 함수 (Pure Function)

실무 예제:

const students = [
  { name: 'Odin', grade: 3, major: '컴퓨터공학', score: 85 },
  { name: 'James', grade: 2, major: '수학', score: 92 },
  { name: 'B', grade: 3, major: '컴퓨터공학', score: 78 },
  { name: 'Derrick', grade: 1, major: '영문학', score: 95 },
  { name: 'Ethan', grade: 3, major: '전자공학', score: 88 },
];

// 1. 특정 학년 학생 필터
const filterByGrade = (students, grade) => {
  const result = [];
  for (const student of students) {
    if (student.grade === grade) {
      result.push(student);
    }
  }
  return result;
};

// 2. 특정 전공 학생 필터
const filterByMajor = (students, major) => {
  const result = [];
  for (const student of students) {
    if (student.major === major) {
      result.push(student);
    }
  }
  return result;
};

// 3. 특정 점수 이상인 학생 필터
const filterByScoreAbove = (students, minScore) => {
  const result = [];
  for (const student of students) {
    if (student.score >= minScore) {
      result.push(student);
    }
  }
  return result;
};

// 3학년 학생 필터링
const grade3Students = filterByGrade(students, 3);
console.log("3학년 학생:", grade3Students);

// 3학년 중 컴퓨터공학 전공 학생 필터링
const grade3ComputerScienceStudents = filterByMajor(grade3Students, '컴퓨터공학');
console.log("3학년 컴퓨터공학 전공 학생:", grade3ComputerScienceStudents);

// 3학년 중 컴퓨터공학 전공 학생 중 점수가 80점 이상인 학생 필터링
const qualifiedGrade3ComputerScienceStudents = filterByScoreAbove(grade3ComputerScienceStudents, 80);
console.log("3학년 컴퓨터공학 전공 학생 중 80점 이상:", qualifiedGrade3ComputerScienceStudents);

 

설명:

  • 학생 목록에서 필터 조건에 맞는 학생 목록을 추출하기 위해 순수 함수가 사용된 예시입니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함