Fundamentals
Block Scope
อธิบายว่า block ใน `{ }` จำกัดการมองเห็นตัวแปรอย่างไร พร้อมแยกแยะความต่างของ `let`, `const` และ `var` แล้วฝึกแยกค่าชั่วคราวออกจากค่าจริงผ่านโจทย์ที่ไล่ระดับ
1) ปัญหา: ค่าชั่วคราวไปทับค่าที่ต้องเก็บไว้
ร้านกาแฟร้านหนึ่งเก็บยอดขายรวมทั้งวันไว้ในตัวแปร `dailyTotal` ต่อมาพนักงานอยากคำนวณยอดของชั่วโมงล่าสุดชั่วคราว (ขายได้ 3 แก้ว แก้วละ 45 บาท) เพื่อนำไปบวกเข้ายอดรวม ถ้าเอาค่าชั่วคราวไปเก็บทับใน `dailyTotal` โดยตรง ยอดขายทั้งวันจะหายไปทันที เพราะ JavaScript จำได้แค่ค่าล่าสุดที่ชื่อนั้นเก็บไว้
โปรแกรมรันได้ไม่ error แต่ยอดทั้งวันเหลือ 135 — ผิดตั้งแต่ค่าที่แสดง
ทายก่อน: ถ้าต้องคำนวณยอดชั่วคราวโดยไม่ให้ทับ `dailyTotal` เราจะแยกค่าชั่วคราวออกจากค่าจริงได้อย่างไร?
2) Block คือพื้นที่ในวงเล็บปีกกา `{ }`
Block คือกลุ่มคำสั่งที่อยู่ในวงเล็บปีกกา `{ }` เราใช้ block แบ่งโค้ดออกเป็นพื้นที่ย่อยได้ทันที โดยไม่ต้องมีคำสั่งอื่นมาครอบ Scope คือขอบเขตที่ชื่อตัวแปรถูกมองเห็นและเรียกใช้ได้ เมื่อตัวแปรถูกประกาศใน block ขอบเขตของมันคือ block นั้น สรุปสั้น ๆ: ตัวแปรที่ประกาศใน `{ }` จะใช้ได้เฉพาะใน `{ }` เดียวกันเท่านั้น
tempTotal มีชีวิตอยู่แค่ใน block ส่วน dailyTotal อยู่นอก block จึงไม่ถูกเขียนทับ
3) `let` และ `const` อยู่แค่ใน block ของตัวเอง
ตัวแปรที่ประกาศด้วย `let` หรือ `const` จะมองเห็นได้เฉพาะใน block ที่ประกาศ เมื่อปิด `{ }` แล้ว ชื่อนั้นจะหายไปจากสายตาของโค้ดด้านนอก ผลที่ได้คือตัวแปรชั่วคราวไม่ไปรบกวนชื่ออื่นด้านนอก และเราตั้งชื่อซ้ำในแต่ละ block ได้อย่างปลอดภัย
บรรทัดใน block ทำงานได้ ส่วนบรรทัดนอก block หยุดเพราะไม่รู้จักชื่อ message
สิ่งที่เกิดขึ้นเมื่อปิด block
ชื่อที่ประกาศข้างในมองไม่เห็นจากข้างนอก
ไม่ใช่ค่าที่หายไป แต่เป็นชื่อที่อยู่นอกขอบเขตการมองเห็นของโค้ดด้านนอก
ค่าถูกเก็บไว้จนกว่า block จะจบ
ภายใน block ตัวแปรทำงานเหมือนตัวแปรทั่วไป ใช้ได้ทุกบรรทัดก่อนปิด `{ }`
4) `var` ไม่ถูกจำกัดด้วย block
`var` เป็นคีย์เวิร์ดรุ่นเก่าที่ยังเจอในโค้ดเดิม ตัวแปรที่ประกาศด้วย `var` ไม่ถูกจำกัดด้วย `{ }` จึงยังมองเห็นจากด้านนอก block ได้
ด้านนอก block ยังเรียก oldStyle ได้แม้จะประกาศไว้ข้างใน
ทายก่อน: ถ้าเปลี่ยน `var oldStyle` เป็น `let oldStyle` ในตัวอย่างข้างบน บรรทัด `console.log(oldStyle)` ด้านนอกจะเกิดอะไรขึ้น?
| คีย์เวิร์ด | ถูก block จำกัดไหม | สิ่งที่ควรจำ |
|---|---|---|
| let | จำกัด | ใช้ได้เฉพาะใน `{ }` ที่ประกาศ และแก้ค่าได้ |
| const | จำกัด | ใช้ได้เฉพาะใน `{ }` ที่ประกาศ และแก้ค่าไม่ได้ |
| var | ไม่จำกัด | หลุดออกมานอก `{ }` ได้ ทำให้ตามบั๊กยากขึ้น |
โค้ดสมัยใหม่จึงเลือก `const` เป็นค่าเริ่มต้น เปลี่ยนเป็น `let` เมื่อค่านั้นต้องแก้ภายหลัง และหลีกเลี่ยง `var` เพราะขอบเขตของมันไม่ตรงกับที่ตาเห็น
5) Shadowing: ชื่อเดียวกันแต่คนละ block
Shadowing คือการประกาศชื่อซ้ำกับตัวแปรด้านนอก แต่ประกาศใน block ด้านใน ตัวแปรด้านในจะบังชื่อด้านนอกไว้ชั่วคราวเฉพาะใน block นั้น เมื่อปิด block แล้ว โค้ดด้านนอกกลับไปเห็นตัวแปรด้านนอกตามเดิม
ค่าด้านในถูกใช้ก่อน ปิด block แล้วค่าด้านนอกกลับมาเหมือนเดิม
Shadowing ไม่ใช่ error แต่ควรใช้เมื่อทำให้โค้ดชัดขึ้นจริง ๆ ถ้าชื่อซ้ำแล้วอ่านยาก ให้ตั้งชื่อใหม่ที่บอกหน้าที่ของค่ามากกว่า
6) แยกค่าชั่วคราวกับค่าจริงให้ถูกที่
ทายก่อน: จากสองแบบนี้ แบบไหนทำให้ยอดทั้งวันยังเป็น 1200 และแบบไหนทำให้ค่าจริงหายไป?
ค่าเดียวกันคนละที่ ต่างกันที่ผลลัพธ์
ค่าจริงหายไปตั้งแต่บรรทัดที่เขียนทับ เพราะมีชื่อเดียวเก็บได้ทีละค่า
ค่าจริงถูกบวกเพิ่ม ส่วนค่าชั่วคราวจบใน block ของตัวเอง
สามคำถามก่อนใช้ block
ค่านี้ต้องใช้ต่อหลังปิด `{ }` ไหม
ถ้าใช้ ให้ประกาศตัวแปรด้านนอกก่อน แล้วกำหนดค่าจากใน block
ชื่อนี้ซ้ำกับด้านนอกหรือเปล่า
ถ้าซ้ำ ตัวแปรใน block จะบังตัวแปรด้านนอกชั่วคราว — ตรวจว่าตั้งใจให้เป็นแบบนั้น
ควรใช้ `var` ไหม
เริ่มจาก `const` เปลี่ยนเป็น `let` เมื่อต้องแก้ค่า แล้วเลี่ยง `var`
Lab 1: แยกค่าชั่วคราวไว้ใน block
Lab 2: ชื่อเดียวกันในคนละ block
Lab 3: ย้ายค่าออกมาใช้ต่อหลัง block
Lab 4: เปลี่ยน `var` ให้อยู่ใน block
Lab 5: สอง block รวมยอดเข้าเป้าเดียว
Lab 6: คำนวณยอดทั้งวันโดยไม่ทับค่าจริง
Lab 7: ซ่อมโปรแกรมที่ประกาศชื่อซ้ำ
สรุปและก้าวต่อไป
- Block `{ }` คือพื้นที่ย่อยที่แยกตัวแปรออกจากกัน
- `let` และ `const` มองเห็นได้เฉพาะใน block ที่ประกาศ ส่วน `var` หลุดออกไปได้ จึงควรเลี่ยง
- ถ้าต้องใช้ค่าหลังปิด block ให้ประกาศตัวแปรด้านนอกแล้วกำหนดค่าจากใน block
- Shadowing คือชื่อซ้ำใน block ซ้อนกัน ตัวในบังตัวนอกชั่วคราวเท่านั้น
- ก้าวต่อไป: เรียน Data types (data-types) เพื่อรู้ว่าค่าแต่ละค่ามีชนิดอะไร และใช้ `typeof` แยกชนิดได้อย่างไร