반응형

바쁜 분은 이것만

  1. X is not a function은 X 자리에 함수가 아닌 값(undefined, 객체, 문자열 등)이 있는데 ()로 불렀다는 뜻
  2. 먼저 console.log(typeof X, X)로 실제 값을 찍어 보면 원인이 대부분 바로 보임
  3. 흔한 원인: 대소문자 오타, 배열이 아닌 값에 map, 같은 이름 덮어쓰기, 세미콜론 없는 ( 줄, import 방식 혼동

TypeError: ... is not a function은 자바스크립트에서 가장 자주 보는 에러 중 하나입니다. 메시지 앞부분에 "무엇을 함수처럼 불렀는지"가 나오니 그 이름부터 보면 돼요. Node.js v26에서 원인별로 하나씩 재현해서 실제 메시지와 함께 정리했습니다.

1. 메서드 이름 오타·대소문자

const names = ["kim", "lee"];
const upper = names.map((n) => n.toUppercase());
file://~/work/n1.mjs:2
const upper = names.map((n) => n.toUppercase());
                                 ^

TypeError: n.toUppercase is not a function
    at file://~/work/n1.mjs:2:34
    at Array.map (<anonymous>)
    at file://~/work/n1.mjs:2:21

정확한 이름은 toUpperCase입니다. 없는 속성은 undefined라서 undefined()를 부른 셈이 돼요.

Math.Max(1, 2)       → TypeError: Math.Max is not a function
JSON.Parse('{}')     → TypeError: JSON.Parse is not a function
[1, 2].forEach2()    → TypeError: [1,2].forEach2 is not a function

(위는 try/catch로 메시지만 모아 찍은 출력입니다. 맞는 이름은 Math.max, JSON.parse, forEach.)

2. 배열이 아닌 값에 map·forEach

객체에 map을 쓰는 경우가 가장 흔합니다. API 응답이 배열일 줄 알았는데 { data: [...] } 같은 객체였을 때 잘 생겨요.

const scores = { kim: 90, lee: 80 };
const doubled = scores.map((s) => s * 2);
file://~/work/n3.mjs:2
const doubled = scores.map((s) => s * 2);
                       ^

TypeError: scores.map is not a function
    at file://~/work/n3.mjs:2:24

배열처럼 생겼지만 배열이 아닌 값도 마찬가지입니다. 함수 안의 arguments, Set은 map이나 forEach가 없거나 일부만 있어요. 배열로 바꾸면 됩니다.

function show() {
  try {
    arguments.forEach((a) => console.log(a));
  } catch (e) {
    console.log("arguments.forEach →", e.message);
  }
  Array.from(arguments).forEach((a) => console.log(" ", a));
}
show("a", "b");

const tags = new Set(["js", "node"]);
try {
  tags.map((t) => t.toUpperCase());
} catch (e) {
  console.log("Set.map →", e.message);
}
console.log([...tags].map((t) => t.toUpperCase()));

const scores = { kim: 90, lee: 80 };
console.log(Object.values(scores).map((s) => s * 2));
console.log(Object.entries(scores).map(([k, v]) => `${k}:${v}`));
console.log("typeof scores.map:", typeof scores.map);
console.log("Array.isArray(scores):", Array.isArray(scores));
arguments.forEach → arguments.forEach is not a function
  a
  b
Set.map → tags.map is not a function
[ 'JS', 'NODE' ]
[ 180, 160 ]
[ 'kim:90', 'lee:80' ]
typeof scores.map: undefined
Array.isArray(scores): false

Array.from()이나 스프레드 [...tags]로 배열을 만든 뒤 map을 쓰고, 객체는 Object.values()·Object.entries()로 배열을 꺼냅니다. 스프레드 문법은 자바스크립트 배열 복사 (스프레드) 글에 정리해 두었어요. 값이 배열인지 확실하지 않으면 Array.isArray()로 먼저 확인하세요.

3. 변수가 함수 이름을 덮어쓴 경우

let sum = (a, b) => a + b;
sum = sum(1, 2);
console.log("sum:", sum);
console.log(sum(3, 4));
sum: 3
file://~/work/n5.mjs:13
console.log(sum(3, 4));
            ^

TypeError: sum is not a function
    at file://~/work/n5.mjs:13:13

(n5.mjs 앞부분에는 다른 코드가 있어서 줄 번호가 13입니다.) 처음엔 함수였던 sum에 계산값 3을 넣는 바람에, 다음 줄에선 3(3, 4)를 부르게 됐어요. 메시지에 나온 이름이 "분명 함수인데?" 싶으면 그 이름에 다른 값을 대입한 곳이 없는지 찾아보세요. 함수는 const로 만들어 두면 다시 대입할 때 바로 에러가 나서 이런 실수를 막을 수 있습니다.

4. 세미콜론 없이 ( 로 시작하는 줄

메시지에 함수 이름 대신 엉뚱한 값이 찍히면 이 경우입니다.

const msg = "hello"
(function () {
  console.log("즉시 실행")
})()
file://~/work/n6.mjs:2
(function () {
^

TypeError: "hello" is not a function
    at file://~/work/n6.mjs:2:1

자바스크립트는 줄 끝에 세미콜론을 자동으로 넣어 주는데(ASI), 다음 줄이 (로 시작하면 앞줄과 이어진 것으로 읽습니다. 그래서 "hello"(function () {...}), 즉 문자열을 함수처럼 부른 코드가 됐어요. [로 시작하는 줄도 앞줄과 붙는데, 이때는 메시지가 다르게 나옵니다.

const a = 1
const b = a
[1, 2].forEach((n) => console.log(n))
file://~/work/n7.mjs:3
[1, 2].forEach((n) => console.log(n))
       ^

TypeError: Cannot read properties of undefined (reading 'forEach')
    at file://~/work/n7.mjs:3:8

a[1, 2].forEach(...), 즉 1[2](undefined)에서 forEach를 찾은 모습입니다. 세미콜론을 안 쓴다면 (·[로 시작하는 줄 맨 앞에 ;를 붙이면 됩니다.

const a = 1
const list = [a]
;[1, 2].forEach((n) => console.log(n))
const msg = "hello";
(function () {
  console.log("즉시 실행", msg)
})()
1
2
즉시 실행 hello

5. var 함수 표현식을 선언 전에 호출

console.log(typeof greet);
greet("kim");

var greet = function (name) {
  console.log("안녕", name);
};
undefined
file://~/work/n8.mjs:2
greet("kim");
^

TypeError: greet is not a function
    at file://~/work/n8.mjs:2:1

var greet는 호이스팅으로 위로 끌어올려지지만 값은 undefined로 채워져 있고, 함수가 대입되는 건 4번째 줄에 도달했을 때입니다. 그래서 "정의되지 않음"이 아니라 "함수가 아님" 에러가 나요. function hello() {} 같은 함수 선언문은 함수째로 끌어올려져서 위에서 불러도 됩니다.

hello("kim");
function hello(name) {
  console.log("함수 선언문은 위에서 불러도 됨:", name);
}
함수 선언문은 위에서 불러도 됨: kim

const·let으로 만든 함수 표현식을 먼저 부르면 이 에러가 아니라 Cannot access 'greet' before initialization이 나옵니다(TDZ).

6. 메서드를 콜백으로 넘겨 this를 잃은 경우

메서드 안에서 this.다른메서드()를 부르는데, 그 메서드를 따로 떼어 넘기면 this가 바뀝니다. 어디서 불리냐에 따라 메시지가 달라져서 헷갈리기 쉬워요.

const counter = {
  count: 0,
  log() {
    this.print(this.count);
  },
  print(n) {
    console.log("count =", n);
  },
};

counter.log();

const log = counter.log;
try {
  log();
} catch (e) {
  console.log("log() →", e.name + ":", e.message);
}

try {
  [1].forEach(counter.log);
} catch (e) {
  console.log("forEach(counter.log) →", e.name + ":", e.message);
}

setTimeout(counter.log, 0);
count = 0
log() → TypeError: Cannot read properties of undefined (reading 'print')
forEach(counter.log) → TypeError: Cannot read properties of undefined (reading 'print')
file://~/work/n9.mjs:8
    this.print(this.count);
         ^

TypeError: this.print is not a function
    at Timeout.log [as _onTimeout] (file://~/work/n9.mjs:8:10)
  • 그냥 떼어 부르거나 forEach에 넘기면 this가 undefined(모듈은 strict 모드)라서 Cannot read properties of undefined가 납니다.
  • Node의 setTimeout에 넘기면 스택의 Timeout.log에서 보이듯 this가 Timeout 객체가 됩니다. 그 객체엔 print가 없으니 this.print is not a function이 나와요.

객체에 묶어 두려면 bind를 쓰거나 화살표 함수로 감싸서 넘깁니다.

[1].forEach(counter.log.bind(counter));
[1].forEach(() => counter.log());
setTimeout(() => counter.log(), 0);
count = 0
count = 0
count = 0

7. import 할 때 기본 내보내기·이름 내보내기 혼동

아래 util.mjs는 기본 내보내기 하나(formatPrice)와 이름 내보내기 하나(sum)를 갖고 있습니다.

// util.mjs
export default function formatPrice(n) {
  return n.toLocaleString("ko-KR") + "원";
}
export function sum(a, b) {
  return a + b;
}

import * as로 받으면 함수가 아니라 모듈 전체를 담은 객체가 옵니다.

import * as util from "./util.mjs";
console.log("util 안에 있는 것:", Object.keys(util));
util("1000");
util 안에 있는 것: [ 'default', 'sum' ]
file://~/work/n11.mjs:3
util("1000");
^

TypeError: util is not a function
    at file://~/work/n11.mjs:3:1

CommonJS 파일(module.exports = { formatPrice })을 기본 import로 받을 때도 같은 일이 생깁니다. 기본 import에는 module.exports 객체 전체가 들어와요.

// price.cjs 끝부분: module.exports = { formatPrice };
import formatPrice from "./price.cjs";
console.log("받은 값:", formatPrice);
formatPrice(1000);
받은 값: { formatPrice: [Function: formatPrice] }
file://~/work/n16.mjs:3
formatPrice(1000);
^

TypeError: formatPrice is not a function
    at file://~/work/n16.mjs:3:1

이 경우는 import { formatPrice } from "./price.cjs";처럼 중괄호로 받으면 1,000원이 정상 출력됩니다. 반대로 ES 모듈에서 없는 이름을 중괄호로 받으면 TypeError가 아니라 실행 전에 SyntaxError가 나요.

import { formatPrice } from "./util.mjs";
file://~/work/n12.mjs:1
import { formatPrice } from "./util.mjs";
         ^^^^^^^^^^^
SyntaxError: The requested module './util.mjs' does not provide an export named 'formatPrice'

기본 내보내기는 중괄호 없이, 이름 내보내기는 중괄호 안에 받습니다.

import formatPrice, { sum } from "./util.mjs";
console.log(formatPrice(12000));
console.log(sum(1, 2));
12,000원
3

8. 부르기 전에 확인하는 방법

const user = { name: "kim" };
console.log("typeof user.getName:", typeof user.getName);
try { user.getName(); } catch (e) { console.log("user.getName() →", e.message); }
try { user.profile.getName(); } catch (e) { console.log("user.profile.getName() →", e.message); }
console.log("user.getName?.() →", user.getName?.());
if (typeof user.getName === "function") {
  user.getName();
} else {
  console.log("getName이 함수가 아니라 건너뜀");
}
typeof user.getName: undefined
user.getName() → user.getName is not a function
user.profile.getName() → Cannot read properties of undefined (reading 'getName')
user.getName?.() → undefined
getName이 함수가 아니라 건너뜀

메서드가 없으면 is not a function, 그 앞의 객체부터 없으면 Cannot read properties of undefined입니다. 없을 수도 있는 함수라면 ?.()나 typeof x === "function"으로 막을 수 있지만, 꼭 있어야 하는 함수라면 실수가 조용히 묻히니 원인부터 고치세요.

정리

원인 고치는 법
toUppercase, Math.Max 같은 오타 정확한 이름·대소문자 확인
객체·arguments·Set에 map/forEach Object.values(), Array.from(), [...set]
같은 이름에 값을 대입 변수 이름 바꾸기, 함수는 const로
"hello" is not a function (ASI) (·[로 시작하는 줄 앞에 ;
var 함수 표현식을 먼저 호출 호출을 아래로, 또는 함수 선언문으로
this.print is not a function bind(obj) 또는 () => obj.method()
import 방식 혼동 기본은 중괄호 없이, 이름은 중괄호로

참고: MDN — TypeError: "x" is not a function, Automatic semicolon insertion(Lexical grammar), Hoisting, Function.prototype.bind(), Optional chaining (?.), import / export, Node.js 공식 문서 — Modules: ECMAScript modules(Interoperability with CommonJS)

반응형

+ Recent posts