JavaScript
Error Handling
TypeError — เมื่อ type ของค่าไม่ตรงกับการใช้งาน
TypeError เกิดเมื่อใช้ค่าผิด type — null.foo, เรียกสิ่งที่ไม่ใช่ฟังก์ชัน — รู้วิธีเช็ก type ก่อนใช้งาน
TypeError คืออะไร
TypeError เป็น Error ประเภทหนึ่งที่เกิดเมื่อ**พยายามใช้ค่าที่ type ของมันไม่รองรับการดำเนินการนั้น** จุดที่ทำให้ TypeError ต่างจาก SyntaxError คือ **TypeError เกิดตอน runtime** — คือตอนโค้ดกำลังทำงาน ไม่ใช่ตอน parse คิดง่าย ๆ: ถ้าค่าใน JavaScript เป็นสิ่งของ — TypeError ก็เหมือนการพยายามใช้สิ่งของผิดวิธี เช่น เอากระดาษไปใช้เหมือนเป็นเครื่องคิดเลข (เรียก `"hello"()` — เรียก string เหมือนเป็น function) หรือพยายามอ่าน property ของสิ่งที่ไม่มีอยู่จริง (`null.name`) เมื่อเจอ TypeError JavaScript จะ: 1. สร้าง Error object ที่มี `.name` = `"TypeError"` 2. แสดง `.message` ที่อธิบายชัดเจนว่าทำอะไรผิด type 3. **หยุดการทำงานที่บรรทัดนั้น** — โค้ดบรรทัดถัดไปใน scope เดียวกันจะไม่ทำงาน TypeError เป็น error ที่**มือใหม่เจอบ่อยที่สุด** — เพราะยังไม่คุ้นเคยว่า method ไหนใช้กับ type ไหนได้บ้าง แต่ข่าวดีคือ error message ของ TypeError มักบอกชัดเจนมากว่าปัญหาคืออะไร
TypeError เกิดตอน runtime — JavaScript รู้ syntax แล้ว แต่พอดำเนินการจริงกลับทำไม่ได้เพราะ type ไม่ถูกต้อง
// ❌ เรียก string เหมือนเป็น function
"hello"();
// TypeError: "hello" is not a function
// .name = "TypeError"
// .message = ""hello" is not a function"
// ^ JavaScript รู้ syntax ถูกต้อง — แต่ว่า "hello" เป็น string ไม่ใช่ function
// ❌ เข้าถึง property ของ null
console.log(null.name);
// TypeError: Cannot read properties of null (reading 'name')
// .name = "TypeError"
// .message = "Cannot read properties of null (reading 'name')"
// ^ null ไม่มี property — เลยอ่าน .name ไม่ได้- TypeError เกิดตอน **runtime** — โค้ด syntax ถูกต้องแล้ว แต่ type ของค่าไม่รองรับการดำเนินการนั้น
- `.name` = `"TypeError"` — บอกว่าเป็น error เกี่ยวกับการใช้ค่าผิด type
- TypeError **หยุดการทำงานที่บรรทัดที่เกิด error** — โค้ดก่อนหน้าทำงานแล้ว แต่โค้ดถัดไปใน scope เดียวกันจะไม่ทำงาน
- error message ของ TypeError มักบอกชัดเจน — เช่น `"X is not a function"` หรือ `"Cannot read properties of null"`
- TypeError เป็น error ที่พบบ่อยที่สุดสำหรับมือใหม่ — แต่ป้องกันได้ด้วยการตรวจ type ก่อนใช้งาน
กรณีที่พบบ่อยของ TypeError
TypeError เกิดได้หลายรูปแบบ — แต่ทั้งหมดมีสาเหตุร่วมกันคือ **ใช้ค่าผิด type** มาดู 5 กรณีที่พบบ่อยที่สุด — แต่ละกรณีจะมีทั้งตัวอย่างที่ผิด (❌) และวิธีแก้ (✅) ให้เห็นภาพชัดเจน ทุกตัวอย่างด้านล่างนี้โค้ดก่อนหน้าทำงานได้ปกติ — จะเกิด TypeError เฉพาะตรงบรรทัดที่ใช้ค่าผิด type
null และ undefined ไม่มี property — พยายามอ่าน .name หรือ .age จาก null/undefined จะเกิด TypeError ทันที
// ❌ null ไม่มี property — TypeError
let user = null;
console.log(user.name);
// TypeError: Cannot read properties of null (reading 'name')
// ❌ undefined ก็เช่นกัน
let address;
console.log(address.city);
// TypeError: Cannot read properties of undefined (reading 'city')
// ✅ แก้ไข: เช็คว่าตัวแปรไม่เป็น null หรือ undefined ก่อนใช้
let user2 = null;
if (user2 !== null && user2 !== undefined) {
console.log(user2.name);
} else {
console.log("ไม่มีข้อมูลผู้ใช้"); // "ไม่มีข้อมูลผู้ใช้"
}
// ✅ อีกวิธี: ใช้ default value กันไว้
let user3 = null;
console.log(user3 && user3.name); // null — ไม่เกิด error
let name = user3 ? user3.name : "ไม่ระบุ";
console.log(name); // "ไม่ระบุ"function เป็น type หนึ่งใน JavaScript — string, number, boolean ไม่ใช่ function เลยเรียก () ไม่ได้
// ❌ string ไม่ใช่ function — TypeError
"สวัสดี"();
// TypeError: "สวัสดี" is not a function
// ❌ number ก็ไม่ใช่ function
123();
// TypeError: 123 is not a function
// ❌ object ธรรมดาก็ไม่ใช่ function (ถึงแม้จะเป็น object)
let obj = { name: "สมชาย" };
obj();
// TypeError: obj is not a function
// ✅ แก้ไข: เช็ค typeof ก่อนเรียก — เรียกเฉพาะตอนที่เป็น function
let maybeFunction = "hello";
if (typeof maybeFunction === "function") {
maybeFunction();
} else {
console.log("maybeFunction ไม่ใช่ function — เรียกไม่ได้");
// output: "maybeFunction ไม่ใช่ function — เรียกไม่ได้"
}
// ✅ ตัวอย่าง: ตัวแปรเป็น function จริง ๆ
function greet() {
console.log("สวัสดี!");
}
if (typeof greet === "function") {
greet(); // "สวัสดี!" — ทำงานได้ปกติ
}method แต่ละตัวถูกออกแบบมาให้ใช้กับ type ใด type หนึ่ง — เช่น toUpperCase() ใช้กับ string เท่านั้น
// ❌ number ไม่มี toUpperCase() — TypeError
let score = 95;
console.log(score.toUpperCase());
// TypeError: score.toUpperCase is not a function
// ^ toUpperCase() เป็น method ของ string ไม่ใช่ number
// ✅ แก้ไข: แปลงเป็น string ก่อน แล้วค่อยใช้ method ของ string
let score2 = 95;
console.log(String(score2).toUpperCase());
// output: "95"
// ✅ ตัวอย่างที่เห็นผลชัดกว่า: ใช้ string method กับ string
let enName = "john";
console.log(enName.toUpperCase());
// output: "JOHN"
// ❌ เรียก array method กับ string
let text = "hello";
text.push("!");
// TypeError: text.push is not a function
// ^ push() เป็น method ของ array ไม่ใช่ string
// ✅ ใช้ string method กับ string แทน
let text2 = "hello";
console.log(text2 + "!"); // "hello!" — ใช้ + ต่อ string`new` ใช้สร้าง object จาก constructor function หรือ class — ใช้กับ string, number, หรือค่าธรรมดาไม่ได้
// ❌ ใช้ new กับ string ธรรมดา — TypeError
let x = "hello";
let obj1 = new x();
// TypeError: x is not a constructor
// ^ "hello" เป็น string — ไม่ใช่ constructor
// ❌ ใช้ new กับ number — TypeError
let y = 42;
let obj2 = new y();
// TypeError: y is not a constructor
// ✅ new ใช้กับ function หรือ class ที่เป็น constructor เท่านั้น
function Person(name) {
this.name = name;
}
let p = new Person("สมชาย");
console.log(p.name); // "สมชาย"
// ✅ Object(), Array(), String(), Number() — ใช้ new ได้
let arr = new Array(3);
console.log(arr); // [ <3 empty items> ]
let obj3 = new Object();
console.log(obj3); // {}`const` ประกาศค่าคงที่ — เปลี่ยนค่าไม่ได้ การพยายามเปลี่ยนค่าจะเกิด TypeError (ไม่ใช่ SyntaxError อย่างที่หลายคนคิด)
// ❌ เปลี่ยนค่า const — TypeError (เกิดตอน runtime)
const name = "สมชาย";
name = "สมศรี";
// TypeError: Assignment to constant variable.
// .name = "TypeError"
// .message = "Assignment to constant variable."
// ^ เกิด TypeError ไม่ใช่ SyntaxError!
// ✅ ใช้ let แทน const ถ้าต้องการเปลี่ยนค่า
let name2 = "สมชาย";
name2 = "สมศรี";
console.log(name2); // "สมศรี" — ทำงานได้ปกติ
// ✅ const ใช้ได้กับ object — เปลี่ยน property ข้างในได้ แต่เปลี่ยนตัว object ไม่ได้
const user = { name: "สมชาย" };
user.name = "สมศรี"; // ✅ เปลี่ยน property ได้ ไม่ใช่การเปลี่ยนค่า user
console.log(user.name); // "สมศรี"
user = { name: "สมใจ" }; // ❌ TypeError — เปลี่ยนตัวแปร user ไม่ได้- **เข้าถึง property ของ null/undefined** — ตรวจสอบก่อนว่าไม่ใช่ null หรือ undefined ก่อนใช้ `.`
- **เรียกสิ่งที่ไม่ใช่ function** — ใช้ `typeof` เช็คก่อนว่าเป็น `"function"` จริง
- **ใช้ method ผิด type** — รู้ว่า method ไหนใช้กับ type ไหน (ดู `.` แล้วเช็คว่ามี method นั้นใน type นั้นหรือไม่)
- **ใช้ `new` กับสิ่งที่ไม่ใช่ constructor** — `new` ใช้กับ function/class ที่เป็น constructor เท่านั้น
- **เปลี่ยนค่า `const`** — ใช้ `let` ถ้าต้องการเปลี่ยนค่าภายหลัง
TypeError กับ built-in objects
JavaScript มี built-in objects หลายตัวที่ใช้ตรวจสอบหรือจัดการข้อมูล — แต่ละตัวมีข้อกำหนดเรื่อง type ของ argument ที่รับ บาง method ยอมรับค่าหลาย type ได้ (flexible) แต่บาง method ต้องการ type ที่เฉพาะเจาะจง — ถ้าส่ง type ผิดไปจะเกิด TypeError มาดูตัวอย่างการใช้งาน built-in objects ที่อาจทำให้เกิด TypeError กัน:
Object.assign() ต้องการ target ที่เป็น object — null และ undefined ไม่ใช่ object ที่รับได้
// ❌ Object.assign() รับ null เป็น target ไม่ได้ — TypeError
Object.assign(null, { a: 1 });
// TypeError: Cannot convert undefined or null to object
// ^ null กับ undefined เป็น object ไม่ได้ — Object.assign เลยใช้ target ไม่ได้
// ✅ target ต้องเป็น object (รวมถึง {} ว่าง)
let target = {};
let result = Object.assign(target, { a: 1, b: 2 });
console.log(result); // { a: 1, b: 2 }
console.log(target); // { a: 1, b: 2 } — target ถูกเปลี่ยนด้วยMath.max() รับ null ได้เพราะแปลง null เป็น 0 — แต่ Array.from() ต้องการ iterable เท่านั้น
// ✅ Math.max() รับ null ได้ — null จะถูกแปลงเป็น 0
console.log(Math.max(null));
// output: 0
console.log(Math.max(5, 10, 3));
// output: 10
// ❌ Array.from() ต้องการ iterable — number ไม่ใช่ iterable
Array.from(123);
// TypeError: 123 is not iterable
// ^ 123 เป็น number — ไม่ใช่ array หรือ iterable
// ✅ Array.from() ใช้กับ string ได้ (string เป็น iterable — วนทีละตัวอักษร)
console.log(Array.from("ABC"));
// output: ["A", "B", "C"]
// ✅ Array.from() ใช้กับ Set ได้ (Set เป็น iterable)
let mySet = new Set([1, 2, 3]);
console.log(Array.from(mySet));
// output: [1, 2, 3]- built-in method แต่ละตัวมีข้อกำหนดเรื่อง type ของ argument ต่างกัน — อ่าน documentation ว่าต้องการ type อะไร
- method ที่รับ `null` หรือ `undefined` บางครั้งก็ใช้งานได้ (เช่น `Math.max(null)` → `0`) — เพราะ JavaScript มี type coercion ที่แปลงค่าให้
- method ที่ต้องการ object มักไม่รับ `null`/`undefined` — เช่น `Object.assign(null, ...)`, `Object.keys(null)`
- `Array.from()` ต้องการ iterable (string, array, Set, Map) — ส่ง number ไปจะเกิด TypeError: `X is not iterable`
วิธีตรวจสอบ type ก่อนใช้งาน
TypeError ป้องกันได้ง่าย ๆ ด้วยการ**ตรวจสอบ type ของค่าก่อนใช้งาน** — วิธีที่ใช้กันทั่วไป:
typeof เป็น operator ที่คืนค่าเป็น string บอก type ของค่านั้น — ใช้เช็คก่อนเรียก function, ก่อนใช้ method, หรือก่อนอ่าน property
// ตรวจว่าเป็น function ก่อนเรียก
function safeCall(fn) {
if (typeof fn === "function") {
fn();
} else {
console.log("ไม่สามารถเรียกได้ — ไม่ใช่ function");
}
}
safeCall(function() { console.log("ทำงาน!"); }); // "ทำงาน!"
safeCall("hello"); // "ไม่สามารถเรียกได้ — ไม่ใช่ function"
safeCall(123); // "ไม่สามารถเรียกได้ — ไม่ใช่ function"
// ตรวจว่าเป็น object จริง ๆ ก่อนอ่าน property
function getName(obj) {
if (typeof obj === "object" && obj !== null) {
return obj.name;
}
return "ไม่ระบุ";
}
console.log(getName({ name: "สมชาย" })); // "สมชาย"
console.log(getName(null)); // "ไม่ระบุ"
console.log(getName("hello")); // "ไม่ระบุ"typeof ไม่สามารถแยก array จาก object ได้ — ต้องใช้ Array.isArray() แทน ส่วน optional chaining (?.) เป็นวิธีที่สั้นและปลอดภัยในการเข้าถึง property ลึก ๆ — ถ้าระหว่างทางเป็น null/undefined จะคืน undefined แทนการเกิด TypeError (มีบทสอนเต็ม ๆ ภายหลัง)
// typeof [] ก็คืน "object" — แยก array จาก object ไม่ได้
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof null); // "object" (bug เก่าแก่ของ JavaScript)
// ✅ ใช้ Array.isArray() แยก array จาก object
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
function processData(data) {
if (Array.isArray(data)) {
console.log("ข้อมูลเป็น array — วนลูปได้");
} else {
console.log("ข้อมูลไม่ใช่ array — ไม่สามารถวนลูปได้");
}
}
processData([1, 2, 3]); // "ข้อมูลเป็น array — วนลูปได้"
processData({ a: 1 }); // "ข้อมูลไม่ใช่ array — ไม่สามารถวนลูปได้"
// ✅ Optional chaining (?.) — ป้องกัน TypeError จาก null/undefined
// (มีบทสอนเต็ม ๆ ในภายหลัง — ดูเป็นตัวอย่างคร่าว ๆ ก่อน)
let user = null;
// ❌ แบบปกติ — TypeError
// console.log(user.address.city); // TypeError!
// ✅ แบบ optional chaining — ปลอดภัย
console.log(user?.address?.city); // undefined — ไม่เกิด error
let user2 = { address: { city: "กรุงเทพ" } };
console.log(user2?.address?.city); // "กรุงเทพ"- **`typeof`** — ตรวจ type พื้นฐาน: `"string"`, `"number"`, `"boolean"`, `"function"`, `"object"`, `"undefined"`
- **`Array.isArray()`** — ตรวจว่าเป็น array หรือไม่ (`typeof []` คืน `"object"` — ใช้แยก array จาก object ไม่ได้)
- **`instanceof`** — ตรวจว่า object นั้นสร้างมาจาก constructor ใด (เช่น `[] instanceof Array` → `true`, `new Date() instanceof Date` → `true`)
- **Optional chaining (`?.`)** — เข้าถึง property ของ object ที่อาจเป็น null/undefined ได้อย่างปลอดภัย (มีบทสอนเต็ม ๆ ภายหลัง)
- **Default value** — `value || "default"` หรือ `value ?? "default"` ใช้เผื่อกรณีค่าที่รับมาเป็น `null` หรือ `undefined`
วิธีป้องกันและแก้ไข TypeError
TypeError เป็น error ที่**ป้องกันได้** — ถ้าเราฝึกนิสัยตรวจสอบ type ก่อนใช้งานและอ่าน error message ให้ดี นี่คือวิธีปฏิบัติที่ช่วยให้เจอ TypeError น้อยลงและแก้ไขได้เร็ว:
- **อ่าน error message ให้ละเอียด** — TypeError มักบอกชัดเจนว่าปัญหาคืออะไร: `"X is not a function"` แปลว่า X ไม่ใช่ function, `"Cannot read properties of null"` แปลว่าตัวแปรเป็น null — ไล่จาก error message แล้วดูบรรทัดที่ console บอก
- **ใช้ `console.log()` ตรวจสอบค่าก่อนใช้** — ถ้าไม่แน่ใจว่าตัวแปรมีค่าเป็นอะไร ให้ `console.log(ตัวแปร)` และ `console.log(typeof ตัวแปร)` ดูก่อน — จะเห็นทันทีว่าค่าจริง ๆ เป็น type อะไร
- **ใช้ `typeof` ตรวจ type ก่อนดำเนินการ** — ก่อนเรียก `something()` ให้เช็ค `typeof something === "function"` — ก่อนใช้ `.property` ให้เช็คว่าตัวแปรไม่ใช่ `null` หรือ `undefined`
- **ใช้ default value กันไว้** — `const name = user.name || "ไม่ระบุ"` จะใช้ `"ไม่ระบุ"` แทนถ้า `user.name` เป็น falsy (รวมถึง `undefined`) — `const count = data.count ?? 0` ก็คล้ายกันแต่ใช้เฉพาะกับ `null`/`undefined`
- **เขียนโค้ดแบบ defensive** — กันไว้ก่อนว่าค่าที่รับมาอาจไม่ใช่ type ที่เราคิด — โดยเฉพาะข้อมูลที่มาจาก API, user input, หรือ localStorage
- **ใช้ editor ที่ดี** — VS Code และ editor สมัยใหม่มี IntelliSense — เมื่อพิมพ์ `.` จะแสดง list ของ method ที่มีอยู่จริงใน type นั้น — ช่วยให้รู้ว่า method ไหนใช้ได้บ้าง
- **อ่าน documentation ของ method ก่อนใช้** — MDN หรือเอกสารอื่นมักบอกว่า method รับ argument type อะไร และคืนค่า type อะไร — อ่านก่อนใช้จะลด TypeError ที่เกิดจาก argument ผิด type
TypeError กับ Error ประเภทอื่น
มือใหม่มักสับสนว่า error ที่เจอเป็น TypeError, SyntaxError หรือ ReferenceError — มาดูตารางเปรียบเทียบให้เห็นความแตกต่างชัดเจน:
| Error ประเภท | เกิดตอน | สาเหตุ | ตัวอย่าง error message | วิธีแก้ |
|---|---|---|---|---|
| SyntaxError | Parsing (ก่อนรัน) | เขียน syntax ผิดกฎของภาษา — อ่านโค้ดแล้วไม่เข้าใจ | `Unexpected token` `Unexpected end of input` | ตรวจความถูกต้องของ syntax — วงเล็บครบคู่, string ปิด, ไม่มี reserved word ผิดที่ |
| TypeError | Runtime (ตอนรัน) | ใช้ค่าที่ type ไม่รองรับการดำเนินการนั้น | `X is not a function` `Cannot read properties of null` `Assignment to constant variable` | ตรวจ type ด้วย typeof ก่อนใช้งาน, ใช้ default value, ใช้ optional chaining |
| ReferenceError | Runtime (ตอนรัน) | อ้างอิงตัวแปรที่ยังไม่เคยประกาศใน scope นั้น | `X is not defined` `Cannot access before initialization` | ประกาศตัวแปรก่อนใช้งาน, ตรวจการสะกดชื่อตัวแปร, สนใจ scope |
| RangeError | Runtime (ตอนรัน) | ค่าอยู่นอกช่วงที่ยอมรับได้ | `Invalid array length` `Maximum call stack size exceeded` | ตรวจสอบว่าค่าที่ส่งให้ method อยู่ในช่วงที่ยอมรับได้ |
สังเกตว่า SyntaxError เกิด**ก่อนรัน** — โค้ดทั้งไฟล์ไม่ทำงานเลย ส่วน TypeError, ReferenceError และ RangeError เกิด**ตอนรัน** — โค้ดก่อนหน้าทำงานได้ แต่หยุดที่บรรทัดที่เกิด error วิธีแยกง่าย ๆ: ดู `.name` ของ error — JavaScript จะเลือกชื่อให้ถูกต้องตามลักษณะของปัญหา
สรุป — TypeError
- **TypeError** คือ Error ที่เกิดเมื่อใช้ค่าที่ type ไม่รองรับการดำเนินการนั้น — เกิดตอน **runtime** (ตอนโค้ดรัน) ไม่ใช่ตอน parse
- **สาเหตุหลัก**: เข้าถึง property ของ null/undefined, เรียกสิ่งที่ไม่ใช่ function, ใช้ method ผิด type, ใช้ `new` กับสิ่งที่ไม่ใช่ constructor, เปลี่ยนค่า `const`
- **จุดสังเกต**: error message มักบอกตรง ๆ — `"X is not a function"`, `"Cannot read properties of null"`, `"X is not a constructor"`, `"Assignment to constant variable"`
- **ป้องกันได้**: ใช้ `typeof` ตรวจ type, เช็ค null/undefined ก่อนใช้ `.`, ใช้ default value (`||` หรือ `??`), ใช้ optional chaining (`?.`), ใช้ `console.log` ตรวจค่าก่อน
- **ต่างจาก SyntaxError**: SyntaxError เกิดตอน **parsing** (ก่อนรัน) — โค้ด syntax ผิดตั้งแต่แรก TypeError เกิดตอน **runtime** — syntax ถูกต้องแล้วแต่ type ไม่ตรง
- TypeError คือเพื่อนที่ honest ที่สุด — มันบอกตรง ๆ ว่าเราทำอะไรผิด type — อ่าน message ให้ดีแล้วตามไปแก้ที่บรรทัดที่มันบอก
| สถานการณ์ | TypeError หรือไม่ | วิธีแก้ |
|---|---|---|
| `null.name` | ใช่ — TypeError | เช็คว่าตัวแปรไม่เป็น null ก่อนอ่าน property |
| `"hello"()` | ใช่ — TypeError | ตรวจ typeof ก่อน — เรียกเฉพาะตอนเป็น function |
| `123.toUpperCase()` | ใช่ — TypeError | ใช้ method ให้ตรงกับ type — แปลงเป็น string ก่อนถ้าต้องการ |
| `new 123()` | ใช่ — TypeError | ใช้ new กับ constructor function/class เท่านั้น |
| `const x = 5; x = 10;` | ใช่ — TypeError | ใช้ let แทน const ถ้าต้องการเปลี่ยนค่า |
| `Object.assign(null, {a:1})` | ใช่ — TypeError | target ต้องเป็น object (ไม่ใช่ null/undefined) |
| ลืมปิดวงเล็บ `)` | ไม่ใช่ — SyntaxError | ตรวจสอบวงเล็บให้ครบคู่ |
| ใช้ตัวแปรที่ยังไม่ประกาศ | ไม่ใช่ — ReferenceError | ประกาศตัวแปรก่อนใช้งาน |