[ javascript ] 공식문서 읽고 열흘 벼락치기: Functions

김쟇연·2025년 9월 5일
post-thumbnail

📝 Day 04 – 함수 (Functions)

공식 문서 해당 챕터 바로가기 ! 🐱

들어가며

함수는 자바스크립트에서 오지게 중요한 블록이다. 같은 코드 계속 복붙할 바에, 그냥 한 군데 쳐넣고 이름 하나 박아두면 된다. 입력 들어오면 겁나 굴려서 처리하고 결과를 던지신다. 함수는 선언(또는 표현)된 스코프(scope) 안에서만 불러와 부려먹을 수 있고, 입력과 출력 사이에는 이해 가능한 관계가 존재해야 한다.


함수 정의(Declaration)

함수 선언문function 키워드, 함수 이름, 괄호 안 매개변수 목록, 그리고 중괄호 블록으로 구성된다.

function square(number) {
  return number * number;
}
  • 매개변수는 값에 의해 전달(pass-by-value)된다. 즉, 함수 몸체에서 매개변수 변수 자체에 새 값을 대입해도 호출자에 반영되지 않는다.
  • 단, 객체/배열의 참조값이 전달되므로, 프로퍼티(혹은 요소)를 변경하면 호출자 쪽에서도 변화가 보인다.
function rename(car) {
  car.make = "Toyota"; // 프로퍼티 변경 → 바깥에서도 보임
}
const myCar = { make: "Honda" };
rename(myCar);
console.log(myCar.make); // "Toyota"
  • 함수는 중첩될 수 있으며, 내부 함수는 바깥 함수의 식별자에 접근한다(아래 “스코프와 클로저” 참조).

함수 표현식(Expression)

함수는 표현식으로도 만들 수 있다. 익명일 수도 있고, 이름을 가진 표현식으로도 작성할 수 있다.

// 익명 함수 표현식
const square = function (n) {
  return n * n;
};

// 이름이 있는 함수 표현식 (재귀/디버깅에 유리)
const factorial = function fac(n) {
  return n < 2 ? 1 : n * fac(n - 1);
};

표현식은 값처럼 전달될 수 있어 고차함수 인자에 쓰기 좋다.

function map(f, arr) {
  const out = new Array(arr.length);
  for (let i = 0; i < arr.length; i++) out[i] = f(arr[i]);
  return out;
}
const cubed = map(function (x) { return x * x * x; }, [0, 1, 2, 5, 10]);
console.log(cubed); // [0, 1, 8, 125, 1000]

조건에 따라 동적으로 정의할 수도 있다(필요한 경로에서만 생성).

let myFunc;
if (num === 0) {
  myFunc = function (obj) { obj.make = "Toyota"; };
}

참고: 런타임에 문자열로부터 함수를 만드는 Function 생성자도 있지만, 보안·최적화 측면에서 권장되지 않는다.


메서드(Method)

메서드는 객체의 프로퍼티로 존재하는 함수이다.

const counter = {
  value: 0,
  inc() { this.value += 1; },
};
counter.inc();

함수 호출(Calling)

함수를 정의하는 것호출하는 것은 별개이다. 호출 시 인자를 넘기고, 함수는 지정된 로직을 실행해 값을 반환한다.

function square(n) { return n * n; }
square(5); // 25

함수는 자기 자신을 호출(재귀)할 수 있다.

function factorial(n) {
  if (n === 0 || n === 1) return 1;
  return n * factorial(n - 1);
}

함수도 객체이므로, 호출 문맥을 제어하는 메서드를 가진다.

  • fn.call(thisArg, ...args)
  • fn.apply(thisArg, argsArray)
  • fn.bind(thisArg) → 새 함수 반환(지연 호출용)

함수 호이스팅(Hoisting)

선언문은 스코프의 최상단으로 끌어올려진 것처럼 동작한다.

console.log(square(5)); // 25
function square(n) { return n * n; }

반면 함수 표현식은 호이스팅되지 않는다(변수 초기화 전 접근 불가).

console.log(square(5)); // ReferenceError
const square = function (n) { return n * n; };

재귀(Recursion)와 호출 스택

재귀는 루프와 유사하게 반복 기제로 쓰이며, 탈출 조건이 필수다. 호출은 스택에 쌓였다가 역순으로 정리된다.

function foo(i) {
  if (i < 0) return;
  console.log(`begin: ${i}`);
  foo(i - 1);
  console.log(`end: ${i}`);
}
foo(3);
// begin: 3 → 2 → 1 → 0 → end: 0 → 1 → 2 → 3

