JavaScript
Object
Arrow function issue
เข้าใจว่า Arrow Function ไม่มี this ของตัวเอง — มันสืบทอด this จาก scope ที่เขียน (lexical this) เรียนรู้วิธีใช้ Arrow Function แก้ปัญหา this หายใน callback และรู้ว่าเมื่อไหร่ควรไม่ควรใช้ Arrow Function เป็น method
Arrow Function ไม่มี this ของตัวเอง
Arrow Function (`=>`) กับ Regular Function (`function`) มีความต่างสำคัญหนึ่งข้อที่เกี่ยวกับ `this`: **Regular Function** — มี `this` ของตัวเอง → ค่าของ `this` เปลี่ยนตาม caller (ใครเป็นคนเรียก) **Arrow Function** — **ไม่มี `this` ของตัวเอง** → ใช้ `this` จาก scope ที่ครอบมันอยู่ตอนเขียนโค้ด (lexical scope) พูดง่าย ๆ: `this` ใน arrow function ถูกกำหนดตอน **เขียน** ไม่ใช่ตอน **เรียก** — ตรงข้ามกับ regular function
// Regular Function — this เปลี่ยนตาม caller
const obj = {
name: "Object",
regularFn: function () {
return "Regular: " + this.name;
},
arrowFn: () => {
return "Arrow: " + this.name;
},
};
console.log(obj.regularFn()); // "Regular: Object" — this = obj ✓
console.log(obj.arrowFn()); // "Arrow: undefined" — this ≠ obj!
// arrowFn ใช้ this จาก scope นอก obj — ใน browser คือ window
// window.name = "" (empty string) หรือ undefinedสังเกต: `obj.arrowFn()` ถูกเรียกผ่าน `obj` แต่ `this.name` กลับเป็น `undefined` — เพราะ arrow function **ไม่สนใจ caller** มันใช้ `this` จากที่ที่มันถูกเขียน (ในที่นี้คือ global scope ซึ่ง `this.name` ไม่มีค่า) นี่คือข้อแตกต่างที่ต้องจำให้ขึ้นใจ: **arrow function ไม่มี implicit binding** — ต่อให้เรียกผ่าน `obj.method()` ก็ไม่ทำให้ `this` เป็น `obj`
Lexical this — สืบทอด this จาก scope ที่เขียน
คำว่า **lexical** แปลว่า "เกี่ยวกับตำแหน่งที่เขียนโค้ด" — Arrow Function ได้ `this` มาจาก scope ที่ครอบมันอยู่ ณ จุดที่เขียนโค้ด (ไม่ใช่จุดที่เรียก) ลองนึกภาพว่า Arrow Function เป็นเหมือน **หน้าต่างที่มองทะลุ** — แทนที่จะมี `this` ของตัวเอง มันมองทะลุไปเอาค่า `this` จากฟังก์ชันที่อยู่ข้างนอกสุดที่เขียนล้อมมันไว้
arrow function มองทะลุไปเอา `this` จาก `outer` — ซึ่ง `outer` ได้ `this` = `obj` จากการเรียก `obj.getArrow()`
// จำลอง scope 3 ระดับ — ดูว่า this ไหลผ่าน arrow อย่างไร
function outer() {
// this = outer's caller
const arrow = () => {
// this = this ของ outer (lexical)
console.log("arrow this:", this);
};
return arrow;
}
const obj = {
name: "Obj",
getArrow: outer, // outer.call(obj) → this ใน outer = obj
};
const fn = obj.getArrow(); // outer ถูกเรียก → this = obj
fn(); // arrow() ถูกเรียก → แต่ this ใน arrow = lexical = obj
// เพราะ arrow "สืบทอด" this จาก outer`showArrow` ใช้ `setTimeout(() => { ... })` — arrow function สืบทอด `this` จาก `showArrow` ซึ่ง `this` = `team` พอดี
// ในทางปฏิบัติ — use case ที่พบบ่อย
const team = {
name: "DevTeam",
// Regular method — this = team (implicit binding)
showRegular: function () {
// this = team
setTimeout(function () {
// this = window (default binding — callback!)
console.log("Regular:", this.name); // undefined
}, 0);
},
// Regular method + arrow callback — แก้ปัญหาแล้ว
showArrow: function () {
// this = team (implicit binding)
setTimeout(() => {
// this = lexical = this ของ showArrow = team ✓
console.log("Arrow:", this.name); // "DevTeam"
}, 0);
},
};
team.showRegular(); // "Regular: undefined"
team.showArrow(); // "Arrow: DevTeam"- **Arrow Function `this` = `this` ของฟังก์ชันที่เขียนล้อมอยู่** — ไม่ใช่ของ caller
- **Regular Function `this` = caller** (implicit/default binding) — เปลี่ยนตามวิธีเรียก
- **กลไก lexical this** คือเหตุผลที่ arrow function ใช้แก้ `this` หายใน callback ได้
แก้ปัญหา this หายใน callback ด้วย Arrow Function
จากบท `this` เราเห็นว่า `setTimeout(obj.method, ms)` ทำให้ `this` หาย — เพราะ callback ถูกเรียกแบบ standalone (default binding) ทางแก้ที่ง่ายและเป็นที่นิยมที่สุด: **เปลี่ยน callback จาก regular function เป็น arrow function** วิธีนี้ใช้ได้เพราะ arrow function สืบทอด `this` จาก method ที่ล้อมมันอยู่ — ซึ่งได้ `this` ที่ถูกต้องจาก implicit binding ตั้งแต่แรก
const alarm = {
message: "ถึงเวลาพักแล้ว!",
// ❌ แบบผิด — regular function callback
bad: function () {
setTimeout(function () {
console.log(this.message); // undefined → this = window
}, 500);
},
// ✅ แบบถูก — arrow function callback
good: function () {
setTimeout(() => {
console.log(this.message); // "ถึงเวลาพักแล้ว!" → this = alarm
}, 500);
},
};
alarm.bad(); // undefined
alarm.good(); // "ถึงเวลาพักแล้ว!"`setInterval(() => { ... })` — arrow function สืบทอด `this` = `timer` จาก `start()` ทำให้ `this.seconds` ทำงานได้ถูกต้อง
// ใช้กับ setInterval ก็ได้ — นับเวลาถอยหลัง
const timer = {
seconds: 5,
start: function () {
const id = setInterval(() => {
// this = timer (lexical จาก start)
if (this.seconds === 0) {
console.log("หมดเวลา!");
clearInterval(id);
} else {
console.log(this.seconds);
this.seconds--;
}
}, 1000);
},
};
timer.start();
// 5
// 4
// 3
// 2
// 1
// หมดเวลา!ห้ามใช้ Arrow Function เป็น method โดยตรง
จากที่เห็นว่า arrow function ใน callback มีประโยชน์มาก — แต่มันมีกับดักสำคัญ: **ห้ามใช้ arrow function เป็น method ของ object โดยตรง!** เพราะ arrow function ไม่เคยได้ `this` จาก caller — มันใช้ `this` จาก scope นอก object เสมอ ซึ่งแทบทุกกรณีคือ global scope → ไม่ใช่สิ่งที่เราต้องการ
// ❌ Arrow Function เป็น method — this ≠ obj
const person1 = {
name: "สมชาย",
greet: () => {
return "สวัสดี " + this.name;
// this = global scope (window) — ไม่ใช่ person1!
},
};
// ✅ Regular Function เป็น method — this = obj
const person2 = {
name: "สมหญิง",
greet: function () {
return "สวัสดี " + this.name;
},
};
console.log(person1.greet()); // "สวัสดี undefined" ✗
console.log(person2.greet()); // "สวัสดี สมหญิง" ✓
// จำง่าย ๆ:
// method ของ object → ใช้ regular function เสมอ
// callback ใน method → ใช้ arrow functionRegular vs Arrow — ตารางเปรียบเทียบเรื่อง this
| เรื่อง | Regular Function | Arrow Function |
|---|---|---|
| มี `this` ของตัวเอง? | มี — เปลี่ยนตาม caller | ไม่มี — สืบทอดจาก scope ที่เขียน |
| `this` ถูกกำหนดเมื่อไหร่? | ตอนเรียก (call-time) | ตอนเขียน (declare-time) |
| Implicit binding ทำงาน? | ✓ — `obj.fn()` → `this` = `obj` | ✗ — `obj.fn()` → `this` จาก lexical scope |
| ใช้เป็น method ได้? | ใช่ — `greet: function() { ... }` | ไม่ควร — `this` ≠ obj |
| ใช้เป็น callback ได้? | ไม่ปลอดภัย — `this` อาจหาย | ใช่ — `this` คงที่ตาม lexical scope |
| ใช้กับ `setTimeout` ได้? | ไม่ควร — `this` = window | ใช่ — `this` = enclosing method |
| ใช้กับ event listener ได้? | ได้ — `this` = element | ได้ — แต่ `this` = lexical scope |
กฎการเลือกใช้ — เมื่อไหร่ควรใช้ Regular vs Arrow
- **Method ของ object** → Regular Function (`greet: function() { ... }` หรือ `greet() { ... }`)
- **Callback ใน method** (`setTimeout`, `setInterval`) → Arrow Function (`() => { ... }`)
- **Callback ทั่วไปที่ไม่ต้องการ `this`** → Arrow Function — สะดวก เขียนสั้น
- **ฟังก์ชันที่ต้องการให้ `this` เปลี่ยนตาม caller** → Regular Function
- **ฟังก์ชันที่ต้องการให้ `this` คงที่** → Arrow Function (หรือใช้ `.bind()` — จะเรียนในบท `call / apply` และ `bind`)
ทุก method (`takeDamage`, `startPoison`, `stopPoison`) ใช้ regular function — แต่ callback ใน `setInterval` ใช้ arrow function เพื่อให้ `this` = `player`
// ✅ รูปแบบที่ถูกต้อง — method = regular, callback = arrow
const player = {
name: "Hero",
hp: 100,
// method → regular function (this = player)
takeDamage: function (amount) {
this.hp = this.hp - amount;
console.log(this.name + " โดนโจมตี! HP: " + this.hp);
},
// method → regular function (this = player)
startPoison: function () {
// callback → arrow function (this = lexical = player)
this.poisonId = setInterval(() => {
this.takeDamage(5); // this.takeDamage ใช้ได้ เพราะ this = player
if (this.hp <= 0) {
console.log(this.name + " หมดแรง...");
clearInterval(this.poisonId);
}
}, 1000);
},
stopPoison: function () {
clearInterval(this.poisonId);
console.log("หยุดพิษแล้ว!");
},
};
player.startPoison();
// "Hero โดนโจมตี! HP: 95"
// "Hero โดนโจมตี! HP: 90"
// ...
setTimeout(() => player.stopPoison(), 4000); // หยุดหลังจาก 4 วินาทีสรุป
Regular Function: this เปลี่ยนตาม caller (ซ้าย) — Arrow Function: this ถูกกำหนดตอนเขียน ไม่เปลี่ยนตาม caller (ขวา)
- **Arrow Function ไม่มี `this` ของตัวเอง** — มันสืบทอด `this` จากฟังก์ชันที่เขียนล้อมอยู่ (lexical scope)
- **`this` ใน arrow ถูกกำหนดตอนเขียน** — ไม่เปลี่ยนตาม caller ต่างจาก regular function ที่เปลี่ยนตาม caller
- **ใช้ arrow function ใน callback** (`setTimeout`, `setInterval`) เพื่อรักษา `this` จาก method ที่ล้อมอยู่
- **ห้ามใช้ arrow function เป็น method โดยตรง** — เพราะ `this` จะเป็น global scope (window) ไม่ใช่ object
- **กฎง่าย ๆ:** method ของ object → regular; callback ใน method → arrow