7 เรื่องพื้นฐานชวนสับสนใน JavaScript สำหรับผู้เริ่มต้น
JavaScript ช่างเป็นภาษาที่น่าปวดหัวสำหรับผู้เริ่มต้นยิ่งนักจนมีคนเขียนบทความ The World's Most Misunderstood Programming Language แต่นั่นละฮะท่านผู้ชม เมื่อเราต้องการใช้ภาษานี้เราก็ต้องเรียนรู้สิ่งที่ภาษานี้เป็น และนี่คือ7สิ่งมหัศจรรย์ของโลก JavaScript ที่อาจทำให้ผู้เริ่มต้นเอ๋อไปตามๆกัน
1. Closure
ก่อนที่ผมจะอธิบายว่าสิ่งนี้คืออะไร อยากให้เพื่อนๆลองพิจารณาตัวอย่างนี้กันก่อนครับ
1function print() {2 const name = 'Nuttavut Thongjor'34 // return function ออกไปเมื่อเรียกฟังก์ชัน print5 return function() {6 console.log(name)7 }8}910const printName = print()11printName() // Nuttavut Thongjor
เพื่อนๆสังเกตุเห็นอะไรจากตัวอย่างนี้บ้างครับ? ในบรรทัดที่10เราเรียกฟังก์ชัน print ที่คืนค่ากลับมาเป็นฟังก์ชันอีกตัวหนึ่ง ภายหลังจากเรียกฟังก์ชันแล้วตัวแปร name ที่อยู่ข้างในควรถูกทำลายใช่ไหมครับ เพราะเป็น local variable เมื่อจบการเรียก print() ควรถูกทำลาย แต่น่าแปลกทำไมเมื่อเราเรียก printNameในบรรทัดที่11กลับพบว่าฟังก์ชันนี้ยังเข้าถึงตัวแปร name ได้อยู่? (ดูบรรทัดที่6ประกอบ)
closure เป็นอ็อบเจ็กต์สุดพิเศษที่รวมฟังก์ชันและสภาพแวดล้อมที่ห่อหุ้มมันอยู่ในตอนที่ closure เองโดนสร้างขึ้นมา ดังนั้นเจ้า printName ของเราจึงเป็น closure ครับ เพราะมันเป็นฟังก์ชันและโดนโอบล้อมด้วยฟังก์ชันอีกตัว เช่นนี้แล้วมันจึงเข้าถึงสภาพแวดล้อมที่ห่อหุ้มมันอยู่ได้ หนึ่งในนั้นคือตัวแปร name นั่นเอง
แม้ฟังก์ชัน print จะสิ้นชีพไปแล้วหลังการเรียกใช้ แต่ตัวแปร name นั้นยังคงอยู่เพราะมีฟังก์ชันภายในที่อ้างไปถึงมัน สิ่งที่ต้องเข้าใจเป็นพิเศษคือ closure ไม่ใช่การก็อบปี้ตัวแปรนะครับ ตัวแปร name ไม่ได้โดนก็อบปี้เข้ามาเก็บไว้ในฟังก์ชันภายในนะ เพราะถ้าเป็นเช่นนั้นจริง เรามีฟังก์ชันซ้อนกันอยู่ซักร้อยตัว อ้างถึง name ทุกฟังก์ชัน การก็อบปี้ name มาใส่แต่ละฟังก์ชันจะเปลืองหน่วยความจำซึ่งไม่สมเหตุสมผล
เราได้ประโยชน์อะไรจาก closure บ้าง? อย่างแรกเลยคือ เราสามารถสร้างฟังก์ชันที่อาศัยตัวแปรจากฟังก์ชันที่ห่อหุ้มมันอยู่ได้ เช่น
1function print(lastName) {2 return function(firstName) {3 // ภายใต้ฟังก์ชันนี้ตัวแปร lastName ไม่หายไปไหน4 console.log(`${firstName} ${lastName}`)5 }6}78// ภายหลังเรียก print ตัวแปร lastName ควรถูกทำลาย9// แต่ด้วยความสามารถของ closure ทำให้ตัวแปรนี้ยังคงอยู่10// และเข้าถึงได้จากฟังก์ชันภายใน11const printSmithFamily = print('Smith')1213printSmithFamily('John') // John Smith14printSmithFamily('Adam') // Adam Smith
นอกจากนี้ closure ยังนำไปใช้กับ module pattern ได้อีกด้วย
1let sum = 023function add(number) {4 sum += number5}67add(10)8console.log(sum)9sum = 010add(10)11console.log(sum)
จากตัวอย่างข้างบน เราประกาศตัวแปรชื่อ sum ไว้นอกสุดแบบนี้ทำให้โค๊ดส่วนอื่นของเราอาจไปเปลี่ยนแปลงแก้ไขมันได้ เช่นในบรรทัดที่9 ทั้งๆที่เราเรียก add(10) สองครั้งควรได้ผลลัพธ์เป็น 20 แต่เราดันเผลอไปแก้ไข sum ซะก่อนโดยไม่ได้ตั้งใจ เพื่อเป็นการป้องกันเหตุบังเอิญเช่นนี้ เราจึงควรให้ sum มีผลเฉพาะที่ด้วยการทำ Immediately-Invoked Function Expression (IIFE) ดังนี้
1let sum = 023const utils = (4 function() {5 // sum ตัวนี้ใช้ใน scope นี้ไม่เกี่ยวกับ sum ตัวนอก6 // การแก้ไข sum ตัวนอกจะไม่กระทบกับ sum ตัวนี้7 let sum = 089 return {10 add(number) {11 // ด้วยผลของ closure12 // sum ตัวนี้จึงหมายถึง sum ในบรรทัดที่713 sum += number14 },15 getSum() {16 return sum17 }18 }19 }20)()21// เนื่องจากของข้างในเป็นฟังก์ชัน เราต้องการเรียกใช้ฟังก์ชันทันทีจึงใส่ () เข้าไป22// ผลลัพธ์ที่ได้จากการเรียกฟังก์ชันนี้จะคืนค่าเป็นอ็อบเจ็กต์ที่ประกอบด้วย add และ getSum23// เรียกเทคนิคนี้ว่า IIFE2425utils.add(10)26console.log(sum) // 027sum = 028utils.add(10)29console.log(sum) // 030console.log(utils.getSum()) // 20
สำหรับเพื่อนๆที่ใช้งาน ES2015 ผมแนะนำให้แยกโมดูลออกเป็นไฟล์แล้วใช้คำสั่ง import/export ในการจัดการโมดูลแทน อ่านเพิ่มเติมที่พื้นฐาน ES2015 สำหรับการเขียน JavaScript สมัยใหม่
เอาหละสิ่งสุดท้ายสำหรับ closure ที่อยากจะพูดถึงครับนั่นคือความเข้าใจผิดในการใช้งาน closure กับ loop
1for(var i = 0; i < 3; i++) {2 setTimeout(3 // callback function ที่จะเรียกทำงานหลังผ่านไป 1 วินาที4 function() { console.log(i) },5 10006 )7}89// 310// 311// 3
เราวนลูปค่าตั้งแต่ 0 ถึง 2 เพื่อให้พิมพ์ค่า i ออกหน้าจอทุก 1 วินาที แต่ทำไมคำตอบที่ได้กลับเป็น3ล้วน?
การทำงานของ setTimeout เป็นแบบ asynchronous คือจะทำงานเมื่อเวลาผ่านไป 1 วินาที JavaScript จะอ่านโค๊ดจากบนลงล่าง พร้อมทั้งทำงานโค๊ดที่เป็น synchronous เช่นการวนลูปทั้งหมด แต่ถ้ามันเจอการทำงานแบบ asynchronous มันจะยังไม่ทำงานเพียงแต่เก็บ callback function ไว้เพื่อปลุกมาทำงานภายหลังอีกที เนื่องจาก callback เป็นฟังก์ชันที่โดนห่อหุ้มภายใต้ตัวแปร i มันจึงเป็น closure ด้วย ถึงตอนนี้ JavaScript จะวนลูปจนเสร็จก่อนเพราะเป็นโค๊ดแบบ synchronous ทำงานทันที เมื่อวนลูปเสร็จจึงไปเรียก callback เมื่อถึงเวลา ในตอนนี้ค่าของตัวแปร i จึงเป็น 3 รายละเอียดเพิ่มเติมศึกษาได้จาก รู้ลึกการทำงานแบบ Asynchronous กับ Event Loop
เพื่อเป็นการแก้ไขปัญหานี้เราสามารถใช้ let ในการประกาศตัวแปรแทนได้ รายละเอียดเพิ่มเติมศึกษาได้จาก พื้นฐาน ES2015 สำหรับการเขียน JavaScript สมัยใหม่
1for(let i = 0; i < 3; i++) {2 setTimeout(3 function() { console.log(i) },4 10005 )6}
2. JavaScript Hoisting
ก่อนที่จะอธิบายถึงเรื่องนี้ ลองดูตัวอย่างกันก่อนดีกว่าครับ
1console.log(x) // undefined2console.log(print()) // print3console.log(y) // y is not defined45var x = 367function print() {8 console.log('print')9}
แปลกใจไหมครับ เราประกาศตัวแปร x ไว้ที่บรรทัด5 นั่นคือประกาศตัวแปรหลังการเรียกใช้ แต่ทำไมบรรทัดแรกสุดถึงไม่บ่น error ว่าไม่ได้ประกาศตัวแปร? เหตุผลก็คือเพราะ JavaScript จะกระดึ๊บๆส่วนประกาศตัวแปรหรือฟังก์ชันไปไว้บนสุดนั่นเอง ย้ำนะครับว่าแค่ส่วนประกาศ แต่มันไม่เอาค่าตั้งต้นไปไว้บนสุดด้วย เพราะถ้าเอาค่าตั้งต้นไปไว้บนสุด เราคงเห็นแล้วว่ามันพิมพ์เลข3ออกมา เราเรียกการกระดึ๊บส่วนประกาศเช่นนี้ว่า Hoisting
คราวนี้ลองเปลี่ยนใหม่ด้วยการประกาศตัวแปรผ่าน let ดังนี้
1console.log(x) // ReferenceError: x is not defined2console.log(print()) // print3console.log(y) // y is not defined45let x = 367function print() {8 console.log('print')9}
let และ const ยังคง hoisting อยู่ครับ แต่มีสองสิ่งที่แตกต่างไปจากการประกาศตัวแปรด้วย var คือ
- var จะ hoist ตัวแปรไปไว้บนสุดของ function scope หรือกระดึ๊บตัวแปรไปไว้ใกล้ๆจุดประกาศฟังก์ชัน แต่ let และ const นั้นต่างออกไป มันจะ hoist ตัวแปรไปไว้บนสุดของ block scope หรือง่ายๆก็คือจุดที่ปีกกา {} ครอบมันอยู่
- ในบรรทัดแรก เราเข้าถึงตัวแปร x แต่ยังไม่เจอการประกาศตัวแปรนี้ สำหรับ let และ const จะโยน ReferenceError ออกมา ทั้งนี้เป็นเพราะ ES2015 ไม่อนุญาตให้เข้าถึงตัวแปรประเภท let และ const ก่อนถึงจุดประกาศตัวแปร เราเรียกจุดบอดก่อนถึงคำสั่งประกาศตัวแปรนี้ว่า Temporal dead zone
สรุปเรื่อง hoisting อย่างสั้นๆได้ตารางนี้ครับ
ประเภท | Hoising |
---|---|
var | function scope |
let/const | block scope + Temporal dead zone |
class | ไม่ทำ |
function | hoist อย่างสมบูรณ์ |
ประโยค import | hoist อย่างสมบูรณ์ |
เพื่อเป็นการป้องกันผลลัพธ์ที่ไม่คาดฝัน จึงแนะนำให้ประกาศตัวแปรไว้บนสุดเลย (ขอบคุณคุณ saknarak ที่แจ้งข้อผิดพลาดบทความในหัวข้อนี้ครับ)
3. Equality Operators
น่าจะทราบกันดีครับว่า JavaScript มีเครื่องหมายตรวจสอบการเท่ากันอยู่สองแบบคือ === และ == โดยเครื่องหมาย == ใช้ตรวจสอบเพียงว่ามีค่าเท่ากันหรือไม่ แต่ === นอกจากตรวจสอบว่ามีค่าเท่ากันหรือไม่แล้วยังตรวจสอบว่าข้อมูลเป็นชนิดเดียวกันหรือไม่ด้วย
1console.log(1 == '1') // true23// ข้างซ้ายเป็นตัวเลข ข้างขวาเป็น string จึงเป็นคนละชนิดข้อมูล4console.log(1 === '1') // false
แต่สำหรับการเปรียบเทียบอ็อบเจ็กต์นั้นหลายคนมักเข้าใจผิดว่าแค่ใช้ === ก็เพียงพอแล้วสำหรับการตรวจสอบค่าที่เท่ากัน ซึ่งความเป็นจริงนั้นไม่ใช่เพราะสิ่งที่ตัวแปรเก็บนั้นคือ memory address ของอ็อบเจ็กต์ ทุกครั้่งที่สร้างอ็อบเจ็กต์ใหม่แม้จะมีไส้ในเหมือนกัน แต่ memory address ที่ได้จะต่างกัน
1const obj1 = { a: 1 }2const obj2 = { a: 1 }34console.log(obj1 === obj2) // false
4. null และ undefined
ใน JavaScript นั้น null และ undefined ไม่ใช่ค่าเดียวกัน การที่เราประกาศตัวแปรโดยไม่กำหนดค่าให้จะได้ว่าตัวแปรนั้นเป็น undefined กรณีของ null จะเกิดขึ้นเมื่อเราตั้งค่าให้ตัวแปรนั้่นเป็น null เพื่อเป็นการบอกว่าตัวแปรนี้ไม่ได้ชี้ไปที่อ็อบเจ็กต์ไหนเลย
1let a2let a3console.log(typeof a) // undefined4console.log(typeof undefined) // undefined5console.log(typeof null) // object
จากตัวอย่างข้างบนพบว่าตัวของ null เองนั้นเป็นอ็อบเจ็กต์ ฉะนั้นแล้วเราจึงกำหนดค่าให้ตัวแปรใดๆเป็น null ในกรณีที่เราต้องการบอกว่าตัวแปรนั้นไม่ได้ชี้ไปที่อ็อบเจ็กต์ใดเลยในขณะนั้น
1let obj = {2 text: 'object'3}45// obj ไม่ได้ชี้ไปที่อ็อบเจ็กต์ไหนแล้ว6obj = null
เมื่อใช้เครื่องหมาย == และ === เพื่อเปรียบเทียบค่าระหว่าง null และ undefined จะพบผลลัพธ์ดังนี้
1// null และ undefined ต่างใช้สื่อความหมายถึงการไม่มีค่าทั้งคู่ จึงมีค่าเท่ากัน2console.log(null == undefined) // true34// แต่ null มีชนิดข้อมูลเป็นอ็อบเจ็กต์จึงต่างจาก undefined5console.log(null === undefined) // false
ด้วยเหตุผลทั้งปวงตามที่กล่าวมาแล้วจึงควรระวังเมื่อใช้ null หรือ undefined ในประโยคเงื่อนไขต่างๆ
1let a23if(a) console.log('exists')4else console.log('not exists')56if(a === undefined) console.log('undefined')7else console.log('defined')
5. คีย์เวิร์ด this
เรื่องนี้เป็นเรื่องชวนปวดหัวมากเมื่อเราอ้างถึง this ใน JavaScript แต่ละครั้งอาจได้ผลลัพธ์ไม่เหมือนกัน ดังนี้
1const obj = {2 text: 'object',3 print() {4 console.log(this.text)5 },6 waitOneSecBeforePrinting() {7 setTimeout(8 function() { console.log(this.text) },9 100010 )11 }12}1314obj.print() // object15obj.waitOneSecBeforePrinting() // ไม่มีอะไรพิมพ์ออกมา
this นั้นขึ้นอยู่กับการเรียก ในบรรทัดที่14เราเรียก print ผ่าน obj ทำให้ this หมายถึงตัว obj เอง บรรทัดที่4จึงสามารถเรียก text ซึ่งเป็น property ของ obj ผ่าน this ได้
ในกรณีของ waitOneSecBeforePrinting คนที่เรียกฟังก์ชันในบรรทัดที่8คือตัว setTimeout ที่จะเรียกเมื่อเวลาผ่านไปหนึ่งวินาที นั่นละครับตัวsetTimeoutเองไม่มี text เป็นของตัวเอง เราจึงไม่สามารถเรียก this.text แล้วได้ผลลัพธ์ออกมาได้
เพื่อเป็นการป้องกันความสับสนในการใช้ this เราสามารถใช้ arrow function ใน ES2015 เพื่อกำหนด lexical scope ได้ดังนี้
1const obj = {2 text: 'object',3 print() {4 console.log(this.text)5 },6 waitOneSecBeforePrinting() {7 setTimeout(8 // เปลี่ยนเป้น arrow function ทำให้ this ชี้ไปที่ obj หรือสโคปที่ครอบมันอยู่9 () => { console.log(this.text) },10 100011 )12 }13}1415obj.print() // object16obj.waitOneSecBeforePrinting() // object
นอกจากนี้เรายังใช้ bind เพื่อเปลี่ยนแปลงค่า this ได้ รายละเอียดสำหรับการใช้ bind และ arrow function สามารถอ่านเพิ่มเติมได้จาก ข้อแตกต่างของ bind, apply และ call ใน JavaScript กับการใช้งาน และ พื้นฐาน ES2015 สำหรับการเขียน JavaScript สมัยใหม่
6. Asynchronous Programming
JavaScript นั้นอุดมไปด้วยโค๊ดแบบ Asynchronous หรือการทำงานแบบไม่ได้เรียงลำดับ เช่น
1const result = $.getJSON('http://api.example.com/v1/books/1')2console.log(result.title)
โค๊ดข้างบนนี้ใช้ฟังก์ชัน getJSON ของ jQuery เพื่อดึงข้อมูลหนังสือที่มี ID เป็น 1 จาก URL ที่กำหนด เมื่อโค๊ดนี้ทำงาน JavaScript จะทำงานตามลำดับจากบนลงล่าง เริ่มจากเจอการร้องขอข้อมูล JSON ในบรรทัดแรกสุด เนื่องจากการขอข้อมูลไปที่เซิร์ฟเวอร์นั้นเป็น asynchronous คือ JavaScript จะไม่หยุดการทำงานเพื่อรอผลลัพธ์จากเซิร์ฟเวอร์ เพราะมันเสียเวลา เราไม่มีทางทราบว่าเมื่อไหร่เซิร์ฟเวอร์จะตอบกลับมา เมื่อ JavaScript ไม่อาจรอคอยได้ จึงทำโค๊ดบรรทัดถัดไปเลยคือพิมพ์ result.title ออกมา นั่นละฮะเราไม่ได้ผลลัพธ์อะไรเลยเพราะในเวลาที่โค๊ดนี้ทำงานยังไม่ได้คำตอบจากเซิร์ฟเวอร์กลับมานั่นเอง
เพื่อให้การทำงานแบบ asynchronous สมบูรณ์ เราต้องมี callback function ส่งเข้าไปเพื่อบอก JavaScript ให้เรียกฟังก์ชันนี้เมื่อผลลัพธ์จากเซิร์ฟเวอร์ส่งกลับมา
1$.getJSON(2 'http://api.example.com/v1/books/1',3 (data) => { console.log(data.title) }4)
รายละเอียดเชิงลึกของการเขียนโปรแกรมแบบ asynchronous ใน JavaScript ศึกษาเพิ่มเติมที่ รู้ลึกการทำงานแบบ Asynchronous กับ Event Loop ครับ
7. Prototype-based programming
และแล้วเราก็มาถึงเรื่องน่าปวดหัวลำดับเจ็ด นั่นคือเรื่องของ prototype ภาษา JavaScript นั้นไม่ได้เป็น OOP แบบที่เราใช้งานกันอยู่ ทุกส่วนของสิ่งที่เราพยายามจะจินตนาการให้เป็น OOP นั้นทำผ่านสิ่งที่เรียกว่า prototype
ในการนิยาม class ของ JavaScript นั้นเราอาศัยการสร้างผ่านฟังก์ชันที่เรียกว่า constructor function ดังนี้
1const Person = function(firstName, lastName) {2 this.firstName = firstName3 this.lastName = lastName4}56const person1 = new Person('Nuttavut', 'Thongjor')
เราสามารถสร้างเมธอดผ่าน constructor ได้เช่นกันดังนี้
1const Person = function(firstName, lastName) {2 this.firstName = firstName3 this.lastName = lastName4 this.getFullName = function() {5 return `${this.firstName} ${this.lastName}`6 }7}89const person1 = new Person('Nuttavut', 'Thongjor')10console.log(person1.getFullName()) // Nuttavut Thongjor1112const person2 = new Person('John', 'Smith')13console.log(person2.getFullName()) // John Smith
เนื่องจากวิธีนี้ทุกครั้งที่สร้างอ็อบเจ็กต์ของ Person ผ่าน constructor มันก็จะสร้างเมธอด getFullName ขึ้นมาใหม่ทุกครั้งเพราะเราดันนิยามเมธอดไว้ภายใต้ constructor ที่จะโดนเรียกทุกครั้งเมื่อเราออกคำสั่ง new นี่ไม่ใช่วิธีที่ดีแน่ เพราะถ้าเรา new อ็อบเจ็กต์ซักร้อยครั้งก็จะมี getFullName เกิดขึ้นร้อยครั้งเช่นกัน เพื่อเป็นการป้องกันเราจึงนิยามเมธอดผ่าน prototype ดังนี้
1const Person = function(firstName, lastName) {2 this.firstName = firstName3 this.lastName = lastName4}56Person.prototype.getFullName = function() {7 return `${this.firstName} ${this.lastName}`8}910const person1 = new Person('Nuttavut', 'Thongjor')11console.log(person1.getFullName()) // Nuttavut Thongjor1213const person2 = new Person('John', 'Smith')14console.log(person2.getFullName()) // John Smith
ในภาษา JavaScript นั้นอ็อบเจ็กต์จะมีการเชื่อมโยงไปหา prototype โดยถ้าเราเรียกเมธอดหรืออื่นใดผ่านอ็อบเจ็กต์ ถ้ามันหาสิ่งนั้นในอ็อบเจ็กต์ไม่เจอ มันจะไปขุดคุ้ยจาก prototype อีกที กรณีข้างต้นเราไม่ได้นิยาม this.getFullName ไว้นั่นหมายความว่าอ็อบเจ็กต์ของเราจะไม่มี getFullName เมื่อเราเรียก getFullName มันจึงต้องวิ่งขึ้นไปหา getFullName จาก prototype อีกที สุดท้ายก็เจอ!
prototype ใน JavaScript นั้นมีความสามารถสูงมาก เราสามารถใช้ prototype เพื่อสร้าง subclass หรือทำ inheritance หรือทำ encapsulation เหมือนการมีคีย์เวิร์ด private และ protected ในภาษาอื่นๆ เป็นต้น
แต่ด้วยความยุ่งยากที่ไม่คุ้นเคยของผู้คนที่มาจาก OOP ภาษาอื่นๆ ใน ES2015 เรามีคีย์เวิร์ด class เพื่อใช้สร้างคลาส และคีย์เวิร์ด extends เพื่อทำการสืบทอดคลาสแล้ว รายละเอียดสามารถอ่านเพิ่มเติมได้ที่พื้นฐาน ES2015 สำหรับการเขียน JavaScript สมัยใหม่ครับ
ตรงนี้ขอทำความเข้าใจก่อนว่าถึงแม้ ES2015 จะมีคีย์เวิร์ดคลาสให้เรียกใช้แล้ว แต่ความสมบูรณ์นั้นเทียบชั้นไม่ติดกับการใช้ prototype เลย คลาสใน ES2015 นั้นไม่สามารถห่อหุ้มข้อมูลด้วยคีย์เวิร์ด private/protected ได้ และอื่นๆ ด้วยเหตุนี้เราจึงอาจต้องพิจารณาใช้ prototype ในกรณีที่ต้องการความยืนหยุ่นในการใช้งานด้าน OOP หรือไม่เช่นนั้นก็โยกย้ายส่ายสะโพกไปใช้ Typescript ครับ
JavaScript นั้นถือว่าเป็นภาษาปราบเซียนภาษานึงเมื่อต้องลงลึกในการใช้งาน ถ้าเราจับต้องแบบผิวเผินมันก็ไม่ได้ต่างอะไรจากภาษาทั่วไปนัก แต่ก็นั่นละฮะเมื่อเราต้องการใช้สิ่งใด เราก็ต้องศึกษาในความเป็นไปของสิ่งนั้นอย่างถ่องแท้ บทความนี้จึงตั้งความหวังไว้ว่าจะช่วยเบิกทางในส่วนที่เข้าใจยากของ JavaScript ให้เพื่อนๆได้เข้าใจมากขึ้นเพื่อเปิดทางในการศึกษา JavaScript ที่สูงขึ้นในลำดับถัดไป
เอกสารอ้างอิง
MDN. let. Retrieved June, 7, 2016, from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Fabrício S. Matté (2015). TEMPORAL DEAD ZONE (TDZ) DEMYSTIFIED. Retrieved June, 7, 2016, from http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified/
Dr. Axel Rauschmayer. Variables and scoping. Retrieved June, 7, 2016, from http://exploringjs.com/es6/ch_variables.html
สารบัญ
- 1. Closure
- 2. JavaScript Hoisting
- 5. คีย์เวิร์ด this
- 6. Asynchronous Programming
- 7. Prototype-based programming
- เอกสารอ้างอิง