트리 순회처럼 자연스러운 계층 구조에는 재귀가 간결하다.

function walk(node) {
  if (!node) return;
  // do something with node
  for (const child of node.childNodes) walk(child);
}

즉시 실행 함수 표현식(IIFE)

정의하자마자 즉시 호출하는 패턴으로, 독립된 스코프를 마련하거나 초기화 로직을 캡슐화할 때 유용하다.

const getCode = (function () {
  const secret = "0]Eal(eh&2";
  return function () { return secret; };
})();
console.log(getCode()); // 비공개 데이터 접근

함수 스코프와 클로저(Closures)

함수는 자기만의 스코프를 만든다. 내부 함수는 바깥 스코프의 식별자를 기억하고, 바깥 스코프 수명이 끝난 뒤에도 접근할 수 있다 → 클로저.

const pet = function (name) {
  return function getName() { return name; }; // name을 캡처
};
const myPet = pet("Vivie");
console.log(myPet()); // "Vivie"

여러 함수를 묶어 상태를 은닉할 수도 있다.

const createPet = function (name) {
  let sex;
  return {
    setName(newName) { name = newName; },
    getName() { return name; },
    setSex(newSex) {
      const s = String(newSex).toLowerCase();
      if (s === "male" || s === "female") sex = s;
    },
    getSex() { return sex; },
  };
};

스코프 체이닝: 더 안쪽 스코프가 언제나 우선한다(이름 충돌 시 내부가 승리).

function outside() {
  const x = 5;
  function inside(x) { return x * 2; }
  return inside;
}
console.log(outside()(10)); // 20 (내부 x가 우선)

arguments 객체

모든 함수 내부에는 배열 비슷한 arguments가 있다(ES5 스타일). 인덱스와 length는 있으나, 배열 메서드는 없다.

function myConcat(separator) {
  let out = "";
  for (let i = 1; i < arguments.length; i++) {
    out += arguments[i] + separator;
  }
  return out;
}
console.log(myConcat(", ", "red", "blue")); // "red, blue, "

실무 팁: ES6 이후에는 rest 파라미터(...args)가 더 안전하고 타입 친화적이다.


함수 파라미터: 기본값과 Rest

기본값 파라미터

function multiply(a, b = 1) {
  return a * b;
}
multiply(5); // 5

과거에는 본문에서 typeof b === "undefined"로 체크했으나, 이제는 머리에서 바로 지정한다.

Rest 파라미터

function multiply(multiplier, ...nums) {
  return nums.map((x) => multiplier * x);
}
multiply(2, 1, 2, 3); // [2, 4, 6]

화살표 함수(Arrow Functions)

짧은 문법this 비바인딩(렉시컬 this)이 특징이다. 자체 this/arguments/super/new.target을 가지지 않는다. 항상 익명이다.

const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
const len1 = a.map(function (s) { return s.length; });
const len2 = a.map((s) => s.length); // 간결

렉시컬 this: 바깥 컨텍스트의 this를 그대로 사용한다. 콜백에서 self = this 해법이나 bind 없이 의도가 보존된다.

function Person() {
  this.age = 0;
  setInterval(() => {
    this.age++; // Person 인스턴스를 가리킴
  }, 1000);
}
const p = new Person();

마무리

  • 선언 vs 표현: 선언문은 호이스팅, 표현식은 값처럼 전달.
  • 전달 방식: 원시값은 값 복사, 객체/배열은 참조 전달 → 프로퍼티 변경은 외부에 반영.
  • 호출 컨트롤: call/apply/bind로 문맥·인자 제어.
  • 스코프/클로저: 내부 함수가 바깥 식별자를 기억해 상태 은닉·API 구성에 유용.
  • 파라미터 문법: 기본값·Rest로 선언부에서 의도를 명확히.
  • 화살표 함수: 간결한 표기 + 렉시컬 this로 콜백 코드 안정화.
  • IIFE: 초기화/은닉 스코프를 즉시 마련할 때 사용.

요약하면, 함수는 로직 재사용의 단위이자 스코프의 경계이다. 선언 방식(선언문/표현식), 호출 문맥(this), 파라미터 문법(기본값/Rest), 그리고 클로저를 이해하면 JavaScript 코드의 구조와 유지보수성이 눈에 띄게 좋아진다.

profile
그래도 해야지 어떡해

0개의 댓글