JavaScript
Object
this
ทำความเข้าใจว่า this คืออะไร — implicit parameter ที่ชี้ไปหา object ที่เป็นเจ้าของการเรียกฟังก์ชัน เรียนรู้ implicit binding, default binding และข้อผิดพลาดที่พบบ่อยเมื่อ this หายไป
this คืออะไร
`this` คือ **keyword พิเศษ** ใน JavaScript ที่ชี้ไปหา object ที่เป็น **"เจ้าของ" การเรียกฟังก์ชัน** ณ ขณะนั้น ต่างจากตัวแปรทั่วไปที่ค่าถูกกำหนดตอนประกาศ — ค่าของ `this` **ถูกกำหนดตอนที่ฟังก์ชันถูกเรียก** (call-time) ไม่ใช่ตอนที่ฟังก์ชันถูกเขียน (declare-time) พูดง่าย ๆ: `this` จะเป็นใคร ขึ้นอยู่กับว่าเรา **เรียกฟังก์ชันอย่างไร** ไม่ใช่เราเขียนฟังก์ชันไว้ที่ไหน
`sayName` ไม่ได้ถูกแก้ไขเลย แต่ `this.name` ได้ผลคนละค่ากัน เพราะ caller เป็นคนละ object
// ฟังก์ชันเดียวกัน — แต่ this ให้ผลลัพธ์คนละค่ากัน
function sayName() {
console.log("ชื่อ:", this.name);
}
const alice = { name: "Alice", say: sayName };
const bob = { name: "Bob", say: sayName };
alice.say(); // "ชื่อ: Alice" — this คือ alice
bob.say(); // "ชื่อ: Bob" — this คือ bobจำหลักการสำคัญข้อแรก: **`this` เปลี่ยนตาม "ใครเป็นคนเรียก" — ไม่ใช่ตาม "ที่ไหนเป็นคนเขียน"** ในตัวอย่างข้างบน `alice.say()` ทำให้ `this` = `alice` ส่วน `bob.say()` ทำให้ `this` = `bob` ฟังก์ชันเดียวกันแท้ ๆ แต่ให้ผลลัพธ์ตาม caller
ทำไมต้องใช้ this — จาก hardcode ชื่อ object สู่โค้ดที่ใช้ซ้ำได้
ในบท **Method** เราเห็นว่า method ใน object สามารถเข้าถึง property อื่นของ object เดียวกันได้ โดยอ้างชื่อตัวแปร object จาก scope ภายนอก เช่น `person.firstName` แต่การอ้างชื่อตัวแปร object โดยตรงมีข้อจำกัดใหญ่ — **method ใช้ซ้ำกับ object คนละตัวไม่ได้** เพราะมันผูกติดกับชื่อตัวแปรแค่ตัวเดียว
`person2.introduce()` ควรบอกว่าสมหญิง แต่ดันบอกว่าสมชาย เพราะ method ผูกกับชื่อ `person1`
// ❌ วิธีเก่า — hardcode ชื่อ object → ใช้ซ้ำไม่ได้
const person1 = {
name: "สมชาย",
introduce: function () {
return "สวัสดี ผมชื่อ " + person1.name; // ผูกกับ person1 เท่านั้น
},
};
const person2 = {
name: "สมหญิง",
introduce: person1.introduce, // ใช้ function เดียวกัน
};
console.log(person1.introduce()); // "สวัสดี ผมชื่อ สมชาย" ✓
console.log(person2.introduce()); // "สวัสดี ผมชื่อ สมชาย" ✗ — ยังอ่าน person1!`this.name` ให้ผลลัพธ์ถูกต้องทั้ง `person1` และ `person2` เพราะ `this` อ่านค่าจาก caller
// ✅ วิธีใหม่ — ใช้ this → ใช้ซ้ำกับ object ไหนก็ได้
const person1 = {
name: "สมชาย",
introduce: function () {
return "สวัสดี ผมชื่อ " + this.name; // this เปลี่ยนตาม caller
},
};
const person2 = {
name: "สมหญิง",
introduce: person1.introduce, // ใช้ function เดียวกัน
};
console.log(person1.introduce()); // "สวัสดี ผมชื่อ สมชาย" ✓
console.log(person2.introduce()); // "สวัสดี ผมชื่อ สมหญิง" ✓ — this คือ person2`this` คือกลไกที่ทำให้ method รู้ว่า "ใครเป็นคนเรียกฉัน" — โดยที่เราไม่ต้อง hardcode ชื่อตัวแปร object ลงไปในโค้ด เปรียบง่าย ๆ: `this` เป็นเหมือน **ใบสั่งที่บอกว่า "คนที่เรียกคือใคร"** — ฟังก์ชันจะอ่านใบสั่งนี้ตอนถูกเรียก แล้วจึงรู้ว่าต้องทำงานกับ object ตัวไหน
Implicit Binding — obj.method()
เวลาเรียกฟังก์ชันผ่าน object ด้วย dot notation เช่น `obj.greet()` — JavaScript จะตั้งค่า `this` ให้เป็น **object ที่อยู่หน้าจุด** โดยอัตโนมัติ เราเรียกกฎนี้ว่า **implicit binding** เพราะเราไม่ได้ระบุค่า `this` เอง แต่ JavaScript ดูว่าเราเรียกฟังก์ชันผ่าน object ตัวไหน แล้วผูก `this` ให้โดยปริยาย หลักการ: **`this` = object หน้าจุด ก่อน method ถูกเรียก**
`cat.sound()` — JavaScript เห็นว่ามี `cat` อยู่หน้าจุด จึงให้ `this` = `cat` แม้ฟังก์ชันจะชื่อว่า `sound` ไม่ใช่ `bark`
const dog = {
name: "เจ้าโชค",
bark: function () {
return this.name + " ร้อง: โฮ่ง ๆ";
},
};
const cat = {
name: "เจ้าเหมียว",
sound: dog.bark, // ใช้ฟังก์ชันเดียวกับ dog
};
console.log(dog.bark()); // "เจ้าโชค ร้อง: โฮ่ง ๆ" — this = dog
console.log(cat.sound()); // "เจ้าเหมียว ร้อง: โฮ่ง ๆ" — this = cat
// ฟังก์ชันชื่อ bark แต่ cat ใช้ชื่อว่า sound — ไม่สำคัญ
// สำคัญที่: cat.sound() → this = catสังเกตว่า **ชื่อ property ที่เก็บฟังก์ชันไม่สำคัญ** — `dog` มี property ชื่อ `bark` ส่วน `cat` ใช้ชื่อ `sound` ก็ได้ `this` ดูแค่ว่าใครเป็น object หน้าจุดเท่านั้น
this เปลี่ยนตาม caller ไม่ใช่ตามที่เขียน
กฎ implicit binding ดูง่าย ๆ ว่า "ใครอยู่หน้าจุด" — แต่มีกรณีที่ทำให้สับสนเมื่อฟังก์ชันถูกส่งต่อผ่านหลายขั้นตอน กฎตายตัว: **`this` ดูที่ปลายทางตอนเรียก — ไม่ได้ดูที่ต้นทางตอน assign**
`report` ถูกประกาศเป็นฟังก์ชันธรรมดานอก object — แต่ตอนเรียก `pageA.show()` มันก็ยังได้ `this` = `pageA` เพราะกฎ implicit binding
function report() {
return "รายงานจาก " + this.label;
}
const pageA = { label: "หน้าแรก", show: report };
const pageB = { label: "หน้าเกี่ยวกับ", show: report };
console.log(pageA.show()); // "รายงานจาก หน้าแรก" — this = pageA
console.log(pageB.show()); // "รายงานจาก หน้าเกี่ยวกับ" — this = pageB
// ฟังก์ชัน report ถูกเขียนไว้ข้างนอก — ไม่ได้อยู่ข้างใน object
// แต่ตอนเรียก pageA.show() → this กลายเป็น pageA อยู่ดี
// เพราะ implicit binding ดูที่ "หน้าจุด" ตอนเรียก ไม่ใช่ที่เขียน`add()` และ `getValue()` ถูกเรียกผ่าน `calculator` เหมือนกัน — `this` จึงเป็น `calculator` ทั้งคู่ จึงอ่าน-เขียน `value` ตัวเดียวกันได้
const calculator = {
value: 10,
add: function (n) {
this.value = this.value + n;
return this.value;
},
getValue: function () {
return this.value;
},
};
console.log(calculator.getValue()); // 10
calculator.add(5); // this.value = 10 + 5
console.log(calculator.getValue()); // 15
calculator.add(3); // this.value = 15 + 3
console.log(calculator.getValue()); // 18
// add() และ getValue() ใช้ this.value ตัวเดียวกัน
// เพราะทั้งคู่ถูกเรียกผ่าน calculator → this ตัวเดียวกันDefault Binding — เรียกแบบ Standalone
ถ้าเรียกฟังก์ชันโดยตรง **ไม่มี object นำหน้า** — JavaScript ไม่รู้จะผูก `this` ให้ใคร จึงใช้กฎสำรองที่เรียกว่า **default binding** ผลลัพธ์: `this` จะเป็น **global object** (ใน browser คือ `window`) หรือ `undefined` ถ้าอยู่ใน **strict mode** (`'use strict'`) ในทางปฏิบัติ default binding มักไม่ใช่สิ่งที่เราต้องการ — มันมักเกิดขึ้นโดยบังเอิญเมื่อเราเผลอทำให้ `this` หลุดจาก object
// default binding — ไม่มี object หน้าจุด
function showThis() {
console.log("this คือ:", this);
}
showThis(); // this = window (ใน browser)
// undefined ใน strict mode
// ต่อให้ฟังก์ชันถูกเขียนใน object — ถ้าเรียกแบบ standalone:
const user = {
name: "Alice",
greet: function () {
console.log("สวัสดี " + this.name);
},
};
const fn = user.greet; // เอา function ออกจาก object
fn(); // "สวัสดี undefined" — this = window → window.name = undefined- **เรียกผ่าน object:** `obj.fn()` → `this` = `obj` (implicit binding)
- **เรียก standalone:** `fn()` → `this` = `window` / `undefined` (default binding)
- **กฎช่วยจำ:** มีจุดนำหน้า → implicit; ไม่มีจุด → default
ข้อผิดพลาดที่พบบ่อย: this หายเมื่อถอด method ออกจาก object
สาเหตุที่ `this` หายบ่อยที่สุด คือการ **ถอด method ออกจาก object** แล้วเรียกแบบ standalone เวลาเราเขียน `const fn = obj.method` — เรากำลัง **คัดลอก reference ของฟังก์ชัน** ออกมา ไม่ได้คัดลอกความสัมพันธ์กับ object มาด้วย พอเรียก `fn()` — มันกลายเป็น default binding ทันที
const timer = {
seconds: 0,
tick: function () {
this.seconds = this.seconds + 1;
console.log("วินาทีที่:", this.seconds);
},
};
// ✅ เรียกผ่าน object — this = timer
timer.tick(); // "วินาทีที่: 1"
timer.tick(); // "วินาทีที่: 2"
// ❌ ถอด method ออกจาก object — this หาย!
const standaloneTick = timer.tick;
standaloneTick(); // "วินาทีที่: NaN" → this.seconds = undefined + 1 = NaN
// เพราะ standaloneTick() เรียกแบบ standalone
// → this = window (default binding)
// → window.seconds = undefined
// → undefined + 1 = NaNจำกฎ: **`const fn = obj.method; fn()` — จุดหาย → `this` หาย** แค่คั่นด้วยตัวแปรตัวเดียว `this` ก็หลุดจาก implicit binding แล้ว เพราะตอนเรียก `fn()` ไม่มี object ใด ๆ นำหน้า
ข้อผิดพลาดที่พบบ่อย: this หายใน callback
อีกกรณีที่ `this` หายบ่อยคือการส่ง method เป็น **callback** — เช่น `setTimeout(obj.method, 100)` สาเหตุคือ callback ถูกเรียกโดย engine ของ JavaScript (เช่น `setTimeout`) โดยไม่ได้ผ่าน object ใด ๆ — จึงเป็น default binding นี่คือกับดักที่พบบ่อยมาก โดยเฉพาะกับ `setTimeout`, `setInterval`, และ event listener
const alarm = {
message: "ถึงเวลาพักแล้ว!",
ring: function () {
console.log("🔔 " + this.message);
},
};
// ✅ เรียกผ่าน object โดยตรง — this = alarm
alarm.ring(); // "🔔 ถึงเวลาพักแล้ว!"
// ❌ ส่ง method เป็น callback ให้ setTimeout — this หาย!
setTimeout(alarm.ring, 1000); // "🔔 undefined"
// เพราะ setTimeout เรียก alarm.ring แบบนี้:
// function setTimeout(callback, delay) {
// ... รอ delay ...
// callback(); // ← ไม่มีจุด! default binding!
// }
// callback() → this = window → window.message = undefinedวิธีแก้มีหลายแบบ — ที่พบบ่อยคือการเปลี่ยน callback เป็น arrow function (เพราะ arrow function ไม่มี `this` ของตัวเอง — จะเรียนในบทถัดไป `Arrow function issue`)
- **ผ่าน object โดยตรง:** `obj.method()` → `this` = `obj` ✓
- **ถอดใส่ตัวแปร:** `const fn = obj.method; fn()` → `this` = `window` ✗
- **ส่งเป็น callback:** `setTimeout(obj.method, ms)` → `this` = `window` ✗
- **ส่งเป็น nested function:** `fn(function() { this.xxx })` → `this` = `window` ✗
สรุปกฎที่ต้องจำ
this ถูกกำหนดตามวิธีเรียก — หน้าจุดมี object → implicit binding; ไม่มีหน้าจุด → default binding
- **`this` ถูกกำหนดตอนเรียก (call-time) — ไม่ใช่ตอนเขียน (declare-time)**
- **Implicit binding:** `obj.method()` → `this` = `obj` — ดูที่ object หน้าจุด
- **Default binding:** `fn()` → `this` = `window` (หรือ `undefined` ใน strict mode) — ไม่มี object หน้าจุด
- **ฟังก์ชันเดียวกัน** ใช้กับหลาย object ได้ — `this` จะเปลี่ยนตาม caller
- **ถอด method ใส่ตัวแปร** → จุดหาย → `this` หาย → กลายเป็น default binding
- **ส่ง method เป็น callback** → ถูกเรียกแบบ standalone → `this` หาย
- **ในบทถัดไป** จะเรียนวิธีแก้ `this` ที่หายไปด้วย arrow function และวิธีควบคุม `this` ด้วย `call`, `apply`, `bind` ในบทต่อ ๆ ไป