Functions & Scope: หัวใจของ JavaScript ที่หลายคนเข้าใจผิด
🎯 สิ่งที่คุณจะได้เรียนในบทนี้
ในบทนี้ คุณจะได้เรียนรู้:
- 🔧 Functions - วิธีสร้างและใช้งาน functions ทุกรูปแบบ
- 🏠 Scope & Hoisting - เข้าใจว่าตัวแปรมองเห็นกันยังไง
- 🎁 Closures - ความลับของการ "จำ" ค่าใน functions
- 🚀 Real Patterns - เทคนิคที่ใช้จริงในงาน
📝 Outcome
หลังจบบทนี้ คุณจะ:
- ✅ เขียน functions ได้ทั้ง 3 แบบ และรู้ว่าควรใช้แบบไหนเมื่อไหร่
- ✅ เข้าใจ scope และ hoisting ไม่งงอีกต่อไป
- ✅ ใช้ closures สร้าง private variables ได้
- ✅ เขียน callbacks, debounce, throttle แบบมืออาชีพ
- ✅ debug functions และแก้ปัญหา this context ได้
🎯 ทำไมต้องเข้าใจ Functions?
🤷 ปัญหาที่เราเจอทุกวัน
ลองนึกภาพว่าคุณต้องคำนวณส่วนลดสินค้า 10% ให้ลูกค้า 5 คน:
// ❌ วิธีที่น่าเบื่อและเสี่ยงผิดพลาด
const customer1_price = 500;
const customer1_discount = customer1_price * 0.1;
const customer1_final = customer1_price - customer1_discount;
console.log("ลูกค้า 1 จ่าย: " + customer1_final); // 450
const customer2_price = 1200;
const customer2_discount = customer2_price * 0.1;
const customer2_final = customer2_price - customer2_discount;
console.log("ลูกค้า 2 จ่าย: " + customer2_final); // 1080
// ... ทำแบบนี้อีก 3 รอบ 😩
ปัญหาคือ:
- 😴 เขียนโค้ดซ้ำๆ น่าเบื่อ
- 🐛 เสี่ยงพิมพ์ผิด เช่น 0.1 เป็น 0.01
- 🔧 ถ้าเปลี่ยนส่วนลดเป็น 15% ต้องแก้ทุกที่!
✨ Function คือคำตอบ
ลองนึกภาพ Function เป็น "เครื่องจักรอัจฉริยะ":
- 📥 รับวัตถุดิบ (input/parameters)
- ⚙️ ประมวลผล (process)
- 📤 ส่งผลลัพธ์ออกมา (output/return)
// ✅ สร้าง "เครื่องคิดส่วนลด" ครั้งเดียว
function calculateDiscount(price) {
const discount = price * 0.1; // คิดส่วนลด 10%
const finalPrice = price - discount;
return finalPrice; // ส่งราคาสุดท้ายออกไป
}
// ใช้ซ้ำได้กี่ครั้งก็ได้!
console.log("ลูกค้า 1 จ่าย: " + calculateDiscount(500)); // 450
console.log("ลูกค้า 2 จ่าย: " + calculateDiscount(1200)); // 1080
console.log("ลูกค้า 3 จ่าย: " + calculateDiscount(800)); // 720
ข้อดีคือ:
- 🎯 เขียนครั้งเดียว ใช้ได้ตลอดชีพ
- 🛡️ ลดโอกาสผิดพลาด
- 🔧 แก้ที่เดียว อัพเดททั้งระบบ
- 📚 อ่านง่าย เข้าใจง่าย
🎩 Functions: กล่องวิเศษที่ทำงานซ้ำได้
🎁 Function คืออะไร?
📖 อธิบายแบบง่ายๆ
Function คือ "กล่องเครื่องมือ" ที่:
- 📥 รับของเข้าไป (parameters)
- ⚙️ ทำอะไรบางอย่าง (process)
- 📤 ส่งผลลัพธ์ออกมา (return)
เหมือนตู้กดน้ำอัตโนมัติ:
- ใส่เหรียญ = ส่ง parameter
- เครื่องทำงาน = โค้ดใน function
- ได้น้ำ = return value
🔍 มาดูโครงสร้างกัน
// ชื่อ function ควรบอกว่ามันทำอะไร
function ชื่อที่อธิบายการทำงาน(สิ่งที่รับเข้ามา) {
// ทำอะไรบางอย่าง
return ผลลัพธ์;
}
🍕 ตัวอย่าง: เครื่องทำพิซซ่า
// 1️⃣ สร้าง Function (สร้างเครื่องทำพิซซ่า)
function makePizza(topping) {
// topping = หน้าที่ใส่
// 2️⃣ ประมวลผล (ทำพิซซ่า)
const pizza = "พิซซ่า " + topping;
// 3️⃣ ส่งผลลัพธ์ (เสิร์ฟพิซซ่า)
return pizza;
}
// 4️⃣ เรียกใช้ Function (สั่งพิซซ่า)
const myPizza = makePizza("ฮาวายเอี้ยน");
console.log(myPizza); // "พิซซ่า ฮาวายเอี้ยน"
🎨 3 วิธีสร้าง Function
1️⃣ Function Declaration (แบบประกาศ)
// 📣 ประกาศชัดเจนว่าเป็น function
function ทักทาย(ชื่อ) {
return "สวัสดี " + ชื่อ;
}
console.log(ทักทาย("สมชาย")); // "สวัสดี สมชาย"
// ⭐ ข้อดี: เรียกใช้ก่อนประกาศได้ (Hoisting)
console.log(บอกเวลา()); // ทำงานได้!
function บอกเวลา() {
return new Date().toLocaleTimeString();
}
2️⃣ Function Expression (แบบนิพจน์)
// 📦 เก็บ function ในตัวแปร
const คำนวณอายุ = function (ปีเกิด) {
const ปีนี้ = new Date().getFullYear();
return ปีนี้ - ปีเกิด;
};
console.log(คำนวณอายุ(2000)); // 25 (ในปี 2025)
// ⚠️ ข้อควรระวัง: ต้องประกาศก่อนใช้
// console.log(อะไรสักอย่าง()); // ❌ Error!
const อะไรสักอย่าง = function () {
return "ต้องประกาศก่อน";
};
3️⃣ Arrow Function (ลูกศร)
// 🏹 เขียนสั้น กระชับ
const บวก = (a, b) => a + b;
const กำลังสอง = (x) => x * x;
console.log(บวก(5, 3)); // 8
console.log(กำลังสอง(4)); // 16
// ถ้ามีหลายบรรทัด ต้องใส่ { } และ return
const คิดส่วนลด = (ราคา, เปอร์เซ็นต์) => {
const ส่วนลด = ราคา * (เปอร์เซ็นต์ / 100);
const ราคาสุดท้าย = ราคา - ส่วนลด;
return ราคาสุดท้าย;
};
console.log(คิดส่วนลด(1000, 20)); // 800
🤔 เลือกใช้แบบไหนดี?
| แบบ | เมื่อไหร่ควรใช้ | ข้อดี |
|---|---|---|
| Function Declaration | ต้องการ hoisting, function หลักๆ | เรียกก่อนประกาศได้, อ่านง่าย |
| Function Expression | ต้องการเก็บใน variable, ส่งเป็น argument | ควบคุมลำดับได้, ยืดหยุ่น |
| Arrow Function | Callback functions, ต้องการ this จาก parent | เขียนสั้น, this ไม่เปลี่ยน |
📥 Parameters vs Arguments: คำที่หลายคนสับสน
📋 ความแตกต่าง
// Parameters (พารามิเตอร์) = ตัวแปรที่รับค่า (ในวงเล็บตอนประกาศ)
// ↓ ↓
function สั่งกาแฟ(ชนิด, ขนาด) {
return `${ชนิด} ${ขนาด}`;
}
// Arguments (อาร์กิวเมนต์) = ค่าที่ส่งเข้าไป (ตอนเรียกใช้)
// ↓ ↓
สั่งกาแฟ("ลาเต้", "ใหญ่");
ลองนึกเหมือนกรอกฟอร์ม:
- Parameters = ช่องว่างในฟอร์ม (ชื่อ: _, นามสกุล: _)
- Arguments = ข้อมูลที่กรอก (ชื่อ: สมชาย, นามสกุล: ใจดี)
🎯 Default Parameters: ค่าเริ่มต้น
// กำหนดค่าเริ่มต้นได้เลย!
function ทักทายภาษาต่างๆ(ชื่อ = "คุณ", ภาษา = "ไทย") {
if (ภาษา === "ไทย") {
return `สวัสดี ${ชื่อ}`;
} else if (ภาษา === "อังกฤษ") {
return `Hello ${ชื่อ}`;
} else {
return `Bonjour ${ชื่อ}`;
}
}
console.log(ทักทายภาษาต่างๆ()); // "สวัสดี คุณ"
console.log(ทักทายภาษาต่างๆ("John")); // "สวัสดี John"
console.log(ทักทายภาษาต่างๆ("John", "อังกฤษ")); // "Hello John"
🎁 Rest Parameters: รับค่าไม่จำกัด
// ใช้ ... (three dots) เพื่อรับค่าหลายๆ ตัว
function รวมตัวเลขทั้งหมด(...ตัวเลข) {
let ผลรวม = 0;
for (let เลข of ตัวเลข) {
ผลรวม += เลข;
}
return ผลรวม;
}
console.log(รวมตัวเลขทั้งหมด(1, 2, 3)); // 6
console.log(รวมตัวเลขทั้งหมด(10, 20, 30, 40, 50)); // 150
console.log(รวมตัวเลขทั้งหมด(5)); // 5
🧿 The Mysterious "this": ปริศนาที่หลายคนงง
🎯 this คืออะไร?
this คือการอ้างถึง "เจ้าของ" ของ function ณ ตอนที่เรียกใช้
🏠 this ใน Object
const ร้านกาแฟ = {
ชื่อร้าน: "Coffee Paradise",
เมนู: ["ลาเต้", "คาปูชิโน่", "อเมริกาโน่"],
แนะนำร้าน() {
// this = ร้านกาแฟ (object ที่เป็นเจ้าของ method)
console.log(`ยินดีต้อนรับสู่ ${this.ชื่อร้าน}`);
console.log(`เรามี ${this.เมนู.length} เมนู`);
},
};
ร้านกาแฟ.แนะนำร้าน();
// ยินดีต้อนรับสู่ Coffee Paradise
// เรามี 3 เมนู
⚠️ ปัญหา this ที่หลายคนเจอ
const ปุ่ม = {
ข้อความ: "คลิกฉันสิ!",
// ❌ Regular function ใน setTimeout
แสดงข้อความผิด() {
setTimeout(function () {
console.log(this.ข้อความ); // undefined! 😱
// this ไม่ใช่ปุ่มแล้ว!
}, 1000);
},
// ✅ Arrow function จำ this จาก parent
แสดงข้อความถูก() {
setTimeout(() => {
console.log(this.ข้อความ); // "คลิกฉันสิ!" ✨
// Arrow function ใช้ this จากข้างนอก
}, 1000);
},
};
🎯 วิธีแก้ปัญหา this
// วิธีที่ 1: เก็บ this ไว้ในตัวแปร
function แบบเก่า() {
const self = this; // เก็บไว้ก่อน
setTimeout(function () {
console.log(self.ข้อความ);
}, 1000);
}
// วิธีที่ 2: ใช้ bind()
function แบบBind() {
setTimeout(
function () {
console.log(this.ข้อความ);
}.bind(this),
1000,
); // ผูก this
}
// วิธีที่ 3: ใช้ Arrow Function (แนะนำ!)
function แบบArrow() {
setTimeout(() => {
console.log(this.ข้อความ); // ง่ายสุด!
}, 1000);
}
🚨 Hoisting: สิ่งที่ทำให้งงมาก
🎢 Hoisting คืออะไร?
Hoisting = JavaScript ยก declarations ขึ้นไปด้านบนของ scope โดยอัตโนมัติ
ลองนึกภาพ: คุณเป็นครูเช็คชื่อนักเรียน
// 🤔 สิ่งที่คุณเขียน
console.log(นักเรียน); // undefined (ไม่ error!)
var นักเรียน = "สมชาย";
// 🔄 สิ่งที่ JavaScript เห็น
var นักเรียน; // ประกาศ (แต่ยังไม่มีค่า)
console.log(นักเรียน); // undefined
นักเรียน = "สมชาย"; // กำหนดค่า
⚡ Hoisting กับ Function Declaration
// ✅ Function Declaration: Hoisted ทั้งตัว!
ทักทาย(); // "สวัสดี!" - ทำงานได้!
function ทักทาย() {
console.log("สวัสดี!");
}
// ❌ Function Expression: ไม่ Hoisted
// ลองใช้(); // Error! Cannot access before initialization
const ลองใช้ = function () {
console.log("ทดสอบ");
};
// ❌ Arrow Function: ไม่ Hoisted เช่นกัน
// ลูกศร(); // Error!
const ลูกศร = () => {
console.log("Arrow");
};
📋 ตารางสรุป Hoisting
| ประเภท | Hoisted? | ใช้ก่อนประกาศได้? |
|---|---|---|
| var | ✅ (แค่ประกาศ) | ⚠️ (ได้ undefined) |
| let/const | ❌ | ❌ (Error) |
| Function Declaration | ✅ (ทั้งตัว) | ✅ |
| Function Expression | ❌ | ❌ |
| Arrow Function | ❌ | ❌ |
💡 เคล็ดลับ: ประกาศทุกอย่างที่ด้านบนก่อนใช้เสมอ จะไม่งงกับ Hoisting!
🏠 Scope: บ้านของตัวแปร
🏠 Scope คืออะไร?
Scope = พื้นที่ที่ตัวแปรมองเห็นและใช้งานได้
ลองนึกภาพเป็น "บ้าน 3 ชั้น":
// 🌍 Global Scope (ชั้นดาดฟ้า - เห็นได้ทุกที่)
const ของชั้นดาดฟ้า = "แสงแดด";
function ชั้นที่2() {
// 🏠 Function Scope (ชั้น 2)
const ของชั้น2 = "โทรทัศน์";
if (true) {
// 📦 Block Scope (ห้องในชั้น 2)
const ของในห้อง = "เตียง";
console.log(ของในห้อง); // ✅ เห็น (อยู่ในห้อง)
console.log(ของชั้น2); // ✅ เห็น (อยู่ชั้นเดียวกัน)
console.log(ของชั้นดาดฟ้า); // ✅ เห็น (มองขึ้นไป)
}
// console.log(ของในห้อง); // ❌ ไม่เห็น! (อยู่นอกห้อง)
}
function ชั้นที่1() {
// 🏠 Function Scope อื่น (ชั้น 1)
const ของชั้น1 = "ตู้เย็น";
// console.log(ของชั้น2); // ❌ ไม่เห็น! (คนละชั้น)
console.log(ของชั้นดาดฟ้า); // ✅ เห็น (มองขึ้นไป)
}
📊 Scope Chain: การค้นหาตัวแปร
const ข้อความGlobal = "สวัสดีจาก Global";
function ภายนอก() {
const ข้อความนอก = "สวัสดีจาก ภายนอก";
function ภายใน() {
const ข้อความใน = "สวัสดีจาก ภายใน";
// 🔍 JavaScript ค้นหาตัวแปรแบบนี้:
// 1. ในตัวเอง (ภายใน) - เจอ ข้อความใน ✅
// 2. ถ้าไม่เจอ ดูชั้นนอก (ภายนอก) - เจอ ข้อความนอก ✅
// 3. ถ้าไม่เจอ ดู Global - เจอ ข้อความGlobal ✅
console.log(ข้อความใน); // "สวัสดีจาก ภายใน"
console.log(ข้อความนอก); // "สวัสดีจาก ภายนอก"
console.log(ข้อความGlobal); // "สวัสดีจาก Global"
}
ภายใน();
}
ภายนอก();
⚠️ var vs let/const: ความแตกต่างสำคัญ
// 🚫 var: Function Scope (ปัญหาเยอะ!)
function ทดสอบVar() {
if (true) {
var x = 1; // function scope
}
console.log(x); // 1 - เห็นนอก block! 😱
}
// ✅ let/const: Block Scope (แนะนำ!)
function ทดสอบLet() {
if (true) {
let y = 1; // block scope
const z = 2; // block scope
}
// console.log(y); // ❌ Error! ไม่เห็นนอก block
// console.log(z); // ❌ Error! ไม่เห็นนอก block
}
// ปัญหาคลาสสิกของ var ใน loop
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 3, 3, 3 - ทั้งหมดเป็น 3! 😱
}, 100);
}
// แก้ด้วย let
for (let j = 0; j < 3; j++) {
setTimeout(() => {
console.log(j); // 0, 1, 2 - ถูกต้อง! ✅
}, 100);
}
🔐 Closures: Functions ที่มีความทรงจำ
🎁 Closure คืออะไร?
Closure = function ที่ "จำ" ตัวแปรจาก scope ข้างนอกได้
ลองนึกภาพ: คุณเป็นนักเรียนที่จบการศึกษาแล้ว
- 🎒 คุณออกจากโรงเรียน (function สิ้นสุด)
- 📚 แต่ยังจำความรู้ที่เรียนได้ (จำตัวแปรจาก parent scope)
// 🏫 ตัวอย่าง: โรงเรียนและนักเรียน
function โรงเรียน() {
const บทเรียน = "JavaScript"; // 📚 ความรู้ในโรงเรียน
function นักเรียน() {
// นักเรียนจำบทเรียนได้!
console.log(`ผมเรียน ${บทเรียน}`);
}
return นักเรียน; // นักเรียนจบการศึกษา
}
const ผู้จบการศึกษา = โรงเรียน();
// โรงเรียน() ทำงานจบแล้ว แต่...
ผู้จบการศึกษา(); // "ผมเรียน JavaScript"
// นักเรียนยังจำบทเรียนได้! นี่คือ Closure!
คุณอาจจะคิดว่า:
- โรงเรียน() ทำงานจบแล้ว
- ตัวแปร บทเรียน น่าจะหายไป
- แต่ไม่! function นักเรียน ยังจำได้
- แม้ออกจากห้องเรียนแล้ว คุณยังมีหนังสือในกระเป๋า!
🔢 ตัวอย่างง่ายๆ: ตัวนับ
// 🎯 สร้างเครื่องนับที่จำค่าได้
function สร้างตัวนับ() {
let ค่า = 0; // 🔒 ตัวแปรส่วนตัว (private)
// function นี้ "จำ" ตัวแปร ค่า ได้!
return () => {
ค่า = ค่า + 1;
return ค่า;
};
}
// สร้างตัวนับ 2 ตัว
const ตัวนับ1 = สร้างตัวนับ();
const ตัวนับ2 = สร้างตัวนับ();
// แต่ละตัวจำค่าของตัวเองแยกกัน!
console.log(ตัวนับ1()); // 1
console.log(ตัวนับ1()); // 2
console.log(ตัวนับ1()); // 3
console.log(ตัวนับ2()); // 1 (เริ่มนับใหม่!)
console.log(ตัวนับ2()); // 2
// ❌ เข้าถึงตรงๆ ไม่ได้
// console.log(ค่า); // Error! ค่า is not defined
🏦 ตัวอย่าง: ระบบธนาคาร
// 💰 สร้างบัญชีธนาคารที่ปลอดภัย
function สร้างบัญชีธนาคาร(เงินเริ่มต้น) {
// 🔒 ยอดเงินเป็นความลับ! (private variable)
let ยอดเงิน = เงินเริ่มต้น;
// สร้าง "เครื่อง ATM" ที่เข้าถึงยอดเงินได้
return {
// 💵 ฝากเงิน
ฝาก(จำนวน) {
if (จำนวน > 0) {
ยอดเงิน = ยอดเงิน + จำนวน;
console.log(`✅ ฝาก ${จำนวน} บาท`);
console.log(`💰 เหลือ ${ยอดเงิน} บาท`);
} else {
console.log("❌ จำนวนเงินไม่ถูกต้อง");
}
},
// 💸 ถอนเงิน
ถอน(จำนวน) {
if (จำนวน > 0 && จำนวน <= ยอดเงิน) {
ยอดเงิน = ยอดเงิน - จำนวน;
console.log(`✅ ถอน ${จำนวน} บาท`);
console.log(`💰 เหลือ ${ยอดเงิน} บาท`);
} else {
console.log("❌ ยอดเงินไม่พอ!");
}
},
// 👀 ดูยอด
ดูยอด() {
return `💰 ยอดคงเหลือ ${ยอดเงิน} บาท`;
},
};
}
// 🏧 ใช้งานบัญชี
const บัญชีของฉัน = สร้างบัญชีธนาคาร(1000);
บัญชีของฉัน.ฝาก(500); // ✅ ฝาก 500 บาท, 💰 เหลือ 1500 บาท
บัญชีของฉัน.ถอน(200); // ✅ ถอน 200 บาท, 💰 เหลือ 1300 บาท
บัญชีของฉัน.ถอน(2000); // ❌ ยอดเงินไม่พอ!
console.log(บัญชีของฉัน.ดูยอด()); // 💰 ยอดคงเหลือ 1300 บาท
// ❌ แฮกไม่ได้! ยอดเงินถูกซ่อนไว้
console.log(บัญชีของฉัน.ยอดเงิน); // undefined
// console.log(ยอดเงิน); // Error!
🔐 ข้อดีของ Closure: สร้าง private variables ได้ ทำให้ข้อมูลปลอดภัยจากการแก้ไขโดยตรง!
🚀 Higher-Order Functions: Functions ที่เล่นกับ Functions
🤔 ทำไมต้องใช้ Higher-Order Functions?
ลองนึกภาพ: คุณเป็นเจ้าของร้านกาแฟ
// ❌ แบบเก่า: เขียนซ้ำๆ
function ทำกาแฟร้อน() {
console.log("☕ บดเมล็ดกาแฟ");
console.log("☕ ชงกาแฟร้อน");
console.log("☕ เสิร์ฟในแก้ว");
}
function ทำกาแฟเย็น() {
console.log("🧊 บดเมล็ดกาแฟ");
console.log("🧊 ชงกาแฟร้อน");
console.log("🧊 ใส่น้ำแข็ง");
console.log("🧊 เสิร์ฟในแก้วพลาสติก");
}
// มีขั้นตอนซ้ำๆ ต้องเขียนใหม่ทุกครั้ง!
✅ แบบใหม่: ใช้ Higher-Order Function
// 🎯 สร้าง "เครื่องทำกาแฟ" ที่รับ "วิธีทำ" มาเป็น parameter
function ทำกาแฟ(วิธีเสิร์ฟ) {
console.log("☕ บดเมล็ดกาแฟ");
console.log("☕ ชงกาแฟร้อน");
// เรียกใช้ "วิธีเสิร์ฟ" ที่รับเข้ามา
วิธีเสิร์ฟ();
}
// สร้างวิธีเสิร์ฟต่างๆ
const เสิร์ฟร้อน = () => console.log("☕ เสิร์ฟในแก้ว");
const เสิร์ฟเย็น = () => {
console.log("🧊 ใส่น้ำแข็ง");
console.log("🧊 เสิร์ฟในแก้วพลาสติก");
};
// ใช้งาน: ส่งวิธีเสิร์ฟเข้าไป
ทำกาแฟ(เสิร์ฟร้อน); // ทำกาแฟร้อน
ทำกาแฟ(เสิร์ฟเย็น); // ทำกาแฟเย็น
📚 Higher-Order Function คืออะไร?
พูดง่ายๆ คือ Function ที่:
- รับ function อื่นเป็น parameter (เหมือนรับคำสั่ง)
- คืนค่าเป็น function (เหมือนสร้างเครื่องจักรใหม่)
🎮 ลองทำดู: Function ที่รับ Function
// 🔁 สร้าง function ทำซ้ำ
function ทำซ้ำ(จำนวนครั้ง, สิ่งที่จะทำ) {
for (let i = 0; i < จำนวนครั้ง; i++) {
สิ่งที่จะทำ(i + 1); // ส่ง "ครั้งที่" ไปด้วย
}
}
// ใช้งาน
ทำซ้ำ(3, (ครั้งที่) => {
console.log(`🏃 วิ่งรอบที่ ${ครั้งที่}`);
});
// 🏃 วิ่งรอบที่ 1
// 🏃 วิ่งรอบที่ 2
// 🏃 วิ่งรอบที่ 3
ทำซ้ำ(5, (ครั้งที่) => {
console.log(`📖 อ่านหน้าที่ ${ครั้งที่}`);
});
// 📖 อ่านหน้าที่ 1
// 📖 อ่านหน้าที่ 2
// ... ถึงหน้าที่ 5
🏭 Function ที่คืน Function: สร้างเครื่องจักร
// 🎯 โรงงานสร้างเครื่องคูณ
function สร้างเครื่องคูณ(ตัวคูณ) {
// คืนค่าเป็น function ใหม่!
return (ตัวตั้ง) => ตัวตั้ง * ตัวคูณ;
}
// สร้างเครื่องคูณต่างๆ
const คูณ2 = สร้างเครื่องคูณ(2);
const คูณ10 = สร้างเครื่องคูณ(10);
const คูณ100 = สร้างเครื่องคูณ(100);
// ใช้งาน
console.log(คูณ2(5)); // 10 (5 x 2)
console.log(คูณ10(5)); // 50 (5 x 10)
console.log(คูณ100(5)); // 500 (5 x 100)
// 💡 สะดวกสำหรับแปลงหน่วย!
const บาทเป็นดอลลาร์ = สร้างเครื่องคูณ(0.03);
const กิโลเป็นปอนด์ = สร้างเครื่องคูณ(2.2);
console.log(บาทเป็นดอลลาร์(1000)); // 30 ดอลลาร์
console.log(กิโลเป็นปอนด์(5)); // 11 ปอนด์
🔍 ตัวอย่างจากชีวิตจริง: ระบบ Log
// 📝 สร้างระบบ log แบบ Curry
function สร้างLogger(ระดับ) => (ระบบ) => (ข้อความ) => {
const เวลา = new Date().toLocaleTimeString();
console.log(`[${เวลา}] [${ระดับ}] [${ระบบ}] ${ข้อความ}`);
};
// สร้าง logger สำหรับแต่ละระบบ
const logDatabase = สร้างLogger("INFO")("Database");
const logAPI = สร้างLogger("INFO")("API");
const logError = สร้างLogger("ERROR");
// ใช้งาน
logDatabase("เชื่อมต่อสำเร็จ");
// [10:30:00] 🔵 [INFO] [Database] เชื่อมต่อสำเร็จ
logAPI("รับ request จาก client");
// [10:30:01] 🔵 [INFO] [API] รับ request จาก client
logError("Payment")("ชำระเงินไม่สำเร็จ");
// [10:30:02] 🔴 [ERROR] [Payment] ชำระเงินไม่สำเร็จ
📞 Callback Pattern: ส่ง Function ไปทำงานให้
🤔 Callback คืออะไร?
ลองนึกภาพ: คุณสั่งพิซซ่า
- 📞 โทรสั่งพิซซ่า
- 🍕 ร้านทำพิซซ่า (ใช้เวลา)
- 🛵 ส่งถึงบ้าน
- 🔔 กดกริ่ง -> นี่คือ Callback!
Callback คือ function ที่เราส่งไป และจะถูกเรียกเมื่อ "งานเสร็จ"
📱 ตัวอย่าง: รอข้อมูลจาก Server
// 🌐 จำลองการดึงข้อมูลจาก server
function ดึงข้อมูลผู้ใช้(userId, เมื่อเสร็จแล้ว) {
console.log("🔄 กำลังดึงข้อมูล...");
// จำลองว่ารอ server ตอบ 2 วินาที
setTimeout(() => {
const ข้อมูล = {
id: userId,
ชื่อ: "สมชาย",
อีเมล: "somchai@example.com",
};
// เรียก callback เมื่อได้ข้อมูล!
เมื่อเสร็จแล้ว(ข้อมูล);
}, 2000);
}
// ใช้งาน: บอกว่าจะทำอะไรเมื่อได้ข้อมูล
ดึงข้อมูลผู้ใช้(123, (ผลลัพธ์) => {
console.log("✅ ได้ข้อมูลแล้ว!");
console.log("👤 ชื่อ:", ผลลัพธ์.ชื่อ);
console.log("📧 อีเมล:", ผลลัพธ์.อีเมล);
});
// โปรแกรมทำงานต่อได้ ไม่ต้องรอ!
console.log("⏳ ทำอย่างอื่นระหว่างรอ...");
🖱️ Event Handlers: Callback ที่ใช้บ่อยที่สุด
// 🔘 เมื่อคลิกปุ่ม
const ปุ่ม = document.querySelector("#myButton");
ปุ่ม.addEventListener("click", (event) => {
console.log("🖱️ คุณคลิกปุ่ม!");
console.log("📍 ตำแหน่งที่คลิก:", event.clientX, event.clientY);
});
// 📝 เมื่อพิมพ์ในช่องค้นหา
const ช่องค้นหา = document.querySelector("#search");
ช่องค้นหา.addEventListener("input", (event) => {
const คำค้นหา = event.target.value;
console.log("🔍 กำลังค้นหา:", คำค้นหา);
// ค้นหาข้อมูลตามคำที่พิมพ์
});
// 📜 เมื่อเลื่อนหน้า
window.addEventListener("scroll", () => {
const เลื่อนไปแล้ว = window.scrollY;
console.log("📜 เลื่อนหน้าไป:", เลื่อนไปแล้ว, "pixels");
});
🎯 Array Methods: Callback สำหรับจัดการข้อมูล
// มีรายการสินค้า
const สินค้า = [
{ ชื่อ: "กาแฟ", ราคา: 60, หมวดหมู่: "เครื่องดื่ม" },
{ ชื่อ: "ขนมปัง", ราคา: 25, หมวดหมู่: "อาหาร" },
{ ชื่อ: "ชาเขียว", ราคา: 45, หมวดหมู่: "เครื่องดื่ม" },
{ ชื่อ: "คุกกี้", ราคา: 35, หมวดหมู่: "อาหาร" },
];
// 🔍 filter: เลือกเฉพาะที่ต้องการ
const เครื่องดื่ม = สินค้า.filter((รายการ) => {
return รายการ.หมวดหมู่ === "เครื่องดื่ม";
});
console.log("☕ เครื่องดื่ม:", เครื่องดื่ม);
// 🔄 map: แปลงข้อมูลทุกตัว
const ชื่อสินค้า = สินค้า.map((รายการ) => {
return รายการ.ชื่อ;
});
console.log("📝 รายชื่อ:", ชื่อสินค้า);
// ["กาแฟ", "ขนมปัง", "ชาเขียว", "คุกกี้"]
// ➕ reduce: รวมเป็นค่าเดียว
const ราคารวม = สินค้า.reduce((ยอดรวม, รายการ) => {
return ยอดรวม + รายการ.ราคา;
}, 0);
console.log("💰 ราคารวม:", ราคารวม, "บาท"); // 165 บาท
🛠️ สร้าง Callback ของเราเอง
// 🎬 function ที่ทำงานเป็นขั้นตอน
function ทำอาหาร(เมนู, เริ่มทำ, ทำเสร็จ, เกิดข้อผิดพลาด) {
console.log(`🍳 เริ่มทำ ${เมนู}...`);
// เรียก callback "เริ่มทำ"
เริ่มทำ(เมนู);
// จำลองการทำอาหาร
setTimeout(() => {
// สุ่มว่าจะสำเร็จหรือไม่
const สำเร็จ = Math.random() > 0.3;
if (สำเร็จ) {
// เรียก callback "ทำเสร็จ"
ทำเสร็จ(`${เมนู} พร้อมเสิร์ฟ!`);
} else {
// เรียก callback "เกิดข้อผิดพลาด"
เกิดข้อผิดพลาด(`${เมนู} ไหม้!`);
}
}, 2000);
}
// ใช้งาน
ทำอาหาร(
"ข้าวผัด",
(เมนู) => console.log(`⏰ ตั้งเวลาทำ ${เมนู}`),
(ผลลัพธ์) => console.log(`✅ ${ผลลัพธ์}`),
(ข้อผิดพลาด) => console.log(`❌ ${ข้อผิดพลาด}`),
);
💡 Callback ช่วยให้: โปรแกรมไม่ต้อง "รอ" ทำงานอื่นต่อได้เลย!
⚡ IIFE: Function ที่รันเองทันที
🤔 IIFE คืออะไร?
IIFE = Immediately Invoked Function Expression (อ่านว่า "อิฟฟี่")
ลองนึกภาพ: เหมือนระเบิดเวลาที่ตั้งไว้ 0 วินาที - ระเบิดทันที!
// 💣 Function ปกติ: ต้องเรียกถึงจะทำงาน
function ทักทาย() {
console.log("สวัสดี!");
}
ทักทาย(); // ต้องเรียก
// 🎆 IIFE: ทำงานเองทันที!
(() => {
console.log("สวัสดี!");
})(); // <- วงเล็บท้ายทำให้รันทันที!
🔨 วิธีสร้าง IIFE
// 📝 รูปแบบที่ 1: Arrow Function
(() => {
console.log("🚀 รันทันที แบบที่ 1");
})();
// 📝 รูปแบบที่ 2: Regular Function
(function () {
console.log("🚀 รันทันที แบบที่ 2");
})();
// 📝 รูปแบบที่ 3: รับค่าได้ด้วย!
((ชื่อ) => {
console.log(`🚀 สวัสดี ${ชื่อ}!`);
})("สมชาย"); // ส่งค่าเข้าไป
🏠 ใช้ IIFE ทำอะไรได้?
1️⃣ สร้างพื้นที่ส่วนตัว (Private Space)
// ❌ ปัญหา: ตัวแปรรั่วไหลออกมา global
var คะแนน = 0;
var ชีวิต = 3;
// คนอื่นแก้ได้!
คะแนน = 999999; // โกง!
// ✅ ใช้ IIFE: ซ่อนตัวแปร
(() => {
let คะแนน = 0;
let ชีวิต = 3;
// เปิดเฉพาะที่ต้องการให้ใช้
window.เกม = {
เพิ่มคะแนน() {
คะแนน += 10;
console.log(`⭐ คะแนน: ${คะแนน}`);
},
ดูคะแนน() {
return คะแนน;
},
};
})();
// ใช้ได้เฉพาะที่เปิดให้
เกม.เพิ่มคะแนน(); // ⭐ คะแนน: 10
console.log(เกม.ดูคะแนน()); // 10
// แฮกไม่ได้!
console.log(คะแนน); // Error: คะแนน is not defined
2️⃣ ทำงานครั้งเดียวตอนเริ่ม
// 🎬 Setup เริ่มต้นของเว็บ
(() => {
console.log("🌟 กำลังเริ่มต้นระบบ...");
// ตรวจสอบ browser
if (!window.localStorage) {
alert("❌ Browser ไม่รองรับ localStorage");
return;
}
// โหลดค่าเริ่มต้น
const การตั้งค่า = localStorage.getItem("settings") || "{}";
const ค่าเริ่มต้น = JSON.parse(การตั้งค่า);
// ตั้งค่า theme
if (ค่าเริ่มต้น.darkMode) {
document.body.classList.add("dark-mode");
}
console.log("✅ ระบบพร้อมใช้งาน!");
})();
3️⃣ ป้องกันตัวแปรชนกัน
// 🏢 Plugin A
(() => {
const version = "1.0";
const name = "PluginA";
window.PluginA = {
info() {
return `${name} v${version}`;
},
};
})();
// 🏢 Plugin B
(() => {
const version = "2.0"; // ไม่ชนกับ Plugin A!
const name = "PluginB";
window.PluginB = {
info() {
return `${name} v${version}`;
},
};
})();
console.log(PluginA.info()); // PluginA v1.0
console.log(PluginB.info()); // PluginB v2.0
🆚 IIFE vs Modern Modules
// 📦 วิธีสมัยใหม่: ES6 Modules
// file: utils.js
const ตัวแปรส่วนตัว = "ซ่อนอยู่ใน module";
export function ฟังก์ชันสาธารณะ() {
console.log("สามารถใช้จากข้างนอกได้");
}
// file: main.js
import { ฟังก์ชันสาธารณะ } from "./utils.js";
💡 ใช้ IIFE เมื่อไหร่?
เขียน script เล็กๆ ที่ไม่ใช้ module system
ทำ bookmarklet หรือ user script
ต้องการรันโค้ดทันทีตอนโหลดหน้า
สร้าง polyfill หรือ library แบบเก่า
🌟 Real Patterns ที่ใช้จริงในงาน
🤔 ทำไมต้องใช้ Patterns พวกนี้?
ลองนึกภาพ: คุณทำช่องค้นหา
- ผู้ใช้พิมพ์ "c" -> เรียก API
- พิมพ์ "o" -> เรียก API อีก
- พิมพ์ "f" -> เรียก API อีก
- พิมพ์ "f" -> เรียก API อีก
- พิมพ์ "e" -> เรียก API อีก
- พิมพ์ "e" -> เรียก API อีก
ผู้ใช้แค่พิมพ์คำว่า "coffee" แต่เรียก API ไป 6 ครั้ง! 💸
⏰ Debounce: รอให้หยุดทำก่อนค่อยตอบสนอง
Debounce = รอให้หยุดทำก่อน แล้วค่อยตอบสนอง
เหมือนลิฟต์ที่รอคนขึ้นให้ครบก่อนค่อยปิดประตู!
// 🎯 สร้าง Debounce function
function debounce(ฟังก์ชันที่จะทำ, เวลารอ) {
let ตัวจับเวลา;
return function (...ค่าที่ส่งมา) {
// ❌ ยกเลิกตัวจับเวลาเก่า (ถ้ามี)
clearTimeout(ตัวจับเวลา);
// ⏰ ตั้งเวลาใหม่
ตัวจับเวลา = setTimeout(() => {
// ✅ ทำงานจริงเมื่อหมดเวลา
ฟังก์ชันที่จะทำ.apply(this, ค่าที่ส่งมา);
}, เวลารอ);
};
}
// 💼 ใช้งานจริง: ช่องค้นหา
const ช่องค้นหา = document.querySelector("#search");
// สร้าง function ที่มี debounce
const ค้นหาอัจฉริยะ = debounce((event) => {
const คำค้นหา = event.target.value;
console.log("🔍 ค้นหา:", คำค้นหา);
// เรียก API ตรงนี้
// fetch(`/api/search?q=${คำค้นหา}`)...
}, 500); // รอ 500ms หลังหยุดพิมพ์
ช่องค้นหา.addEventListener("input", ค้นหาอัจฉริยะ);
// 📝 ผู้ใช้พิมพ์:
// c -> เริ่มจับเวลา 500ms
// co -> ยกเลิกเก่า, เริ่มใหม่ 500ms
// cof -> ยกเลิกเก่า, เริ่มใหม่ 500ms
// coff -> ยกเลิกเก่า, เริ่มใหม่ 500ms
// coffe -> ยกเลิกเก่า, เริ่มใหม่ 500ms
// coffee -> ยกเลิกเก่า, เริ่มใหม่ 500ms
// [หยุดพิมพ์ 500ms]
// ✅ ค้นหา: "coffee" (เรียก API แค่ครั้งเดียว!)
🚦 Throttle: จำกัดความถี่การทำงาน
Throttle = ทำได้แค่ตามจังหวะที่กำหนด
เหมือนไฟจราจร - เขียวทุก 30 วินาที ไม่ว่าจะมีรถรอกี่คัน!
// 🎯 สร้าง Throttle function
function throttle(ฟังก์ชันที่จะทำ, ระยะเวลา) {
let กำลังรอ = false;
return function (...ค่าที่ส่งมา) {
// 🔒 ถ้ากำลังรออยู่ = ข้าม
if (กำลังรอ) return;
// ✅ ทำงานได้!
ฟังก์ชันที่จะทำ.apply(this, ค่าที่ส่งมา);
// 🚫 ปิดไม่ให้ทำงานชั่วคราว
กำลังรอ = true;
// ⏰ เปิดให้ทำงานอีกครั้งเมื่อครบเวลา
setTimeout(() => {
กำลังรอ = false;
}, ระยะเวลา);
};
}
// 💼 ใช้งานจริง: ติดตาม scroll
const ติดตามScroll = throttle(() => {
const ตำแหน่ง = window.scrollY;
const เปอร์เซ็นต์ = Math.round((ตำแหน่ง / document.body.scrollHeight) * 100);
console.log(`📜 เลื่อนไปแล้ว ${เปอร์เซ็นต์}%`);
// อัปเดต progress bar
// updateProgressBar(เปอร์เซ็นต์);
}, 200); // ทำงานได้ทุก 200ms (5 ครั้ง/วินาที)
window.addEventListener("scroll", ติดตามScroll);
// 📝 ผลลัพธ์เมื่อเลื่อนหน้าเร็วๆ:
// [0ms] 📜 เลื่อนไปแล้ว 5% ✅ ทำงาน
// [50ms] (เลื่อน) ❌ ข้าม (ยังไม่ครบ 200ms)
// [100ms] (เลื่อน) ❌ ข้าม
// [150ms] (เลื่อน) ❌ ข้าม
// [200ms] 📜 เลื่อนไปแล้ว 25% ✅ ทำงาน
// [250ms] (เลื่อน) ❌ ข้าม
🤔 Debounce vs Throttle ใช้อันไหนดี?
| Debounce | Throttle |
|---|---|
| รอให้หยุดทำก่อนค่อยตอบสนอง | ทำงานเป็นจังหวะสม่ำเสมอ |
| ✅ ช่องค้นหา | ✅ Scroll tracking |
| ✅ ปุ่ม Save อัตโนมัติ | ✅ ปรับขนาดหน้าต่าง |
| ✅ ตรวจสอบ username | ✅ อัปเดต progress |
| "รอให้นิ่งก่อนค่อยทำ" | "ทำเป็นระยะๆ ไม่ถี่เกิน" |
🍛 Currying: แบ่ง Parameters เป็นขั้นๆ
Currying = แปลง function ที่รับหลายค่า ให้รับทีละค่า
เหมือนร้านกาแฟที่มี "เมนูพิเศษประจำ":
- ลูกค้าประจำ: "เอาแบบเดิม" (บันทึกไว้แล้ว)
- ลูกค้าใหม่: ต้องบอกทุกอย่าง
// 🍳 ตัวอย่างง่ายๆ: ร้านกาแฟ
// ❌ แบบเดิม: ต้องบอกทุกอย่างทุกครั้ง
function สั่งกาแฟ(ขนาด, ชนิด, ความหวาน, ชื่อลูกค้า) {
return `☕ ${ชนิด} ${ขนาด} หวาน ${ความหวาน}% สำหรับคุณ ${ชื่อลูกค้า}`;
}
// ต้องพิมพ์ซ้ำๆ
สั่งกาแฟ("ใหญ่", "ลาเต้", 50, "สมชาย");
สั่งกาแฟ("ใหญ่", "ลาเต้", 50, "สมหญิง"); // ซ้ำ!
สั่งกาแฟ("ใหญ่", "ลาเต้", 50, "สมศักดิ์"); // ซ้ำอีก!
// ✅ ใช้ Currying: สร้างเมนูพิเศษ
const สร้างเมนูพิเศษ = (ขนาด) => (ชนิด) => (ความหวาน) => (ชื่อลูกค้า) => {
return `☕ ${ชนิด} ${ขนาด} หวาน ${ความหวาน}% สำหรับคุณ ${ชื่อลูกค้า}`;
};
// สร้างเมนูโปรด
const ลาเต้ใหญ่หวาน50 = สร้างเมนูพิเศษ("ใหญ่")("ลาเต้")(50);
// ใช้ง่ายขึ้นมาก!
console.log(ลาเต้ใหญ่หวาน50("สมชาย"));
console.log(ลาเต้ใหญ่หวาน50("สมหญิง"));
console.log(ลาเต้ใหญ่หวาน50("สมศักดิ์"));
💼 ตัวอย่างจากงานจริง: ระบบ Log
// 📝 สร้าง Logger แบบ Curry
const createLogger = (ระดับ) => (ระบบ) => (ข้อความ) => {
const เวลา = new Date().toLocaleTimeString();
const สี = {
INFO: "🔵",
WARN: "🟡",
ERROR: "🔴",
};
console.log(`[${เวลา}] ${สี[ระดับ]} [${ระดับ}] [${ระบบ}] ${ข้อความ}`);
};
// สร้าง logger สำหรับแต่ละระบบ
const logDatabase = createLogger("INFO")("Database");
const logAPI = createLogger("INFO")("API");
const logError = createLogger("ERROR");
// ใช้งาน
logDatabase("เชื่อมต่อสำเร็จ");
// [10:30:00] 🔵 [INFO] [Database] เชื่อมต่อสำเร็จ
logAPI("รับ request จาก client");
// [10:30:01] 🔵 [INFO] [API] รับ request จาก client
logError("Payment")("ชำระเงินไม่สำเร็จ");
// [10:30:02] 🔴 [ERROR] [Payment] ชำระเงินไม่สำเร็จ
💡 ใช้ Currying เมื่อไหร่?
มี parameters ที่ใช้ซ้ำบ่อยๆ
ต้องการสร้าง function เฉพาะทาง
ทำ configuration ที่ใช้หลายที่
❌ Common Mistakes: ข้อผิดพลาดที่ทุกคนเคยเจอ
😵 Mistake #1: ลืม return
ปัญหา: Function ทำงานแล้วแต่ไม่ได้ค่า
// ❌ ลืม return
function คำนวณราคารวม(ราคา, จำนวน) {
const รวม = ราคา * จำนวน;
// คำนวณแล้วแต่ไม่ได้ส่งค่ากลับ!
}
const ยอดชำระ = คำนวณราคารวม(100, 3);
console.log(ยอดชำระ); // undefined 😭
// ✅ แก้ไข: ใส่ return
function คำนวณราคารวม(ราคา, จำนวน) {
return ราคา * จำนวน; // ไม่ลืมแล้ว!
}
console.log(คำนวณราคารวม(100, 3)); // 300 🎉
🤷 Mistake #2: this หายไปไหน?
ปัญหา: Regular function ใน setTimeout ทำให้ this หาย
// ❌ this หาย
const ผู้ใช้ = {
ชื่อ: "สมชาย",
ทักทายหลังจากนี้() {
setTimeout(function () {
// 🚨 regular function ใน setTimeout
console.log(`สวัสดี ${this.ชื่อ}`);
// สวัสดี undefined 😱
}, 1000);
},
};
// ✅ แก้ไข: ใช้ Arrow Function
const ผู้ใช้ = {
ชื่อ: "สมชาย",
ทักทายหลังจากนี้() {
setTimeout(() => {
// ✨ arrow function จำ this ได้!
console.log(`สวัสดี ${this.ชื่อ}`);
// สวัสดี สมชาย ✅
}, 1000);
},
};
👾 Mistake #3: แก้ไข Array ต้นฉบับ
ปัญหา: แก้ไข parameter โดยตรง ทำให้ข้อมูลเดิมเปลี่ยน
// ❌ แก้ไขข้อมูลเดิม (Mutate)
function เพิ่มรายการ(ตะกร้า, สินค้า) {
ตะกร้า.push(สินค้า); // แก้ไข array เดิม!
return ตะกร้า;
}
const ตะกร้าเดิม = ["🍎", "🍌"];
const ตะกร้าใหม่ = เพิ่มรายการ(ตะกร้าเดิม, "🍓");
console.log(ตะกร้าเดิม); // ["🍎", "🍌", "🍓"] - เอ้า! เปลี่ยนไปด้วย!
// ✅ แก้ไข: สร้าง Array ใหม่ (Immutable)
function เพิ่มรายการ(ตะกร้า, สินค้า) {
return [...ตะกร้า, สินค้า]; // สร้างใหม่!
}
const ตะกร้าเดิม = ["🍎", "🍌"];
const ตะกร้าใหม่ = เพิ่มรายการ(ตะกร้าเดิม, "🍓");
console.log(ตะกร้าเดิม); // ["🍎", "🍌"] - เดิมไม่เปลี่ยน! ✅
console.log(ตะกร้าใหม่); // ["🍎", "🍌", "🍓"]
😕 Mistake #4: Parameters เยอะเกิน
ปัญหา: เรียกใช้ยาก จำลำดับไม่ได้
// ❌ Parameters เยอะเกิน
function สร้างผู้ใช้(
ชื่อ,
อายุ,
อีเมล,
เบอร์,
ที่อยู่,
ตำแหน่ง,
แผนก,
เงินเดือน,
) {
// จำไม่ได้ว่าอันไหนคืออะไร!
}
// เรียกใช้:
สร้างผู้ใช้(
"สมชาย",
25,
"somchai@email.com",
"081234567",
"123 ถ.สุขุมวิท",
"Developer",
"IT",
35000,
); // อันไหนคืออะไร? 😵
// ✅ แก้ไข: ใช้ Object Parameter
function สร้างผู้ใช้({ ชื่อ, อายุ, อีเมล, เบอร์, ที่อยู่, ตำแหน่ง }) {
// ชัดเจนกว่าเดิมมาก!
console.log(`👤 ชื่อ: ${ชื่อ}`);
console.log(`🎂 อายุ: ${อายุ}`);
// ฯลฯ
}
// เรียกใช้: ชัดเจนมาก!
สร้างผู้ใช้({
ชื่อ: "สมชาย",
อายุ: 25,
อีเมล: "somchai@email.com",
// ข้ามบางค่าได้ด้วย!
});
🔍 Debugging Functions: วิธีหา Bug ให้เจอ
🤔 ทำไมต้อง Debug?
ลองนึกภาพ: คุณเป็นนักสืบที่ต้องหาว่าทำไมโค้ดไม่ทำงาน
📊 ดูข้อมูล Function
// 🔍 ดูคุณสมบัติของ function
function คำนวณภาษี(ราคา, อัตราภาษี) {
return ราคา * (อัตราภาษี / 100);
}
// ดูชื่อ function
console.log("🏷️ ชื่อ:", คำนวณภาษี.name);
// 🏷️ ชื่อ: คำนวณภาษี
// ดูจำนวน parameters
console.log("🔢 รับกี่ค่า:", คำนวณภาษี.length);
// 🔢 รับกี่ค่า: 2
// ดู source code
console.log("📝 โค้ด:", คำนวณภาษี.toString());
🔬 ใช้ debugger ดู Scope
// 🏠 ตัวอย่าง Scope ซ้อนกัน
function ภายนอก() {
const ข้อความนอก = "🌳 ฉันอยู่ข้างนอก";
const ค่าคงที่ = 100;
function ภายใน() {
const ข้อความใน = "🏠 ฉันอยู่ข้างใน";
// 🛑 หยุดตรงนี้! เปิด DevTools ดู Scope
debugger;
// ใน DevTools จะเห็น:
// Local: { ข้อความใน }
// Closure: { ข้อความนอก, ค่าคงที่ }
// Global: { window, document, ... }
console.log(ข้อความใน);
console.log(ข้อความนอก);
console.log(ค่าคงที่);
}
ภายใน();
}
ภายนอก();
// เปิด Console และดู Sources tab ใน DevTools
📈 ติดตาม Function Calls
// 🎯 สร้าง Function Tracker
function ติดตามFunction(functionToTrack) {
let จำนวนครั้ง = 0;
let ประวัติ = [];
return function (...args) {
จำนวนครั้ง++;
const เวลา = new Date().toLocaleTimeString();
console.log(`🔄 เรียกครั้งที่ ${จำนวนครั้ง}`);
console.log(`⏰ เวลา: ${เวลา}`);
console.log(`📥 ค่าที่ส่งมา:`, args);
// เรียก function จริง
const ผลลัพธ์ = functionToTrack.apply(this, args);
console.log(`📤 ผลลัพธ์:`, ผลลัพธ์);
console.log("-".repeat(30));
// เก็บประวัติ
ประวัติ.push({ เวลา, args, ผลลัพธ์ });
return ผลลัพธ์;
};
}
// ใช้งาน
const บวก = (a, b) => a + b;
const บวกแบบติดตาม = ติดตามFunction(บวก);
บวกแบบติดตาม(5, 3);
// 🔄 เรียกครั้งที่ 1
// ⏰ เวลา: 10:30:00
// 📥 ค่าที่ส่งมา: [5, 3]
// 📤 ผลลัพธ์: 8
บวกแบบติดตาม(10, 20);
// 🔄 เรียกครั้งที่ 2
// ...
🐛 เทคนิคหา Bug เกี่ยวกับ Scope
// 🔍 ตรวจสอบว่าตัวแปรอยู่ scope ไหน
function ตรวจสอบScope() {
const ตัวแปรLocal = "ฉันอยู่ใน function";
console.log("🏠 Local:", ตัวแปรLocal);
// ตรวจสอบ global
if (typeof window !== "undefined") {
console.log("🌍 Global มี window");
}
// ตรวจสอบว่าตัวแปรมีจริงไหม
try {
console.log(ตัวแปรที่ไม่มี);
} catch (error) {
console.log("❌ ตัวแปรนี้ไม่มี!");
}
}
ตรวจสอบScope();
💡 เทคนิค Debug ที่ควรรู้:
ใช้ console.log() แบบมีหัวข้อ เช่น console.log("🔍 ค่า x:", x)
ใช้ debugger; เพื่อหยุดโค้ด
ดู Call Stack ใน DevTools
ใช้ breakpoints แทน console.log เยอะๆ
⚡ Performance Tips: เทคนิคทำให้โค้ดเร็วขึ้น
🚫 อย่าสร้าง Function ใน Loop
ปัญหา: สร้าง function ใหม่ซ้ำๆ กิน memory
// ❌ แย่มาก: สร้าง 1000 functions!
const ปุ่มทั้งหมด = document.querySelectorAll(".button");
for (let i = 0; i < ปุ่มทั้งหมด.length; i++) {
ปุ่มทั้งหมด[i].addEventListener("click", () => {
// สร้าง function ใหม่ทุกรอบ!
console.log(`คลิกปุ่ม ${i}`);
});
}
// ถ้ามี 1000 ปุ่ม = สร้าง 1000 functions! 💣
// ✅ ดี: ใช้ function เดียว
function จัดการคลิก(event) {
// ดึงข้อมูลจาก data attribute
const หมายเลข = event.target.dataset.index;
console.log(`คลิกปุ่ม ${หมายเลข}`);
}
for (let i = 0; i < ปุ่มทั้งหมด.length; i++) {
ปุ่มทั้งหมด[i].dataset.index = i;
ปุ่มทั้งหมด[i].addEventListener("click", จัดการคลิก);
}
// ใช้ function เดียว = ประหยัด memory! 🎉
🧠 Memoization: จำคำตอบไว้
คืออะไร? เหมือนนักเรียนที่จดคำตอบ - ถ้าเคยคิดแล้วไม่ต้องคิดใหม่!
// 🎯 สร้าง Memoization
function memoize(functionToCache) {
const กล่องเก็บคำตอบ = new Map();
return function (...args) {
// สร้าง "กุญแจ" จาก arguments
const กุญแจ = JSON.stringify(args);
// 🔍 เคยคิดแล้ว?
if (กล่องเก็บคำตอบ.has(กุญแจ)) {
console.log("📦 เจอในกล่อง!");
return กล่องเก็บคำตอบ.get(กุญแจ);
}
// 🧮 ไม่เคยคิด - คิดใหม่
console.log("🧮 คิดใหม่...");
const คำตอบ = functionToCache.apply(this, args);
// 💾 เก็บไว้ในกล่อง
กล่องเก็บคำตอบ.set(กุญแจ, คำตอบ);
return คำตอบ;
};
}
// 🎮 ตัวอย่าง: คำนวณ Fibonacci
function fibonacci(n) {
// คำนวณนานมากเมื่อ n ใหญ่
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// ใช้ memoization
const fibonacciฉลาด = memoize(fibonacci);
console.log(fibonacciฉลาด(10));
// 🧮 คิดใหม่... -> 55
console.log(fibonacciฉลาด(10));
// 📦 เจอในกล่อง! -> 55 (เร็วมาก!)
console.log(fibonacciฉลาด(20));
// 🧮 คิดใหม่... -> 6765
🎯 Event Delegation: ใช้ Event ร่วมกัน
// ❌ แย่: ใส่ listener ทุกตัว
const รายการทั้งหมด = document.querySelectorAll("li");
รายการทั้งหมด.forEach((li) => {
li.addEventListener("click", () => {
console.log("คลิกรายการ");
});
});
// 100 รายการ = 100 event listeners! 😱
// ✅ ดี: ใช้ Event Delegation
const กล่องใส่รายการ = document.querySelector("ul");
กล่องใส่รายการ.addEventListener("click", (event) => {
// ตรวจสอบว่าคลิกที่ li
if (event.target.tagName === "LI") {
console.log("คลิกรายการ:", event.target.textContent);
}
});
// 1 event listener จัดการทุกรายการ! 🎆
💡 สรุป Performance Tips:
อย่าสร้าง function ใน loop
ใช้ memoization กับคำนวณที่ซับซ้อน
ใช้ event delegation กับ elements เยอะๆ
ใช้ debounce/throttle กับ event ที่เกิดถี่ๆ
🎮 ลองทำดู: แบบฝึกหัด
📝 Exercise 1: สร้าง Calculator Function
// 🎯 จงสร้าง function คำนวณภาษี VAT 7%
// ที่รับราคาสินค้าและคืนค่าราคารวม VAT
function calculateVAT(price) {
// เขียนโค้ดที่นี่
}
// ทดสอบ:
console.log(calculateVAT(100)); // ควรได้ 107
console.log(calculateVAT(500)); // ควรได้ 535
console.log(calculateVAT(1000)); // ควรได้ 1070
💡 เฉลย
function calculateVAT(price) {
const vat = price * 0.07;
return price + vat;
}
// หรือเขียนสั้นๆ
const calculateVAT = (price) => price * 1.07;
🔐 Exercise 2: Closure Counter
// 🎯 สร้าง counter ที่มี methods: increment, decrement, getValue
// โดยที่ค่า count ต้องเป็น private (เข้าถึงจากข้างนอกไม่ได้)
function createCounter(initialValue = 0) {
// เขียนโค้ดที่นี่
}
// ทดสอบ:
const counter = createCounter(10);
counter.increment(); // 11
counter.increment(); // 12
counter.decrement(); // 11
console.log(counter.getValue()); // 11
console.log(counter.count); // undefined (private!)
💡 เฉลย
function createCounter(initialValue = 0) {
let count = initialValue; // private variable
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getValue() {
return count;
},
};
}
⏱️ Exercise 3: Debounce Search
// 🎯 สร้าง debounced search function
// ที่จะค้นหาเมื่อผู้ใช้หยุดพิมพ์แล้ว 500ms
function createDebouncedSearch(searchFunction, delay = 500) {
// เขียนโค้ดที่นี่
}
// ทดสอบ:
const search = createDebouncedSearch((query) => {
console.log("Searching for:", query);
}, 500);
search("j"); // ไม่ค้นหา
search("ja"); // ไม่ค้นหา
search("jav"); // ไม่ค้นหา
search("java"); // ไม่ค้นหา
// รอ 500ms
// "Searching for: java" (ค้นหาครั้งเดียว!)
💡 เฉลย
function createDebouncedSearch(searchFunction, delay = 500) {
let timeoutId;
return function (query) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
searchFunction(query);
}, delay);
};
}
🎉 Outcome: สิ่งที่คุณทำได้แล้ว
ยินดีด้วย! 🎊 หลังจบบทนี้ คุณสามารถ:
🔧 ทักษะการสร้าง Functions
- ✅ เข้าใจความต่างของ 3 วิธีเขียน Function - Declaration (hoisted), Expression (flexible), Arrow (short & this inheritance)
- ✅ จัดการ Parameters แบบมืออาชีพ - default values, rest parameters, destructuring
- ✅ เข้าใจ this binding - รู้ว่า this ชี้ไปไหน และแก้ปัญหาด้วย arrow functions หรือ bind()
🏗️ ทักษะการจัดการ Scope
- ✅ เข้าใจ Scope และ Hoisting - รู้ว่าตัวแปรมองเห็นกันยังไง JavaScript ยกอะไรขึ้นก่อน
- ✅ ใช้ Closures สร้าง Private State - ซ่อนข้อมูลและสร้าง factory functions
- ✅ เข้าใจ Scope Chain - รู้ว่า JavaScript หาตัวแปรยังไง ทำไมบางตัวเจอบางตัวไม่เจอ
🚀 ทักษะขั้นสูง
- ✅ เขียน Higher-Order Functions - function ที่รับหรือ return function ใช้ในทุก modern framework
- ✅ ใช้ Callback Pattern - ส่ง function ไปทำงานให้ foundation ของ async programming
- ✅ Apply Real Patterns - debounce (รอให้หยุดก่อน), throttle (จำกัดความถี่) ใช้จริงในทุกเว็บ
🐛 ทักษะ Debug และ Optimize
- ✅ Debug Functions และ Scope - ใช้ DevTools, console.trace(), breakpoints หา bug ได้เร็วขึ้น
- ✅ เขียน Clean Functions - single responsibility, pure functions, avoid side effects
- ✅ Optimize Performance - memoization, avoid recreating functions, smart event handling
🚀 ที่สำคัญที่สุด
- ✅ มีพื้นฐานแข็งแรงสำหรับทุก Framework - React hooks, Vue composition, Angular services ทั้งหมดคือ functions
- ✅ เขียนโค้ดที่ maintain ได้ - แยก logic เป็น functions เล็กๆ ทดสอบง่าย แก้ไขง่าย
💡 Key Takeaways - บทเรียนสำคัญ
🎯 สิ่งที่ต้องจำ
- Function Declaration hoisted, Expression/Arrow ไม่ hoisted - Declaration เรียกก่อนประกาศได้ แต่อีก 2 แบบต้องประกาศก่อน
- Arrow functions ไม่มี this ของตัวเอง - inherit จาก parent scope เหมาะกับ callbacks และ event handlers
- const/let = block scope, var = function scope - ลืม var ไปได้เลย ใช้แต่ const/let
- Closures จำ environment ได้ - function ที่เข้าถึงตัวแปรจาก outer scope ใช้สร้าง private state
🛠️ เคล็ดลับการใช้งาน
- Higher-order functions คือพื้นฐาน - map, filter, reduce, React hooks ทั้งหมดคือ higher-order functions
- Debounce รอให้หยุดก่อน, Throttle จำกัดความถี่ - debounce ใช้กับ search, throttle ใช้กับ scroll
- Pure functions เขียนง่าย test ง่าย - รับ input เดียวกัน ได้ output เดียวกัน ไม่มี side effects
🌟 Mindset ที่ถูกต้อง
- Functions คือ building blocks - แยกโค้ดเป็น functions เล็กๆ แต่ละอันทำงานเดียว
- Scope ช่วยป้องกัน bugs - ใช้ block scope จำกัดขอบเขตตัวแปร ป้องกันการเขียนทับโดยไม่ตั้งใจ
- Debug ด้วย DevTools ไม่ใช่ console.log - ใช้ breakpoints, call stack, scope inspector จะเห็นภาพรวมดีกว่า
🎯 Next Step - ก้าวต่อไป
📚 บทหน้า: Objects & Arrays
ในบทหน้า เราจะเรียนรู้การทำงานกับข้อมูลจริง:
🔹 Objects - ข้อมูลแบบมีโครงสร้าง
- Object methods และ this context
- Prototypes และ inheritance
- Object destructuring และ shortcuts
🔹 Arrays - จัดการข้อมูลหลายตัว
- Array methods ขั้นสูง (map, filter, reduce)
- Chaining methods เขียนโค้ดสายเดียว
- Array destructuring และ spread operator
🔹 Modern Patterns
- Immutability - ทำไมต้องไม่แก้ข้อมูลเดิม
- Functional programming กับ arrays
- Real-world: data transformation, filtering, aggregation
🚀 พร้อมแล้วใช่ไหม?
คุณมี functions และ scope แข็งแรงแล้ว ต่อไปเรียนการจัดการข้อมูล!
💭 Functions:
Functions ไม่ใช่แค่กล่องเก็บโค้ด แต่เป็น "first-class citizens" ใน JavaScript - ส่งเป็น argument ได้, return จาก function ได้, เก็บในตัวแปรได้ นี่คือพลังที่ทำให้ JavaScript ยืดหยุ่นมาก และเป็นหัวใจของทุก modern framework
ถ้าเข้าใจ functions และ scope ดีๆ คุณจะเข้าใจว่าทำไม React ถึงใช้ hooks, Vue ใช้ composition API, และ Angular ใช้ dependency injection - ทั้งหมดคือการเล่นกับ functions และ scope! ✨