notes
18장. 함수와 일급 객체
모던 JS Deep Dive — 함수의 값·객체성·일급 객체 특성 핵심 정리.
참고: 이웅모 《모던 자바스크립트 Deep Dive》 18장 · [MDN First-class Function](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function) · [MDN arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments)
함수를 “실행 버튼”으로만 보면 콜백·고차함수·클로저가 갑자기 마법처럼 보인다. JS에서 함수는 값이자 객체다. 변수에 담고, 인자로 넘기고, 반환한다. 그게 일급 객체(first-class)다. 버튼이 아니라 이동 가능한 행동 덩어리다.
flowchart LR
F[함수 객체] --> V[변수에 할당]
F --> A[인자로 전달]
F --> R[반환값으로 전달]
F --> P[프로퍼티 소유]
P --> ARG[arguments]
P --> LEN[length]
P --> NAME[name]
P --> PROTO[prototype]
18.1 일급 객체
일급이면 대략 이런 일이 된다.
- 런타임에 생성 가능
- 변수·자료구조에 저장 가능
- 함수 인자로 전달 가능
- 함수 결과로 반환 가능
JS 함수는 위 조건을 충족한다. 그래서 map/filter에 함수를 넘기고, 미들웨어를 합성하고, 이벤트 리스너를 등록한다.
const add = function (a, b) {
return a + b;
};
function operate(fn, x, y) {
return fn(x, y); // 행동을 인자로
}
function makeMultiplier(k) {
return function (n) {
return n * k; // 행동을 반환
};
}
console.log(operate(add, 2, 3)); // 5
const triple = makeMultiplier(3);
console.log(triple(4)); // 12
WHY: “함수를 어떻게 부르나”만 알면 12장, “함수를 어디로 옮기나”를 알면 18장이다. 콜백은 이 이동성 위의 별칭이다.
함수는 객체이므로 프로퍼티를 붙일 수도 있다(실무에선 남용 금지).
function ping() {
ping.count = (ping.count || 0) + 1;
return 'pong';
}
ping();
ping();
console.log(ping.count); // 2
18.2 함수 객체의 프로퍼티
console.dir(fn)으로 보면 함수도 키를 가진다. 자주 만지는 것만 정리한다.
18.2.1 arguments — 호출 시점의 인수 묶음
함수 안에서만 보이는 유사 배열(iterable) 객체. 전달된 인수 정보를 담는다.
arguments.length= 실제 넘어온 인수 개수- 인덱스로 접근
arguments[0] - 진짜 Array는 아니라
map이 바로 안 붙는다 →Array.from(arguments)또는 rest
JS는 매개변수 개수와 인수 개수를 엄격히 맞추지 않는다. 더 오면 남고, 덜 오면 undefined. 예전엔 가변 인자를 arguments로 받았다.
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
모던 대체:
function sumModern(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
함정:
- 화살표 함수는 자기 arguments가 없다. 상위 스코프 arguments를 본다(또는 없음).
- strict mode에선 매개변수 재할당과 arguments의 연결이 끊긴다(20.6.2).
Function.arguments같은 외부 접근은 폐기·비권장. 함수 내부 식별자arguments만 생각하자.
const outer = function () {
const arrow = () => {
// console.log(arguments); // 화살표 기준이 아님 — outer의 arguments를 볼 수 있음
};
arrow();
};
18.2.2 caller — 참고만, 실무 금지
비표준. 함수를 호출한 쪽을 가리키려 했던 흔적. 보안·최적화·가독성 모두 이유로 쓰지 않는다. “있다” 정도만.
18.2.3 length — 정의 시점의 매개변수 개수
fn.length는 선언한 파라미터 개수다. 실제 인수 개수(arguments.length)와 다를 수 있다.
function zero() {}
function one(a) {}
function two(a, b) {}
function withRest(a, ...rest) {}
console.log(zero.length); // 0
console.log(one.length); // 1
console.log(two.length); // 2
console.log(withRest.length); // 1 — rest는 length에 안 잡히는 편
기본값 파라미터도 length 계산에 영향을 준다(첫 기본값 이전까지만 세는 식). 라이브러리 메타프로그래밍·커리에서 가끔 본다.
function f(a, b = 1, c) {}
console.log(f.length); // 1
18.2.4 name — 함수 이름 문자열
디버거·스택 트레이스·개발자 도구에 찍히는 이름. ES6에서 표준화되며 익명 표현식도 할당된 식별자를 이름으로 추론하는 경우가 많다.
function declared() {}
console.log(declared.name); // "declared"
const expr = function named() {};
console.log(expr.name); // "named" (기명 표현식의 내부 이름)
const anon = function () {};
console.log(anon.name); // ES6+ 환경에선 보통 "anon"
함정: 묶어서 export하거나 bind하면 이름이 바뀌거나 bound 접두가 붙는다. 스택만 보고 원본을 단정하지 말 것.
18.2.5 __proto__ 접근자 — [[Prototype]] 창구
모든 객체(함수 포함)는 [[Prototype]] 내부 슬롯을 갖는다. __proto__는 그걸 건드리는 접근자(레거시 창구). 진단용. 생성·연결은 Object.create / Object.getPrototypeOf / Object.setPrototypeOf(최후) / class·new 경로를 쓴다. 본편은 19장.
function foo() {}
console.log(Object.getPrototypeOf(foo) === Function.prototype); // true
console.log(foo.__proto__ === Function.prototype); // 대개 true — 직접 의존은 비추
18.2.6 prototype — constructor만의 인스턴스 쪽 청사진
prototype 프로퍼티는 constructor(new 가능한 함수)가 가진다. new Foo()로 만든 인스턴스의 [[Prototype]]이 Foo.prototype을 가리킨다.
function Foo() {}
console.log(Foo.hasOwnProperty('prototype')); // true
console.log(({}).hasOwnProperty('prototype')); // false
const f = new Foo();
console.log(Object.getPrototypeOf(f) === Foo.prototype); // true
화살표·축약 메서드처럼 non-constructor는 이 프로퍼티가 없거나 new가 막힌다(17.2.5). “함수니까 전부 prototype이 있다”는 거짓.
호출 네 갈래와의 연결 (예고)
함수가 값으로 옮겨진 뒤 어떻게 호출되느냐가 this를 가른다(22장).
fn()일반 호출obj.method()메서드 호출new Fn()생성자 호출fn.call/apply/bind간접 호출
일급이라 1→2로 “옮겨 붙이기”가 쉽다. 그래서 this 분실이 자주 난다.
const obj = {
n: 1,
m() { return this.n; },
};
const detached = obj.m; // 값은 옮겨짐
// detached(); // strict에선 this undefined → 깨짐
const bound = obj.m.bind(obj);
console.log(bound()); // 1
예제 — 고차로 사고 연습
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
function boot(label) {
console.log('boot', label);
return label;
}
const bootOnce = once(boot);
bootOnce('a');
bootOnce('b'); // 두 번째 본문 실행 안 함
WHY: once는 함수를 받아 함수를 돌려준다. 일급이 아니면 패턴 자체가 성립하지 않는다.
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
const trimLower = pipe(
(s) => s.trim(),
(s) => s.toLowerCase(),
);
console.log(trimLower(' Hi ')); // "hi"
헷갈리던 장면 / 함정
- arguments.length vs fn.length 혼동
- 화살표 + arguments / this 기대
- 함수에 임의 프로퍼티를 상태 저장소로 남용 (테스트·동시성 지옥)
__proto__로 상속 “설정” (19장 방식으로)- “일급 = 무조건 좋아” → 가독성 없는 콜백 지옥도 일급의 그림자
- IIFE를 모듈 대체재로 과신 (14장·ESM)
표
질문 → 자답
Q. 객체인데 왜 typeof fn === 'function'인가?
A. 역사·사양상의 타입 태그. 객체 능력이 없다는 뜻이 아니다.
Q. 즉시실행(IIFE)은 일급이라서 가능? A. 표현식으로 만든 함수 값을 바로 호출하는 패턴. 일급·표현식 문법의 합작품.
Q. 메서드도 일급인가? A. 프로퍼티 값이 함수면 그 값을 떼서 전달할 수 있다. 떼는 순간 this 바인딩은 별문제(22).
웹 보강 메모
MDN First-class Function 정의와 arguments 페이지의 “rest 권장”을 확인했다. 코드베이스에서 arguments 잔존·화살표 혼용을 검색해 rest로 옮길 후보를 표시했다.
실무로 바로
- 가변 인자 → rest
- 한 번만 실행/디바운스/파이프 → 고차 유틸
- 디버깅 시
fn.name·스택 확인 - 프로토타입 메서드 공유는 19·25로 미루되, “함수=객체” 감각은 여기서 고정
여기서 잠깐 💁♂️
17이 공장이면 18은 공장에서 나온 물건이 이동하는 물류. 19는 그 물건들이 위임으로 능력을 빌리는 체인이다.