diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/.gitignore b/.gitignore index 6f90fd190..1a71fb7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ sftp-config.json Thumbs.db +/svgs \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index e2efd175e..f183b7da8 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -1,121 +1,121 @@ -# มาทำความรู้จักจาวาสคริปต์ +# บทนำสู่ JavaScript -มาดูกันว่าจาวาสคริปต์ทำอะไรได้บ้าง มีอะไรพิเศษ และสำรวจเทคโนโลยีในโลกจาวาสคริปต์กัน +มาดูกันว่า JavaScript มีลักษณะพิเศษอะไรบ้าง ทำอะไรได้บ้าง และเข้ากันได้ดีกับเทคโนโลยีใดบ้าง -## จาวาสคริปต์คืออะไร +## JavaScript คืออะไร? -เริ่มแรกเดิมที *จาวาสคริปต์* มีหน้าที่ทำให้เว็บไซต์ *ดูกับราวมีชีวิต* +*JavaScript* ถือกำเนิดขึ้นมาเพื่อ "ทำให้เว็บเพจมีชีวิตชีวา" -โปรแกรมแบบนี้เรียกกันว่า *สคริปต์* โปรแกรมจะถูกเขียนลงบนไฟล์ HTML และทำงานอัตโนมัติเมื่อโหลดหน้าเว็บ +โปรแกรมที่เขียนด้วยภาษานี้เรียกว่า *สคริปต์* ซึ่งสามารถแทรกลงในโค้ด HTML ของเว็บเพจได้โดยตรง และทำงานโดยอัตโนมัติเมื่อเพจนั้นโหลดขึ้นมา -สคริปต์ดังกล่าวจะทำงานได้เหมือนการแสดงข้อความทั่วไป ที่ไม่จำเป็นต้องติดตั้งโปรแกรม หรือคอมไพล์ไฟล์ดังกล่าวไว้ก่อน +สคริปต์เขียนและส่งให้เบราว์เซอร์รันในรูปแบบข้อความธรรมดา ไม่ต้องมีขั้นตอนคอมไพล์ใดๆ ก่อนเลย -ด้วยเรื่องนี้ทำให้จาวาสคริปต์แตกต่างไปจากภาษาที่ชื่อคล้ายกันอย่าง [จาวา](https://th.wikipedia.org/wiki/ภาษาจาวา)​ (Java) +ในแง่นี้ JavaScript แตกต่างจากภาษา [Java](https://en.wikipedia.org/wiki/Java_(programming_language)) อย่างชัดเจน -```smart header="Why JavaScript?" -ในตอนแรกจาวาสคริปต์มีชื่อว่า "LiveScript" แต่ด้วยความที่ภาษาจาวาดังมากในตอนนั้น ผู้สร้างเลยตัดสินใจเปลี่ยนชื่อภาษานี้เป็นจาวาสคริปต์ เพื่อหวังผลว่าชื่อของจาวาจะให้ส่งเสริมให้นักพัฒนาคนอื่นๆหันมาใช้ภาษาใหม่นี้ ในฐานะน้องเล็กของภาษาจาวา +```smart header="ทำไมถึงมีชื่อว่า JavaScript?" +ในยุคแรก JavaScript มีชื่อว่า "LiveScript" แต่เนื่องจาก Java กำลังได้รับความนิยมอย่างสูงในขณะนั้น จึงมีการตัดสินใจนำเสนอภาษาใหม่นี้ในฐานะ "น้องชาย" ของ Java เพื่อให้เป็นที่สนใจมากขึ้น -จาวาสคริปต์ก็วิวัฒนาการในทางของตัวเอง ก็เติบใหญ่จนพร้อมเป็นอิสระเสียแล้ว มีผู้พัฒนาฟีเจอร์และสเปคของภาษาเรียกกันว่า "[ECMAScript](https://th.wikipedia.org/wiki/อีซีเอ็มเอสคริปต์) (เอ็กม่าสคริปต์ ชื่อไม่เป็นทางการ)" +แต่เมื่อค่อยๆ พัฒนาต่อมา JavaScript ก็กลายเป็นภาษาที่มีเอกลักษณ์เฉพาะตัวอย่างสมบูรณ์ มีมาตรฐานของตัวเองที่เรียกว่า [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript) และปัจจุบันไม่มีความเกี่ยวข้องใดๆ กับ Java อีกต่อไป ``` -ในทุกวันนี้จาวาสคริปต์ไม่จำเป็นต้องใช้เว็บเบราเซอร์ในการทำงานอีกแล้ว เพราะจาวาสคริปต์สามารถทำงานได้บนเซิฟเวอร์หรืออุปกรณ์อะไรก็ได้ ที่ติดตั้งโปรแกรมพิเศษที่ชื่อ [JavaScript engine (จาวาสคริปต์เอนจิน)](https://en.wikipedia.org/wiki/JavaScript_engine). +ทุกวันนี้ JavaScript ไม่ได้จำกัดอยู่แค่ในเบราว์เซอร์อีกต่อไป แต่รันได้บนเซิร์ฟเวอร์หรืออุปกรณ์ใดก็ตามที่มีโปรแกรมพิเศษที่เรียกว่า [JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine) -เว็บบราวเซอร์จะฝังตัวจาวาสคริปต์เอนจินไว้อยู่แล้ว ซึ่งก็มีชื่อเฉพาะว่า "JavaScript virtual machine (จาวาสคริปต์เวอชวลมะชีน)" +เบราว์เซอร์แต่ละตัวมี engine ของตัวเองฝังอยู่ในตัว บางครั้งเรียกว่า "JavaScript virtual machine" -เอนจินต่างกัน ก็ใช้ชื่อ (codename) ต่างกันไปอีกอย่างเช่น +Engine แต่ละตัวมีชื่อเรียก (codename) ที่แตกต่างกัน เช่น: -- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- ใน Chrome และ Opera. -- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- ใน Firefox. -- นอกจากนี้ยังมีชื่ออื่นๆอย่าง Trident (ไทร'เดินทฺ) และ Chakra (จักระ) ใน IE, ChakraCore (จักระคอร์) ใน Microsoft Edge, Nitro (ไนโตร) และ SquirrelFish (สเควอ'เริลฟิช) ใน Safari +- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- ใน Chrome, Opera และ Edge +- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- ใน Firefox +- ...มีชื่อเรียกอื่นๆ อีก เช่น "Chakra" ใน IE, "JavaScriptCore", "Nitro" และ "SquirrelFish" ใน Safari เป็นต้น -การจดจำชื่อด้านบนจะมีประโยชน์ โดยเฉพาะเวลาอ่านบทความที่พัฒนาเขียนขึ้น ตัวอย่างเช่น "ฟีเจอร์บางอย่างได้รับการสนับสนุนใน V8 แล้ว" นั่นหมายถึงฟีเจอร์บางอย่างใช้ได้เฉพาะบน Chrome กับ Opera แล้ว +จำชื่อเหล่านี้ไว้จะเป็นประโยชน์ เพราะมักปรากฏในบทความสำหรับนักพัฒนาอยู่เสมอ เราก็จะใช้ชื่อเหล่านี้เช่นกัน ตัวอย่างเช่น ถ้าได้ยินว่า "ฟีเจอร์ X รองรับโดย V8" ก็หมายความว่าน่าจะทำงานได้ใน Chrome, Opera และ Edge -```smart header="แล้วเอนจินทำงานยังไง?" +```smart header="engine ทำงานอย่างไร?" -เอนจินเป็นเรื่องซับซ้อน หากแต่หลักการนั้นเป็นเรื่องง่าย +Engine มีกระบวนการทำงานที่ค่อนข้างซับซ้อน แต่หลักการพื้นฐานไม่ยากเลย -1. เอนจินอ่านสคริปต์ -2. เอนจินแปล (บ้างเรียกคอมไพล์) ภาษาสคริปต์เป็นภาษาเครื่อง -3. เครื่องอ่านภาษาเครื่องและทำงานตามนั้น ในเวลาเพียงเสี้ยววินาที +1. Engine (ที่ฝังมากับเบราว์เซอร์) อ่าน ("parse") สคริปต์ +2. แล้วแปลง ("compile") สคริปต์ให้เป็นภาษาเครื่อง +3. จากนั้นก็รันโค้ดภาษาเครื่องได้อย่างรวดเร็ว -ตัวเอนจินเริ่ม optimizations ในแต่ละขั้นตอน โดยการ เอนจินยังคอยตรวจสอบสคริปต์ขณะคอมไพล์ว่าสามารถทำงานได้ไหม วิเคราห์ flow ของข้อมูล กระทั่งทำการเพิ่มประสิทธิภาพของภาษาเครื่องอีกด้วย +Engine จะใช้เทคนิคการปรับแต่งในทุกขั้นตอน โดยติดตามข้อมูลของสคริปต์ที่ผ่านการคอมไพล์แล้ว วิเคราะห์การไหลของข้อมูล และนำข้อมูลที่ได้มาปรับแต่งรหัสภาษาเครื่องให้ดีขึ้นไปอีก ``` -## แล้วจาวาสคริปต์บนบราวเซอร์สามารถทำอะไรได้บ้าง +## JavaScript ในเบราว์เซอร์ทำอะไรได้บ้าง? -จาวาสคริปต์เป็นภาษาโปรแกรมมิ่งที่ "ปลอดภัย" เพราะว่าจาวาสคริปต์ไม่สามารถเข้าถึงหน่วยความจำ ควบคุมซีพียูได้ เพราะว่าจาวาสคริปต์ทำงานอยู่บนเว็บบราวเซอร์ ที่ไม่ได้ต้องการความสามารถดังกล่าวอยู่แล้ว +JavaScript สมัยใหม่เป็นภาษาโปรแกรมที่ "ปลอดภัย" ไม่สามารถเข้าถึงระดับล่างของหน่วยความจำหรือซีพียูได้โดยตรง เพราะออกแบบมาสำหรับเบราว์เซอร์ที่ไม่จำเป็นต้องใช้ฟีเจอร์เหล่านั้น -ความสามารถของจาวาสคริปต์จึงขึ้นอยู่กับ environment (เอนไว'เรินเมินทฺ) ที่จาวาสคริปต์ทำงานด้วย อย่างเช่น [Node.js](https://wikipedia.org/wiki/Node.js) ที่อนุญาตให้จาวาสคริปต์สามารถอ่านหรือเขียนไฟล์ได้, ส่ง request หา endpoint อื่นๆ, และความสามารถอื่นๆอีกมากมาย +ความสามารถของ JavaScript ขึ้นอยู่กับสภาพแวดล้อมที่ใช้งานเป็นอย่างมาก ตัวอย่างเช่น [Node.js](https://wikipedia.org/wiki/Node.js) เสริมความสามารถให้ JavaScript อ่านหรือเขียนไฟล์ใดๆ ก็ได้ ส่งคำขอผ่านเน็ตเวิร์ค และอื่นๆ อีกมาก -บนเบราเซอร์จาวาสคริปต์ก็สามารถทำทุกอย่างที่เกี่ยวข้องกับ การจัดการเว็บไซต์ การโต้ตอบผู้ใช้งาน รวมไปถึง เป็นเว็ปเซิฟเวอร์ +ส่วน JavaScript ในเบราว์เซอร์นั้น ทำได้ทุกอย่างที่เกี่ยวกับการจัดการเว็บเพจ การโต้ตอบกับผู้ใช้ และเว็บเซิร์ฟเวอร์ -ตัวอย่างเช่น +ตัวอย่างเช่น JavaScript ในเบราว์เซอร์สามารถ: -- เพิ่มหรือจัดการกับ HTML, เพิ่ม แก้ไข หรือ ลบ เนื้อหาบนเว็บไซต์, เปลี่ยนรูปลักษณ์ของเว็บไซต์ -- ตอบโต้การกระทำจากผู้ใช้ ทำงานเมื่อคลิกเมาส์ จับการเคลื่อนไหวของเมาส์ หรือ การกดบนแป้นพิมพ์ -- ส่ง request ผ่านเครือข่ายไปยังเซิฟเวอร์, ดาวน์โหลด และอัพโหลดไฟล์ หรือที่เรียกว่า [AJAX](https://th.wikipedia.org/wiki/เอแจ็กซ์) (เอแจ็กซ์) และ [COMET](https://en.wikipedia.org/wiki/Comet_(programming)) (คัม'มิท) -- รับ และ ตั้งค่าคุกกี้ (cookies) ถามคำถามผู้ใช้งาน หรือ แสดงข้อความ -- จำจดข้อมูลในฝั่งไคลเอ็นต์ (client-side) หรือที่เรียกว่าที่เก็บข้อมูลในตัวเครื่อง (local storage) +- เพิ่ม HTML ใหม่ลงในหน้าเว็บ เปลี่ยนแปลงเนื้อหาที่มีอยู่ แก้ไขรูปแบบ (style) ของหน้าเว็บ +- ตอบสนองต่อการกระทำของผู้ใช้ เช่นการคลิกเมาส์ การเลื่อนเคอร์เซอร์ และการกดปุ่ม +- ส่งคำขอไปยังเซิร์ฟเวอร์ผ่านเครือข่าย ดาวน์โหลดและอัปโหลดไฟล์ (เรียกกันว่า [AJAX](https://en.wikipedia.org/wiki/Ajax_(programming)) และ [COMET](https://en.wikipedia.org/wiki/Comet_(programming))) +- รับและตั้งค่าคุกกี้ ถามคำถามผู้ใช้ แสดงข้อความ +- จำข้อมูลฝั่งไคลเอนต์ ("local storage") -## แล้วจาวาสคริปต์บนเบราเซอร์ทำอะไรไม่ได้บ้าง +## JavaScript ในเบราว์เซอร์ทำอะไรไม่ได้บ้าง? -ความสามารถของจาวาสตริป์บนเบราเซอร์ก็มีจำกัดเช่นกัน เพื่อความปลอดภัยของผู้ใช้ โดยมีจุดประสงค์ไม่ให้เว็บไม่ดีบางเว็บเข้าถึงข้อมูลส่วนตัว หรือทำการใดๆต่อข้อมูลของผู้ใช้งาน +JavaScript ในเบราว์เซอร์มีข้อจำกัดด้านความสามารถ เพื่อปกป้องความปลอดภัยของผู้ใช้ เป้าหมายคือป้องกันไม่ให้หน้าเว็บอันตรายเข้าถึงข้อมูลส่วนตัวหรือทำให้ข้อมูลเสียหาย -ตัวอย่างเช่น +ตัวอย่างของข้อจำกัดเหล่านั้น ได้แก่: -- จาวาสคริปต์บนเบราเซอร์ไม่สามารถอ่านหรือเขียนไฟล์ใดๆบนฮาร์ดดิสก์ คัดลอกหรือสั่งโปรแกรมอื่นๆทำงาน ไม่สามารถเข้าถึงฟังก์ชั่นของระบบปฎิบัติการ (OS) ได้ +- JavaScript ในหน้าเว็บไม่สามารถอ่าน/เขียน/คัดลอกไฟล์ในฮาร์ดดิสก์ หรือเรียกโปรแกรมอื่นๆ ได้โดยตรง ไม่มีสิทธิ์เข้าถึงฟังก์ชันระบบของระบบปฏิบัติการโดยตรง - เบราเซอร์ในทุกวันนี้อนุญาตให้ทำงานกับไฟล์ได้ แต่ก็มีข้อจำกัดไว้ โดยทำได้เพียงแค่บางอย่างเท่านั้น เช่น วางไฟล์จากเครื่องบนหน้าต่างเบราเซอร์ + เบราว์เซอร์รุ่นใหม่อนุญาตให้ทำงานกับไฟล์ได้บ้าง แต่การเข้าถึงมีขอบเขตจำกัดอย่างเข้มงวดและต้องได้รับความยินยอมจากผู้ใช้ก่อน เช่น การ "ลาก" ไฟล์มาวางในหน้าเว็บ หรือเลือกผ่านแท็ก `` - นอกจากนี้ยังอนุญาตให้เข้าถึงกล้อง ไมโครโฟน และอุปกรณ์อื่นๆ แต่ต้องได้รับอนุญาตจากผู้ใช้งานก่อน -- หน้าต่างและแท็บต่างกันก็ไม่รู้ตัวตนของอีกฝ่าย แต่จะรู้เมื่อจาวาสคริปต์สั่งเบราเซอร์เปิดอีกแท็บหรือหน้าต่างหนึ่งมา แต่ในกรณีนี้ จาวาสคริปต์หน้าหนึ่ง จะไม่สามารถเข้าถึงอะไรก็ตามของอีกหน้าหนึ่งได้ เมื่อมาจากต่างเว็บไซต์ ต่างพอร์ต (port) ต่างโดเมน ต่างโปรโตคอล และอื่นๆ + มีวิธีโต้ตอบกับกล้อง ไมโครโฟน และอุปกรณ์อื่นๆ ได้ แต่ต้องขออนุญาตผู้ใช้อย่างชัดเจนทุกครั้ง ดังนั้นหน้าเว็บจึงไม่มีทางแอบเปิดเว็บแคมดูโดยที่ผู้ใช้ไม่รู้ตัวแล้วส่งข้อมูลไปยัง [NSA](https://en.wikipedia.org/wiki/National_Security_Agency) ได้ +- แท็บ/วินโดว์ต่างๆ โดยปกติแล้วไม่รู้จักกัน แม้บางครั้งอาจทำได้ เช่นเมื่อหน้าต่างหนึ่งใช้ JavaScript เปิดหน้าต่างอื่นขึ้นมา แต่แม้ในกรณีนี้ JavaScript จากหน้าหนึ่งก็ยังไม่สามารถเข้าถึงอีกหน้าได้ หาก URL นั้นมาจากโดเมน โปรโตคอล หรือพอร์ตที่ต่างกัน นโยบายนี้เรียกว่า "Same Origin Policy" - ในที่นี้เรียกกันว่า "Same Origin Policy" ดังนั้นใน*ทั้งสองหน้า*จะต้องยอมรับการแลกเปลี่ยนข้อมูลระหว่างกัน จาวาสคริปต์จึงมีฟีเจอร์มาเพื่อจัดการกับปัญหาข้างต้น จะมีพูดถึงเรื่องนี้กันอีกทีในภายหลังด้วย -้ - ความปลอดภัยของผู้ใช้จึงมาพร้อมกับข้อจำกัด ดังนั้นเว็บไซต์ A จะไม่สามารถขโมยข้อมูล gmail.com ที่เปิดอีกแท็บหนึ่งได้ -- จาวาสคริปต์สติดต่อเซิฟเวอร์ผ่านเน็ตได้สบายหากเป็นแหล่งที่มาเดียวกัน แต่ก็มีข้อจำกัด เมื่อต้องรับส่งข้อมูลระหว่างเว็บไซต์หรือโดเมนที่ต่างกัน แม้ว่าจะมีทาง แต่ก็ต้องมีข้อตกลงระหว่างทั้งสอง อย่างเช่น HTTP headers ทั้งนี้เหตุผลก็มาจากความปลอดภัยของผู้ใช้งานอีกเช่นกัน + การจะแลกเปลี่ยนข้อมูลระหว่างหน้าเว็บได้นั้น *ทั้งสองฝ่ายต้องตกลงยินยอม* และต้องมีโค้ด JavaScript พิเศษเพื่อจัดการ ซึ่งเราจะพูดถึงในบทเรียนข้างหน้า + + ข้อจำกัดนี้ก็เพื่อความปลอดภัยของผู้ใช้เช่นกัน ลองนึกดูว่าจะเป็นอย่างไรถ้าหน้า `http://anysite.com` ที่เปิดอยู่ในแท็บหนึ่งแอบเข้าถึง `http://gmail.com` ในอีกแท็บแล้วขโมยข้อมูลออกไป +- JavaScript ติดต่อกับเซิร์ฟเวอร์ต้นทางของหน้านั้นได้สะดวก แต่การรับข้อมูลจากโดเมนหรือไซต์อื่นมีข้อจำกัดเข้มงวด จะทำได้ก็ต่อเมื่อได้รับความยินยอมชัดเจนจากเซิร์ฟเวอร์ปลายทาง (ผ่าน HTTP header) ซึ่งก็เป็นมาตรการด้านความปลอดภัยอีกเช่นกัน ![](limitations.svg) -ข้อจำกัดดังกล่าวมีเฉพาะบนเว็บเบราเซอร์เท่านั้น นอกจากนี้เบราเซอร์ทุกวันนี้ก็ยังอนุญาตให้ใช้ปลั๊กอิน บ้างเรียกส่วนขยาย (extensions) ซึ่งอาจขอสิทธิ์อนุญาตจากเบราเซอร์เพิ่มเติมได้ +ข้อจำกัดเหล่านี้จะหมดไปทันทีหาก JavaScript ทำงานนอกเบราว์เซอร์ เช่นบนเซิร์ฟเวอร์ นอกจากนี้ เบราว์เซอร์สมัยใหม่ยังอนุญาตให้ปลั๊กอิน/ส่วนขยายขอสิทธิ์เพิ่มเติมได้ -## สิ่งที่ทำให้จาวาสคริปต์ไม่เหมือนใคร +## อะไรทำให้ JavaScript โดดเด่น? -มีสามอย่างใหญ่ๆที่ทำให้จาวาสคริปต์ไม่เหมือนใคร +JavaScript มีอย่างน้อย *3 จุดเด่น* ที่ทำให้มันแตกต่าง: ```compare -+ ทำงานกับ HTML/CSS ได้อย่างสมบูรณ์ -+ เรื่องเล็กๆเสร็จง่าย -+ เบราเซอร์ใหญ่ๆ ดังๆ สนับสนุน ++ ผสานเข้ากับ HTML/CSS ได้อย่างลงตัว ++ ทำสิ่งง่ายๆ ได้อย่างง่ายดาย ++ รองรับโดยเบราว์เซอร์หลักทั้งหมด และเปิดใช้งานเป็นค่าเริ่มต้น ``` -จาวาสคริปต์เป็นเทคโนโลยีเบราเซอร์ที่รวมสามสิ่งนี้ -ทำให้จาวาสคริปต์มีความไม่เหมือนใคร จึงเป็นเหตุผลที่เทคโนโลยีนี้ถูกใช้แพร่หลายที่สุด ในการสร้างส่วนต่อผสานกับเบราเซอร์ +JavaScript เป็นเทคโนโลยีเบราว์เซอร์เพียงอย่างเดียวที่รวม 3 จุดเด่นนี้เข้าด้วยกัน -สร้างเซิฟเวอร์ หรือแอพฯบนมือถือและอื่นๆ +นั่นคือสิ่งที่ทำให้ JavaScript โดดเด่น และนั่นคือเหตุผลที่ JavaScript กลายเป็นเครื่องมือที่แพร่หลายที่สุดสำหรับสร้างส่วนติดต่อผู้ใช้บนเบราว์เซอร์ -## ภาษาที่ "นอกเหนือจาก" จาวาสคริปต์ +อย่างไรก็ตาม ปัจจุบัน JavaScript ยังใช้สร้างเซิร์ฟเวอร์ แอปมือถือ และอื่นๆ ได้อีกด้วย -syntax ของจาวาสคริปต์ไม่ได้ตอบโจทย์ความต้องการได้ทุกคน ต่างคนก็ต่างต้องการฟีเจอร์ในแบบของตัวเอง +## ภาษาที่ "transpile" เป็น JavaScript -โจทย์จึงแตกต่างกันไปตาม project และ requirement ของแต่ละคน +ไวยากรณ์ของ JavaScript อาจไม่ตอบโจทย์ความต้องการของทุกคน แต่ละคนอยากได้ฟีเจอร์ที่ต่างกัน เรื่องนี้เป็นเรื่องปกติ เพราะแต่ละโปรเจกต์และแต่ละคนมีบริบทที่ไม่เหมือนกัน -เพียงไม่กี่ปีมานี้ ภาษาใหม่ๆจึงถือกำเนิดขึ้นจำนวนมาก ซึ่งจะแปลงเป็นจาวาสคริปต์ก่อน จะทำงานบนเบราเซอร์ +ดังนั้น ในช่วงไม่กี่ปีมานี้จึงมีภาษาโปรแกรมใหม่ๆ เกิดขึ้นมากมาย โดยภาษาเหล่านี้จะถูก *transpile* (แปลง) เป็น JavaScript ก่อนรันในเบราว์เซอร์ -เครื่องมือในทุกวันนี้ช่วยให้การแปลเป็นอีกภาษานั้นรวดเร็วขึ้นมาก จริงๆแล้วนักพัฒนาสามารถโค้ดเป็นอีกภาษาหนึ่ง แล้วแปลเป็นอีกภาษาหนึ่งได้โดยอัตโนมัติ โดยที่ไม่ต้องทำอะไรเพิ่มเติมเลย +เครื่องมือสมัยใหม่ทำให้ขั้นตอน transpile รวดเร็วและโปร่งใส นักพัฒนาสามารถเขียนโค้ดด้วยภาษาอื่นแล้วให้แปลงเป็น JavaScript เบื้องหลังโดยอัตโนมัติ -ตัวอย่างภาษาเหล่านั้นได้แก่: +ตัวอย่างของภาษาเหล่านั้น ได้แก่: -- [CoffeeScript](http://coffeescript.org/) เป็นภาษาที่ดีไซน์์เพื่อให้อ่านง่าย เป็นที่นิยมในหมู่นักพัฒนาภาษารูบี้ (Ruby) -- [TypeScript](http://www.typescriptlang.org/) เป็นภาษาที่เพิ่มความเข้มงวดเรื่องชนิดข้อมูล (data typing) เพื่อลดความยุ่งยากในการพัฒนา และสนับสนุนระบบที่ซับซ้อน พัฒนาโดยไมโครซอฟท์ (Microsoft) -- [Flow](http://flow.org/) เป็นภาษาเพิ่มความเข้มงวดเรื่องชนิดข้อมูล พัฒนาโดยเฟสบุ๊ค (Facebook) -- [Dart](https://www.dartlang.org/) เป็นภาษาสแตนด์อโลน (standalone) ที่มีเอนจินของตัวเอง ทำงานบน environment ที่ไม่ใช่เบราเซอร์ (อย่างเช่น มือถือ) แต่สามารถแปลงสู่จาวาสคริปต์ได้ พัฒนาโดยกูเกิ้ล (Google) -- [Brython](https://brython.info/) เป็นตัวแปลภาษาจากไพธอน (Python) เป็นจาวาสคริปต์ ทำให้เขียนเว็บแอพฯจากภาษาไพธอนล้วนๆได้ +- [CoffeeScript](https://coffeescript.org/) เป็น "syntactic sugar" สำหรับ JavaScript โดยนำเสนอไวยากรณ์ที่กระชับกว่า ช่วยให้เราเขียนโค้ดได้ชัดเจนและถูกต้องมากขึ้น เป็นที่นิยมในหมู่นักพัฒนา Ruby +- [TypeScript](https://www.typescriptlang.org/) เน้นการเพิ่ม "strict data typing" เพื่อให้การพัฒนาและดูแลระบบที่ซับซ้อนง่ายขึ้น พัฒนาโดย Microsoft +- [Flow](https://flow.org/) ก็เพิ่ม data typing เช่นกัน แต่ในรูปแบบที่แตกต่างออกไป พัฒนาโดย Facebook +- [Dart](https://www.dartlang.org/) เป็นภาษาแยกต่างหากที่มี engine เป็นของตัวเอง สามารถทำงานในสภาพแวดล้อมที่ไม่ใช่เบราว์เซอร์ได้ (เช่น แอปมือถือ) และยังสามารถ transpile เป็น JavaScript ได้ พัฒนาโดย Google +- [Brython](https://brython.info/) เป็น transpiler ที่แปลง Python เป็น JavaScript ทำให้สามารถเขียนแอปด้วย Python บริสุทธิ์ได้โดยไม่ต้องใช้ JavaScript +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) เป็นภาษาโปรแกรมสมัยใหม่ที่กระชับและปลอดภัย สามารถกำหนดเป้าหมายไปที่เบราว์เซอร์หรือ Node ได้ -ยังมีภาษาอื่นๆที่ไม่ได้อยู่ในตัวอย่างนี้อีก แต่แน่นอนว่าหลายๆคนไม่ค่อยได้เขียนจาวาสคริปต์ตรงๆอีกแล้ว แต่เขียนเป็นภาษาที่แปลเป็นจาวาสคริปต์อีกทีแทน แต่รู้จักกับจาวาสคริปต์จะช่วยให้เราเข้าใจภาษาที่เราเขียนอยู่ได้ดียิ่งขึ้น +ยังมีภาษาอื่นๆ อีกมากมาย แม้จะใช้ภาษาเหล่านี้ก็ตาม แต่การเรียนรู้ JavaScript โดยตรงก็ยังจำเป็น เพื่อให้เข้าใจอย่างถ่องแท้ว่ากำลังทำอะไรอยู่ ## สรุป -- จาวาสคริปต์ตอนแรกถูกสร้างมาให้ทำงานเฉพาะเบราเซอร์ แต่ก็ยังสามารถทำงานนอกเบราเซอร์ได้เช่นกัน -- ทุกวันนี้จาวาสคริปต์ไม่เหมือนก่อนอีกแล้ว ภาษานี้ถูกใช้อย่างแพร่หลายมาก แถมทำงานกับ HTML/CSS ได้อย่างสมบูรณ์ -- มีภาษาที่ถูกแปลเป็นจาวาสคริปต์อีกที โดยภาษาเหล่านี้ก็จะมีฟีเจอร์ต่างกัน หากเริ่มมั่นใจในจาวาสคริปต์แล้ว ขอแนะนำให้ลองสัมผัสพวกนี้สักหน่อย +- JavaScript เกิดมาเพื่อใช้กับเบราว์เซอร์โดยเฉพาะ แต่ปัจจุบันแพร่หลายไปในสภาพแวดล้อมอื่นๆ อีกมากมาย +- ปัจจุบัน JavaScript เป็นภาษาเบราว์เซอร์ที่ได้รับการยอมรับกว้างขวางที่สุด ทำงานร่วมกับ HTML/CSS ได้อย่างลงตัว +- มีหลายภาษาที่ "transpile" เป็น JavaScript และมีฟีเจอร์เสริมบางอย่าง แนะนำให้ลองศึกษาคร่าวๆ หลังจากเชี่ยวชาญ JavaScript แล้ว diff --git a/1-js/01-getting-started/1-intro/limitations.svg b/1-js/01-getting-started/1-intro/limitations.svg index a7863c63c..76ea43fd7 100644 --- a/1-js/01-getting-started/1-intro/limitations.svg +++ b/1-js/01-getting-started/1-intro/limitations.svg @@ -1 +1 @@ -https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file +https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index f37ca5fe0..14fb29b66 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,39 +1,37 @@ +# คู่มือและข้อกำหนด -# คู่มือและข้อมูลเชิงลึก - -คู่มือเล่มนี้เป็น *คู่มือการสอน* โดยมีจุดมุ่งหมายเพื่อให้ผู้เรียนค่อยๆคุ้นเคยกับภาษาก่อน +หนังสือเล่มนี้เป็น *บทเรียน* ที่มุ่งหวังให้ค่อยๆ เรียนรู้ภาษา JavaScript อย่างเป็นขั้นเป็นตอน แต่เมื่อคุ้นเคยกับพื้นฐานแล้ว ก็อาจต้องการแหล่งข้อมูลอ้างอิงเพิ่มเติม ได้แก่ ## ข้อกำหนด -ข้อกำหนด ECMA-262 (The ECMA-262 specification)** ประกอบด้วยข้อมูลเชิงลึก มีรายละเอียด และมีความเป็นทางการสูง ทั้งยังเป็นมาตรฐานของจาวาสคริปต์ +[ข้อกำหนด ECMA-262](https://www.ecma-international.org/publications/standards/Ecma-262.htm) เป็นเอกสารที่มีรายละเอียดเชิงลึก ครบถ้วน และเป็นทางการที่สุดเกี่ยวกับ JavaScript เป็นตัวกำหนดมาตรฐานของภาษานี้ -แต่ด้วยทั้งข้อมูลและรายละเอียดในเชิงลึก ข้อกำหนดนี้เลยทำความเข้าใจยากมาก ดังนั้นหากต้องการข้อมูลที่น่าเชื่อถือ และอัดแน่นไปด้วยข้อมูลของภาษา ก็อยากดังกล่าวให้อ่านกำหนดนี้ +แต่เนื่องจากมีลักษณะเป็นทางการมาก จึงอาจทำความเข้าใจได้ยากในช่วงแรก ดังนั้น หากต้องการแหล่งอ้างอิงที่ถูกต้องที่สุดเกี่ยวกับรายละเอียดต่างๆ ของภาษา ข้อกำหนดนี้คือคำตอบ แต่ไม่เหมาะสำหรับการใช้งานในชีวิตประจำวัน -รายการสเปคของเวอร์ชั่นในจาวาสคริปต์จะออกใหม่ทุกๆปี ในระหว่างจะทำการออกเวอร์ชั่นใหม่ ผู้ออกจะออกหนังสือร่างรายการสเปคเอาไว้ สามาถอ่านได้ที่ลิงค์นี้ +ข้อกำหนดเวอร์ชันใหม่จะออกมาทุกปี ระหว่างนั้น สามารถดูร่างข้อกำหนดล่าสุดได้ที่ -หากต้องการอ่านข้อมูลฟีเจอร์ที่กำลังพัฒนา และฟีเจอร์ที่กำลังจะออกมาเป็นมาตรฐาน โปรดเข้าไปที่ลิงค์นี้ . +สำหรับฟีเจอร์ใหม่ล่าสุด รวมถึงฟีเจอร์ที่ "ใกล้จะเป็นมาตรฐาน" (หรือที่เรียกว่า "stage 3") สามารถดูได้ที่ -หรือต้องการพัฒนาเว็บแอพฯบนเบราเซอร์ ก็จะมีอีกข้อกำหนดหนึ่ง จะพูดถึงอีกทีใน[ส่วนที่สอง](info:browser-environment) +นอกจากนี้ ถ้ากำลังพัฒนาเว็บสำหรับเบราว์เซอร์ ยังมีข้อกำหนดเพิ่มเติมที่กล่าวถึงใน[ส่วนที่สอง](info:browser-environment) ของบทเรียนนี้ด้วย -## คู่มือ +## คู่มืออ้างอิง -- **MDN (Mozilla) คู่มืออ้างอิงสำหรับจาวาสคริปต์** คู่มือพร้อมตัวอย่าง เป็นคู่มือที่ให้รายละเอียดเชิงลึกดีมาก ยิ่งถ้ารายเป็นฟังก์ชั่นหรือเมธอด (Method) +- **MDN (Mozilla) JavaScript Reference** เป็นคู่มือหลักที่มีตัวอย่างและข้อมูลเชิงลึกอีกมากมาย เหมาะสำหรับค้นหารายละเอียดเกี่ยวกับฟังก์ชัน เมธอด และอื่นๆ - เข้าชมที่ลิงต์นี้ . + เข้าถึงได้ที่ - ค้นหาจากอินเตอร์เน็ตโดยพิมพ์ "MDN [ตามด้วยฟังก์ชั่นหรือเมธอดที่อยากรู้]" เช่น อยากรู้จักฟังชั่นก์ parseInt + อย่างไรก็ตาม บ่อยครั้งการค้นหาผ่าน Google จะสะดวกกว่า เพียงพิมพ์ "MDN [คำที่ต้องการ]" เช่น เพื่อหาข้อมูลเกี่ยวกับฟังก์ชัน `parseInt` -- **MSDN** -คู่มือของไมโครซอฟท์ก็เป็นอีกทางเลือกหนึ่งที่ดี รวมไปถึงจาวาสคริปต์ (พูดถึงในชื่อ JScript) แต่หากต้องทำงานหรืออยากรู้อะไรบางอย่างเกี่ยวกับ IE มาที่ลิงค์นี้จะดีกว่า +## ตารางความเข้ากันได้ - ค้นหาจากอินเตอร์เน็ตโดยพิมพ์ "[ตามด้วยฟังก์ชั่นหรือเมธอดที่อยากรู้] MSDN" หรือ "[ตามด้วยฟังก์ชั่นหรือเมธอดที่อยากรู้] MSDN jscript" ก็ได้ +JavaScript เป็นภาษาที่พัฒนาอยู่ตลอดเวลา มีฟีเจอร์ใหม่ๆ เพิ่มเข้ามาอยู่เสมอ -## ตารางเช็คว่าเอนจินแต่ละตัวสนับสนุนฟีเจอร์ใดบ้าง +หากต้องการตรวจสอบว่าฟีเจอร์ต่างๆ รองรับโดยเบราว์เซอร์และเอ็นจิ้นอื่นๆ มากน้อยเพียงใด สามารถดูได้จาก: -จึงเป็นเรื่องที่ควรรู้ว่าเบราเซอร์หรือเอนจินที่ทำงานด้วยนั้น สนับสนุนฟีเจอร์นี้ด้วยหรือไม่ โปรดดูตามลิสต์ด้านล่าง +- - ตารางการรองรับฟีเจอร์แยกตามหมวดหมู่ เช่น หากต้องการดูว่าเอ็นจิ้นใดบ้างรองรับฟังก์ชันการเข้ารหัสลับสมัยใหม่ ค้นหาได้ที่ -- - เว็บไซต์ที่ใช้ตรวจว่าฟีเจอร์นั้นๆ เบราเซอร์สนับสนุนหรือไม่ เช่น อยากจะรู้ว่าเบราเซอร์ไหนที่สนับสนุนฟีเจอร์เกี่ยวกับการถอดรหัส (cryptography) บ้าง . -- - ตารางสำหรับดูชุดฟีเจอร์ใหม่ๆ ได้รับการสนับสนุนจากเอนจินใดบ้างแล้ว +- - ตารางแสดงฟีเจอร์ต่างๆ ของภาษา พร้อมระบุว่าเอ็นจิ้นใดรองรับหรือไม่รองรับบ้าง -แหล่งข้อมูลที่กล่าวมาล้วนมีรายละเอียด และข้อมูลที่มีประโยชน์ที่ใช้ในการพัฒนาจริงๆ +แหล่งข้อมูลเหล่านี้มีประโยชน์มากในการพัฒนาจริง เพราะครอบคลุมรายละเอียดของภาษา ความเข้ากันได้ และข้อมูลสำคัญอื่นๆ -หากต้องการข้อมูลเชิงลึกต่างๆ โปรดบุ๊กมาร์คหน้าหน้านี้ โดยเราจะรออัพเดทให้ทันสมัยอยู่เสมอ +จดจำแหล่งอ้างอิงเหล่านี้ไว้ (หรือบุ๊กมาร์กหน้านี้) สำหรับตอนที่ต้องการข้อมูลเชิงลึกเกี่ยวกับฟีเจอร์ใดๆ ของภาษา diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index e58806b8b..d38e11799 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -1,45 +1,49 @@ -# Code editors +# ตัวแก้ไขโค้ด -Code editor เป็นสิ่งที่นักพัฒนามักใช้เวลาร่วมมากที่สุด +ตัวแก้ไขโค้ด (code editor) คือเครื่องมือที่โปรแกรมเมอร์ใช้เวลาส่วนใหญ่ในการทำงานด้วย -โดยจะมี 2 ประเภทหลักๆ นักพัฒนาจะใช้หนึ่งในสองอย่างนี้ คือ IDE และ lightweight editor +ตัวแก้ไขโค้ดมีสองประเภทหลักๆ คือ IDE และตัวแก้ไขแบบเบา (lightweight editor) ซึ่งนักพัฒนาหลายคนมักจะใช้ทั้งสองแบบควบคู่กันไป ## IDE -คำว่า [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) หมายถึงเครื่องมือที่ประกอบด้วยฟีเจอร์มากมายที่ใช้จัดการงานระดับโปรเจ็คต์ IDE จึงไม่ใช่แค่ text editor ธรรมดา แต่เป็น "development environment" เต็มรูปแบบ +[IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) ย่อมาจาก Integrated Development Environment หมายถึงตัวแก้ไขโค้ดที่ทรงพลัง พร้อมฟีเจอร์ครบครัน และทำงานในระดับ "โปรเจ็กต์ทั้งหมด" ตามชื่อนั่นเอง ไม่ใช่แค่ตัวแก้ไขธรรมดา แต่เป็น "สภาพแวดล้อมการพัฒนา" อย่างครบวงจร -IDE โหลดโปรเจค (ที่ประกอบด้วยไฟล์จำนวนมาก) ที่มีฟีเจอร์หลักๆคือ navigation ระหว่างไฟล์, autocompletion, บางตัวจะมี version management system อย่างกิต (git) ให้ด้วย, testing environment และฟีเจอร์ที่จัดการกับโปรเจ็คต์โดยเฉพาะ +IDE จะโหลดโครงการ (ซึ่งอาจประกอบด้วยหลายไฟล์) เข้ามา ช่วยให้เราสามารถสลับไปมาระหว่างไฟล์ต่างๆ ได้ มีระบบเติมโค้ดอัตโนมัติตามบริบทของโปรเจ็กต์ทั้งหมด (ไม่ใช่เฉพาะไฟล์ที่กำลังเปิดอยู่) และเชื่อมต่อกับระบบจัดการเวอร์ชัน เช่น [git](https://git-scm.com/) รวมถึงมีการผนวกรวมกับสภาพแวดล้อมการทดสอบ และอื่นๆ ในระดับของโครงการด้วย -หากยังไม่มี IDE ในดวงใจ ลองดู IDE ด้านล่างไว้พิจารณา +ยังไม่เคยใช้ IDE มาก่อน? ลองพิจารณาตัวเลือกเหล่านี้: -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, ฟรี). -- [WebStorm](http://www.jetbrains.com/webstorm/) (cross-platform, เสียตัง). +- [Visual Studio Code](https://code.visualstudio.com/) (รองรับหลายแพลตฟอร์ม, ฟรี) +- [WebStorm](https://www.jetbrains.com/webstorm/) (รองรับหลายแพลตฟอร์ม, มีค่าใช้จ่าย) -สำหรับผู้ใช้ Windows หลายๆคนอาจมีความสับสนกับชื่อระหว่าง Visual Studio Code กับ Visual Studio ตัวหลังเป็น IDE แบบเสียตัง และดีที่สุดของ IDE สำหรับ Windows โดยเฉพาะฝั่งของ .NET และ JavaScript ก็พอใช้ได้ ยังมีเวอร์ชั่นฟรีให้ใช้ด้วย [Visual Studio Community](https://www.visualstudio.com/vs/community/). +สำหรับ Windows ยังมี "Visual Studio" ด้วย อย่าสับสนกับ "Visual Studio Code" นะ "Visual Studio" เป็น IDE ที่ทรงพลังและเสียค่าใช้จ่าย มีเฉพาะบน Windows เท่านั้น เหมาะสำหรับการพัฒนาบนแพลตฟอร์ม .NET และสามารถใช้งานกับ JavaScript ได้เป็นอย่างดีเช่นกัน โดยมีเวอร์ชันฟรีชื่อ [Visual Studio Community](https://www.visualstudio.com/vs/community/) -IDE ส่วนมากจะเสียตัง แต่จะมีระยะเวลาทดลองใช้ แต่ค่าใช้จ่ายส่วนนี้มักจะเล็กน้อยเมื่อเทียบกับรายได้ของนักพัฒนาต่างประเทศ เก่งๆ หรือทำงานมาหลายปี ดังนั้นเลือกที่ตัวเองชอบมากที่สุด +IDE หลายตัวมีค่าใช้จ่าย แต่ก็มีระยะทดลองใช้ฟรี ซึ่งเมื่อเทียบกับเงินเดือนของนักพัฒนาที่ดีแล้ว ค่าใช้จ่ายเหล่านั้นถือว่าน้อยมาก ดังนั้นควรเลือกตัวที่เหมาะสมกับตัวเองที่สุด -## Lightweight editors +## ตัวแก้ไขแบบเบา -"Lightweight editors" ไม่ได้มีฟีเจอร์เทียบเท่า IDE แต่เร็ว สวย และใช้งานง่าย +ตัวแก้ไขแบบเบา (lightweight editor) อาจจะไม่ทรงพลังเท่า IDE แต่เปิดไวเบาสบาย และใช้งานง่าย -ส่วนใหญ่มักจะใช้เพื่อเปิดไฟล์แก้ไขได้ทันที +ส่วนใหญ่แล้วจะใช้เพื่อเปิดและแก้ไขไฟล์ได้อย่างรวดเร็ว -ความแตกต่างระหว่าง IDE กับ Lightweight editors คือ IDE มักจะทำงานในระดับโปรเจค ที่ต้องอาศัยการประมวล โหลดข้อมูลจำนวนมาก หรือวิเคราะห์โครงสร้างในโปรเจ็คต์ และอื่นๆ ส่วนแบบ lightweight จะตอบโจทย์ด้านความเร็ว หากเราต้องการแก้ไขไฟล์แค่ไฟล์เดียว +จุดต่างสำคัญคือ IDE ทำงานในระดับโปรเจ็กต์ จึงใช้เวลาโหลดตอนเริ่มต้นมากกว่า ต้องวิเคราะห์โครงสร้างโปรเจ็กต์และอื่นๆ อีกมาก ในขณะที่ตัวแก้ไขแบบเบาเปิดไฟล์ได้เร็วกว่ามาก -ในทางปฎิบัติ lightweight editors บางตัวอาจมีปลั๊กอินไว้ติดตั้งเพิ่มเติมด้วย เช่น ตัววิเคราะห์ directory-level syntax และ autocompleters ดังนั้นจึงไม่มีเส้นแบ่งระหว่างตัวที่เป็น IDE กับ lightweight ชัดเจนมากนัก +ในทางปฏิบัติ ตัวแก้ไขแบบเบาก็อาจมีปลั๊กอินต่างๆ เพิ่มเข้ามาได้มากมาย รวมถึงมีตัวช่วยวิเคราะห์ไวยากรณ์และระบบเติมโค้ดอัตโนมัติในระดับไดเรกทอรี ทำให้แยกความแตกต่างระหว่างตัวแก้ไขแบบเบากับ IDE ได้ไม่ชัดเจนนัก -ก็จะมีตัวเลือกดังต่อไปนี้ +มีตัวเลือกมากมาย เช่น: -- [Atom](https://atom.io/) (cross-platform, ฟรี). -- [Sublime Text](http://www.sublimetext.com) (cross-platform, ให้ทดลองใช้). -- [Notepad++](https://notepad-plus-plus.org/) (Windows, ฟรี). -- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) IDEs สำหรับโปรแกรมเมอร์ที่แท้จริง +- [Sublime Text](https://www.sublimetext.com/) (รองรับหลายแพลตฟอร์ม, shareware) +- [Notepad++](https://notepad-plus-plus.org/) (Windows, ฟรี) +- [Vim](https://www.vim.org/) และ [Emacs](https://www.gnu.org/software/emacs/) ก็เจ๋งมากสำหรับคนที่เชี่ยวชาญ -## อย่าเถียงกันเลย +## อย่าถกเถียงกันเลย -รายการ editor ข้างที่แนะนำข้างต้น เป็น editor ที่คนรอบข้างที่เป็นนักพัฒนาและตัวผมเองใช้ (แค่ตัวเดียวนะ) เป็นเวลานาน และมีความสุขที่ได้ใช้ +ตัวแก้ไขโค้ดที่ยกมาข้างต้นเป็นตัวที่ผู้เขียนและเพื่อนๆ นักพัฒนาใช้มานานและพอใจกัน -มี editor อีกมากมายพร้อมเป็นตัวเลือกสำหรับเรา จงเลือกตัวที่เหมาะกับเรามากที่สุด +แน่นอนว่ายังมีตัวเลือกอื่นๆ อีกมากมายในโลกกว้าง เลือกตัวที่ถูกใจที่สุดแล้วกัน -ตัว editor เอง ก็เป็นเหมือนเครื่องมืออื่นๆ ดังนั้นเหตุผลจึงขึ้นอยู่กับโปรเจคที่ทำ นิสัย และความชอบของเรามากกว่า +การเลือกใช้ตัวแก้ไขโค้ด ก็เหมือนกับการเลือกใช้เครื่องมืออื่นๆ เป็นเรื่องของรสนิยมส่วนบุคคล และขึ้นอยู่กับประเภทของโปรเจ็กต์ นิสัยการทำงาน และความชอบเฉพาะตัวของแต่ละคน + +ความเห็นส่วนตัวของผู้เขียน: + +- ผมใช้ [Visual Studio Code](https://code.visualstudio.com/) หากพัฒนา frontend (ฝั่งหน้าบ้าน) เป็นหลัก +- แต่หากใช้ภาษา/แพลตฟอร์มอื่นๆ เป็นส่วนใหญ่ โดยมี frontend แค่บางส่วน ผมจะพิจารณาใช้ตัวอื่น เช่น XCode (Mac), Visual Studio (Windows) หรือ IDE จากค่าย Jetbrains (WebStorm สำหรับ JavaScript, PHPStorm สำหรับ PHP, RubyMine สำหรับ Ruby เป็นต้น) ทั้งนี้ขึ้นอยู่กับภาษาหลักของโปรเจ็กต์ diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index fcc37789d..dd1df8089 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -1,62 +1,63 @@ -# Developer console +# คอนโซลนักพัฒนา -โค้ดที่เขียนมักจะมาพร้อมข้อผิดพลาด และเหล่านักพัฒนามักจะทำเรื่องนี้อยู่เป็นนิจ +เมื่อเขียนโค้ด มักจะเกิดข้อผิดพลาดได้เสมอ ไม่ว่าจะระมัดระวังแค่ไหน ก็ยังมีโอกาสทำผิดพลาดอยู่ดี... เอ่อ กำลังพูดถึงอะไรอยู่น่ะ? ต้องขอแก้ใหม่ ถ้าเป็นมนุษย์ *ย่อมต้อง* ทำผิดพลาดแน่ๆ ยกเว้นว่าจะเป็น[หุ่นยนต์](https://en.wikipedia.org/wiki/Bender_(Futurama)) -แต่ในเบราเซอร์ ผู้ใช้งานจะไม่เห็นความผิดพลาด ที่เหล่านักพัฒนาทำไว้ ดังนั้นหากสคริปต์ที่เราเขียนมีข้อผิดพลาด เราจะไม่รู้และแก้ไขมันได้เลย +แต่โดยค่าเริ่มต้น ผู้ใช้จะไม่เห็นข้อผิดพลาดเหล่านั้นบนหน้าเว็บ ดังนั้น หากสคริปต์มีจุดบกพร่อง เราก็จะไม่รู้ว่าพังตรงไหน ทำให้แก้ไขไม่ได้ -เพื่อย้อนไปดูว่าเราพลาดอะไรไป "เครื่องมือสำหรับนักพัฒนา (developer tools)" จึงติดมากับเบราเซอร์ด้วย +เพื่อให้เห็นข้อผิดพลาดและข้อมูลดีบั๊กที่มีประโยชน์อื่นๆ เกี่ยวกับสคริปต์ เบราว์เซอร์จึงมี "เครื่องมือนักพัฒนา" (developer tools) ติดตั้งมาด้วย -นักพัฒนาส่วนใหญ่มักจะใช้ Chrome และ Firefox นั่นก็เพราะว่าทั้งสองตัวมี เครื่องมือสำหรับการพัฒนาที่ดีที่สุดในตลาด โดยปกตินักพัฒนามักเลือกใช้เบราเซอร์เพียงตัวเดียวในการพัฒนา แต่จะใช้เบราเซอร์อื่นร่วมด้วย แต่หลังจากที่ปัญหาเกิดแล้ว เช่น โค้ดที่เขียนไม่ซัพพอร์ตใน IE +นักพัฒนาส่วนใหญ่นิยมใช้ Chrome หรือ Firefox ในการพัฒนา เพราะมีเครื่องมือนักพัฒนาที่ทรงพลังที่สุด แม้ว่าเบราว์เซอร์อื่นๆ ก็มีเครื่องมือที่คล้ายกัน บางทีอาจจะมีฟีเจอร์พิเศษด้วย แต่มักจะตามหลัง Chrome หรือ Firefox เสมอ ดังนั้นนักพัฒนาจึงมักมีเบราว์เซอร์ "โปรด" สำหรับทำงาน และเปลี่ยนไปใช้เบราว์เซอร์อื่นเป็นครั้งคราวเมื่อพบปัญหาเฉพาะของเบราว์เซอร์นั้น -เครื่องมือเหล่านี้มาพร้อมฟีเจอร์ที่ตอบโจทย์การพัฒนาหลากหลาย เราจะเริ่มไล่ดูกันไปทีละอย่าง +เครื่องมือนักพัฒนานั้นมีประสิทธิภาพสูงและมีฟีเจอร์มากมาย ในช่วงแรก เราจะเรียนรู้วิธีเปิดใช้งาน ดูข้อผิดพลาด และรันคำสั่ง JavaScript กัน ## Google Chrome -ลองเปิดหน้า [bug.html](bug.html). +ลองเปิดหน้า [bug.html](bug.html) ดู -เปิด developer tools ดู จะเห็นว่ามี error ในโค้ดจาวาสคริปต์ แต่ผู้ใช้จะมองไม่เห็น +ในโค้ด JavaScript บนหน้านั้นมีบั๊กซ่อนอยู่ ซึ่งผู้ใช้ทั่วไปมองไม่เห็น ลองเปิดเครื่องมือนักพัฒนาขึ้นมาดูกัน -กด `F12` บน Windows หรือ `Cmd+Opt+J` บน Mac +กด `key:F12` หรือหากใช้ Mac ให้กด `key:Cmd+Opt+J` -developer tools จะเปิดหน้า console ขึ้นมาโดย default +เครื่องมือนักพัฒนาจะเปิดขึ้นที่แท็บ Console เป็นค่าเริ่มต้น -หน้าตาจะคล้ายๆแบบนี้ +หน้าตาจะออกมาประมาณนี้: -![chrome](chrome.png) +![chrome](chrome.webp) -หน้าตาตัว developer tools จะเปลี่ยนไปตามเวอร์ชั่น ถึงหน้าตาที่เปิดออกมาจะไม่เป๊ะเว่อร์ แต่เค้าโครงจะคล้ายๆกัน +หน้าตาของเครื่องมือนักพัฒนาขึ้นอยู่กับเวอร์ชันของ Chrome ที่ใช้ อาจเปลี่ยนไปบ้างตามเวลา แต่โดยรวมจะคล้ายๆ กับในภาพ -- ลองมองไปที่ console จะเห็นว่ามี error ที่เป็นแถบและตัวอักษรสีแดงอยู่ นั่นเพราะว่าเบราเซอร์ไม่รู้จักคำสั่ง "lalala" -- ซ้ายมือของแถบ จะเห็นว่ามีการบอกบรรทัดที่ error นี้เกิดขึ้น +- ตรงนี้เราจะเห็นข้อความแสดงข้อผิดพลาดสีแดง ในกรณีนี้ สคริปต์มีคำสั่งที่ไม่รู้จักชื่อ "lalala" +- ทางด้านขวา จะมีลิงก์ที่คลิกได้ ซึ่งจะระบุไฟล์ต้นทาง `bug.html:12` พร้อมเลขบรรทัดที่เกิดข้อผิดพลาด -ข้างล่างข้อความ error จะสังเกตเห็นว่ามีลูกศรสีน้ำเงินอยู่ ตัวมันเองแทน "command line" เราสามารถพิมพ์จาวาสคริปต์ลงไป แล้วกด `enter` เพื่อสั่งโค้ดทำงาน หรือ `shift-enter` เพื่อพิมพ์โค้ดหลายบรรทัดแทน +ใต้ข้อความแสดงข้อผิดพลาดจะมีสัญลักษณ์ `>` สีฟ้า นี่คือ "บรรทัดคำสั่ง" (command line) ที่เราสามารถพิมพ์คำสั่ง JavaScript แล้วกด `key:Enter` เพื่อรันมันได้ -เมื่อเราเปิดมาพบข้อผิดพลาด นั่นก็ถือเป็นการเริ่มต้นที่ดีแล้ว เราจะกลับมาพูดรายละเอียดในเชิงเกี่ยวกับเครื่องมือนักพัฒนา และการดีบักกันต่อในบท +ตอนนี้เราก็เห็นข้อผิดพลาดแล้ว ซึ่งเพียงพอสำหรับการเริ่มต้น เราจะกลับมาศึกษาเครื่องมือนักพัฒนากันอีกในภายหลัง และจะคุยเรื่องการดีบั๊กอย่างละเอียดมากขึ้นในบท -## Firefox, Edge, และเบราเซอร์ตัวอื่นๆ -```smart header="ป้อนคำสั่งหลายบรรทัด" -เมื่อเราพิมพ์โค้ดจาวาสคริปต์ลงไปในหน้าคอนโซล เมื่อกด `key:Enter` โค้ดชุดนั้นก็จะทำงาน +```smart header="การป้อนหลายบรรทัด" +โดยปกติ เมื่อเราพิมพ์โค้ดบรรทัดเดียวลงไปในคอนโซล และกด `key:Enter` มันจะรันคำสั่งนั้นทันที -เมื่อจะป้อนโค้ดหลายบรรทัด, ให้กดปุ่ม `key:Shift+Enter` จะช่วยให้เราสามารถพิมพ์โค้ดหลายบรรทัดได้ +แต่ถ้าต้องการพิมพ์หลายบรรทัด ให้กด `key:Shift+Enter` แทน วิธีนี้จะช่วยให้เราใส่ชิ้นส่วนโค้ด JavaScript ยาวๆ ได้ ``` -นักพัฒนาส่วนใหญ่ใช้ `F12` เพื่อเปิดหน้า console +## Firefox, Edge และอื่นๆ -developer tools มักจะคล้ายๆกันไม่ว่าจะต่างเวอร์ชั่น ต่างเบราเซอร์ หากเชี่ยวชาญเพียงหนึ่ง ก็ใช้ตัวอื่นได้สูสีกัน +เบราว์เซอร์ส่วนใหญ่ใช้ `key:F12` เพื่อเปิดเครื่องมือนักพัฒนา + +ลักษณะและการใช้งานค่อนข้างใกล้เคียงกัน พอรู้วิธีใช้เครื่องมือใดเครื่องมือหนึ่งแล้ว (เช่น เริ่มจาก Chrome) ก็สามารถเปลี่ยนไปใช้อันอื่นได้ไม่ยาก ## Safari -Safari (เบราเซอร์ของผู้ใช้ Mac) ตัวนี้จะมาพร้อมความไม่เหมือนใคร โดยเราต้องไปเปิด "Develop menu" ก่อน +Safari (เบราว์เซอร์ของ Mac ที่ไม่รองรับ Windows/Linux) มีความแตกต่างนิดหน่อย ต้องเปิดใช้งาน "Develop menu" ก่อน -เปิดหน้า "Preferences" ไปที่ "Advanced" และติ๊ก checkbox ด้านล่าง +เปิด Settings แล้วไปที่แท็บ "Advanced" จะเห็นช่องทำเครื่องหมายที่ด้านล่างของหน้า: ![safari](safari.png) -ทีนี้กด `Cmd+Opt+C` หน้า console จะปรากฎขึ้น แถมเมนูด้านบน ยังปรากฎแถบ "Develop" เมื่อคลิกก็จะมีคำสั่งหลากหลายให้เลือกใช้ +ตอนนี้ `key:Cmd+Opt+C` จะสามารถเปิด/ปิดคอนโซลได้ และสังเกตว่าจะมีเมนูใหม่ชื่อ "Develop" ปรากฏที่ด้านบนขวา ซึ่งมีคำสั่งและตัวเลือกต่างๆ มากมาย ## สรุป -- Developer tools ช่วยให้เห็น error, สั่งโค้ดทำงาน, ตรวจค่าตัวแปร และอื่นๆ -- เปิดโดยกด `F12` สำหรับเบราเซอร์ส่วนใหญ่บน Windows แต่ Chrome บน Mac จะใช้ `Cmd+Opt+J` Safari จะเป็น `Cmd+Opt+C` อย่าลืมไปเปิดโหมดนักพัฒนาบน Safari ก่อน +- เครื่องมือนักพัฒนาช่วยให้เราเห็นข้อผิดพลาด รันคำสั่ง ตรวจสอบตัวแปร และอีกสารพัด +- สามารถเปิดใช้งานได้ด้วย `key:F12` ในเบราว์เซอร์ส่วนใหญ่บน Windows ส่วน Chrome บน Mac ใช้ `key:Cmd+Opt+J`, Safari ใช้ `key:Cmd+Opt+C` (ต้องเปิดใช้ก่อน) -ทีนี้ปูพื้นเรื่อง environment กันมาสักพักแล้ว ก็พร้อมที่จาวาสคริปต์ออกโรง +ตอนนี้เตรียมสภาพแวดล้อมเรียบร้อยแล้ว ในบทถัดไป เราจะลงมือศึกษา JavaScript กัน diff --git a/1-js/01-getting-started/4-devtools/chrome.png b/1-js/01-getting-started/4-devtools/chrome.png deleted file mode 100644 index 4cb3ea2f4..000000000 Binary files a/1-js/01-getting-started/4-devtools/chrome.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp new file mode 100644 index 000000000..bdf067079 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2.webp b/1-js/01-getting-started/4-devtools/chrome@2.webp new file mode 100644 index 000000000..2aeca5898 Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome@2.webp differ diff --git a/1-js/01-getting-started/4-devtools/chrome@2x.png b/1-js/01-getting-started/4-devtools/chrome@2x.png deleted file mode 100644 index b87404a8f..000000000 Binary files a/1-js/01-getting-started/4-devtools/chrome@2x.png and /dev/null differ diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png index 64c7a3f6c..4538827eb 100644 Binary files a/1-js/01-getting-started/4-devtools/safari.png and b/1-js/01-getting-started/4-devtools/safari.png differ diff --git a/1-js/01-getting-started/4-devtools/safari@2x.png b/1-js/01-getting-started/4-devtools/safari@2x.png index 27def4d09..1561b2bd9 100644 Binary files a/1-js/01-getting-started/4-devtools/safari@2x.png and b/1-js/01-getting-started/4-devtools/safari@2x.png differ diff --git a/1-js/01-getting-started/index.md b/1-js/01-getting-started/index.md index 7e349a1a7..685f6dad9 100644 --- a/1-js/01-getting-started/index.md +++ b/1-js/01-getting-started/index.md @@ -1,3 +1,3 @@ -# มาทำความรู้จักจาวาสคริปต์ +# บทนำ -ภาษาจาวาสริปต์และ environment ที่ใช้ในการพัฒนา +ในบทนี้เราจะพูดถึงเกี่ยวกับภาษา JavaScript และสภาพแวดล้อมที่ใช้ในการพัฒนาโปรแกรมด้วยภาษานี้ diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index 969061491..8ef9a548d 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -1,16 +1,16 @@ -# Hello, world +# สวัสดี ชาวโลก! -ส่วนนี้เป็นบทสอนเกี่ยวกับหัวใจหลักของจาวาสคริปต์ +ในส่วนนี้ของบทเรียน เราจะเริ่มศึกษาพื้นฐานของภาษา JavaScript กัน -แต่เราต้องเตรียมสภาพแวดล้อม (environment) ที่เหมาะสมแก่การทำงานสคริปต์ของเรากันก่อน แต่ด้วยความที่คู่มือฉบับนี้อยู่บนเว็บไซต์อยู่แล้ว ผู้เรียนจึงไม่มีความจำเป็นต้องจัดเตรียมสภาพแวดล้อมใดๆเลย เช่น Node.js ผู้เรียนสามารถสั่งสคริปต์ทำงานผ่านเบราเซอร์โดยตรง ก่อนจะนอกเรื่องกันเราจะกลับมาพูดถึงจาวาสคริปต์ที่ทำงานบนเบราเซอร์อีกทีใน[บทถัดไป]((/ui)) +แต่ก่อนอื่น เราต้องมีสภาพแวดล้อมสำหรับรันสคริปต์ก่อน เนื่องจากหนังสือเล่มนี้อยู่บนออนไลน์ เบราว์เซอร์จึงเป็นตัวเลือกที่สะดวกที่สุด เราจะพยายามจำกัดการใช้คำสั่งเฉพาะของเบราว์เซอร์ (เช่น `alert`) ให้น้อยที่สุด เผื่อว่าต้องการมุ่งเน้นไปที่สภาพแวดล้อมอื่นๆ เช่น Node.js แทน ส่วนการใช้ JavaScript ในเบราว์เซอร์โดยเฉพาะจะกล่าวถึงในรายละเอียดใน[ส่วนถัดไป](/ui)ของบทเรียน -ก่อนอื่นเลย เรามาดูวิธีการแนบสคริปต์ไปบนเว็บเพจกันก่อน โดยที่สภาพแวดล้อมฝั่งเซิฟเวอร์ (อย่าง Node.js) เราสามารถสั่งสคริปต์ทำงานได้โดยการพิมพ์ว่า `"node my.js"` ในเทอร์มินัล +ทีนี้มาดูกันว่าจะแทรกสคริปต์ลงในหน้าเว็บได้อย่างไร สำหรับสภาพแวดล้อมฝั่งเซิร์ฟเวอร์อย่าง Node.js นั้น รันสคริปต์ด้วยคำสั่ง `"node my.js"` ได้เลย -## ภายใต้แท็กสคริปต์ +## แท็ก "script" -เราสามารถเขียนจาวาสคริปต์ไปบนส่วนใดก็ได้ในไฟล์ HTML โดยใช้แท็ก ` */!* -

...After the script.

+

...หลังสคริปต์

@@ -34,51 +34,51 @@ ``` ```online -เราสามารถสั่งสคริปต์นี้ทำงานได้โดยคลิกปุ่ม "play" ที่มุมขวาบน +ลองรันตัวอย่างนี้ได้โดยคลิกปุ่ม "เล่น" ที่มุมบนขวาของกล่องโค้ด ``` -เมื่อถึงคิวที่เบราเซอร์ประมวลผลในแท็กนี้ สคริปต์เหล่านี้ก็จะทำงานอัตโนมัติ +แท็ก ` - ``` + ```html no-beautify + + ``` -ในโมเดิร์นจาวาสคริปต์ (ตั้งแต่ ES6 ขึ้นไป) เราเลิกใช้การคอมเม้นแบบนี้ไปแล้ว แต่จะพบได้ในโค้ดเก่าๆ เพราะเบราเซอร์ในสมัยนั้นไม่ทราบวิธีการประมวลแท็ก `script` รวมถึงสิ่งที่อยู่ภายในแท็กนี้ + เทคนิคนี้ไม่ได้ใช้กันแล้วในยุค JavaScript สมัยใหม่ คอมเมนต์แบบนี้มีไว้ซ่อนโค้ดจากเบราว์เซอร์รุ่นเก่ามากๆ ที่ยังไม่รู้จักแท็ก ` ``` -ดังตัวอย่าง `/path/to/script.js` โดยจะใช้ absolute path หรือ relative path ก็ได้ โดย path จะเป็นไปตามที่อยู่ของไฟล์ HTML ตัวอย่างเช่น, `src="script.js"` หมายความว่าไฟล์ `"script.js"` อยู่ในโฟลเดอร์เดียวกันกับไฟล์ `HTML` +ตรงนี้ `/path/to/script.js` คือพาธแบบ absolute นับจาก root ของเว็บไซต์ นอกจากนี้ยังระบุพาธแบบ relative จากหน้าปัจจุบันได้ด้วย เช่น `src="script.js"` หรือ `src="./script.js"` ต่างหมายถึงไฟล์ `"script.js"` ในโฟลเดอร์เดียวกันกับหน้า HTML -หรือจะใช้เป็น URL ก็ได้ดังตัวอย่างด้านล่าง +ระบุ URL แบบเต็มก็ได้เช่นกัน: ```html ``` -แนบสคริปต์แบบหลายไฟล์ดังตัวอย่างด้านล่าง +ถ้ามีหลายไฟล์ ให้ใช้แท็กหลายอัน: ```html @@ -87,29 +87,25 @@ attribute `language`: <script language=...> ``` ```smart -เรามักจะใส่สคริปต์ที่ทำงานง่ายๆลงในไฟล์ HTML ส่วนสคริปต์ที่ซับซ้อนกว่านั้น จะแยกเป็นอีกไฟล์หนึ่งตะหาก +โดยทั่วไป HTML จะมีแค่สคริปต์สั้นๆ เท่านั้น ส่วนโค้ดที่ซับซ้อนกว่ามักแยกไปอยู่ในไฟล์ต่างหาก -ประโยชน์อย่างหนึ่งของการแยกไฟล์ก็คือ เบราเซอร์จะดาวน์โหลดและเก็บไฟล์นั้นเอาไว้ หรือที่เรียกว่า[แคช](https://en.wikipedia.org/wiki/Web_cache) - -ในหน้าอื่นๆหากมีการอ้างถึงไฟล์ที่มีที่อยู่เดียวกัน เบราเซอร์จะเอาสคริปต์นั้นมาจากแคชแทน ดังนั้นไฟล์สคริปต์เหล่านี้จะดาวน์โหลดเพียงครั้งเดียว - -มันช่วยลดระยะการเดินทางของข้อมูล ซึ่งทำให้หน้าเว็บโหลดเร็วยิ่งขึ้น +ข้อดีคือเบราว์เซอร์จะดาวน์โหลดไฟล์นั้นมาแล้วเก็บไว้ใน[แคช](https://en.wikipedia.org/wiki/Web_cache) หน้าอื่นๆ ที่อ้างถึงสคริปต์ไฟล์เดียวกันจะดึงจากแคชแทน ไม่ต้องดาวน์โหลดใหม่ — ดาวน์โหลดจริงๆ แค่ครั้งเดียว ช่วยลด traffic และหน้าเว็บโหลดเร็วขึ้น ``` -มีสิ่งที่ต้องเตือนกันเล็กน้อย ถ้าในแท็ก `script` มีการระบุค่าไว้แล้วของ `src` ไว้แล้ว สคริปต์ที่อยู่ภายใต้แท็ก `script` จะไม่ทำงาน -ฉะนั้นแท็ก ` ``` -เราต้องตัดสินใจเองว่าจะเลือกแบบสคริปต์ภายนอก ` @@ -117,11 +113,12 @@ attribute `language`: <script language=...> alert(1); ``` +```` ## สรุป -- เราสามารถใช้แท็ก `` +- เราสามารถใช้แท็ก `` -ยังมีเรื่องที่ต้องเรียนรู้อีกมากเกี่ยวกับจาวาสคริปต์มีปฎิสัมพันธ์ต่อหน้าเว็บอย่างไร แต่อย่าลืมว่าจาวาสคริปต์นั้นสามารถทำงานได้หลากหลาย ไม่ใช่เฉพาะบนเบราเซอร์อย่างเดียว แต่เราจะใช้เบราเซอร์ในทางที่ช่วยให้สคริปต์ทำงานได้สะดวกยิ่งขึ้น +ยังมีอีกมากที่จะเรียนรู้เกี่ยวกับสคริปต์ในเบราว์เซอร์และการสื่อสารกับหน้าเว็บ แต่ขอย้ำว่าส่วนนี้เน้นที่ภาษา JavaScript เป็นหลัก ดังนั้นเราจะไม่ออกนอกเรื่องมากเกินไป เบราว์เซอร์เป็นเพียงหนึ่งในหลายสภาพแวดล้อมที่รัน JavaScript ได้ — แค่สะดวกสำหรับบทเรียนออนไลน์เท่านั้น diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 0625af757..883cf0eeb 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -1,160 +1,157 @@ # โครงสร้างโค้ด -สิ่งแรกที่เราจะเริ่มศึกษากันจริงจังคือหน่วยของโค้ด (blocks of code) +ก่อนอื่น มาทำความรู้จักกับองค์ประกอบพื้นฐานของโค้ดกัน นั่นคือ statements (คำสั่ง) และ comments (คอมเมนต์) -## คำสั่ง (Statements) +## Statements (คำสั่ง) -คำสั่ง (Statements) คือโครงสร้างไวยากรณ์ที่ประกอบไปด้วยคำสั่งที่พร้อมให้คอมพิวเตอร์ดำเนินการ +Statements คือโครงสร้างและคำสั่งทางไวยากรณ์ที่ใช้สั่งให้โปรแกรมทำบางสิ่ง -เราเคยเห็นคำสั่ง (Statements) หน่ึงกันมาแล้วคือ `alert('Hello, world!')`, ซึ่งคำสั่งของมันก็คือให้แสดงข้อความ "Hello World!" +เราเคยเห็นตัวอย่างของ statement มาแล้วนั่นคือ `alert('Hello, world!')` ซึ่งเป็นคำสั่งให้แสดงข้อความ "Hello, world!" -เราจะเขียนคำสั่ง (Statements) กี่ครั้งก็ได้ตามที่เราต้องการ และ คำสั่ง (Statements) มักจบด้วย semicolon เสมอ เพื่อแยกแต่ละคำสั่ง (Statements) ออกจากกัน คล้ายๆ full stop ในภาษาอังกฤษที่แบ่งประโยคออกจากกัน +เราสามารถเขียน statements ในโค้ดได้หลายคำสั่ง โดยแยกแต่ละคำสั่งด้วยเครื่องหมายอัฒภาค (semicolon) -ดั่งตัวอย่างเราแบ่ง "Hello" กับ "World" ออกมาเป็นสองข้อความ +ตัวอย่างเช่น เราแยก "Hello World" ออกเป็นสอง alerts: ```js run no-beautify alert('Hello'); alert('World'); ``` -โดยปกติแล้ว เราจะเขียนแยกบรรทัดกันเพื่อให้โค้ดอ่านง่ายขึ้น +โดยปกติแล้ว statements จะเขียนแยกบรรทัดกันเพื่อให้อ่านโค้ดง่ายขึ้น: ```js run no-beautify alert('Hello'); alert('World'); ``` -## Semicolons [#semicolon] +## เครื่องหมายอัฒภาค (Semicolons) [#semicolon] -การขึ้นบรรทัดใหม่ก็เหมือนมี semicolon อยู่ในตัว เมื่อเราแบ่งบรรทัดจึงมีหรือไม่มี semicolon ก็ได้ +ในหลายกรณี JavaScript อนุญาตให้ละเครื่องหมายอัฒภาคได้ถ้ามีการขึ้นบรรทัดใหม่ -แบบนี้ก็ได้ผลเช่นเดียวกันกับด้านบน: +ตัวอย่างเช่น โค้ดนี้จะทำงานได้เหมือนกัน: ```js run no-beautify alert('Hello') -alert('World') +alert('World') ``` -จาวาสคริปต์จะตีความการขึ้นบรรทัดใหม่ว่าเป็น semicolon ในที่นี้เราเรียกว่า [การแทรก semicolon ให้อัตโนมัติ](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion). +JavaScript จะถือว่าการขึ้นบรรทัดใหม่เป็นการใส่เครื่องหมายอัฒภาคโดยนัย ซึ่งเรียกว่า [Automatic Semicolon Insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion) -**ส่วนใหญ่การขึ้นบรรทัดใหม่คือจะแทรก semicolon ไปโดยปริยายด้วย แต่ก็ไม่ได้หมายความว่าจะเป็นแบบนั้นเสมอไป!** +**ในหลายๆ กรณี การขึ้นบรรทัดใหม่หมายถึงการใส่เครื่องหมายอัฒภาค แต่ "หลายๆ กรณี" ไม่ได้แปลว่า "เสมอไป"!** -บรรทัดใหม่แบบนี้ จาวาสคริปต์จะไม่ตีความว่าเป็น semicolon +มีบางสถานการณ์ที่การขึ้นบรรทัดใหม่ไม่ได้หมายถึงการใส่เครื่องหมายอัฒภาค เช่นในตัวอย่างนี้: ```js run no-beautify alert(3 + -1 +1 + 2); ``` -เมื่อเราสั่งคำสั่งนี้ออกไป เราจะได้ผลลัพธ์เป็น 6 แทน จาวาสคริปต์ไม่ได้แทรก semicolon เอาว่าทันทีที่เริ่มบรรทัด เพราะจาวาสคริปต์มองว่าบรรทัดใหม่จบด้วยเครื่องหมาย `+` สำหรับจาวาสคริปต์นี่แสดงว่าคำสั่งนี้ยังไม่สมบูรณ์ (incomplete expression) ดังนั้น semicolon จึงไม่จำเป็น จะคำสั่งนี้ก็ได้ผลลัพธ์ตามที่เราคาดหวัง +โค้ดจะแสดงผลเป็น `6` เพราะ JavaScript ไม่ได้ใส่เครื่องหมายอัฒภาค JavaScript engine รู้ได้เองว่าถ้าบรรทัดจบด้วย `+` แสดงว่า expression ยังไม่จบ การใส่เครื่องหมายอัฒภาคตรงนั้นจะไม่ถูกต้อง และในกรณีนี้ก็ทำงานตรงตามที่ต้องการ -**แต่ก็มีบางคำสั่งที่จาวาสคริปต์ได้ตีความผิดพลาดไป โดยจาวาสคริปต์มองว่าคำสั่งดังกล่าวจำเป็นต้องมี semicolon ด้วย;** +**แต่มีบางสถานการณ์ที่ JavaScript "ไม่สามารถ" ใส่เครื่องหมายอัฒภาคให้เราในตำแหน่งที่ควรจะมี** -error หาเจอได้ยาก และยังแก้ไขได้ยากอีกด้วย +ข้อผิดพลาดที่เกิดจากการไม่ใส่เครื่องหมายอัฒภาคมักจะหาสาเหตุและแก้ไขได้ยาก -````smart header="An example of an error" -ตัวอย่างข้อผิดพลาดที่กล่าวมาดูได้จากโค้ดด้านล่าง +````smart header="ตัวอย่างข้อผิดพลาด" +ถ้าอยากเห็นตัวอย่างของข้อผิดพลาดเช่นนี้ ลองพิจารณาโค้ดนี้ดู: ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` -ณ ตอนนี้ยังไม่ต้องสนใจว่า `[]` และ `forEach` หมายถึงอะไร ให้เข้าใจว่ามันจะแสดงข้อความแจ้งเตือนว่า 1 และ 2 พอ +ตอนนี้ยังไม่ต้องสนใจความหมายของ `[]` และ `forEach` เรายังไม่ได้เรียนเรื่องพวกนี้ จำแค่ว่าโค้ดนี้จะแสดง `Hello` ตามด้วย `1` และ `2` ทีละอัน -แล้วเมื่อเราลองเพิ่ม `alert` ไปบรรทัดก่อนหน้าตัว `[]` ของเรา และปล่อยไว้แบบนั้นโดยที่ไม่มี semicolon ปิดใดๆ +ทีนี้ลองลบเครื่องหมายอัฒภาคหลัง `alert` ออกดู: ```js run no-beautify -alert("There will be an error") - -[1, 2].forEach(alert) -``` - -เมื่อเราลองสั่งรันดู จะมีเพียงแต่ข้อความแรกเท่านั้นที่แจ้งเตือน จากนั่นโค้ดของเราจะ error +alert("Hello") -ทุกอย่างจะกลับมาทำงานได้ปกติ เพียงเติม semicolon หลังคำสั่ง `alert`: -```js run -alert("All fine now"); - -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` -ทีนี้เราก็จะได้รับข้อความแจ้งเตือนสามครั้งได้แก่ "All fine now", "1" และ "2" ตามลำดับ +ต่างจากโค้ดก่อนหน้าเพียงหนึ่งตัวอักษร — เครื่องหมายอัฒภาค `;` ที่ท้ายบรรทัดแรก +รันโค้ดนี้ดู — จะแสดง `Hello` แล้วเกิด error (อาจต้องเปิดคอนโซลเพื่อดู) โดยไม่มีการแสดงเลข 1 และ 2 -error ดังกล่าวเกิดขึ้นเพราะว่าจาวาสคริปต์จะไม่ใส่ semicolon ให้ก่อน square brackets หรือก้ามปู `[]` นั่นเอง +นั่นเป็นเพราะ JavaScript ไม่ได้ใส่เครื่องหมายอัฒภาคโดยอัตโนมัติก่อนวงเล็บ `[...]` ดังนั้น โค้ดในตัวอย่างหลังจึงถูกรวมเป็นคำสั่งเดียวกันหมด -เมื่อ semicolon ไม่ได้ถูกใส่มา ดังนั้นมันจึงกลายเป็นคำสั่งเดียวกัน ข้างล่างคือสิ่งที่เอนจินเห็น +จริงๆ แล้ว JavaScript engine มองเห็นโค้ดเป็นแบบนี้: ```js run no-beautify -alert("There will be an error")[1, 2].forEach(alert) +alert("Hello")[1, 2].forEach(alert); ``` -มันควรแยกเป็นสองคำสั่งสิ ไม่ใช่คำสั่งเดียว จึงทำให้เครื่องทำงานผิดพลาด ดังนั้นสิ่งนี้จึงอาจเกิดขึ้นได้อยู่เสมอ +ดูแปลกๆ ใช่ไหม? การรวมแบบนี้ไม่ถูกต้องแน่นอน เราต้องใส่เครื่องหมายอัฒภาคหลัง `alert` เพื่อให้โค้ดทำงานได้ถูกต้อง + +ปัญหาแบบนี้อาจเกิดขึ้นได้ในสถานการณ์อื่นๆ ด้วย ```` -ในทางที่ดีเราจึงอยากแนะนำให้ใส่ semicolon เมื่อจบคำสั่งหนึ่งๆเสมอ แม้คำสั่งเหล่านั้นจะแยกการด้วยบรรทัดใหม่แล้วก็ตาม และกฎนี้ใช้กันอย่างกว้างขวางในชุมชนนักพัฒนา โปรดทราบอีกครั้งว่า *มันเป็นไปได้* ที่จะไม่ใส่ semicolon ลงไป แต่สำหรับมือใหม่ก็แนะนำให้ใส่ไว้จะปลอดภัยกว่า +เราแนะนำให้ใส่เครื่องหมายอัฒภาคระหว่าง statements แม้จะขึ้นบรรทัดใหม่แล้วก็ตาม แนวทางนี้เป็นที่ยอมรับอย่างกว้างขวางในชุมชนนักพัฒนา แม้ว่า *จะสามารถ* ละเครื่องหมายอัฒภาคได้ในหลายๆ กรณี แต่โดยเฉพาะสำหรับมือใหม่ การใส่ไว้ตลอดจะปลอดภัยกว่า -## คอมเม้นต์ +## Comments (คอมเมนต์) -เมื่อเขียนโปรแกรมไปเรื่อยๆ ก็จะพบว่าโปรแกรมยิ่งทวีความซับซ้อนมากขึ้นเรื่อยๆ จึงจำเป็นต้องมี *comments* มาอธิบายว่าโค้ดที่เราเขียนทำงานอย่างไร +เมื่อโปรแกรมซับซ้อนขึ้นเรื่อยๆ จำเป็นต้องมี *คอมเมนต์* อธิบายว่าโค้ดทำอะไรและทำไมถึงเขียนแบบนี้ -คอมเม้นสามารถเขียนลงไปส่วนไหนก็ได้ของสคริปต์ คอมเม้นจะไม่ส่งผลต่อการดำเนินงาน เพราะว่า เอนจินจะไม่สนใจคอมเม้นพวกนี้อยู่แล้ว +คอมเมนต์เขียนได้ทุกที่ในสคริปต์ และไม่ส่งผลต่อการทำงานของโค้ดเลย เพราะ JavaScript engine จะข้ามพวกมันไปทั้งหมด -**คอมเม้นแบบบรรทัดเดียวจะใช้เครื่องหมาย forward slash `//`** +**คอมเมนต์บรรทัดเดียวเริ่มต้นด้วย `//`** -เราสามารถคอมเม้นทั้งบรรทัด หรือคอมเม้นด้านหลังคำสั่งก็ได้ +ทุกอย่างหลังเครื่องหมาย `//` จนถึงสิ้นบรรทัดจะถูกนับเป็นคอมเมนต์ทั้งหมด จะเขียนแยกบรรทัดเต็มหรือต่อท้าย statement ก็ได้ แบบนี้: ```js run -// This comment occupies a line of its own +// คอมเมนต์นี้เป็นบรรทัดเดียว alert('Hello'); -alert('World'); // This comment follows the statement +alert('World'); // คอมเมนต์นี้ต่อท้าย statement ``` -**คอมเม้นแบบหลายบรรทัด จะเริ่มต้นด้วย forward slash และเครื่องหมายดอกจัน `/*` และลงท้ายด้วย เครื่องหมายดอกจันและ forward slash `*/`** +**คอมเมนต์หลายบรรทัดเริ่มด้วย /* และจบด้วย */** แบบนี้: ```js run -/* An example with two messages. -This is a multiline comment. +/* ตัวอย่างที่มีสองข้อความ +นี่คือคอมเมนต์ +หลายบรรทัด */ alert('Hello'); -alert('World'); +alert('World'); ``` -เอนจินไม่สนใจเนื้อหาภายในคอมเม้นต์ ต่อให้เราใส่จาวาสคริปต์ของเราไปภายในคอมเม้นก็จะไม่เกิดการรันคำสั่งใดๆ +เนื้อหาของคอมเมนต์จะถูกข้ามไปไม่ถูกประมวลผล ดังนั้นถ้าใส่โค้ดลงไปใน /* ... */ โค้ดนั้นก็จะไม่ทำงาน -หลายครั้ง เป็นประโยชน์อย่างมากที่ช่วยให้ไม่ต้องบางคำสั่งไม่ต้องดำเนินการ +เทคนิคนี้มีประโยชน์เวลาต้องการปิดโค้ดบางส่วนชั่วคราว: ```js run -/* Commenting out the code +/* ใส่คอมเมนต์โค้ดนี้ alert('Hello'); */ alert('World'); ``` -```smart header="ใช้คีย์ลัด!" -ใน editor ส่วนใหญ่มีคีย์ลัดเพื่อเอาไว้คอมเม้นต์โค้ด โดยกด `key:Ctrl+/` สำหรับคอมเม้นต์บรรทัดเดียว และ `key:Ctrl+Shift+/` สำหรับคอมเม้นต์หลายบรรทัด สำหรับผู้ใช้ Mac ให้ใชปุ่ม `key:Cmd` แทน `key:Ctrl` และ `key:Option` แทน `key:Shift` +```smart header="ใช้ปุ่มลัดเพื่อใส่คอมเมนต์!" +ใน code editor ส่วนใหญ่ กด `key:Ctrl+/` เพื่อใส่คอมเมนต์บรรทัดเดียว สำหรับคอมเมนต์หลายบรรทัด ให้เลือกโค้ดแล้วกด `key:Ctrl+Shift+/` (บน Mac ใช้ `key:Cmd` แทน `key:Ctrl` และ `key:Option` แทน `key:Shift`) ``` -````warn header="คอมเม้นต์ซ้อนคอมเม้นต์ไม่ได้" -ไม่ควรมี `/*...*/` ข้างในคอมเม้นต์แบบนี้ `/*...*/` อีกที +````warn header="คอมเมนต์ซ้อนกันไม่รองรับ!" +ไม่ควรมี /* ... */ อยู่ข้างในคอมเมนต์หลายบรรทัด /* ... */ อีก -โค้ดแบบนี้จะพังด้วย error +โค้ดแบบนี้จะเกิดข้อผิดพลาด: ```js run no-beautify /* - /* nested comment ?!? */ + /* คอมเมนต์ซ้อนกัน ?!? */ */ alert( 'World' ); ``` ```` -อย่าลังเลที่จะคอมเม้นต์โค้ดของตัวเอง +ใส่คอมเมนต์ให้เยอะๆ อย่าลังเล -คอมเม้นเพิ่มบรรทัดของโค้ด แต่นั่นไม่ใช่ปัญหาเลย มีเครื่องมือมากมายที่ช่วยนำคอมเม้นเหล่านั้นออกจากโค้ด ก่อนเผยแพร่ออกสู่เซิฟเวอร์ที่ใช้งานจริง ดังนั้นอย่างกังวลที่จะใช้คอมเม้นโค้ด +คอมเมนต์อาจทำให้ปริมาณโค้ดมากขึ้น แต่ไม่ใช่ปัญหา มีเครื่องมือมากมายที่ช่วย minify สคริปต์ก่อนนำขึ้น server โดยจะตัดคอมเมนต์ทิ้ง ทำให้ไม่ปรากฏในโค้ดที่ใช้งานจริง คอมเมนต์จึงไม่ส่งผลกระทบต่อ production เลย -ในภายหลังจะมีบทเรียนเกี่ยวกับ ที่จะช่วยอธิบายความสำคัญของคอมเม้นต์ และการเขียนคอมเม้นต์ที่ดีอีกด้วย +ในบทต่อๆ ไปของบทเรียนอย่าง เราจะอธิบายเพิ่มเติมถึงวิธีเขียนคอมเมนต์ที่ดีด้วย diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 846cd5a2f..6ed7f960d 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -1,92 +1,89 @@ -# โหมดใหม่ "use strict" +# โหมดสมัยใหม่ "use strict" -เป็นเวลานานแล้วที่จาวาสคริปต์ออกฟีเจอร์ใหม่ๆโดยไม่มีปัญหาเรื่องความเข้ากันได้ (Compatibility) ถึงจะมีฟีเจอร์ใหม่ๆ แต่ฟีเจอร์เดิมๆก็ยังคงทำงานได้อยู่นั่นเอง +JavaScript วิวัฒนาการมาอย่างยาวนานโดยไม่ต้องกังวลเรื่องความเข้ากันได้ มีการเพิ่มฟีเจอร์ใหม่ๆ เข้ามาอย่างต่อเนื่อง ในขณะที่ฟังก์ชันเดิมๆ ยังคงทำงานเหมือนเดิม -ข้อดีก็คือโค้ดของเรากับโค้ดเมื่อสิบปีที่แล้วก็สามารถทำงานร่วมกันได้ แต่มันมีข้อเสียเพราะตัวผู้สร้างภาษาเอง ก็ดันไปสร้างบาปกำเนิดกับจาวาสคริปต์ด้วย โดยไม่รู้ตัว ดังนั้นเมื่อการหักกับฟีเจอร์เก่าๆแล้ว บาปกำเนิดของจาวาสคริปต์ จึงกลายเป็นคำสาป ชำระล้างไม่ได้ไปตลอดกาล +ในแง่หนึ่งก็เป็นเรื่องดี เพราะโค้ดเดิมไม่พัง แต่ในอีกแง่ก็ทำให้ข้อผิดพลาดหรือการตัดสินใจออกแบบที่ไม่สมบูรณ์ของผู้พัฒนา JavaScript ถูกสืบทอดมาในภาษาเรื่อยๆ -จนเวลาล่วงเลยมาถึงปี 2009 มาตรฐานภาษาชุดใหม่เปิดตัว มันคือ ECMAScript5 ที่เพิ่มทั้งฟีเจอร์ใหม่ รวมถึงไปโมฟีเจอร์เก่าๆบางตัวด้วย ผลก็คือ `แหก` นั่นเอง แต่ทางผู้สร้างมาตรฐานชุดนี้ตระหนักในเรื่องนี้ดี เพื่อให้โค้ดเก่าๆทำงานได้ พวกเขาจึงได้ปิดฟีเจอร์โมภาษานี้ไป แต่เราสามารถเปิดตัวโมนี้ได้ผ่านคำสั่ง `"use strict"` +สถานการณ์เป็นแบบนั้นจนถึงปี 2009 เมื่อ ECMAScript 5 (ES5) เปิดตัว มีการเพิ่มฟีเจอร์ใหม่และปรับปรุงบางส่วนที่มีอยู่เดิม แต่เพื่อให้โค้ดเก่ายังทำงานได้ การปรับปรุงส่วนใหญ่จึงปิดไว้เป็นค่าเริ่มต้น ต้องเปิดใช้งานเองผ่าน directive พิเศษที่ชื่อว่า `"use strict"` ## "use strict" -ตัวคำสั่งหน้าตาเหมือนสตริง `"use strict"` หรือ `'use strict'` แต่คำสั่นี้จะต้องอยู่บนสุดของสคริปต์คือบรรทัดที่หนึ่ง ทีนี้สคริปต์ทั้งหมดของเราก็ทำงานแบบ "สมัยใหม่ (modern)" +Directive จะมีลักษณะคล้ายสตริง: `"use strict"` หรือ `'use strict'` เมื่อวางไว้ที่ต้นสคริปต์ ทั้งสคริปต์จะทำงานในโหมด "สมัยใหม่" -ดั่งตัวอย่าง: +ตัวอย่างเช่น: ```js "use strict"; -// โค้ดที่เขียนต่อจากนี้จะทำงานแบบจาวาสคริปต์สมัยใหม่ +// โค้ดนี้จะทำงานในโหมดสมัยใหม่ ... ``` -เราจะเรียนรู้เรื่องฟังก์ชั่น (วิธีการจัดกลุ่มคำสั่ง) ในไม่ช้า แต่อยากบอกในเบื้องต้นว่า `"use strict"` สามารถวางไว้ที่บรรทัดเริ่มต้นของฟังก์ชั่นได้ เพื่อจะใช้โหมด `"use strict"` แค่ในฟังก์ชั่นนี้เท่านั้น แต่ปกติแล้ว นิยมวางไว้บรรทัดแรกมากกว่าแต่เปิดใช้ทั้งไฟล์สคริปต์ +เร็วๆ นี้เราจะได้เรียนรู้เรื่องฟังก์ชัน (การจัดกลุ่มคำสั่ง) ดังนั้นจึงขอบอกไว้ก่อนว่า `"use strict"` สามารถวางไว้ในฟังก์ชันได้ จะเปิดใช้งานโหมดเข้มงวดเฉพาะในฟังก์ชันนั้นเท่านั้น แต่โดยทั่วไปแล้วผู้คนจะใช้กับทั้งสคริปต์ +````warn header="ตรวจสอบให้แน่ใจว่า \"use strict\" อยู่ที่ต้นสคริปต์" +โปรดตรวจสอบให้แน่ใจว่า `"use strict"` อยู่ด้านบนสุดของสคริปต์ ไม่เช่นนั้นโหมดเข้มงวดอาจไม่ถูกเปิดใช้งาน -````warn header="อย่าลืมว่า \"use strict\" ต้องอยู่บรรทัดแรกเสมอ" -อย่าลืมว่า `"use strict"` ต้องอยู่บรรทัดแรกของไฟล์สคริปต์เสมอ ไม่อย่างนั้นโหมดนี้จะไม่ทำงาน - -เช่นตัวอย่างด้านล่าง +ดังเช่นในตัวอย่างนี้ โหมดเข้มงวดจะไม่ทำงาน: ```js no-strict alert("some code"); -// ตัวเอนจินจะไม่สนใจ "use strict" ด้านล่าง โหมดนี้จึงไม่ได้ถูกทำงาน +// "use strict" ด้านล่างถูกเพิกเฉย -- ต้องอยู่บรรทัดบนสุด "use strict"; -// strict mode จะไม่ทำงาน +// โหมดเข้มงวดยังไม่เปิดใช้งาน ``` -หากมีคอมเม้นอยู่เหนือ "use strict" ตัวของ "use strict" ก็จะทำปกติ +มีเพียงคอมเมนต์เท่านั้นที่สามารถปรากฏขึ้นเหนือ `"use strict"` ได้ ```` -```warn header="ไม่มีทางยกเลิกโหมด `use strict` ได้" -มันไม่มีคำสั่งอย่าง "no use strict" เพื่อได้เอนจินกลับไปทำงานในโหมดเดิมได้ +```warn header="ไม่มีทางยกเลิก `use strict`" +ไม่มี directive อย่างเช่น `"no use strict"` ที่จะทำให้เอนจินย้อนกลับไปใช้พฤติกรรมแบบเก่า -ดังนั้นเมื่อเราเปิดโหมด `use strict` แล้ว เราก็ไม่มีทางปิดมันได้ +เมื่อเราเข้าสู่โหมดเข้มงวดแล้ว ก็ไม่มีทางย้อนกลับได้อีก ``` -## คอนโซลบนเบราเซอร์ +## คอนโซลเบราว์เซอร์ + +เมื่อใช้ [คอนโซลนักพัฒนา](info:devtools) รันโค้ด โปรดสังเกตว่าคอนโซลไม่ได้เปิด `use strict` ไว้โดยค่าเริ่มต้น -หากเราใช้[คอนโซลนักพัฒนา](info:devtools) เพื่อรันโค้ด คอนโซลบนเบราเซอร์ปิด `use strict` เอาไว้เป็นค่าเริ่มต้น +บางครั้งเมื่อ `use strict` ทำให้พฤติกรรมต่างออกไป อาจได้ผลลัพธ์ที่ไม่ถูกต้อง -แล้ว ถ้าอยากใช้ `use strict ในคอนโซลจะทำไงดี +แล้วจะใช้ `use strict` ในคอนโซลได้อย่างไร? -ก่อนอื่น ให้พิมพ์ `use strict` ไว้ก่อนแล้วให้กด `key:Shift+Enter` เพื่อขึ้นบรรทัดใหม่ +วิธีแรก ลองกด `key:Shift+Enter` เพื่อป้อนหลายบรรทัด แล้ววาง `use strict` ไว้บรรทัดแรก แบบนี้: ```js -'use strict'; -// ...โค้ดของเรา -<อย่าลืมกด Enter เพื่อให้โค้ดทำงาน> +'use strict'; +// ...โค้ดของคุณ + ``` -ฟีเจอร์นี้รับรองบนเบราเซอร์ส่วนใหญ่แล้วอย่าง Chrome และ Firefox +วิธีนี้ใช้ได้กับเบราว์เซอร์ส่วนใหญ่ เช่น Firefox และ Chrome -หากใช้ไม่ได้ นั่นแสดงว่าเรากำลังใช้งานเบราเซอร์ที่เก่าเอามากๆ ดังนั้นเพื่อให้แน่ใจได้ว่า `use strict` จะถูกใช้กับทุกคำสั่งที่เราป้อนลงในคอนโซล ลองทำตามตัวอย่างโค้ดด้านล่างดูสิ +ถ้าไม่ได้ผล เช่นในเบราว์เซอร์รุ่นเก่า มีอีกวิธีที่อาจดูไม่หรูแต่ใช้งานได้แน่นอน นั่นคือห่อโค้ดด้วยฟังก์ชันแบบนี้: ```js (function() { 'use strict'; - // ...ใส่โค้ดที่ต้องการตั้งแต่ตรงนี้... + // ...โค้ดของคุณที่นี่... })() ``` -## ควรเปิด "use strict" - -คำตอบก็คือควร +## เราควรใช้ "use strict" หรือไม่? -ดังนั้นให้เขียน `"use strict"` จนเป็นนิสัยได้เลย แต่ยังมีเกร็ดเล็กน้อยอีกนิดนึง +คำถามอาจดูชัดเจน แต่จริงๆ แล้วไม่ได้ชัดเจนขนาดนั้น -ใน Modern Javascript ได้สนับสนุน "classes" และ "modules" ซึ่งยังไม่สอนตอนนี้ (แต่เดี๋ยวเข้าใจแน่นอน) ที่อยากจะบอกก็คือ มันเปิด `use strict` โดยอัตโนมัติ ดังนั้นเราไม่ต้องเพิ่ม `"use strict"` ในสคริปต์เลย +บางคนอาจแนะนำให้เริ่มสคริปต์ทุกตัวด้วย `"use strict"`... แต่รู้อะไรมั้ย? มีเรื่องที่น่าสนใจกว่านั้น -จนถึงตอนนี้ เราก็รู้จักกับ `use strict` ในเบื้องต้นกันแล้ว +JavaScript สมัยใหม่รองรับ "classes" และ "modules" ซึ่งเป็นโครงสร้างขั้นสูงของภาษา (เราจะได้เรียนแน่นอน) — และสิ่งเหล่านี้จะเปิดใช้งาน `use strict` โดยอัตโนมัติ ดังนั้นจึงไม่จำเป็นต้องเพิ่ม directive `"use strict"` เองเมื่อใช้โครงสร้างเหล่านี้ -ในบทภัดไป เราจะเรียนเกี่ยวกับฟีเจอร์ของภาษา เราจะได้เห็นความแตกต่างระหว่าง แบบใช้ `use strict` กับแบบไม่ใช้ มาดูกันว่า `use strict` ช่วยให้เราชีวิตง่ายขึ้นยังไง +**สรุปก็คือ ตอนนี้ `"use strict"` เปรียบเหมือนแขกต้อนรับที่ดีที่ควรมีไว้ต้นสคริปต์ แต่ในอนาคตเมื่อโค้ดทั้งหมดอยู่ใน classes และ modules ก็อาจละมันไปได้** -ทุกตัวอย่างในบทเรียนใช้ `use strict` หมด จะมีอยู่น้อยมากที่เราตั้งใจจะไม่ใช้ +ถึงตอนนี้ก็ได้รู้จัก `use strict` ในแบบกว้างๆ แล้ว -1. คำสั่ง `"use strict"` เป็นคำสั่งสำหรับเอนจินยุค ES5 ซึ่งเอาไว้สลับโหมดระหว่างโหมดปกติกับโหมด `"use strict"` เราจะมาพูดถึงรายละเอียดกันอีกทีในบทถัดไป -2. เปิด strict โหมดง่ายๆเพียงแค่ใส่ `"use strict"` ไว้บนสุดของสคริปต์หรือฟังชั่นก์ เมื่อเอนจินอ่านเจอมันจะเปิดโหมดนี้โดยอัตโนมัติ -3. โมเดิร์นเบราเซอร์ รองรับโหมด strict ทั้งหมดแล้ว -4. แนะนำว่าควรเปิด strict โหมดไว้เสมอ +ในบทต่อๆ ไป เมื่อเรียนรู้ฟีเจอร์ต่างๆ ของภาษา จะเห็นความแตกต่างระหว่างโหมดเข้มงวดและโหมดปกติ โชคดีที่ต่างกันไม่มากนัก และจริงๆ แล้วโหมดเข้มงวดทำให้ชีวิตสะดวกขึ้นด้วยซ้ำ +ทุกตัวอย่างโค้ดในบทเรียนนี้จะถือว่าใช้โหมดเข้มงวด ยกเว้น (ซึ่งพบได้น้อยมาก) ที่ระบุเป็นอย่างอื่น diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index 8c7997eb4..5db98e945 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -6,7 +6,7 @@ let ourPlanetName = "Earth"; ``` -จำไว้ว่า ที่จริงเราควรใช้ชื่อที่สั้นอย่าง `planet` แต่ตัวแปรนี้ก็ยังคลุมเครือว่าดาวเคราะห์อะไร มันจะดีขึ้นเมื่อเราใช้คำมากขึ้น หากการใช้คำมากขึ้น ไม่ได้ทำให้ชื่อตัวแปรยาวเกินไป +จำไว้ว่า ที่จริงเราควรใช้ชื่อที่สั้นอย่าง `planet` แต่ตัวแปรก็ยังไม่ชัดเจนว่าจะสื่อถึงดาวเคราะห์ดวงไหน เราจะตั้งชื่อให้ยาวไปอีกสักนิดเพื่อการสื่อความ ตราบใดที่ชื่อตัวแปรไม่ได้ isNotTooLong ## ชื่อตัวแปรที่เก็บชื่อผู้เยี่ยมชมเว็บไซต์ปัจจุบัน diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 2e40ce362..cd4ff6475 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -17,8 +17,7 @@ const age = someCode(birthday); จะให้ชื่อตัวแปรไหนเป็น ตัวพิมพ์เล็ก หรือ ตัวใหญ่ดีตัวแปร `birthday` ดีไหม? หรือตัวแปร `age` หรือจะทั้งคู่ดีละ ```js -const BIRTHDAY = '18.04.1982'; // make uppercase? +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? -const AGE = someCode(BIRTHDAY); // make uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? ``` - diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 36ddb789a..fbf9a1715 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -1,273 +1,273 @@ # ตัวแปร -โดยปกติแล้วแอพพลิชั่นที่พัฒนาด้วยจาวาสคริปต์ มักจะต้องทำงานร่วมกับข้อมูลมากมาย -1. แอพฯร้านค้าออนไลน์ -- ข้อมูลที่ว่าก็คือ สินค้าที่ขาย ตะกร้าสินค้า และอื่นๆ -2. แอพฯแชท -- ข้อมูลที่ว่าก็คือ ผู้ใช้ ข้อความ และอื่นๆ +ในการพัฒนาแอปพลิเคชัน JavaScript ส่วนใหญ่ เรามักจะต้องทำงานกับข้อมูล ยกตัวอย่างเช่น +1. ร้านค้าออนไลน์ -- ข้อมูลอาจประกอบด้วยสินค้าที่จำหน่ายและตะกร้าสินค้า +2. แอปพลิเคชันแชท -- ข้อมูลอาจประกอบด้วยผู้ใช้ ข้อความ และอื่นๆ อีกมากมาย -ตัวแปรจึงมีหน้าที่คอยเก็บข้อมูลในแอพฯ +ตัวแปรใช้เก็บข้อมูลเหล่านี้ -## ตัวแปรหนึ่งๆ +## ตัวแปรคืออะไร -[ตัวแปร](https://th.wikipedia.org/wiki/ตัวแปร_(วิทยาการคอมพิวเตอร์)) คือ "หน่วยเก็บข้อมูลที่ระบุชื่อ​ (named storage)" ที่เราสามารถใช้เก็บช้อมูลสินค้า ผู้เข้าชม และข้อมูลอื่นๆได้ +[ตัวแปร](https://en.wikipedia.org/wiki/Variable_(computer_science)) คือ "พื้นที่จัดเก็บข้อมูลที่มีชื่อกำกับ" เราสามารถใช้ตัวแปรเพื่อเก็บสินค้า ข้อมูลผู้เยี่ยมชม และข้อมูลอื่นๆ ได้ -เพื่อสร้างตัวแปรในจาวาสคริปต์ เราจะใช้คีย์เวิร์ดว่า `let` +ในการสร้างตัวแปรใน JavaScript ให้ใช้คีย์เวิร์ด `let` -โดยที่คำสั่งด้านล่างหมายถึงการสร้าง (บางทีจะเรียกว่า: *ประกาศ*) ตัวแปรโดยตั้งชื่อให้ว่า "message": +คำสั่งด้านล่างจะสร้าง (หรือที่เรียกอีกอย่างว่า *ประกาศ*) ตัวแปรที่มีชื่อว่า "message": ```js let message; ``` -ทีนี้ เราสามารถเก็บข้อมูลลงไปในตัวแปรนี้ได้โดยใช้ assignment operator อย่าง `=`: +ตอนนี้เราสามารถใส่ข้อมูลลงในตัวแปรได้ด้วยการใช้เครื่องหมาย `=` หรือที่เรียกว่าตัวดำเนินการกำหนดค่า (assignment operator): ```js let message; *!* -message = 'Hello'; // เก็บข้อมูลที่เป็นสตริงไว้ +message = 'สวัสดี'; // เก็บข้อความ 'สวัสดี' ในตัวแปรที่ชื่อว่า message */!* ``` -สตริงตัวนี้จะถูกบันทึกลงในหน่วยความจำที่เกี่ยวข้องกับตัวแปรที่เราสร้างไว้ เราจึงสามารถเข้าถึงค่าที่เก็บไว้โดยใช้ชื่อตัวแปร +ข้อความจะไปเก็บอยู่ในพื้นที่หน่วยความจำที่สัมพันธ์กับตัวแปรนั้น และเข้าถึงได้ด้วยการอ้างชื่อตัวแปร: ```js run let message; -message = 'Hello!'; +message = 'สวัสดี!'; *!* -alert(message); // ดูว่า "message" เก็บอะไรไว้ +alert(message); // แสดงค่าในตัวแปร */!* ``` -เพื่อให้โค้ดดูกระชับขึ้น เราได้รวมบรรทัดที่สร้างตัวแปร กับ กำหนดค่าให้ตัวแปร ไว้บรรทัดเดียวกัน +เพื่อความกระชับ เราสามารถรวมการประกาศตัวแปรและกำหนดค่าเริ่มต้นไว้ในบรรทัดเดียวกันได้: ```js run -let message = 'Hello!'; // ทั้งสร้างตัวแปร และ กำหนดค่าให้ตัวแปร ไปพร้อมกัน +let message = 'สวัสดี!'; // ประกาศตัวแปรและกำหนดค่าในคำสั่งเดียว -alert(message); // Hello! +alert(message); // สวัสดี! ``` -เราสามารถสร้างตัวแปร และ กำหนดค่าให้ตัวแปร พร้อมกันหลายๆตัวได้ ดังนี้ +นอกจากนี้ เรายังสามารถประกาศตัวแปรหลายตัวในบรรทัดเดียวได้ด้วย: ```js no-beautify -let user = 'John', age = 25, message = 'Hello'; +let user = 'จอห์น', age = 25, message = 'สวัสดี'; ``` -โค้ดด้านบนที่ดูทั้งสั้นและกระชับ แต่เราไม่แนะนำให้ทำแบบนี้ ทางที่ดีเราควรจะสร้างและกำหนดค่าตัวแปรต่อบรรทัด เพื่อให้ง่ายต่อการอ่านโค้ด +วิธีนี้อาจจะดูสั้นกระชับดี แต่เราไม่แนะนำให้ใช้ เพื่อความสะดวกในการอ่านโค้ด ควรประกาศตัวแปรแต่ละตัวแยกเป็นคนละบรรทัดจะดีกว่า -ตัวแปรหลายบรรทัดแบบนี้จะยาวขึ้นหน่อย แต่มันก็ช่วยให้อ่านง่ายขึ้น: +แบบนี้อาจยาวกว่าเล็กน้อย แต่อ่านง่ายขึ้นมาก: ```js -let user = 'John'; +let user = 'จอห์น'; let age = 25; -let message = 'Hello'; +let message = 'สวัสดี'; ``` -บางคนก็เขียนในรูปแบบนี้ +บางคนยังชอบประกาศตัวแปรหลายตัวแบบหลายบรรทัดแบบนี้: + ```js no-beautify -let user = 'John', +let user = 'จอห์น', age = 25, - message = 'Hello'; + message = 'สวัสดี'; ``` -...หรือกระทั่งเขียนแบบ "ใข้ลูกน้ำเปิดบรรทัด" +หรือแม้กระทั่งเขียนในรูปแบบ "เริ่มต้นด้วยเครื่องหมายจุลภาค": ```js no-beautify -let user = 'John' +let user = 'จอห์น' , age = 25 - , message = 'Hello'; + , message = 'สวัสดี'; ``` -ในทางเทคนิคแล้ว ตัวอย่างทั้งหมดทำในสิ่งเดียวกัน คือสร้างและกำหนดค่าตัวแปรหลายตัว การเขียนแบบด้านบนจึงเป็นเรื่องของรสนิยม และสุนทรียส่วนตัว +ในเชิงเทคนิคแล้ว ตัวแปรทั้งหมดที่ยกตัวอย่างมาทำงานในลักษณะเดียวกัน จึงเป็นเรื่องของรสนิยมและความชอบส่วนตัว -````smart header="`var` แทนด้วย `let`" -ในโค้ดเก่าๆ เราจะเจอการประกาศตัวแปรด้วย `var` แทนที่จะเป็น `let`: +````smart header="`var` กับ `let`" +ในสคริปต์เก่าๆ อาจเจอการใช้คีย์เวิร์ด `var` แทน `let` ในการประกาศตัวแปร: ```js -*!*var*/!* message = 'Hello'; +*!*var*/!* message = 'สวัสดี'; ``` -การใช้ `var` *แทบจะ* เหมือน `let` ทุกประการ +คีย์เวิร์ด `var` นั้น*เกือบจะ*เหมือนกับ `let` คือใช้ประกาศตัวแปร แต่จะมีความแตกต่างเล็กน้อยในสไตล์ที่ค่อนข้าง "เชย" + +ความแตกต่างระหว่าง `let` กับ `var` นั้นไม่ใช่ประเด็นสำคัญสำหรับเราในตอนนี้ เราจะกล่าวถึงรายละเอียดในบทเรียน -ระหว่าง `let` กับ `var` มีความแตกต่างกันเล็กน้อย โดยเรายังจะไม่ลงลึกในส่วนนี้ แต่จะลงลึกในบท แทน ```` -## เปรียบเทียบกับชีวิตจริง +# การเปรียบเทียบกับสถานการณ์ในชีวิตจริง -ให้เราจินตนาการว่า "ตัวแปร" ก็เหมือน "กล่อง" แต่แทนที่จะบรรจุของอยู่ภายใน มันดันเป็นกล่องที่บรรจุข้อมูลแทน แถมยังมีสติกเกอร์ชื่อกล่องติดไว้อีกด้วย +ลองนึกภาพ "ตัวแปร" เป็น "กล่อง" ไว้ใส่ข้อมูล แปะสติกเกอร์ชื่อไว้ด้านนอก -ตัวอย่างเช่นตัวแปร `message` ก็คือกล่องที่มีสติกเกอร์ชื่อติดไว้ว่า `message` พร้อมกับบรรจุข้อมูล `"Hello!"` อยู่ภายใน +ยกตัวอย่างเช่น ตัวแปร `message` จะเปรียบได้กับกล่องที่มีป้ายชื่อ `"message"` และมีค่า `"สวัสดี!"` ข้างใน: ![](variable.svg) -เราสามารถใส่ค่าอะไรก็ได้ลงไปในกล่อง +ใส่ค่าอะไรก็ได้ลงในกล่อง และเปลี่ยนค่าได้ตามใจชอบ: -เรายังสามารถเปลี่ยนมันเป็นก็ได้ทุกเมื่อที่เราต้องการ ```js run let message; -message = 'Hello!'; +message = 'สวัสดี!'; -message = 'World!'; // เปลี่ยนค่า +message = 'ชาวโลก!'; // เปลี่ยนค่าแล้ว alert(message); ``` -เมื่อค่าเปลี่ยน ข้อมูลเก่าจะถูกลบจากตัวแปรทันที: +เมื่อเปลี่ยนค่า ข้อมูลเก่าก็จะหายไปจากตัวแปร: ![](variable-change.svg) -เรายังสามารถสร้างตัวแปร และคัดลอกข้อมูลจากอีกตัวแปรหนึ่ง ให้อีกตัวแปรหนึ่งได้ด้วย +นอกจากนี้ เรายังสามารถประกาศตัวแปรสองตัว แล้วคัดลอกข้อมูลจากตัวหนึ่งไปอีกตัวหนึ่งได้ด้วย ```js run -let hello = 'Hello world!'; +let hello = 'สวัสดีชาวโลก!'; let message; *!* -// คัดลอก 'Hello world' จากตัวแปร hello มาให้ตัวแปร message +// คัดลอก 'สวัสดีชาวโลก!' จาก hello มาเก็บใน message message = hello; */!* -// ทีนี้ตัวแปรสองตัวจะมีค่าเดียวกัน -alert(hello); // Hello world! -alert(message); // Hello world! +// ตอนนี้ตัวแปรทั้งสองมีข้อมูลชุดเดียวกัน +alert(hello); // สวัสดีชาวโลก! +alert(message); // สวัสดีชาวโลก! ``` -````warn header="หากประกาศซ้ำกัน ก็จะเกิด error ขึ้น" -ตัวแปรควรประกาศเพียงครั้เดียว +````warn header="การประกาศตัวแปรซ้ำจะเกิดข้อผิดพลาด" +ควรประกาศตัวแปรเพียงครั้งเดียวเท่านั้น -การประกาศตัวแปรเหมือนกับตัวแปรเดิมจะเกิด error ได้ +หากประกาศตัวแปรเดิมซ้ำอีก จะถือเป็นข้อผิดพลาด: ```js run -let message = "This"; +let message = "นี่"; -// ประกาศ 'let' ซ้ำก็จะ error -let message = "That"; // SyntaxError: 'message' has already been declared +// ประกาศ 'let' ซ้ำ ทำให้เกิด error +let message = "นั่น"; // SyntaxError: 'message' ถูกประกาศไปแล้ว ``` -ดังนั้น เราควรประกาศตัวแปรเพียงครั้งเดียว และโดยที่ไม่ต้องประกาศ `let` ซ้ำอีกที + +ดังนั้น ประกาศตัวแปรแค่ครั้งเดียว จากนั้นอ้างถึงได้เลยโดยไม่ต้องมี `let` อีก ```` -```smart header="ภาษาเชิงฟังชั่นก์" -สิ่งที่น่าสนใจสำหรับภาษาโปรแกรมมิ่งเชิง[ฟังชั่น](https://en.wikipedia.org/wiki/Functional_programming) อย่างเช่น [Scala](http://www.scala-lang.org/) หรือ [Erlang](http://www.erlang.org/) ก็คือห้ามเปลี่ยนค่าของตัวแปร +```smart header="ภาษาการเขียนโปรแกรมแบบฟังก์ชัน" +น่าสนใจว่า มีภาษาโปรแกรมที่เรียกว่า [ฟังก์ชันเชิงบริสุทธิ์](https://en.wikipedia.org/wiki/Purely_functional_programming) เช่น [Haskell](https://en.wikipedia.org/wiki/Haskell) ซึ่งห้ามไม่ให้เปลี่ยนค่าตัวแปรเด็ดขาด -ในภาษาประเภทนี้เมื่ออะไรก็ตามถูกเก็บไว้ในกล่อง สิ่งๆนั้นจะต้องอยู่ในกล่องตลอดกาล ถ้าเราต้องการเก็บค่าอื่นๆ เราจะต้องสร้างตัวแปรขึ้นมาใหม่แทน โดยตัวภาษาจะบังคับให้เราทำกล่องใบใหม่ขึ้นมา เราไม่สามารถใช้กล่องเก่าซ้ำได้ +ในภาษาเหล่านี้ เมื่อเก็บค่าใส่ "กล่อง" ไปแล้ว มันจะอยู่ในนั้นตลอดกาล หากต้องการเก็บข้อมูลอย่างอื่น ภาษาจะบังคับให้เราต้องสร้างกล่องใหม่ (ประกาศตัวแปรใหม่) จะนำกล่องเก่ามาใช้ใหม่ไม่ได้ -แม้ว่ามันอาจจะดูแปลกๆเล็กน้อย แต่ข้อจำกัดเหล่านี้กลับมีประโยชน์ในบางอย่างในด้านการพัฒนาแอพฯอย่างมาก โดยเฉพาะการทำงานได้มากกว่าสองอย่างในเวลาเดียวกัน เราแนะนำการศึกษาภาษาจำพวกเชิงฟังก์ชั่น เพื่อเปิดมุมมองให้กว้างขึ้น +ฟังดูแปลกๆ ใช่ไหม? แต่ภาษาพวกนี้ก็พัฒนาโปรแกรมจริงจังได้นะ ยิ่งไปกว่านั้น ในบางด้านอย่างการประมวลผลแบบขนาน ข้อจำกัดนี้กลับมีประโยชน์เสียด้วยซ้ำ ``` ## การตั้งชื่อตัวแปร [#variable-naming] -มีข้อยกเว้นในการตั้งชื่อตัวแปรอยู่ 2 ข้อด้วยกัน +ในภาษา JavaScript มีข้อจำกัด 2 ประการในการตั้งชื่อตัวแปร: -1. ชื่อควรประกอบด้วย ข้อความ, ตัวเลข หรือเครื่องหมาย `$` และ `_` -2. ชื่อตัวแปรห้ามขึ้นต้นด้วยตัวเลข +1. ชื่อต้องประกอบด้วยตัวอักษร ตัวเลข หรือสัญลักษณ์ `$` และ `_` เท่านั้น +2. ตัวอักษรตัวแรกต้องไม่ใช่ตัวเลข -ตัวอย่างการตั้งชื่อ: +ตัวอย่างชื่อที่ใช้ได้: ```js let userName; let test123; ``` -หากชื่อประกอบด้วยคำมากกว่าหนึ่งคำ เราจะใช้การแบ่งคำแบบ [camelCase](https://en.wikipedia.org/wiki/CamelCase) นั่นก็คือ: คำแรกจะขึ้นต้นด้วยตัวเล็ก คำต่อๆมาก็จะขึ้นต้นด้วยตัวใหญ่ `myVeryLongName` +เมื่อชื่อประกอบด้วยหลายคำ มักนิยมใช้รูปแบบ [camelCase](https://en.wikipedia.org/wiki/CamelCase) นั่นคือเขียนคำต่อกันเรื่อยๆ โดยขึ้นต้นคำแรกด้วยตัวพิมพ์เล็ก ส่วนคำถัดๆ ไปให้ขึ้นต้นด้วยตัวพิมพ์ใหญ่ เช่น `myVeryLongName` -สิ่งที่น่าสนใจก็คือ -- สัญลักษณ์ดอลล่าร์ `'$'` และ underscore `'_'` สามารถนำมาตั้งชื่อตัวแปรได้ด้วย สัญลักษณ์เหล่านี้เป็นเพียงสัญลักษณ์ธรรมดา เหมือนตัวอักษรไม่ได้มีความหมายพิเศษอะไร +ที่น่าสนใจคือ เครื่องหมายดอลลาร์ `'$'` และอันเดอร์สกอร์ `'_'` ก็ใช้เป็นส่วนหนึ่งของชื่อได้ เป็นแค่สัญลักษณ์ธรรมดาเหมือนตัวอักษร ไม่ได้มีความหมายพิเศษอะไร -สามารถตั้งชื่อแบบนี้ได้ +ชื่อเหล่านี้ใช้ได้: ```js run untrusted -let $ = 1; // ตั้งชื่อตัวแปรด้วย "$" -let _ = 2; // ทีนี้ก็ตั้งด้วย "_" +let $ = 1; // ประกาศตัวแปรชื่อ "$" +let _ = 2; // และนี่ตัวแปรชื่อ "_" alert($ + _); // 3 ``` -ตัวอย่างชื่อตัวแปรที่ไม่สามารถตั้งได้ +ตัวอย่างชื่อตัวแปรที่ใช้ไม่ได้: ```js no-beautify let 1a; // ห้ามขึ้นต้นด้วยตัวเลข -let my-name; // hyphens '-' ห้ามมีในชื่อ +let my-name; // เครื่องหมายขีด '-' ไม่อนุญาตให้ใช้ในชื่อ ``` -```smart header="พิมพ์เล็กพิมพ์ใหญ่นั้นสำคัญ" -ตัวแปรชื่อ `apple` กับตัวแปรชื่อ `AppLE` เป็นคนละตัวแปรกัน +```smart header="ตัวพิมพ์ใหญ่-เล็กมีผล" +ตัวแปร `apple` กับ `APPLE` ถือเป็นคนละตัวกัน ``` -````smart header="สามารถตั้งชื่อตัวแปรที่ไม่ใช่ตัวอักษรละตินได้ แต่ไม่แนะนำ" -สามารถตั้งชื่อตัวแปรเป็นภาษาอะไรก็ได้ แม้กระทั่ง อักษรซีริลลิก (cyrillic) หรือ อักษรอียิปต์โบราณ (hieroglyphs) แบบนี้ +```smart header="อักษรที่ไม่ใช่ภาษาอังกฤษใช้ได้ แต่ไม่แนะนำ" +เราสามารถใช้ภาษาอื่นๆ ได้ รวมถึงอักษรภาษารัสเซีย อักษรจีน หรืออื่นๆ เช่น: ```js let имя = '...'; let 我 = '...'; ``` -ในทางเทคนิค เราสามารถทำได้โดยที่ไม่มีข้อผิดพลาดเกิดขึ้น เราสามารถตั้งชื่อตัวแปรแบบนี้ได้ แต่สากลนิยมจะใช้ภาษาอังกฤษเป็นชื่อตัวแปร แม้ว่าจะเราเขียนไฟล์สคริปต์ไม่กี่บรรทัด มันก็มีโอกาสที่จะมีชาวต่างชาติมาอ่านเพื่อทำความเข้าใจสคริปต์ไม่กี่บรรทัดนี้ - -```` +ในทางเทคนิคแล้ว ตัวอย่างข้างบนไม่มีข้อผิดพลาด การตั้งชื่อแบบนั้นใช้ได้ แต่เรามีข้อตกลงสากลว่าควรใช้ภาษาอังกฤษในการตั้งชื่อตัวแปร เพราะถึงแม้จะเป็นสคริปต์สั้นๆ แต่ก็อาจจะมีชีวิตอยู่ได้นานมาก และในอนาคตอาจมีคนจากประเทศอื่นต้องเข้ามาอ่านด้วยก็เป็นได้ +``` -````warn header="ชื่อที่ถูกสงวน" -นี่คือ[รายชื่อคำที่ถูกสงวนไว้](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords) คำเหล่านี้ไม่สามารถนำมาตั้งชื่อตัวแปรได้ เพราะถูกสงวนไว้ให้ตัวภาษาใช้ +```warn header="ชื่อที่สงวนไว้" +มี[รายการคำสงวน](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords) ที่ตัวภาษาจองไว้ใช้เอง จึงนำมาตั้งเป็นชื่อตัวแปรไม่ได้ -ตัวอย่างเช่น: `let`, `class`, `return`, และ `function` ทั้งหมดเป็นคำสงวน +ตัวอย่างเช่น: `let`, `class`, `return` และ `function` เป็นคำสงวน -หากเรานำคำสงวนไปตั้งชื่อตัวแปรก็จะเกิดข้อผิดพลาดขึ้น +โค้ดด้านล่างจะเกิด syntax error: ```js run no-beautify -let let = 5; // ไม่สามารถตั้งชื่อตัวแปรว่า "let" ได้ -let return = 5; // ไม่สามารถตั้งชื่อตัวแปรว่า "return" ได้ +let let = 5; // ไม่สามารถตั้งชื่อตัวแปรว่า "let" เพราะเป็นคำสงวน เกิด error! +let return = 5; // เช่นเดียวกัน ห้ามตั้งชื่อเป็น "return" เกิด error! +``` ``` -```` -````warn header="การกำหนดค่าตัวแปรเมื่อไม่มี "use strict"" +```warn header="การกำหนดค่าโดยไม่ใช้ `use strict`" -ในทางเทคนิคเราสามารถสร้างตัวแปรโดยไม่จำเป็นต้องใช้คีย์เวิร์คอย่าง `let` ได้ โค้ดส่วนนี้สามาถทำงานได้ ถ้าเราไม่ใส่ `"use strict"` ลงไป +โดยปกติต้องประกาศตัวแปรก่อนใช้งาน แต่ในสมัยก่อนมีช่องโหว่ทางเทคนิคที่ทำให้สร้างตัวแปรได้โดยแค่กำหนดค่าโดยตรง โดยไม่ต้องใช้ `let` วิธีนี้ยังใช้ได้อยู่หากไม่ใส่ `use strict` ในสคริปต์ เพื่อรักษาความเข้ากันได้กับโค้ดเก่า ```js run no-strict -// อย่าลืมว่า: ไม่มี "use strict" ในตัวอย่างนี้ +// หมายเหตุ: ไม่มีการใช้ "use strict" ในตัวอย่างนี้ -num = 5; // ตัวแปรชื่อ "num" ถูกสร้าง +num = 5; // ถ้าไม่มีตัวแปร "num" จะถูกสร้างขึ้นอัตโนมัติ alert(num); // 5 ``` -แต่ด้านบนเป็นแนวทางปฎิบัติที่ไม่ดี เพราะโค้ดจะเกิดข้อผิดพลาดทันที เมื่อไปอยู่ในโหมด "use strict" +วิธีนี้ถือเป็นแนวปฏิบัติที่ไม่ดี และจะเกิดข้อผิดพลาดในโหมดเข้มงวด (strict mode): ```js "use strict"; *!* -num = 5; // error: num is not defined +num = 5; // error: ตัวแปร num ไม่ได้ถูกประกาศไว้ก่อน */!* ``` -```` -## ค่าคงที่ +## ค่าคงที่ (Constants) -ค่าคงที่คือตัวแปรชนิดหนึ่งที่ไม่สามารถเปลี่ยนแปลงข้อมูลภายหลังได้ เราจะใช้คีย์เวิร์ค `const` +เพื่อประกาศตัวแปรค่าคงที่ (ไม่เปลี่ยนแปลงค่า) ให้ใช้ `const` แทน `let`: ```js const myBirthday = '18.04.1982'; ``` -ตัวแปรที่สร้างโดยใช้คีย์เวิร์ค `const` จะเรียกว่า "ค่าคงที่ (constants)" ค่าคงที่ไม่สามารถกำหนดค่าใหม่ได้ หากเรามีกำหนดค่าใหม่ให้ตัวแปรดังกล่าวจะทำให้เกิดข้อผิดพลาดขึ้นได้ +ตัวแปรที่ประกาศด้วย `const` จะเรียกว่า "ค่าคงที่" ซึ่งไม่สามารถกำหนดค่าใหม่ได้ หากพยายามทำเช่นนั้นจะเกิดข้อผิดพลาด: ```js run const myBirthday = '18.04.1982'; -myBirthday = '01.01.2001'; // error, can't reassign the constant! +myBirthday = '01.01.2001'; // error เพราะไม่สามารถกำหนดค่าใหม่ให้ค่าคงที่ได้! ``` -เมื่อโปรแกรมเมอร์แน่ใจแล้วว่าตัวแปรที่สร้างจะไม่มีวันเปลี่ยนค่าแน่นอน พวกเขาจะใช้ `const` แทน และช่วยให้โปรแกรมเมอร์คนอื่นๆเข้าใจโค้ดที่เขียนด้วย +เมื่อโปรแกรมเมอร์มั่นใจว่าตัวแปรจะไม่มีวันเปลี่ยนแปลงค่า ก็สามารถประกาศเป็นค่าคงที่ด้วย `const` เพื่อการันตีและสื่อสารข้อเท็จจริงดังกล่าวให้ทุกคนรับทราบ -### ชื่อของค่าคงที่มักใช้ตัวพิมพ์ใหญ่ +### ค่าคงที่ที่เขียนด้วยตัวพิมพ์ใหญ่ -วิธีปฎิบัติที่ใช้กันทั่วไปคือ ตัวแปรที่เป็นค่าคงที่ จะเป็นค่าที่ยากต่อการจดจำ +ในทางปฏิบัติ มักนิยมใช้ค่าคงที่เป็นนามแทนสำหรับค่าที่จำยาก ซึ่งทราบค่าตายตัวก่อนการประมวลผลโปรแกรมแล้ว -ค่าคงที่เราจะใช้ตัวพิมพ์ใหญ่และตามด้วย underscore แบ่งระหว่างคำ +ค่าคงที่ลักษณะนี้มักตั้งชื่อโดยใช้ตัวพิมพ์ใหญ่และอันเดอร์สกอร์ -ตัวอย่าง สร้างค่าคงที่สำหรับเก็บสีที่ใช้ในเว็บ (ในรูปแบบเลขฐานสิบหก) +ยกตัวอย่างเช่น ลองสร้างค่าคงที่แทนรหัสสีในฟอร์แมต "web" (เขียนเป็นเลขฐานสิบหก): ```js run const COLOR_RED = "#F00"; @@ -275,70 +275,68 @@ const COLOR_GREEN = "#0F0"; const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00"; -// ...เมื่อเราจะเลือกสี +// เมื่อต้องการเลือกสีใดสีหนึ่ง let color = COLOR_ORANGE; alert(color); // #FF7F00 ``` -ประโยชน์: +ข้อดี: -- `COLOR_ORANGE` จำง่ายกว่า `"#FF7F00"` -- มีโอกาสพิมพ์ `"#FF7F00"` ผิด แทนที่จะเป็น `COLOR_ORANGE` -- เวลาคนอื่นอ่านโค้ด `COLOR_ORANGE` จะสื่อความหมายมากกว่า `#FF7F00`. +- `COLOR_ORANGE` จำได้ง่ายกว่า `"#FF7F00"` มาก +- พิมพ์ผิดเป็น `"#FF7F00"` ได้ง่ายกว่า `COLOR_ORANGE` +- เวลาอ่านโค้ด `COLOR_ORANGE` สื่อความหมายได้ชัดเจนกว่า `#FF7F00` -ค่าคงค่าแบบไหนควรจะตั้งชื่อเป็นตัวพิมพ์ใหญ่ และแบบไหนควรจะใช้ชื่อแบบปกติ (camelCase) เราจะมาช่วยให้กระจ่างกัน +แล้วเราควรใช้ตัวพิมพ์ใหญ่กับค่าคงที่เมื่อไหร่ และควรตั้งชื่อปกติเมื่อไหร่? ลองมาทำความเข้าใจกัน -การเป็น "ค่าคงที่" หมายถึงค่าที่เก็บอยู่ในตัวแปร จะต้องไม่มีการเปลี่ยนแปลง มีค่าคงที่ที่เป็นค่าตายตัวไม่เปลี่ยนแปลง (อย่างสีแบบเลขฐานสิบหก) และ มีค่าคงที่ที่จะถูก*คำนวณ*ตอนโปรแกรมทำงาน ซึ่งค่าจะเปลี่ยนแปลงหลังจากโปรแกรมทำงาน ไม่ได้เปลี่ยนค่าเริ่มต้น +คำว่า "ค่าคงที่" หมายถึงค่าของตัวแปรจะไม่มีวันเปลี่ยนแปลงเท่านั้น แต่ค่าคงที่บางตัวเป็นที่รู้จักก่อนการประมวลผล (เช่นค่าฐานสิบหกของสีแดง) ส่วนค่าคงที่อีกประเภทคือถูก*คำนวณ*ระหว่างรันไทม์ ในช่วงการประมวลผล แต่จะไม่เปลี่ยนแปลงหลังจากกำหนดค่าไปแล้ว -ตัวอย่างเช่น +ตัวอย่างเช่น: ```js -const pageLoadTime = /* กำหนดเวลาโหลดหน้าเว็บ */; +const pageLoadTime = /* เวลาที่ใช้ในการโหลดเว็บเพจ */; ``` -เราไม่รู้ว่าค่าของ `pageLoadTime` เป็นเท่าไหร่ จนกว่าเว็บจะถึงเวลาโหลดเว็บจริงๆ ดังนั้นชื่อตัวแปรนี้เลยตั้งแบบปกติ(camelCase) แต่ตัวแปรนี้ยังถือเป็นค่าคงที่ เพราะมันถูกกำหนดค่าใหม่เลย - -อีกนัยหนึ่ง การใช้ตัวพิมพ์ใหญ่เป็นชื่อของค่าคงที่จะใช้ในค่าที่เราเรียกกันว่า "hard-coded" +ค่าของ `pageLoadTime` ไม่เป็นที่ทราบก่อนโหลดเพจ จึงตั้งชื่อแบบปกติ แต่ก็ยังเป็นค่าคงที่อยู่ดี เพราะไม่มีการเปลี่ยนแปลงค่าหลังจากกำหนดไปแล้ว -## ตั้งชื่อให้ถูก +หรือพูดอีกอย่างคือ ค่าคงที่ที่ตั้งชื่อด้วยตัวพิมพ์ใหญ่จะใช้เป็นเพียงแค่นามแทนสำหรับค่าที่ "ฮาร์ดโค้ด" เข้าไปโดยตรงเท่านั้น -พูดถึงตัวแปร มีข้อสำคัญสำหรับการตั้งชื่อตัวแปร +## ตั้งชื่อให้ถูกต้อง -ชื่อตัวแปรควรจะมีความหมายที่ชัดเจน และอธิบายค่าที่ตัวแปรนี้เก็บไว้ +ยังมีอีกเรื่องสำคัญมากเกี่ยวกับตัวแปร นั่นคือ ชื่อตัวแปรควรมีความหมายชัดเจน เข้าใจง่าย และสื่อถึงข้อมูลที่เก็บอยู่ภายใน -การตั้งชื่อตัวแปรเป็นทักษะที่สำคัญที่สุดอย่างหนึ่งในการเขียนโปรแกรม การตั้งชื่อตัวแปรสามารถบอกได้เลยว่า โค้ดนี้ถูกเขียนมาจากผู้เริ่มต้น หรือผู้มีประสบการณ์แล้ว +การตั้งชื่อตัวแปรถือเป็นหนึ่งในทักษะที่สำคัญและซับซ้อนที่สุดในการเขียนโปรแกรม เพียงแค่กวาดตามองผ่านชื่อตัวแปร ก็สามารถบอกได้แล้วว่าโค้ดนั้นเขียนโดยมือใหม่หรือนักพัฒนาที่มีประสบการณ์ -ในโปรเจคจริง เราจะใช้เวลาส่วนใหญ่ไปกับการปรับแต่งและเพิ่มเติมโค้ดที่มีอยู่แล้ว มากกว่าจะไปเขียนอะไรใหม่เริ่มจากศูนย์เลย ฉะนั้นหากเราห่างจากโค้ดเดิมๆไปนานๆ แล้วกลับมาเขียนใหม่ การตั้งชื่อที่ดีมีผลช่วยให้เราหย่อนระยะเวลาในการไล่โค้ดของเราเอง +ในโปรเจ็กต์จริง เราใช้เวลาส่วนใหญ่ไปกับการแก้ไขและต่อยอดโค้ดเดิม มากกว่าเขียนใหม่จากศูนย์ เมื่อต้องย้อนกลับมาดูโค้ดหลังจากห่างไปสักพัก ตัวแปรที่ตั้งชื่อดีจะช่วยให้หาสิ่งที่ต้องการได้ง่ายขึ้นมาก -ใช้เวลาคิดสักครู่หนึ่งเกี่ยวกับการตั้งชื่อตัวแปร การทำเช่นนี้จะตอบแทนเราอย่างงาม +ดังนั้น ใช้เวลาคิดชื่อที่เหมาะสมก่อนประกาศตัวแปรสักนิด รับรองว่าคุ้มค่าในภายหลังแน่นอน -กฎที่ควรปฎิบัติตาม +ต่อไปนี้คือกฎที่ควรนำไปปฏิบัติ: -- ใช้ชื่อที่มนุษย์อ่านแล้วเข้าใจอย่าง `userName` หรือ `shoppingCart` -- หลีกเลี่ยงการใช้ชื่อย่ออย่าง `a`, `b`, `c`, เว้นแต่เรารู้จริงๆว่าตัวแปรนี้ทำอะไร -- ตั้งชื่อให้เป็นคำอธิบายที่สั้นและกระชับมากที่สุด ชื่อที่ไม่ดีอย่าง `data` และ `value` ชื่อแบบนี้ไม่ได้สื่ออะไรเลย ดังนั้นควรทำให้ชื่อตัวแปรเหล่านี้ชัดเจน เหมาะสมกับบริบทนั้นๆ หรือค่าที่ตัวแปรนั้นอ้างอิงอยู่ -- ทำข้อตกลงกับทีม ถ้าผู้เยี่ยมชมเว็บไซต์จะเรียกว่า "user" เราควรตั้งชื่อตัวแปรที่เกี่ยวข้องว่า `currentUser` หรือ `newUser` แทนที่จะเป็น `currentVisitor` +- ใช้ชื่อที่มนุษย์อ่านเข้าใจได้ เช่น `userName` หรือ `shoppingCart` +- หลีกเลี่ยงคำย่อหรือชื่อสั้นๆ อย่าง `a`, `b` หรือ `c` ยกเว้นจะมั่นใจว่ากำลังทำอะไรอยู่ +- ตั้งชื่อให้อธิบายได้ชัดเจนที่สุดและกระชับ ตัวอย่างชื่อที่ไม่ดี ได้แก่ `data` และ `value` เพราะไม่ได้สื่อความหมายอะไร ยกเว้นบริบทของโค้ดจะบ่งชี้ชัดว่าตัวแปรนั้นหมายถึงข้อมูลหรือค่าใด +- ตกลงศัพท์เฉพาะกันภายในทีม ถ้าเรียกผู้เยี่ยมชมเว็บว่า "user" ก็ควรตั้งชื่อตัวแปรที่เกี่ยวข้องว่า `currentUser` หรือ `newUser` แทนที่จะเป็น `currentVisitor` หรือ `newManInTown` -ฟังดูง่ายไหม? อันที่จริงก็ใช่ แต่ในทางปฎิบัติแล้วการตั้งชื่อแปรที่สั้น กระชับ พร้อมทั้งอธิบายค่าที่เก็บในตัวแปรได้อย่างรัดกุม ไม่ใช่เรื่องที่ง่ายเลย +ฟังดูง่ายใช่ไหม? แต่ในทางปฏิบัติ การตั้งชื่อตัวแปรให้สื่อความหมายและกระชับไม่ใช่เรื่องง่ายเลย ลองทำดู -```smart header="ใช้ซ้ำหรือสร้างใหม่ไปเลย" -ก่อนจะจากกัน มีโปรแกรมเมอร์ขี้เกียจบางคน แทนที่จะสร้างตัวแปรใหม่ กลับใช้ตัวแปรเดิมซ้ำๆ +```smart header="ใช้ซ้ำหรือสร้างใหม่" +และอีกหนึ่งข้อสังเกต มีนักเขียนโปรแกรมบางส่วนที่ขี้เกียจ ชอบเอาตัวแปรที่มีอยู่แล้วมาใช้ซ้ำ แทนที่จะประกาศตัวแปรใหม่ -ผลก็คือ ตัวแปรนั้นจะเหมือนกล่องสาธารณะที่แต่ละคนนำไปใช้ และเอาสิ่งใหม่เข้าไปแทนสิ่งเดิม ทีนี้เราก็จะไม่รู้ว่ากล่องนี้เก็บอะไรไว้ +ผลที่ได้ก็คือ ตัวแปรของพวกเขาจะเหมือนกล่องที่มีคนเอาของต่างๆ ใส่ลงไป โดยไม่เปลี่ยนป้ายข้างนอก แล้วตอนนี้ในกล่องมีอะไรบ้าง? ไม่มีใครรู้ ต้องเดินเข้าไปดูใกล้ๆ -การปฎิบัติเช่นนี้ช่วยประหยัดเวลาเพียงเล็กน้อย เพื่อจะได้ไม่ต้องสร้างตัวแปรใหม่ แต่จะเสียเวลาเป็นสิบเท่าจากการหาบั๊ก +โปรแกรมเมอร์พวกนี้อาจจะประหยัดการประกาศตัวแปรไปได้นิดหน่อย แต่กลับเสียเวลาดีบั๊กไปมากกว่าสิบเท่า -การสร้างตัวแปรเพิ่มนั้น ไม่ได้เลวร้ายเลย +การมีตัวแปรเพิ่มถือว่าดี ไม่ใช่เรื่องไม่ดี -การใช้ตัวแปรที่มากขึ้น เพื่อรับรองค่าที่หลากหลายในโมเดิร์นจาวาสคริปต์จะช่วยกำจัดสิ่งที่ไม่จำเป็นออกไป (minifiers) และเบราเซอร์ก็ช่วยปรับปรุง (optimize) โค้ดของเราส่วนหนึ่งให้ดีพอ ดังนั้นมันจะไม่สร้างปัญหาเรื่อง performance +JavaScript minifier และเบราว์เซอร์สมัยใหม่ สามารถปรับแต่งโค้ดได้ดีพอที่จะไม่ก่อให้เกิดปัญหาด้านประสิทธิภาพ และการใช้ตัวแปรต่างกันเพื่อเก็บค่าที่ต่างกัน ยังช่วยให้เอนจินสามารถปรับแต่งโค้ดให้ดีขึ้นได้อีกด้วย ``` ## สรุป -เราสร้างตัวแปรเพื่อใช้เก็บค่าหรือข้อมูล โดยใช้คีย์เวิร์คอย่าง `var`, `let`, หรือ `const` +เราสามารถประกาศตัวแปรเพื่อเก็บข้อมูลโดยใช้คีย์เวิร์ด `var`, `let` หรือ `const` -- `let` -- การสร้างตัวแปรแบบใหม่ -- `var` -- การสร้างตัวแปรแบบเก่า ตอนนี้เราจะไม่ใช้มันแล้ว แต่เราจะพูดถึงความแตกต่างกันระหว่างของเก่ากับของใหม่กันในบท -- `const` -- เหมือน `let` แต่ค่าที่เก็บอยู่ในตัวแปรนี้ไม่สามารถเปลี่ยนแปลงได้ +- `let` -- เป็นรูปแบบการประกาศตัวแปรแบบสมัยใหม่ +- `var` -- เป็นการประกาศตัวแปรแบบเก่า ปกติไม่ค่อยใช้แล้ว แต่จะกล่าวถึงความแตกต่างจาก `let` ในบทเรียน สำหรับผู้ที่จำเป็นต้องใช้ +- `const` -- คล้ายกับ `let` แต่ค่าของตัวแปรจะไม่สามารถเปลี่ยนแปลงได้ -ควรตั้งชื่อตัวแปรในทางที่ช่วยให้เราเข้าใจสิ่งที่เก็บอยู่ภายใน +ตัวแปรควรตั้งชื่อให้เข้าใจได้ทันทีว่าข้างในเก็บข้อมูลอะไรไว้ diff --git a/1-js/02-first-steps/04-variables/variable-change.svg b/1-js/02-first-steps/04-variables/variable-change.svg index 427a6388c..1b2679238 100644 --- a/1-js/02-first-steps/04-variables/variable-change.svg +++ b/1-js/02-first-steps/04-variables/variable-change.svg @@ -1 +1 @@ -"World!""Hello!"message \ No newline at end of file +"World!""Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/04-variables/variable.svg b/1-js/02-first-steps/04-variables/variable.svg index 5d15c9e4e..1c3d8b0cb 100644 --- a/1-js/02-first-steps/04-variables/variable.svg +++ b/1-js/02-first-steps/04-variables/variable.svg @@ -1 +1 @@ -"Hello!"message \ No newline at end of file +"Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index f118844ec..883726473 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,225 +1,234 @@ -# ชนิดของข้อมูล +# ชนิดข้อมูล +ค่าใน JavaScript จะมีชนิดข้อมูลเฉพาะเสมอ เช่น `string` หรือ `number` -ค่าในจาวาสคริปต์จะเป็นมีชนิดของข้อมูลหนึ่งอย่างเสมอ อย่างเช่น สตริงหรืือตัวเลข +ใน JavaScript มีชนิดข้อมูลพื้นฐาน 8 ประเภท ในที่นี้เราจะกล่าวถึงภาพรวมของแต่ละประเภท และในบทต่อๆ ไปจะอธิบายแต่ละประเภทอย่างละเอียด -ชนิดของข้อมูลในจาวาสคริปต์มีทั้งหมด 8 อย่าง ในบทเรียนนี้เราจะพูดถึงที่ใช้กันทั่วไปก่อนจะลงรายละเอียดในบทถัดไป - -เราสามารถเก็บข้อมูลชนิดใดก็ได้ในตัวแปร ตัวอย่างเช่น ตัวแปรเก็บสตริงไว้ และก็เปลี่ยนมาเก็บตัวเลขแทน +ตัวแปรหนึ่งตัวอาจเป็น `string` ในขณะหนึ่ง แล้วเก็บ `number` ในอีกขณะหนึ่งก็ได้: ```js // ไม่มี error let message = "hello"; message = 123456; ``` -ภาษาโปรแกรมมิ่งที่อนุญาตให้เปลี่ยนชนิดเปลี่ยนได้แบบง่ายๆแบบนี้ เรียกว่า "dynamically typed" หมายถึง ตัวแปรไม่ผูกอยู่กับชนิดข้อมูลใดๆ แบบในบางภาษาจะมีระบุว่าให้ตัวแปรเก็บข้อมูลชนิดใด แล้วตัวแปรนี้ต้องเป็นข้อมูลชนิดนั้นไปตลอด -## ตัวเลข (Number) +ภาษาโปรแกรมที่อนุญาตให้ทำเช่นนี้ได้ เช่น JavaScript จะเรียกว่า "dynamically typed" ซึ่งหมายความว่ามีชนิดข้อมูลหลากหลาย แต่ตัวแปรไม่จำเป็นต้องถูกผูกไว้กับชนิดข้อมูลใดชนิดหนึ่งตายตัว + +## Number ```js let n = 123; n = 12.345; ``` -ตัวแปรชนิด *number* คือตัวเลขนั่นเอง สามารถเป็นได้ทั้งจำนวนเต็ม (Integer) และทศนิยม (Floating Point) +ชนิดข้อมูล *number* ใช้แทนได้ทั้งจำนวนเต็มและจำนวนทศนิยม -เราสามารถนำตัวเลข 2 ตัวมาทำการบวก (addition) ด้วยเครื่องหมาย `+` ลบ (subtraction) `-` คูณ (multiplication) `*` หรือหาร (division) `/` และอื่นๆ +มีตัวดำเนินการหลายอย่างสำหรับจำนวน เช่น การคูณ `*`, การหาร `/`, การบวก `+`, การลบ `-` และอื่นๆ -นอกจากตัวเลขปกติแล้ว ในจาวาสคริปต์ ยังมีจำนวนพิเศษอีก ได้แก่ `Infinity`, `-Infinity` และ `NaN` +นอกเหนือจากตัวเลขทั่วไปแล้ว ยังมีค่าที่เรียกว่า "ค่าตัวเลขพิเศษ" ซึ่งก็จัดอยู่ในชนิดข้อมูลนี้ด้วย ได้แก่ `Infinity`, `-Infinity` และ `NaN` -- `Infinity` คือ [อนันต์](https://th.wikipedia.org/wiki/อนันต์)(∞) ในทางคณิตศาสตร์ เป็นค่าพิเศษที่มีค่ามากกว่าตัวเลขใดๆก็คาม +- `Infinity` หมายถึง [อนันต์](https://en.wikipedia.org/wiki/Infinity) ∞ ในเชิงคณิตศาสตร์ เป็นค่าพิเศษที่มากกว่าตัวเลขใดๆ - เราจะได้ค่านี้ก็ต่อเมื่อ เราหารตัวเลขอะไรก็ตามด้วยศูนย์ + เราอาจได้ค่านี้จากการหารด้วยศูนย์: ```js run alert( 1 / 0 ); // Infinity ``` - หรือจะพิมพ์ไปตรงๆเลยก็ได้ + หรืออ้างอิงถึงโดยตรง: ```js run alert( Infinity ); // Infinity ``` -- `NaN` คือ ค่าที่เกิดขึ้นจากข้อผิดพลาดทางการคำนวณ เป็นผลมาจากการดำเนินการทางคณิตศาสตร์ที่ไม่ถูกต้อง ตัวอย่างเช่น + +- `NaN` หมายถึงความผิดพลาดในการคำนวณ เป็นผลลัพธ์จากการดำเนินการทางคณิตศาสตร์ที่ไม่ถูกต้องหรือไม่ได้กำหนดไว้ เช่น: ```js run - alert( "not a number" / 2 ); // NaN, สตริงหารตัวเลขไม่ได้ + alert( "not a number" / 2 ); // NaN เพราะการหารแบบนี้ผิด ``` - `NaN` เป็นค่าที่ติดหนึบ เพราะไม่ว่าเราจะทำอะไรกับค่า `NaN` มันก็จะเป็น `NaN` เสมอ + `NaN` มีคุณสมบัติเหนียว การดำเนินการทางคณิตศาสตร์ใดๆ กับ `NaN` จะให้ผลลัพธ์เป็น `NaN` เสมอ: ```js run - alert( "not a number" / 2 + 5 ); // NaN + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "not a number" / 2 - 1 ); // NaN ``` - ดังนั้น หากเกิด `NaN` ขึ้นมาในโค้ดของเรา มันก็อาจจะลามไปที่อื่นในโค้ดของเราด้วย + ดังนั้นถ้ามี `NaN` อยู่ในนิพจน์ทางคณิตศาสตร์ตรงไหน ค่านั้นจะแผ่ขยายไปทั่วผลลัพธ์ทั้งหมด (มีข้อยกเว้นเพียงอย่างเดียวคือ `NaN ** 0` จะเป็น `1`) -```smart header="การดำเนินการทางคณิตศาสตร์นั้นปลอดภัย" -การกระทำใดๆที่เกี่ยวกับตัวเลขนั้นปลอดภัย ต่อไปเราจะ หารอะไรด้วยศูนย์ เอาข้อมูลชนิดอื่นมาบวกตัวเลข และอื่นๆ +```smart header="การดำเนินการทางคณิตศาสตร์มีความปลอดภัย" +การคำนวณทางคณิตศาสตร์ใน JavaScript นั้น "ปลอดภัย" เราสามารถทำอะไรก็ได้ เช่น หารด้วยศูนย์ หรือดำเนินการกับสตริงที่ไม่ใช่ตัวเลขเหมือนเป็นตัวเลข เป็นต้น -สคริปต์ของเราจะไม่หยุดทำงาน (fatal error) หรือตาย อย่างแย่ที่สุดก็คือ ได้ผลลัพธ์เป็น `NaN` +สคริปต์จะไม่มีวันหยุดทำงานด้วย fatal error ("ตาย") ในกรณีที่แย่ที่สุด ผลลัพธ์ที่ได้จะเป็น `NaN` ``` -จำนวนพิเศษเหล่านั้นจัดอยู่ในข้อมูลชนิด `number` แต่เป็นจำนวนทางโปรแกรมมิ่ง ที่ขัดกับหลักสามัญสำนึกของเรา +ค่าตัวเลขพิเศษเหล่านี้จัดอยู่ในชนิดข้อมูล "number" แต่แน่นอนว่าไม่ใช่ตัวเลขในความหมายทั่วไปของคำนี้ + +เราจะเรียนรู้เพิ่มเติมเกี่ยวกับการใช้งานตัวเลขในบทเรียน + +## BigInt [#bigint-type] -หากต้องการทราบรายละเอียดเกี่ยวกับตัวเลขที่เยอะขึ้น ให้คลิกที่บทนี้ . +ในภาษา JavaScript ชนิดข้อมูล "number" ไม่สามารถแสดงค่าจำนวนเต็มที่มากกว่า (253-1) (ซึ่งก็คือ `9007199254740991`) หรือน้อยกว่า -(253-1) สำหรับจำนวนลบได้อย่างปลอดภัย -## จำนวนเต็มที่เยอะมากๆ (BigInt) +ถ้าจะให้พูดให้ถูกต้องจริงๆ ชนิดข้อมูล "number" จะเก็บจำนวนเต็มที่ใหญ่กว่านั้นได้ (สูงสุดถึง 1.7976931348623157 * 10308) แต่นอกเหนือจากช่วงจำนวนเต็มที่ปลอดภัยคือ ±(253-1) แล้ว จะมีข้อผิดพลาดในการแสดงตัวเลขที่ละเอียด เพราะไม่สามารถเก็บตัวเลขได้ทุกหลักในพื้นที่จัดเก็บแบบคงที่ขนาด 64 บิต ดังนั้นค่าที่เก็บอาจจะเป็นค่า "โดยประมาณ" -ในจาวาสคริปต์ ตัวเลขจะมีจำนวนเป็นบวกต้องไม่เกิน (253-1) และ หากเป็นลบต้องไม่น้อยกว่า -(253-1) เนื่องจากเป็นข้อจำกัดทางเทคนิคของภาษา +ยกตัวอย่างเช่น จำนวนสองตัวนี้ (ที่อยู่เหนือช่วงปลอดภัยเล็กน้อย) จะมีค่าเท่ากัน: -สำหรับการใช้งานทั่วไปแล้ว ช่วงตัวเลขดังกล่าวก็ใช้เพียงพอ แต่มีบางกรณีเราก็ต้องใช้ตัวเลขจำนวนมาก (big numbers) เพื่อการเข้ารหัส (cryptography) หรืือ การเก็บ timestamps ในระดับมิลลิวินาที +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +หรือพูดได้ว่า จำนวนเต็มคี่ทุกตัวที่มากกว่า (253-1) จะไม่สามารถเก็บในชนิดข้อมูล "number" ได้เลย -ข้อมูลชนิด `BigInt` เพิ่งจะถูกเพิ่มเข้ามาในจาวาสคริปต์ไม่นานนี้ เพื่อให้ตัวแปรสามารถเก็บข้อมูล เป็นตัวเลขทีมีความยาวเกิน 16 หลักได้ +ในการใช้งานส่วนใหญ่ ช่วง ±(253-1) ก็เพียงพอแล้ว แต่บางครั้งเราอาจต้องการช่วงของจำนวนเต็มที่ใหญ่จริงๆ เช่น สำหรับการเข้ารหัสลับหรือการระบุเวลาแบบละเอียดถึงระดับไมโครวินาที -เราจะสร้างข้อมูลชนิด `BigInt` ได้ด้วยการเติม `n` ไว้ข้างหลังเลขจำนวนเต็ม +ชนิดข้อมูล `BigInt` ถูกเพิ่มเข้ามาในภาษาเมื่อไม่นานนี้ เพื่อใช้แสดงจำนวนเต็มที่มีความยาวเท่าใดก็ได้ + +สร้างค่า `BigInt` ได้โดยใส่ `n` ต่อท้ายจำนวนเต็ม: ```js -// the "n" at the end means it's a BigInt +// ตัว "n" ท้ายสุดหมายความว่าเป็น BigInt const bigInt = 1234567890123456789012345678901234567890n; ``` -จำนวน `BigInt` เป็น rare care ฉะนั้นเราจึงอยากแยกไว้เป็นอีกบท เผืื่อสักวันเราจะได้ใช้ +เนื่องจากไม่ค่อยได้ใช้ `BigInt` บ่อยนัก เราจึงไม่ลงรายละเอียดในที่นี้ แต่จะแยกอธิบายไว้ในบทเรียน — อ่านได้เมื่อต้องการใช้งานจำนวนขนาดใหญ่มากๆ -```smart header="Compatibility issues" -ตอนนี้, `BigInt` สามารถใช้ได้บน Firefox/Chrome/Edge/Safari, ยกเว้นใน IE. +```smart header="ปัญหาความเข้ากันได้" +ปัจจุบัน `BigInt` รองรับใน Firefox/Chrome/Edge/Safari แต่ยังไม่รองรับใน IE ``` -เราสามารถเปิด [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) เพื่อดูว่าเบราเซอร์เวอร์ชั่นไหนใช้ได้บ้าง +ดูรายละเอียดว่ารองรับในเบราว์เซอร์เวอร์ชันใดบ้างได้จาก [ตารางความเข้ากันได้ของ BigInt บน *MDN*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) -## สตริง +## String -สตริงในจาวาสคริปต์จะต้องล้อมรอบด้วยเครื่องหมาย quotes +ใน JavaScript string ต้องถูกล้อมรอบด้วยเครื่องหมายคำพูด ```js let str = "Hello"; -let str2 = 'Single quotes are ok too'; -let phrase = `can embed another ${str}`; +let str2 = 'ใช้เครื่องหมายคำพูดเดี่ยวก็ได้'; +let phrase = `สามารถฝัง ${str} ได้ด้วย`; ``` -ในจาวาสคริปต์ เรามีสตริงทั้งหมด 3 ประเภท +ใน JavaScript มีเครื่องหมายคำพูด 3 แบบ -1. แบบ Double quotes: `"Hello"`. -2. แบบ Single quotes: `'Hello'`. -3. แบบ Backticks: `Hello`. +1. เครื่องหมายคำพูดคู่: `"สวัสดี"` +2. เครื่องหมายคำพูดเดี่ยว: `'สวัสดี'` +3. แบ็กติก (Backticks): `สวัสดี` -Double และ single quotes เป็นสตริงที่ไม่มีอะไรซับซ้อน และไม่มีความแตกต่างระหว่างกัน +เครื่องหมายคำพูดคู่และเดี่ยวเป็นแบบ "ธรรมดา" ในทางปฏิบัติแล้วไม่มีความแตกต่างระหว่างสองแบบนี้ใน JavaScript -Backticks จะพิเศษออกไป เพราะมีความสามารถ ในการฝังตัวแปรหรือนิพจน์ (expression) ลงในสตริงได้ด้วยการล้อมรอบด้วย `${…}` ดั่งตัวอย่าง +ส่วนแบ็กติกเป็นเครื่องหมายคำพูดที่มี "ฟังก์ชันเพิ่มเติม" ช่วยให้แทรกตัวแปรและนิพจน์ลงใน string ได้ โดยครอบด้วย `${…}` เช่น: ```js run -let name = "John"; +let name = "สมชาย"; -// ฝังตัวแปร -alert( `Hello, *!*${name}*/!*!` ); // Hello, John! +// แทรกตัวแปร +alert( `สวัสดี *!*${name}*/!*!` ); // สวัสดี สมชาย! -// ฝังนิพจน์ (expression) -alert( `the result is *!*${1 + 2}*/!*` ); // จะได้ผลลัพธ์เป็น 3 +// แทรกนิพจน์ +alert( `ผลลัพธ์คือ *!*${1 + 2}*/!*` ); // ผลลัพธ์คือ 3 ``` -ตัว `${…}` จะหาค่าตัวนิพจน์ (expression) ที่อยู่ภายใน และผลลัพธ์จาการดำเนินการ จะกลายเป็นส่วนหนึ่งของสตริง เราจะใส่อะไรลงไปภายใน `${…}` ก็ได้ทั้ง ตัวแปร นิพจน์ (expression) หรืออะไรที่ซับซ้อนกว่านี้ +นิพจน์ภายใน `${…}` จะถูกประเมินค่า และผลลัพธ์จะกลายเป็นส่วนหนึ่งของ string เราสามารถใส่อะไรก็ได้ลงไปในนั้น ไม่ว่าจะเป็นตัวแปรอย่าง `name` หรือนิพจน์ทางคณิตศาสตร์อย่าง `1 + 2` หรือสิ่งที่ซับซ้อนกว่านั้น + +จำไว้ว่า การแทรกแบบนี้ทำได้เฉพาะในแบ็กติกเท่านั้น เครื่องหมายคำพูดรูปแบบอื่นไม่รองรับ! -โปรดอย่าลืมว่าความสามารถนี้มีเฉพาะ Backticks เท่านั้น เครื่องหมาย quotes อื่นๆ ไม่มีความสามารถนี้ ```js run -alert( "the result is ${1 + 2}" ); // ผลลัพธ์ก็คือ ${1 + 2} (double quote ไม่ได้มีผลอะไร) +alert( "ผลลัพธ์คือ ${1 + 2}" ); // ผลลัพธ์คือ ${1 + 2} (เครื่องหมายคำพูดคู่ไม่ได้แทรกอะไร) ``` -รายละเอียดเกี่ยวกับสตริงเชิงลึกสามารถคลิกดูที่บทนี้ได้เลย. +เราจะพูดถึง string อย่างละเอียดอีกครั้งในบทเรียน -```smart header="ไม่มีข้อมูลชนิด *อักขระ (character)*" -ในบางภาษาโปรแกรมม่ิง จะมีข้อมูลชนิดอักขระ (character) คือเก็บตัวอักษรจำนวน 1 ตัว ตัวอย่างเช่น ภาษาซีหรือจาวา +```smart header="ไม่มีชนิดข้อมูล *ตัวอักษร*" +ในบางภาษา จะมีชนิดข้อมูลพิเศษ "ตัวอักษร" ไว้แทนอักขระตัวเดียว เช่นในภาษา C และ Java จะเรียกว่า "char" -ในจาวาสคริปต์ จะไม่มีข้อมูลชนิด อักขระนี้ ทุกอย่างที่ใส่ quote ล้วนเก็บเป็นสตริง ดังนั้นจะไม่มีอักขระสักตัวเลย ใส่หนึ่งตัว หรือ ใส่มากกว่าหนึ่งก็ได้ +แต่ใน JavaScript ไม่มีชนิดข้อมูลเช่นนั้น มีเพียงชนิด `string` เท่านั้น ซึ่ง string อาจเป็นอักขระศูนย์ตัว (ว่างเปล่า), หนึ่งตัว หรือหลายตัวก็ได้ ``` -## บูลีน (Boolean - logical type) +## Boolean (ชนิดข้อมูลตรรกะ) -ข้อมูลชนิดบูลีนมีค่าเพียงสองแบบคือ `true` และ `false` +ชนิดข้อมูล boolean มีค่าได้เพียงสองค่าเท่านั้น คือ `true` และ `false` -โดยปกติค่าบูลีน จะเหมือนกับเก็บค่า ใช่/ไม่ใช่ เอาไว้ `true` หมายถึง ใช่ และ `false` หมายถึง ไม่ใช่ +ชนิดข้อมูลนี้มักใช้สำหรับเก็บค่าแบบ ใช่/ไม่ใช่ โดย `true` หมายถึง "ใช่ ถูกต้อง" ส่วน `false` หมายถึง "ไม่ใช่ ไม่ถูกต้อง" -For instance: +ยกตัวอย่างเช่น: ```js -let nameFieldChecked = true; // ใช่ ช่องชื่อถูกติ๊ก -let ageFieldChecked = false; // ไมใช่ ช่องอายุไม่ได้ถูกติ๊ก +let nameFieldChecked = true; // ใช่ ช่องชื่อถูกเลือก +let ageFieldChecked = false; // ไม่ใช่ ช่องอายุไม่ได้ถูกเลือก ``` -ค่าบูลีนสามารถเกิดจาก ผลลัพธ์ของการเปรียบเทียบกัน เช่นในตัวอย่าง +นอกจากนี้ ค่า boolean ยังเป็นผลลัพธ์ที่ได้จากการเปรียบเทียบด้วย: ```js run let isGreater = 4 > 1; -alert( isGreater ); // true (หมายถึงว่าใช่ 4 มากกว่า 1) +alert( isGreater ); // true (ผลการเปรียบเทียบคือ "ใช่") ``` -รายละเอียดเกี่ยวกับบูลีนเชิงลึกสามารถดูได้ที่บทนี้ . +เราจะกล่าวถึง boolean โดยละเอียดอีกครั้งในบทเรียน -## ค่า "null" +## ค่า "null" -ค่า "null" เป็นค่าชนิดพิเศษ ที่ไม่ได้อยู่ในข้อมูลชนิดใดๆเลย +ค่าพิเศษ `null` ไม่ได้จัดอยู่ในชนิดข้อมูลที่กล่าวมาข้างต้นเลย -ค่า "null" เป็นชนิดข้อมูลที่แยกออกมาตะหาก ซึ่งมีเพียงค่าเดียวนั่นคือ `null` +เป็นชนิดข้อมูลแยกต่างหาก ที่มีเพียงค่า `null` เท่านั้น: ```js let age = null; ``` -ในจาวาสคริปต์ค่า `null` ไม่ได้หมายถึง "การอ้างถึงออบเจ็กต์ที่ไม่มีอยู่" หรือ "ตัวที่ชี้ดันไม่มีค่า (null pointer)" เหมือนภาษาโปรแกรมมิ่งบางภาษา +ใน JavaScript `null` ไม่ใช่ "การอ้างอิงถึงออบเจ็กต์ที่ไม่มีอยู่จริง" หรือ "null pointer" เหมือนในบางภาษา -เป็นเพียงค่าพิเศษที่แสดงถึง ความว่างเปล่า ไม่มีค่า หรือ ไม่ทราบค่า +แต่เป็นค่าพิเศษ ที่ใช้แทนความหมาย "ไม่มีอะไร", "ว่างเปล่า" หรือ "ไม่ทราบค่า" -โค้ดด้านบนสื่อว่า ยังไม่รู้ว่า `age` จะเป็นอะไร +ในตัวอย่างโค้ดข้างต้น หมายความว่า `age` เป็นค่าที่ไม่ทราบ ## ค่า "undefined" -ค่า undefined ก็เป็นชนิดข้อมูลอีกประเภท ไม่เหมือนกับ `null` +ค่าพิเศษ `undefined` นั้นมีลักษณะเฉพาะตัว โดยจัดเป็นชนิดข้อมูลแยกออกมาต่างหาก เช่นเดียวกับ `null` -ความหมายของ `undefined` คือ "ค่ายังไม่ได้ถูกกำหนด" +ความหมายของ `undefined` คือ "ยังไม่ได้กำหนดค่า" -หากเราสร้างตัวแปรขึ้นมา แต่ไม่ได้กำหนดค่าให้ตัวแปร ค่าที่ได้จะเป็น `undefined`: +หากตัวแปรถูกประกาศแต่ยังไม่ได้กำหนดค่า ค่าของตัวแปรนั้นจะเป็น `undefined`: ```js run let age; -alert(age); // แสดง "undefined" +alert(age); // แสดงผลเป็น "undefined" ``` -ในทางเทคนิค ตัวภาษาจะ assign ค่า `undefined` ให้ตัวแปรที่ไม่เก็บค่าใดอัตโนมัติ +ในทางเทคนิค กำหนดค่า `undefined` ให้กับตัวแปรได้โดยตรงเช่นกัน: ```js run let age = 100; -// เปลี่ยนจาก 100 เป็น undefined +// เปลี่ยนค่าเป็น undefined age = undefined; -alert(age); // "undefined" +alert(age); // แสดงผลเป็น "undefined" ``` -...แต่เราไม่แนะนำให้ทำแบบนั้น ปกติแล้ว จะใช้ `null` assign แทนสำหรับตัวแปรที่ เป็นค่าว่าง หรือ ยังไม่รู้ ขณะที่ `undefined` คือย้อนกลับไปค่าเริ่มต้นคือ ไม่ได้ assign ค่าใดๆให้ตัวแปรนี้ -## ออบเจ็กต์ (Objects) และซิมโบล (Symbols) +อย่างไรก็ตาม เราไม่แนะนำให้ทำเช่นนั้น โดยทั่วไปนิยมใช้ `null` เพื่อกำหนดค่า "ว่างเปล่า" หรือ "ไม่ทราบค่า" ให้กับตัวแปร ส่วน `undefined` สงวนไว้เป็นค่าเริ่มต้นสำหรับสิ่งที่ยังไม่ได้กำหนดค่า -ข้อมูลชนิด `object` ก็เป็นชนิดพิเศษ +## Object และ Symbol -ชนิดข้อมูลที่กล่าวมาทั้งหมดเรียกว่า "primitive" เพราะว่าชนิดข้อมูลนี้เก็บค่าเพียงค่าเดียว (จะเก็บสตริงหรือตัวเลขก็ตามแต่) แต่เรายังมี objects ที่ไว้ใช้เก็บข้อมูลเป็น collections มีความซับซ้อนทางโครงสร้างอีก +ชนิดข้อมูล `object` นั้นมีความพิเศษ -เนื่องจาก objects สำคัญมาก เหมาะแก่การแยกไว้เป้นบทพิเศษ เราจะจัดเต็มกับ objects ในบท เมื่ือเราเรียนรู้เกี่ยวกับ primitives มากกว่านี้แล้ว +ชนิดข้อมูลอื่นๆ ทั้งหมดเรียกว่า "primitive" เนื่องจากค่าของชนิดข้อมูลเหล่านั้นสามารถเก็บได้เพียงสิ่งเดียว (ไม่ว่าจะเป็น string, number หรืออย่างอื่น) ในทางตรงกันข้าม object ใช้สำหรับเก็บชุดของข้อมูลและสิ่งที่มีโครงสร้างซับซ้อนกว่า -ข้อมูลชนิด `symbol` ถูกใช้เพื่อสร้างเอกลักษณ์สำหรับ objects เราจะขอแค่เกริ่นๆให้ครบก่อน จะพูดถึงรายละเอียดอีกทีหลังจากเรียนเรื่อง objects แล้ว +ด้วยความสำคัญดังกล่าว object จึงสมควรได้รับการจัดการเป็นพิเศษ เราจะกล่าวถึงรายละเอียดเกี่ยวกับ object ในบท หลังจากที่เราได้เรียนรู้เพิ่มเติมเกี่ยวกับ primitive แล้ว -## typeof operator [#type-typeof] - -ตัวโอเปอเรเตอร์ `typeof` จะส่งคืนชนิดของข้อมูลนั้นๆออกมา โอเปอเรเตอร์ตัวนี้มีประโยชน์อย่างมาก เมื่อเราต้องประมวลผลชนิดข้อมูลที่ต่างกัน หรือ เช็คชนิดของชนิดข้อมูลนั้นๆ - -สามารถเขียนได้ทั้งสองรูปแบบดังนี้ +ชนิดข้อมูล `symbol` ใช้สำหรับสร้างตัวระบุ (identifier) ที่ไม่ซ้ำกันสำหรับ object กล่าวถึงไว้ตรงนี้เพื่อให้ครบถ้วน แต่จะเลื่อนรายละเอียดไปจนกว่าเราจะเข้าใจเรื่อง object -1. เขียนแบบโอเปอเรเตอร์ `typeof x` -2. เขียนแบบฟังชั่นก์ `typeof(x)` +## typeof operator [#type-typeof] -ทั้งสองแบบได้ผลลัพธ์แบบเดียวกัน +`typeof` operator คืนค่าชนิดข้อมูลของ operand มีประโยชน์เมื่อต้องการประมวลผลค่าที่มีชนิดข้อมูลแตกต่างกัน หรือเพียงแค่ต้องการตรวจสอบแบบรวดเร็ว -การเรียก `typeof x` จะส่งคืนค่าสตริงที่เป็นชนิดของข้อมูลนั้นๆออกมา +การเรียกใช้ `typeof x` จะคืนค่าเป็น string ที่ระบุชื่อชนิดข้อมูล: ```js typeof undefined // "undefined" @@ -247,29 +256,41 @@ typeof alert // "function" (3) */!* ``` -คำอธิบายเพิ่มเติมเกี่ยวกับสามบรรทัดสุดท้าย +สามบรรทัดสุดท้ายอาจต้องมีคำอธิบายเพิ่มเติม: + +1. `Math` เป็น built-in object ที่มีฟังก์ชันทางคณิตศาสตร์ให้ใช้งาน เราจะเรียนรู้เพิ่มเติมในบท ที่นี่ใช้เพียงเป็นตัวอย่างของ object เท่านั้น +2. ผลลัพธ์ของ `typeof null` คือ `"object"` ซึ่งเป็นข้อผิดพลาดที่ยอมรับอย่างเป็นทางการใน `typeof` โดยมีมาตั้งแต่ยุคแรกเริ่มของ JavaScript และคงไว้เพื่อให้เข้ากันได้กับโค้ดเก่า อันที่จริง `null` ไม่ใช่ object แต่เป็นค่าพิเศษที่มีชนิดข้อมูลเป็นของตัวเอง การทำงานของ `typeof` ในกรณีนี้จึงไม่ถูกต้อง +3. ผลลัพธ์ของ `typeof alert` คือ `"function"` เพราะ `alert` เป็นฟังก์ชัน เราจะศึกษาฟังก์ชันในบทถัดๆ ไป โดยจะเห็นว่าใน JavaScript ไม่มีชนิดข้อมูล "function" โดยเฉพาะ ฟังก์ชันจัดเป็นส่วนหนึ่งของชนิดข้อมูล object แต่ `typeof` ปฏิบัติต่อฟังก์ชันแตกต่างออกไป โดยคืนค่าเป็น `"function"` ซึ่งเป็นพฤติกรรมที่มีมาตั้งแต่ยุคแรกของ JavaScript ในทางเทคนิคไม่ถูกต้อง แต่ในทางปฏิบัติอาจสะดวกกว่า -1. `Math` เป็น built-in object (ออบเจ็กต์ที่ภาษาให้มา) ที่มีฟีเจอร์เรื่องการคำนวณ เดี๋ยวเราจะได้เรียนในบท ทีนี้จะบอกว่ามันก็เป็น object เหมือนกันนะ -2. ผลลัพธ์จาก `typeof null` คือ `"object"` แต่นี่เป็นข้อผิดพลาดของภาษา มาจากในตอนที่จาวาสคริปต์เกิดแรกๆ และต้องการความ compatibility แต่แน่นอนว่า `null` ไม่ใช่ object และมันควรเป็นข้อมูลชนิดพิเศษ แยกไปเป้นของตัวเองต่างหาก -3. ผลลัพธ์จาก `typeof alert` คือ `"function"` เพราะ `alert` เป็นฟังชั่นก์ ซึ่งเราจะก็เห็นว่าไม่มีข้อมูลชนิดฟังชั่นก์อยู่ในจาวาสคริปต์ อันที่จริงฟังชั่นก์เป็นหนึ่งใน object แต่ `typeof` จะมองเป็นฟังชั่นก์ แล้ว return "function" ออกมาแทน ในทางเทคนิคแล้ว พฤติกรรมแบบนี้ถือว่าเป้นบัคของภาษา แต่บัคตัวนี้กลับทำให้ชีวิตเราง่ายขึ้น +```smart header="ไวยากรณ์ `typeof(x)`" +อาจเจอไวยากรณ์รูปแบบอื่นด้วย เช่น `typeof(x)` ซึ่งมีความหมายเหมือนกับ `typeof x` + +เพื่อให้ชัดเจน: `typeof` เป็น operator ไม่ใช่ฟังก์ชัน วงเล็บที่เห็นไม่ใช่ส่วนหนึ่งของ `typeof` แต่เป็นวงเล็บที่ใช้จัดกลุ่มในเชิงคณิตศาสตร์ + +โดยปกติวงเล็บแบบนี้จะใช้ครอบนิพจน์ เช่น `(2 + 2)` แต่ที่นี่มีเพียงอาร์กิวเมนต์เดียวคือ `(x)` ในแง่ไวยากรณ์ ช่วยให้ไม่ต้องเว้นวรรคระหว่าง operator กับอาร์กิวเมนต์ และบางคนก็ชอบเขียนแบบนี้ + +อย่างไรก็ตาม `typeof x` ยังเป็นรูปแบบที่นิยมกว่า `typeof(x)` +``` ## สรุป -มีข้อมูลอยู่ 8 ชนิดในจาวาสคริปต์ +ใน JavaScript มีชนิดข้อมูลพื้นฐาน 8 ชนิด ได้แก่ -- `number` คือตัวเลขอะไรก็ตาม ไม่ว่าจะเป็น จำนวนเต็ม หรือ ทศนิยม โดยที่จำนวนเต็มจะจำกัดอยู่แค่ ±253 -- `bigint` คือตัวเลขที่เป็นจำนวนเต็ม และจะมีกี่หลักก็ได้ -- `string` คือสตริงหรือข้อความ จะมีอักขระกี่ตัวก็ได้ โดยไม่แยกระหว่างอักขระตัวเดียวกับ ชุดอักขระเหมือนภาษาอื่น -- `boolean` คือค่า `true` และ `false` -- `null` คือค่าว่าง เป็นทั้งค่า และ ชนิดของข้อมูล -- `undefined` คือค่าที่ยังไม่ถูกกำหนด เป็นทั้งค่า และ ชนิดของข้อมูล -- `object` สำหรับ เก็บข้อมูลมากกว่าหนึ่งค่า มีความซับซ้อนในเรื่องโครงสร้างข้อมูล -- `symbol` สำหรับ เป็นตัวระบุเอกลักษณ์ใน object +- ชนิดข้อมูล primitive 7 ชนิด: + - `number` สำหรับตัวเลขทุกประเภท ทั้งจำนวนเต็มและทศนิยม โดยจำนวนเต็มจะมีขอบเขตอยู่ที่ ±(253-1) + - `bigint` สำหรับจำนวนเต็มที่มีความยาวเท่าใดก็ได้ + - `string` สำหรับข้อความ โดยข้อความอาจมีตัวอักษรตั้งแต่ศูนย์ตัวขึ้นไป และไม่มีชนิดข้อมูลแยกต่างหากสำหรับตัวอักษรเพียงหนึ่งตัว + - `boolean` สำหรับค่า `true` หรือ `false` + - `null` สำหรับค่าที่ไม่ทราบ เป็นชนิดข้อมูลแยกต่างหากที่มีค่าเพียงค่าเดียวคือ `null` + - `undefined` สำหรับค่าที่ยังไม่ได้กำหนด เป็นชนิดข้อมูลแยกต่างหากที่มีค่าเพียงค่าเดียวคือ `undefined` + - `symbol` สำหรับการสร้างตัวระบุที่ไม่ซ้ำกัน +- และชนิดข้อมูลที่ไม่ใช่ primitive 1 ชนิด: + - `object` สำหรับโครงสร้างข้อมูลที่มีความซับซ้อนมากขึ้น -ตัว `typeof` สามารถช่วยเราดูข้อมูลเก็บอยู่ในตัวแปร เป็นข้อมูลชนิดใด +`typeof` operator ช่วยตรวจสอบชนิดข้อมูลที่เก็บอยู่ในตัวแปร -- เขียนได้แบบสอง `typeof x` หรือ `typeof(x)` -- ส่งคืนเป็นสตริง บอกชนิดของข้อมูลออกมา อย่าง `"string"`. -- แต่กับ `null` จะส่งค่าเป็น `"object"` -- เป็นข้อผิดพลาดทางภาษา เพราะมันไม่ใช่ออบเจ็กต์ +- โดยทั่วไปใช้ในรูปแบบ `typeof x` แต่ `typeof(x)` ก็สามารถใช้ได้เช่นกัน +- คืนค่าเป็น string ที่ระบุชื่อชนิดข้อมูล เช่น `"string"` +- สำหรับ `null` จะคืนค่าเป็น `"object"` ซึ่งถือเป็นข้อผิดพลาดในภาษา เพราะจริง ๆ แล้ว `null` ไม่ใช่ object -ในบทหน้า เราจะเน้นไปที่ข้อมูลประเภท "primitive" พอคุ้นเคยแล้ว จะไปเน้นที่ออบเจ็กต์กันต่อ +ในบทต่อๆ ไป เราจะมุ่งเน้นไปที่ค่า primitive และเมื่อคุ้นเคยกับมันแล้ว จึงค่อยไปศึกษาเรื่อง object ต่อไป \ No newline at end of file diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md index ef0f333cb..fb2bc1dbc 100644 --- a/1-js/02-first-steps/06-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -1,44 +1,44 @@ -# Interaction: alert, prompt, confirm +# การโต้ตอบ: alert, prompt, confirm -As we'll be using the browser as our demo environment, let's see a couple of functions to interact with the user: `alert`, `prompt` and `confirm`. +ในการสาธิตตัวอย่างต่อไปนี้เราจะใช้เบราว์เซอร์เป็นสภาพแวดล้อมหลัก ทีนี้มาทำความรู้จักกับฟังก์ชันสำหรับโต้ตอบกับผู้ใช้สามตัวกัน ได้แก่ `alert`, `prompt` และ `confirm` ## alert -This one we've seen already. It shows a message and waits for the user to press "OK". +เราเคยเห็น `alert` มาแล้ว ใช้สำหรับแสดงข้อความและรอให้ผู้ใช้กดปุ่ม "OK" -For example: +ตัวอย่างเช่น: ```js run alert("Hello"); ``` -The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc, until they have dealt with the window. In this case -- until they press "OK". +หน้าต่างขนาดเล็กที่แสดงข้อความนี้เรียกว่า *modal window* คำว่า "modal" หมายความว่าผู้ใช้จะไม่สามารถโต้ตอบกับส่วนอื่นๆ ของหน้าเว็บ กดปุ่มอื่นๆ หรือทำอย่างอื่นได้ จนกว่าจะจัดการกับหน้าต่างนี้เสร็จ ในกรณีนี้คือจนกว่าจะกดปุ่ม "OK" ## prompt -The function `prompt` accepts two arguments: +ฟังก์ชัน `prompt` รับอาร์กิวเมนต์สองตัว: ```js no-beautify result = prompt(title, [default]); ``` -It shows a modal window with a text message, an input field for the visitor, and the buttons OK/Cancel. +จะแสดง modal window ที่มีข้อความ ช่องให้ผู้ใช้กรอกข้อมูล และปุ่ม OK/Cancel `title` -: The text to show the visitor. +: ข้อความที่จะแสดงให้ผู้ใช้เห็น `default` -: An optional second parameter, the initial value for the input field. +: อาร์กิวเมนต์ที่สองซึ่งเป็นตัวเลือก ใช้เป็นค่าเริ่มต้นสำหรับช่องกรอกข้อมูล -```smart header="The square brackets in syntax `[...]`" -The square brackets around `default` in the syntax above denote that the parameter is optional, not required. +```smart header="วงเล็บในไวยากรณ์ `[...]`" +วงเล็บรอบๆ `default` ในไวยากรณ์ด้านบนหมายความว่าพารามิเตอร์นี้เป็นตัวเลือก ไม่จำเป็นต้องใส่ก็ได้ ``` -The visitor can type something in the prompt input field and press OK. Then we get that text in the `result`. Or they can cancel the input by pressing Cancel or hitting the `key:Esc` key, then we get `null` as the `result`. +ผู้ใช้พิมพ์ข้อความลงในช่อง prompt แล้วกดปุ่ม OK เราก็จะได้ข้อความนั้นใน `result` แต่ถ้ากด Cancel หรือ `Esc` เพื่อยกเลิก `result` จะมีค่าเป็น `null` -The call to `prompt` returns the text from the input field or `null` if the input was canceled. +`prompt` คืนค่าเป็นข้อความจากช่องกรอกข้อมูล หรือ `null` หากยกเลิก -For instance: +ตัวอย่างเช่น: ```js run let age = prompt('How old are you?', 100); @@ -46,60 +46,60 @@ let age = prompt('How old are you?', 100); alert(`You are ${age} years old!`); // You are 100 years old! ``` -````warn header="In IE: always supply a `default`" -The second parameter is optional, but if we don't supply it, Internet Explorer will insert the text `"undefined"` into the prompt. +```warn header="ใน IE: ควรระบุ `default` เสมอ" +อาร์กิวเมนต์ที่สองเป็นตัวเลือก แต่หากเราไม่ระบุมัน Internet Explorer จะแทรกข้อความ `"undefined"` ลงในช่อง prompt -Run this code in Internet Explorer to see: +ลองรันโค้ดนี้ใน Internet Explorer เพื่อดู: ```js run let test = prompt("Test"); ``` -So, for prompts to look good in IE, we recommend always providing the second argument: +ดังนั้น เพื่อให้ prompt แสดงผลได้ถูกต้องใน IE เราแนะนำให้ระบุอาร์กิวเมนต์ที่สองเสมอ: ```js run -let test = prompt("Test", ''); // <-- for IE +let test = prompt("Test", ''); // <-- สำหรับ IE +``` ``` -```` ## confirm -The syntax: +ไวยากรณ์: ```js result = confirm(question); ``` -The function `confirm` shows a modal window with a `question` and two buttons: OK and Cancel. +ฟังก์ชัน `confirm` จะแสดง modal window ที่มี `question` และปุ่มสองปุ่มคือ OK และ Cancel -The result is `true` if OK is pressed and `false` otherwise. +ผลลัพธ์จะเป็น `true` หากกดปุ่ม OK และเป็น `false` หากกดปุ่มอื่น -For example: +ตัวอย่างเช่น: ```js run let isBoss = confirm("Are you the boss?"); -alert( isBoss ); // true if OK is pressed +alert( isBoss ); // true ถ้ากด OK ``` -## Summary +## สรุป -We covered 3 browser-specific functions to interact with visitors: +เราได้กล่าวถึงฟังก์ชันเฉพาะของเบราว์เซอร์ 3 ตัวที่ใช้ในการโต้ตอบกับผู้ใช้: `alert` -: shows a message. +: แสดงข้อความ `prompt` -: shows a message asking the user to input text. It returns the text or, if Cancel button or `key:Esc` is clicked, `null`. +: แสดงข้อความขอให้ผู้ใช้ป้อนข้อความ คืนค่าเป็นข้อความนั้น หรือคืนค่า `null` หากกดปุ่ม Cancel หรือ `Esc` -`confirm` -: shows a message and waits for the user to press "OK" or "Cancel". It returns `true` for OK and `false` for Cancel/`key:Esc`. +`confirm` +: แสดงข้อความและรอให้ผู้ใช้กดปุ่ม "OK" หรือ "Cancel" คืนค่า `true` สำหรับปุ่ม OK และ `false` สำหรับปุ่ม Cancel หรือ `Esc` -All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed. +เมธอดทั้งสามเป็น modal: จะหยุดการทำงานของสคริปต์และไม่ให้ผู้ใช้โต้ตอบกับส่วนอื่นของหน้าจนกว่าจะปิดหน้าต่างนั้น -There are two limitations shared by all the methods above: +มีข้อจำกัดสองอย่างที่ใช้กับทุกเมธอดข้างต้น: -1. The exact location of the modal window is determined by the browser. Usually, it's in the center. -2. The exact look of the window also depends on the browser. We can't modify it. +1. ตำแหน่งของ modal window ขึ้นอยู่กับเบราว์เซอร์ — โดยปกติจะอยู่ตรงกลางหน้าจอ +2. รูปลักษณ์ของหน้าต่างก็ขึ้นอยู่กับเบราว์เซอร์เช่นกัน ปรับแต่งไม่ได้ -That is the price for simplicity. There are other ways to show nicer windows and richer interaction with the visitor, but if "bells and whistles" do not matter much, these methods work just fine. +นั่นคือข้อแลกเปลี่ยนกับความเรียบง่าย มีวิธีอื่นที่แสดงหน้าต่างสวยงามและโต้ตอบกับผู้ใช้ได้ซับซ้อนกว่านี้ แต่ถ้าไม่ได้ต้องการอะไรพิเศษ เมธอดเหล่านี้ก็ทำงานได้ดีทีเดียว \ No newline at end of file diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index fdc18fa8f..d34943586 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -1,102 +1,102 @@ -# การแปลงชนิดของข้อมูล +# การแปลงชนิดข้อมูล -ในจาวาสคริปต์มีหลายครั้งที่ ฟังชั่นก์และตัวดำเนินการ (operators) จะแปลงข้อมูลจากชนิดหนึ่ง เป็นอีกชนิดโดยอัตโนมัติ +โดยทั่วไปแล้ว ตัวดำเนินการและฟังก์ชันจะแปลงค่าที่รับเข้ามาให้เป็นชนิดข้อมูลที่เหมาะสมโดยอัตโนมัติ -ตัวอย่างเช่น `alert` จะแปลงค่าชนิดใดๆก็ตาม ให้เป็นสตริง และ ตัวดำเนินการทางคณิตศาสตร์ (Mathematical operations) ก็จะแปลงค่าชนิดใดๆก็ตามเป็นตัวเลข +เช่น `alert` แปลงค่าเป็น string อัตโนมัติเพื่อนำไปแสดงผล ส่วนการดำเนินการทางคณิตศาสตร์ก็แปลงค่าเป็นตัวเลข -There are also cases when we need to explicitly convert a value to the expected type. +อย่างไรก็ตาม บางครั้งเราก็จำเป็นต้องแปลงค่าเป็นชนิดข้อมูลที่ต้องการเองอย่างชัดเจน -```smart header="ยังไม่พูดถึง object" -ในบทเรียนนี้ เรายังไม่ได้แตะเรื่อง object ตอนนี้จะพูดถึงแค่ primitives ก่อน +```smart header="ยังไม่กล่าวถึง object" +ในบทนี้เรายังไม่ครอบคลุมถึง object เราจะพูดถึงแค่ค่า primitive เท่านั้น -เราจะเรียนเรื่อง object เราจะได้เข้าใจมากขึ้น +หลังจากที่เราได้เรียนรู้เรื่อง object แล้ว ในบท เราจะได้เห็นว่า object เข้ามาเกี่ยวข้องอย่างไร ``` -## การแปลงเป็นสตริง +## การแปลงเป็น String -การแปลงสตริงสามารถทำได้ง่ายๆ เราสามารถแปลงข้อมูลชนิดใดๆ เป็นสตริงก็ได้ +การแปลงเป็น string จะเกิดขึ้นเมื่อเราต้องการแสดงค่าในรูปแบบ string -ตัวอย่างเช่น `alert(value)` จะแสดงค่าที่เก็บอยู่ในตัวแปร `value` ออกมาเป็นสตริง +เช่น `alert(value)` จะแปลงค่าเป็น string เพื่อนำไปแสดงผล -เรายังสามารถใช้ `String(value)` ซึ่งเป็นฟังชั่นก์ที่จะแปลงข้อมูลชนิดใดๆก็ตามให้เป็นสตริง +นอกจากนี้ยังเรียกฟังก์ชัน `String(value)` โดยตรงเพื่อแปลงค่าเป็น string ได้เช่นกัน: ```js run let value = true; -alert(typeof value); // บูลีน +alert(typeof value); // boolean *!* -value = String(value); // ทีนี้เป็นสตริง "true" แล้ว -alert(typeof value); // จะได้ว่าสตริง +value = String(value); // ตอนนี้ value กลายเป็น string "true" +alert(typeof value); // string */!* ``` -เมื่อข้อมูลชนิดใดๆ ถูกแปลงเป็นสตริงแล้ว เราสังเกตเห็นได้อย่างชัดเจน ว่าค่าจะรายล้อมด้วยเครื่องหมาย `quotes` เช่น `false` เป็น `"false"`, `null` เป็น `"null"` เป็นต้น +การแปลงเป็น string โดยทั่วไปมักจะตรงไปตรงมาอยู่แล้ว เช่น `false` กลายเป็น `"false"`, `null` กลายเป็น `"null"` เป็นต้น ## การแปลงเป็นตัวเลข -ฟังชั่นก์หรือนิพจน์ (expressions) ทางคณิตศาสตร์ จะแปลงข้อมูลชนิดใดๆ เป็นตัวเลขโดยอัตโนมัติ +ในฟังก์ชันและนิพจน์ทางคณิตศาสตร์ การแปลงเป็นตัวเลขจะเกิดขึ้นโดยอัตโนมัติ -ตัวอย่างเช่น, การหาร `/` เราสามารถหารข้อมูลที่ไม่ใช่ตัวเลขได้ด้วย +ยกตัวอย่างเช่น เมื่อมีการหาร `/` กับสิ่งที่ไม่ใช่ตัวเลข: ```js run -alert( "6" / "2" ); // 3, สตริงจะถูกแปลงเป็นตัวเลข +alert( "6" / "2" ); // 3, string ถูกแปลงเป็นตัวเลขก่อน ``` -เรายังสามาถใช้ `Number(value)` ซึ่งเป็นฟังชั่นก์ที่จะแปลงข้อมูลชนิดใดๆก็ตามให้เป็นตัวเลข +เราสามารถใช้ฟังก์ชัน `Number(value)` เพื่อแปลง `value` เป็นตัวเลขได้: ```js run let str = "123"; -alert(typeof str); // ได้สตริง +alert(typeof str); // string let num = Number(str); // กลายเป็นตัวเลข 123 -alert(typeof num); // ได้ตัวเลข +alert(typeof num); // number ``` -การแปลงชนิดของข้อมูลเป็นตัวเลข จะใช้บ่อยครั้งกับสตริงที่ภายในเป็นตัวเลข โดยเราต้องการนำตัวเลขเหล่านี้ไปประมวลผลต่อ +โดยทั่วไปแล้ว การแปลงเป็นตัวเลขแบบชัดเจนจะจำเป็นก็ต่อเมื่อเราอ่านค่าจากแหล่งที่เก็บเป็น string เช่น ฟอร์มข้อความ แต่คาดหวังว่าจะได้ตัวเลขกลับมา -หากสตริงใดๆ ไม่สามารถแปลงเป็นตัวเลขได้ เราจะได้ค่า `NaN` มาแทน ตัวอย่างเช่น +ถ้า string ไม่ใช่ตัวเลขที่ถูกต้อง ผลลัพธ์จากการแปลงจะได้เป็น `NaN` ตัวอย่างเช่น: ```js run let age = Number("an arbitrary string instead of a number"); -alert(age); // NaN เป็นผลลัพธ์เมื่อแปลงเป็นตัวเลขไม่ได้ +alert(age); // NaN, การแปลงล้มเหลว ``` กฎการแปลงเป็นตัวเลข: -| ค่า | ได้เป็น... | +| ค่า | จะกลายเป็น... | |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| -|true and false | `1` และ `0` | -| `string` | Whitespaces ทั้งหน้าและหลังโดนเอาออก หากเป็นสตริงว่างค่าที่ได้จะเป็น `0` หากเป็นตัวเลข ก็จะได้ตัวเลข หากสตริงมีอักขระอื่นๆ นอกเหนือจากตัวเลข ค่าที่ได้จะเป็น `NaN` เกิดจากข้อผิดพลาดที่ไม่สามารถแปลงเป็นตัวเลขได้ | +|true และ false | `1` และ `0` | +| `string` | ช่องว่างที่ขึ้นต้นและลงท้าย (รวมถึงเว้นวรรค, tab `\t`, ขึ้นบรรทัดใหม่ `\n` เป็นต้น) จะถูกตัดทิ้ง ถ้า string ที่เหลือว่างเปล่า ผลลัพธ์จะเป็น `0` มิฉะนั้นจะ "อ่าน" ตัวเลขจาก string และถ้าอ่านไม่ได้จะได้เป็น `NaN` | -ตัวอย่างเช่น: +ตัวอย่าง: ```js run alert( Number(" 123 ") ); // 123 -alert( Number("123z") ); // NaN (เกิดข้อผิดพลาดจากตัว "z") +alert( Number("123z") ); // NaN (เกิด error ตอนอ่านตัวเลขที่ "z") alert( Number(true) ); // 1 alert( Number(false) ); // 0 ``` -จำไว้ว่า หากเราแปลงค่า `null` และ `undefined` เป็นตัวเลข ค่า `null` จะกลายเป็น `0` ส่วน `undefined` จะเป็น `NaN` +สังเกตว่า `null` และ `undefined` มีพฤติกรรมต่างกัน: `null` จะกลายเป็นศูนย์ ส่วน `undefined` จะกลายเป็น `NaN` -ตัวดำเนินการทางคณิตศาสตร์ (mathematical operators) ส่วนใหญ่จะแปลงค่าที่ผ่านเข้ามา เป็นตัวเลขด้วย ตัวอย่างจะมีในบทถัดไป +ตัวดำเนินการทางคณิตศาสตร์ส่วนใหญ่ก็แปลงค่าในลักษณะนี้เช่นกัน ซึ่งจะได้เห็นในบทถัดไป -## การแปลงเป็นบูลีน +## การแปลงเป็น Boolean -การแปลงเป็นบูลีนเป็นวิธีที่ง่ายที่สุดในจาวาสคริปต์ +การแปลงเป็น boolean เป็นการแปลงที่ง่ายที่สุด -การแปลงเป็นบูลีน จะเกิดขึ้นจาก ตัวดำเนินการทางตรรกะ (logical operations) แต่ก็สามารถแปลงได้ตรงๆผ่านฟังชั่นก์ `Boolean(value)` +การแปลงนี้จะเกิดขึ้นในการดำเนินการเชิงตรรกะ (ต่อไปเราจะได้เจอกับการทดสอบเงื่อนไขและสิ่งที่คล้ายคลึงกัน) แต่ก็ทำได้อย่างชัดเจนด้วยการเรียก `Boolean(value)` -กฎของการแปลงบูลีน: +กฎการแปลง: -- ค่าที่ถูกจัดว่าเป็นค่าว่างในทางโปรแกรมมิ่ง จะกลายเป็น `false` เช่น `0`, สตริงว่าง, `null`, `undefined`, และ `NaN` -- ที่เหลือจะกลายเป็น `true` +- ค่าที่ "ว่างเปล่า" ตามความเข้าใจทั่วไป เช่น `0`, string เปล่า, `null`, `undefined` และ `NaN` จะกลายเป็น `false` +- ค่าอื่นๆ จะกลายเป็น `true` -ตัวอย่างเช่น +ตัวอย่างเช่น: ```js run alert( Boolean(1) ); // true @@ -106,45 +106,45 @@ alert( Boolean("hello") ); // true alert( Boolean("") ); // false ``` -````warn header="Please note: the string with zero `\"0\"` is `true`" -Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript, a non-empty string is always `true`. +```warn header="สังเกตด้วย: string ที่มีเลขศูนย์ `\"0\"` จะเป็น `true`" +ในบางภาษา (โดยเฉพาะ PHP) `"0"` จะถูกมองว่าเป็น `false` แต่ใน JavaScript string ที่ไม่ว่างเปล่าจะเป็น `true` เสมอ ```js run -alert( Boolean("0") ); // true -alert( Boolean(" ") ); // มี spaces จะแปลงได้ true ยกเว้นสตริงว่างเท่านั้น +alert( Boolean("0") ); // true +alert( Boolean(" ") ); // เว้นวรรค ก็เป็น true (string ที่ไม่ว่างเปล่าทั้งหมดจะเป็น true) +``` ``` -```` ## สรุป -การแปลงชนิดของข้อมูลที่มักจะได้ใช้บ่อยๆ คือ แปลงเป็นสตริง แปลงเป็นตัวเลข และ แปลงเป็นบูลีน +การแปลงชนิดข้อมูลที่พบบ่อยที่สุดมี 3 ประเภท ได้แก่ การแปลงเป็น string, การแปลงเป็นตัวเลข และการแปลงเป็น boolean -**`การแปลงเป็นสตริง`** -- เกิดจากการส่งข้อมูลออกมา หรือจะแปลงตรงๆโดยใช้ฟังชั่นก์ `String(value)` ก็ได้ +**`การแปลงเป็น String`** -- เกิดขึ้นเมื่อเราแสดงผลอะไรบางอย่าง สามารถทำได้ด้วย `String(value)` โดยทั่วไปการแปลงเป็น string มักจะชัดเจนอยู่แล้วสำหรับค่า primitive -**`Numeric Conversion`** -- เกิดจากตัวดำเนินการทางคณิตศาสตร์ หรือจะแปลงตรงๆโดยใช้ฟังชั่นก์ `Number(value)` ก็ได้ +**`การแปลงเป็นตัวเลข`** -- เกิดขึ้นในการดำเนินการทางคณิตศาสตร์ สามารถทำได้ด้วย `Number(value)` -กฎของการแปลงเป็นตัวเลขจะเป็นไปตามนี้ +การแปลงจะเป็นไปตามกฎ: -| ค่า | แปลงเป็น | +| ค่า | จะกลายเป็น... | |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | -| `string` | Whitespaces ทั้งหน้าและหลังโดนเอาออก หากเป็นสตริงว่างค่าที่ได้จะเป็น `0` หากเป็นตัวเลข ก็จะได้ตัวเลข หากสตริงมีอักขระอื่นๆ นอกเหนือจากตัวเลข ค่าที่ได้จะเป็น `NaN` เกิดจากข้อผิดพลาดที่ไม่สามารถแปลงเป็นตัวเลขได้ | +| `string` | string จะถูกอ่าน "ตามที่เป็น" ส่วนช่องว่าง (รวมถึงเว้นวรรค tab `\t` ขึ้นบรรทัดใหม่ `\n` เป็นต้น) ที่ทั้งสองฝั่งจะถูกละเว้น string เปล่าจะกลายเป็น `0` เมื่อเกิด error จะได้เป็น `NaN` | -**`การแปลงเป็นบูลีน`** -- เกิดจากตัวดำเนินการทางตรรกะ หรือจะแปลงตรงๆโดยใช้ฟังชั่นก์ `Boolean(value)` ก็ได้ +**`การแปลงเป็น Boolean`** -- เกิดขึ้นในการดำเนินการเชิงตรรกะ สามารถทำได้ด้วย `Boolean(value)` -กฎของการแปลงเป็นบูลีนจะเป็นไปตามนี้ +ทำตามกฎ: -| Value | Becomes... | +| ค่า | จะกลายเป็น... | |-------|-------------| |`0`, `null`, `undefined`, `NaN`, `""` |`false`| -|any other value| `true` | +|ค่าอื่นๆ| `true` | -กฎการแปลงจะตรงไปตรงมา ทำให้ง่ายต่อการจดจำ แต่ก็ยังมีข้อยกเว้น ที่มักจะทำให้มือใหม่ผิดพลาดอยู่บ่อยๆอย่าง +กฎส่วนใหญ่เหล่านี้เข้าใจและจำได้ง่าย มีแค่ข้อยกเว้นบางอย่างที่มักทำให้สับสน ได้แก่: -- `undefined` เมื่อแปลงเป็นตัวเลขจะเป็น `NaN` ไม่ใช่ `0` -- `"0"` และ สตริงที่มี spaces `" "` เมื่อแปลงเป็นบูลีนจะเป็น `true` +- `undefined` จะเป็น `NaN` ในรูปแบบตัวเลข ไม่ใช่ `0` +- `"0"` และ string ที่มีแต่เว้นวรรคอย่าง `" "` จะเป็น true ในรูปแบบ boolean -ออบเจ็กต์จะยังไม่มีบทในตอนนี้ โดยจะยกไปพูดในบท ซึ่งจะพูดถึงออบเจ็กต์ล้วนๆ หลังจากเราเรียนพื้นฐานกันหมดแล้ว +เรื่อง object ยังไม่ได้กล่าวถึงในที่นี้ เราจะกลับมาพูดถึงอีกครั้งในบท ซึ่งอุทิศให้กับ object โดยเฉพาะ หลังจากที่เรียนรู้พื้นฐาน JavaScript เพิ่มเติมแล้ว \ No newline at end of file diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index a690e9144..954044192 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -9,7 +9,6 @@ true + false = 1 "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN -7 / 0 = Infinity " -9 " + 5 = " -9 5" // (3) " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index 3a2a45c8a..e963dfbcc 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 509185f1a..d3b88fac1 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -1,130 +1,143 @@ -# ตัวดำเนินการ เบื้องต้น และ คณิตศาสตร์ +# ตัวดำเนินการพื้นฐานและคณิตศาสตร์ -ตัวดำเนินการในโลกโปรแกรมมิ่ง เราเคยได้เรียนกันมาแล้วสมัยโรงเรียน นั่นก็คือ การบวก `+` การลบ `-` การคูณ `*` การหาร `/` และ ตัวอื่นๆ +หลายคนคุ้นเคยกับตัวดำเนินการทางคณิตศาสตร์มาตั้งแต่สมัยเรียนแล้ว ไม่ว่าจะเป็นการบวก `+` การคูณ `*` การลบ `-` และอื่นๆ อีกมากมาย -ในบทนี้ เราจะเริ่มด้วยตัวดำเนินการ ง่ายๆ จากนั้นจะเจาะไปที่ตัวดำเนินการ ในจาวาสคริปต์ ซึ่งจะไม่เหมือนกับตัว operator ที่เราเคยเรียนที่โรงเรียน +ในบทนี้ เราจะเริ่มต้นด้วยตัวดำเนินการพื้นฐาน จากนั้นจะเจาะลึกไปที่ลักษณะเฉพาะของ JavaScript ซึ่งไม่ได้เรียนกันในวิชาคณิตศาสตร์ทั่วไป -## คำว่า: "เดี่ยว (unary)", "คู่ (binary)", "ตัวถูกดำเนินการ (operand)" +## ศัพท์น่ารู้: "unary", "binary", "operand" -ก่อนที่เราจะเข้าเรื่อง ขออธิบายศัพท์เหล่านี้เพิ่มเติมอีกนิด +ก่อนจะเริ่มเรื่องต่อไป เรามาทำความเข้าใจศัพท์ที่ใช้กันทั่วไปเสียก่อน -- *ตัวถูกดำเนินการ* (operand) -- หมายถึง ตัวเลขหรือตัวแปรในสมการใด ๆ ที่ถูกดำเนินการ ด้วยตัวดำเนินการ (operator) หรือ "เครื่องหมายทางคณิตศาสตร์" ตัวอย่างเช่น `5 * 2` มีตัวถูกดำเนินการ 2 ตัว ด้านซ้ายคือ `5` และด้านขวาคือ `2` บางคนก็เรียกว่าอาร์กิวเม้นท์ (arguments) แทนตัวถูกดำเนินการ (operands) -- ตัวดำเนินการ (operator) เดี่ยว *unary* หมายถึง การมีตัวถูกดำเนินการ (operand) เพียงแค่ตัวเดียว อย่างเช่น การเติมเครื่องหมายลบไว้ที่ข้างหน้าตัวแปร (negation) เพื่อกลับค่าบวก ให้เป็นค่าลบ +- _ตัวถูกดำเนินการ (operand)_ คือสิ่งที่ถูกนำไปคำนวณด้วยตัวดำเนินการนั่นเอง ยกตัวอย่างเช่น ในการคูณ `5 * 2` จะมีตัวถูกดำเนินการสองตัว ได้แก่ `5` ที่อยู่ทางซ้าย และ `2` ที่อยู่ทางขวา บางครั้งอาจเรียกตัวถูกดำเนินการว่า "อาร์กิวเมนต์ (argument)" แทนก็ได้ - ```js run - let x = 1; +- ตัวดำเนินการจะเป็นแบบ _unary_ หากมีตัวถูกดำเนินการเพียงตัวเดียว อย่างเช่น การกลับเครื่องหมาย `-` ที่จะกลับเครื่องหมายของตัวเลข: - *!* - x = -x; - */!* - alert( x ); // -1 - ``` -- ตัวดำเนินการ (operator) คู่ *binary* หมายถึง การมีตัวถูกดำเนินการ (operand) สองตัวนั่นเอง อย่างเช่น การลบกันของแปรสองตัว +```js run +let x = 1; - ```js run no-beautify - let x = 1, y = 3; - alert( y - x ); // ได้ 2 เกิดจากการลบกันของตัวแปรสองตัว - ``` +*!* +x = -x; +*/!* - ตามสองตัวอย่างด้านบน เราได้เห็นการดำเนินการสองแบบ ที่ใช้เครื่องหมายลบร่วมกัน การลบแบบเดี่ยว จะกลับค่าจากบวกเป็นลบ จากลบเป็นบวก การลบแบบคู่ จะลบค่าตัวหน้าด้วยค่าตัวหลัง +alert( x ); // -1, มีการใช้ unary negation +``` -## คณิต +- ตัวดำเนินการจะเป็นแบบ _binary_ หากมีตัวถูกดำเนินการสองตัว เครื่องหมายลบก็มีในรูปแบบ binary ด้วยเช่นกัน: -สัญลักษณ์ทางคณิตศาสตร์ที่สามารถใช้ได้ในจาวาสคริปต์ได้แก่: +```js run no-beautify +let x = 1, y = 3; +alert( y - x ); // 2, binary minus ใช้ลบค่าทั้งสองออกจากกัน +``` -- บวก `+`, -- ลบ `-`, -- คูณ `*`, -- หาร `/`, -- เศษเหลือ `%`, -- ยกกำลัง `**`. +พูดอย่างเป็นทางการ ในตัวอย่างด้านบนมีตัวดำเนินการสองตัวที่ใช้สัญลักษณ์เดียวกัน ได้แก่ ตัวดำเนินการ negation (unary) ที่กลับเครื่องหมาย และตัวดำเนินการลบ (binary) ที่ลบจำนวนหนึ่งออกจากอีกจำนวนหนึ่ง -มีสี่ตัวแรกที่ตรงไปตรงมา ขณะที่เศษเหลือใช้ `%` และยกกำลังใช้ `**` +## คณิตศาสตร์ -### เศษเหลือ % +JavaScript รองรับการดำเนินการทางคณิตศาสตร์ต่อไปนี้ -ตัวดำเนินการเศษเหลือจะใช้ `%` ถึงหน้าตาจะเหมือนเปอร์เซ็นต์ แต่ในจาวาสคริปต์จะไม่ใช่เปอร์เซ็นต์ +- บวก `+` +- ลบ `-` +- คูณ `*` +- หาร `/` +- หารเอาเศษ `%` +- ยกกำลัง `**` -ผลลัพธ์ของ `a % b` คือ[เศษเหลือ](https://en.wikipedia.org/wiki/Remainder) ของจำนวนเต็ม `a` หารด้วย `b` +สี่ตัวดำเนินการแรกนั้นไม่ต้องอธิบายอะไรมาก ส่วน `%` และ `**` ขออธิบายเพิ่มเติมสักนิด -ตัวอย่างเช่น +### การหารเอาเศษ % + +แม้จะดูคล้ายเปอร์เซ็นต์ แต่ตัวดำเนินการ `%` ไม่ได้เกี่ยวข้องกับเปอร์เซ็นต์เลย + +ผลลัพธ์ของ `a % b` คือ [เศษ](https://en.wikipedia.org/wiki/Remainder) ที่ได้จากการหาร `a` ด้วย `b` แบบจำนวนเต็ม + +ยกตัวอย่างเช่น ```js run -alert( 5 % 2 ); // 1, เศษเหลือของ 5 หารด้วย 2 -alert( 8 % 3 ); // 2, เศษเหลือของ 8 หารด้วย 3 +alert( 5 % 2 ); // 1, เศษของ 5 หาร 2 +alert( 8 % 3 ); // 2, เศษของ 8 หาร 3 +alert( 8 % 4 ); // 0, เศษของ 8 หาร 4 ``` -### ยกกำลัง ** +### การยกกำลัง ** -ตัวดำเนินการยกกำลัง จะใช้ดอกจันสองตัว และ `a ** b` หมายถึง `a` คูณตัวเองเป็นจำนวน `b` ครั้ง +ตัวดำเนินการยกกำลัง `a ** b` จะยกค่า `a` ด้วยเลขชี้กำลัง `b` -ตัวอย่างเช่น +ในวิชาคณิตศาสตร์ เราจะเขียนสิ่งนี้เป็น ab + +ยกตัวอย่างเช่น ```js run -alert( 2 ** 2 ); // 4 (2 คูณกัน 2 ครั้ง) -alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 ครั้ง) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 ครั้ง) +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 ``` -ในทางคณิตศาสตร์ เราสามารถยกกำลังตัวเลขที่ไม่ใช่จำนวนเต็ม อย่าง การยกกำลังด้วย square root `1/2` -Mathematically, : + +เหมือนในคณิตศาสตร์ ตัวดำเนินการยกกำลังใช้กับจำนวนที่ไม่ใช่จำนวนเต็มได้ด้วย + +อย่างเช่น การหารากกำลังสองก็คือการยกกำลังด้วย ½ ```js run -alert( 4 ** (1/2) ); // 2 (ยกกำลังของ 1/2 เหมือน square root) -alert( 8 ** (1/3) ); // 2 (ยกกำลังขอฝ 1/3 เหมือน cubic root) +alert( 4 ** (1/2) ); // 2 (ยกกำลัง 1/2 เท่ากับหารากกำลังสอง) +alert( 8 ** (1/3) ); // 2 (ยกกำลัง 1/3 เท่ากับหารากกำลังสาม) ``` +## การต่อสตริงด้วย binary + -## รวมสตริงด้วย + - -เป็นฟีเจอร์ของจาวาสคริปต์ ที่ไม่มีในคณิตศาสตร์โรงเรียน +ทีนี้มาทำความรู้จักกับฟีเจอร์ของตัวดำเนินการใน JavaScript ที่นอกเหนือจากคณิตศาสตร์ในห้องเรียนกัน -โดยปกติแล้ว เราจะเห็นแต่การใช้เครื่องหมายบวก `+` กับตัวเลข +ปกติแล้ว ตัวดำเนินการบวก `+` ใช้สำหรับบวกตัวเลข -แต่ หากเราใช้เครื่องหมายบวกกับสตริง มันจะเป็นการต่อสตริงสองชุดไว้ด้วยกัน +แต่ถ้าใช้ `+` แบบ binary กับสตริง จะเชื่อม (ต่อ) สตริงเข้าด้วยกัน ```js let s = "my" + "string"; alert(s); // mystring ``` -โปรดจำไว้ว่าหากตัวถูกดำเนินการเป็นสตริง อีกตัวก็จะถูกแปลงเป็นสตริงด้วย +ข้อสังเกตคือ ถ้าตัวถูกดำเนินการตัวใดตัวหนึ่งเป็นสตริง ตัวอื่นก็จะแปลงเป็นสตริงด้วยเช่นกัน -ตัวอย่างเช่น: +ยกตัวอย่างเช่น ```js run alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` -เห็นไหม ไม่สำคัญว่าสตริงจะอยู่ด้านซ้าย หรืือด้านขวา หากมีสตริง สิ่งใดไม่ใช่สตริงจะถูกแปลงทั้งหมด +จะเห็นว่าไม่สำคัญว่าสตริงจะอยู่ตัวถูกดำเนินการตัวแรกหรือตัวที่สองก็ตาม -ตัวอย่างที่ยากขึ้นมาอีกหน่อย: +ลองดูตัวอย่างที่ซับซ้อนขึ้นอีกนิด ```js run -alert(2 + 2 + '1' ); // ได้ "41" ไม่ใช่ "221" +alert(2 + 2 + '1' ); // "41" ไม่ใช่ "221" ``` -ทีนี้ ตัวดำเนินการตัวแรกจะทำงานก่อน รวมผลลัพธ์ระหว่าง `2 + 2` จะได้ `4` ถัดไป `+` จะบวกสตริง `1` ก็จะกลายเป็น `4 + '1' = 41` +ในที่นี้ ตัวดำเนินการจะทำงานทีละตัว `+` ตัวแรกจะบวกเลขสองจำนวน ได้ผลเป็น `4`, จากนั้น `+` ตัวถัดไปก็นำ `4` ไปต่อกับสตริง `'1'` เหมือนกับการเขียน `4 + '1' = '41'` -ตัวดำเนินการ `+` แบบคู่เป็นตัวดำเนินการเพียงตัวเดียวที่สามารถรวมสตริงได้ ตัวดำเนินการอื่นๆจะใช้งานได้กับตัวเลข และจะแปลงข้อมูลชนิดอื่นให้เป็นตัวเลขด้วย +```js run +alert('1' + 2 + 2); // "122" ไม่ใช่ "14" +``` -ตัวอย่างของการลบ และการหาร +ในตัวอย่างนี้ ตัวถูกดำเนินการตัวแรกเป็นสตริง ตัวแปลภาษาจึงมองว่าตัวถูกดำเนินการอีกสองตัวเป็นสตริงด้วย `2` จะถูกเชื่อมต่อท้าย `'1'` เหมือนการเขียน `'1' + 2 = "12"` และ `"12" + 2 = "122"` + +`+` แบบ binary เป็นตัวดำเนินการตัวเดียวที่รองรับการทำงานกับสตริงในลักษณะนี้ ส่วนตัวดำเนินการคณิตศาสตร์อื่นๆ จะทำงานกับตัวเลขเท่านั้น และจะแปลงค่าตัวถูกดำเนินการเป็นตัวเลขเสมอ + +ต่อไปนี้คือตัวอย่างของการลบและการหาร ```js run -alert( 6 - '2' ); // 4, จะแปลงสตริง '2' เป็นตัวเลข -alert( '6' / '2' ); // 3, จะแปลงสตริงทั้งสองตัวเป็นตัวเลข +alert( 6 - '2' ); // 4, แปลง '2' เป็นตัวเลข +alert( '6' / '2' ); // 3, แปลงตัวถูกดำเนินการทั้งสองตัวเป็นตัวเลข ``` -## แปลงข้อมูลเป็นตัวเลขด้วยเครื่องหมายบวก +## การแปลงเป็นตัวเลข, unary + -เครื่องหมาย `+` เราสามารถใช้ได้สองแบบ แบบคู่ (binary) ตามตัวอย่างด้านบน และ แบบเดี่ยว (unary) +เครื่องหมาย `+` มีสองรูปแบบคือ แบบ binary ที่ใช้ข้างบน และแบบ unary -บวกแบบเดี่ยว (unary) หากเราเพิ่มเครื่องหมายบวก ไว้ข้างหน้าตัวแปร จะเป็นการแปลงข้อมูลที่ไม่ใช่ตัวเลข ให้เป็นตัวเลข +unary plus หรือพูดอีกอย่างคือ ตัวดำเนินการบวก `+` ที่ใช้กับค่าเดี่ยว จะไม่ส่งผลอะไรกับตัวเลข แต่ถ้าตัวถูกดำเนินการไม่ใช่ตัวเลข unary plus จะแปลงให้เป็นตัวเลข ตัวอย่างเช่น ```js run -// แปลงตัวเลข ก็จะได้ตัวเลขเหมือนเดิม +// ไม่มีผลกับตัวเลข let x = 1; alert( +x ); // 1 @@ -132,77 +145,77 @@ let y = -2; alert( +y ); // -2 *!* -// แปลงจากข้อมูลที่ไม่ใช่ตัวเลข +// แปลงสิ่งที่ไม่ใช่ตัวเลข alert( +true ); // 1 alert( +"" ); // 0 */!* ``` -จริงๆแล้ว ก็เหมือนกับใช้ฟังชั่นก์ `Number(...)` แต่สั้นกว่า +โดยทำงานเหมือนกับ `Number(...)` เลย แต่เขียนได้สั้นกว่า -ในงานจริวเรามักจะเปลี่ยนสตริงเป็นตัวเลขอยู่บ่อยๆเหมือนกัน เช่น เรากำลังรับค่าที่กรอกในแท็ก `form` เพราะค่าที่อยู่ในแท็กนี้มักจะเป็นสตริงเสมอ เพราะหากเราจับบวกเลย +เรามักจำเป็นต้องแปลงสตริงเป็นตัวเลขอยู่บ่อยๆ เช่น เวลารับค่าจากช่องกรอกข้อมูลใน HTML ค่าเหล่านั้นมักอยู่ในรูปสตริง แล้วถ้าเราต้องการบวกมันเข้าด้วยกันล่ะ? -มันจะก็กลายเป็นการนำสตริงมาต่อกันแทน +binary plus จะเอาสตริงมาต่อกัน ```js run let apples = "2"; let oranges = "3"; -alert( apples + oranges ); // ได้ "23" ซึ่งเกิดจากการต่อสตริง +alert( apples + oranges ); // "23", binary plus ต่อสตริงเข้าด้วยกัน ``` -หากเราต้องการให้สตริงเป้นตัวเลข เราต้องแปลงเป็นตัวเลขก่อนแล้วค่อยจับมาบวกกัน +หากต้องการให้เป็นตัวเลข ต้องแปลงค่าก่อนแล้วจึงบวกกัน ```js run let apples = "2"; let oranges = "3"; *!* -// ทั้งสองตัวแปรถูกแปรเป็นตัวเลขก่อน แล้วบวกกัน +// ค่าทั้งสองถูกแปลงเป็นตัวเลขก่อนใช้ binary plus alert( +apples + +oranges ); // 5 */!* -// หรือจะใช้เป็นฟังชั่นก์เพื่อป้องกันความสับสน +// หรือเขียนแบบยาวๆ ก็ได้ // alert( Number(apples) + Number(oranges) ); // 5 ``` -สำหรับมุมมองของนักคณิตศาสตร์ การที่มีเครื่องหมายบวกเยอะ อาจจะดูแปลกๆ แต่จากมุมมองของโปรแกรมเมอร์ ก็แค่แปลงเป็นตัวเลขก่อน จากนั้นก็นำตัวเลขทั้งสองตัวมาบวกกัน +มองในแง่ของนักคณิตศาสตร์ เครื่องหมายบวกที่เยอะแยะอาจดูแปลกๆ แต่สำหรับโปรแกรมเมอร์แล้วไม่มีอะไรพิเศษ — unary plus ทำงานกับค่าก่อน เพื่อแปลงสตริงเป็นตัวเลข จากนั้น binary plus จึงเอาตัวเลขมาบวกกัน -แล้วทำไมเครื่องหมายบวกหน้าตัวแปรถึงดำเนินการก่อน เครื่องหมายบวกอีกตัว นั่นก็เพราะว่าเครื่องหมายบวกหน้าตัวแปรมีวรรณะสูงกว่า *(higher precedence)* +ทำไม unary plus ถึงทำงานก่อน binary plus? อย่างที่เราจะได้เห็นต่อไป นั่นเป็นเพราะ *ลำดับความสำคัญ (precedence) ที่สูงกว่า* -## ศักดิ์ของโอเปอเรเตอร์แต่ละตัว (Operator precedence) +## ลำดับความสำคัญของตัวดำเนินการ -หากนิพนธ์ (expression) มีตัวดำเนินการมากกว่าหนึ่งตัว ลำดับการทำงานก่อน-หลังจะถูกนิยามจากวรรณะ *(precedence)* ของโอเปอเรเตอร์แต่ละตัว หรือ ตัวดำเนินการที่มีวรรณะสูงกว่าตัวอื่นๆ จะถูกดำเนินการก่อน +หากนิพจน์มีตัวดำเนินการมากกว่าหนึ่งตัว ลำดับการประมวลผลจะขึ้นอยู่กับ *ลำดับความสำคัญ (precedence)* ของแต่ละตัวดำเนินการ -ทบทวนความรู้คณิตศาสตร์สมัยเรียน สมมุติว่ามีนิพจน์อย่าง `1 + 2 * 2` ตัวคูณจะดำเนินการก่อน จากนั่นถึงค่อยบวก นี่คือความต่างวรรณะในแต่ละเครื่องหมาย โดยคูณมีวรรณะสูงกว่าบวกนั่นเอง +พวกเราทุกคนเรียนรู้ตั้งแต่สมัยอยู่ในโรงเรียนแล้วว่าในนิพจน์ `1 + 2 * 2` ต้องคำนวณการคูณก่อนการบวก นั่นแหละคือสิ่งที่เรียกว่าลำดับความสำคัญ การคูณถือว่ามี *ลำดับความสำคัญสูงกว่า* การบวก -การใช้วงเล็บสามารถแทนที่ความต่างทางวรรณะตรงนี้ได้ หากเราต้องการให้บวกก่อนแล้วค่อยคูณ เราก็จะเขียนเป็น `(1 + 2) * 2` แทน +วงเล็บสามารถแทนที่ลำดับความสำคัญใดๆ ก็ได้ ดังนั้นหากเราไม่พอใจกับลำดับเริ่มต้น เราก็สามารถใช้วงเล็บเพื่อเปลี่ยนแปลงได้ เช่น เขียนเป็น `(1 + 2) * 2` -ตัวดำเนินการในจาวาสคริปต์มีหมายเลข บ่งบอกความสูงวรรณะของตัวเองไว้อยู่แล้ว หากมีตัวเลขที่สูง วรรณะก็จะสูง หากต่างวรรณะกัน การดำเนินการจะเริ่มจากตัววรรณะสูงก่อนเสมอ หากศักดิ์เท่ากัน ดำเนินการจะเริ่มจากซ้ายไปขวาเหมือนปกติ +JavaScript มีตัวดำเนินการอยู่มากมาย แต่ละตัวจะมีตัวเลขลำดับความสำคัญของตัวเอง ตัวที่มีค่ามากกว่าจะถูกประมวลผลก่อน หากลำดับความสำคัญเท่ากัน ลำดับการประมวลผลจะเป็นจากซ้ายไปขวา -ตารางด้านล่างสรุปมาจาก [ศักดิ์ของโอเปอเรเตอร์](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (ไม่จำเป้นต้องจำทั้งหมด จำแค่ตัว operator เดี่ยว (unary) จะมีศักดิ์สูงกว่า operator คู่เสมอ (binary)): +ต่อไปนี้คือข้อมูลบางส่วนจาก[ตารางลำดับความสำคัญ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (ไม่จำเป็นต้องจำ แต่ให้สังเกตว่า unary operator จะสูงกว่า binary operator ที่เทียบเท่ากัน): -| หมายเลขวรรณะ | ชื่อ | หน้าตาเครื่องหมาย | +| ลำดับความสำคัญ | ชื่อ | เครื่องหมาย | |------------|------|------| | ... | ... | ... | -| 17 | unary plus | `+` | -| 17 | unary ลบ | `-` | -| 16 | ยกกำลัง | `**` | -| 15 | คูณ | `*` | -| 15 | หาร | `/` | -| 13 | บวก | `+` | -| 13 | ลบ | `-` | +| 14 | unary plus | `+` | +| 14 | unary negation | `-` | +| 13 | exponentiation | `**` | +| 12 | multiplication | `*` | +| 12 | division | `/` | +| 11 | addition | `+` | +| 11 | subtraction | `-` | | ... | ... | ... | -| 3 | การ assign | `=` | +| 2 | assignment | `=` | | ... | ... | ... | -เราจะเห็นว่าบรรดาบวกแบบเดี่ยวมีหมายเลขวรรณะเป็น 16 ซึ่งสูงกว่าบวกแบบคู่ซึ่งมีแค่ 13 นี่จึงเป็นเหตุผลว่าทำไม `"+apples + +oranges" จึงแปลงเป็นตัวเลขก่อนบวก +ดังที่เห็น "unary plus" มีลำดับความสำคัญ `14` ซึ่งสูงกว่า "addition" (binary plus) ที่มีค่า `11` นั่นจึงเป็นเหตุผลว่าทำไมในนิพจน์ `"+apples + +oranges"` unary plus จึงทำงานก่อน addition ## การกำหนดค่า (Assignment) -การกำหนดค่า (assignment) หรือเครื่องหมาย `=` ก็เป็นตัวดำเนินการเช่นเดียวกัน แถมยังมีชื่อในตารางวรรณะด้านบนด้วย โดยมีหมายเลขวรรณะอยู่ที่ `3` +เราขอเน้นย้ำว่า assignment `=` ก็ถือเป็นตัวดำเนินการเช่นกัน ปรากฏอยู่ในตารางลำดับความสำคัญด้วยค่าที่ต่ำมากคือ `2` -นี่เป็นเหตุผลว่าทำไม เวลาเราประกาศตัวแปรแบบ `x = 2 * 2 + 1` จะคำนวณด้านขวาให้เสร็จก่อน แล้วค่อยนำผลลัพธ์สุดท้าย มาเก็บไว้ใน `x` +นั่นจึงเป็นเหตุผลว่าทำไมเวลาที่เรากำหนดค่าให้กับตัวแปร เช่น `x = 2 * 2 + 1` การคำนวณจะเสร็จสิ้นก่อน แล้วจึงเริ่มประเมิน `=` และเก็บผลลัพธ์ไว้ใน `x` ```js let x = 2 * 2 + 1; @@ -210,15 +223,15 @@ let x = 2 * 2 + 1; alert( x ); // 5 ``` -### การกำหนดค่า คือ การส่งค่ากลับ +### Assignment = คืนค่ากลับ -การกำหนดค่า `=` ไม่ได้มีอะไรพิสดาร แต่มีนัยบางอย่างซ่อนอยู่ +ข้อเท็จจริงที่ว่า `=` เป็นตัวดำเนินการ ไม่ใช่ส่วนประกอบ "วิเศษ" ของภาษา มีนัยยะบางอย่างที่น่าสนใจ -ตัวดำเนินการส่วนใหญ่ในจาวาสคริปต์จะส่งค่ากลับเสมอ ที่เห็นชัดๆก็คือ `+` และ `-` แต่ก็รวมถึง `=` ด้วย +ตัวดำเนินการทุกตัวใน JavaScript จะคืนค่าเสมอ ซึ่งเห็นได้ชัดสำหรับ `+` และ `-` รวมถึง `=` ด้วย -การเรียก `x = value` คือ การเขียน `value` ไปเก็บใน `x` *หรืออีกนัยหนึ่งคือส่ง value ไปกลับหา x*. +การเรียก `x = value` จะเขียน `value` ลงใน `x` *แล้วคืนค่านั้น* -ด้านล่างคือตัวอย่างการใช้ตัวกำหนดค่า (assignment) เป็นส่วนหนึ่งนิพจน์ (expression) ที่ซับซ้อน +นี่คือตัวอย่างที่ใช้ assignment เป็นส่วนหนึ่งของนิพจน์ที่ซับซ้อนกว่า: ```js run let a = 1; @@ -230,17 +243,17 @@ let c = 3 - (a = b + 1); alert( a ); // 3 alert( c ); // 0 -``` +``` -จากตัวอย่างด้านบน ผลลัพธ์จากนิพจน์ (expression) อย่าง `(a = b + 1)` เป็นค่าที่สุดท้ายจะถูกเก็บ (assigned) ไปที่ `a` (นั่นก็คือ `3`) จากนั่นก็เป็น `3 - 3` ผลลัพธ์สุดท้ายก็จะเก็บไว้ใน `c` ต่อไป +ในตัวอย่างข้างต้น ผลลัพธ์ของนิพจน์ `(a = b + 1)` คือค่าที่ถูก assign ให้ `a` (นั่นก็คือ `3`) และนำไปใช้ในการประเมินค่าต่อไป -แปลกๆดีใช่ไหม การเข้าใจการทำงาน ช่วยให้เราเข้าใจไลบรารี่อื่นๆของจาวาสคริปต์ด้วย เพราะบางครั้งเราจะเห็นไลบรารี่ชาวบ้านเขียนแบบนี้ +โค้ดนี้ดูแปลกๆ นิดนึงนะ เราควรเข้าใจว่าทำงานอย่างไร เพราะบางครั้งจะเห็นในไลบรารี JavaScript -แต่ๆ อย่าเขียนโค้ดแบบนี้ มันก็ดูเท่ดี แต่จะทำให้คนอื่นอ่านโค้ดเราไม่เข้าใจ และก็ดูไม่สะอาดสักเท่าไหร่ +อย่างไรก็ตาม โปรดอย่าเขียนโค้ดแบบนั้นเลย เล่ห์เหลี่ยมเช่นนี้ไม่ได้ช่วยให้โค้ดชัดเจนหรืออ่านง่ายขึ้นแน่นอน -### การกำหนดค่าตัวแปรหลายตัวพร้อมกัน +### การต่อ assignment แบบลูกโซ่ -อีกฟีเจอร์ที่น่าสนใจของจาวาสคริปต์คือ การกำหนดค่าตัวแปรหลายตัวพร้อมกัน +อีกฟีเจอร์ที่น่าสนใจคือความสามารถในการต่อ assignment แบบลูกโซ่: ```js run let a, b, c; @@ -254,90 +267,90 @@ alert( b ); // 4 alert( c ); // 4 ``` -การกำหนดค่าพร้อมกัน จะเริ่มจากขวาไปซ้าย เริ่มแรก `2 + 2` ก่อน เมื่อบวกกันเสร็จ ค่าที่ได้จะส่งคืนไปหา ตัวแปรที่อยู่ด้านซ้าย `c`, `b` และ `a` สุดท้าย ตัวแปรทั้งสามก็จะมีค่าเดียวกัน +assignment แบบลูกโซ่ประเมินผลจากขวาไปซ้าย แรกสุด นิพจน์ `2 + 2` ทางขวาสุดจะถูกประเมิน แล้วจึง assign ให้กับตัวแปรทางซ้ายตามลำดับ ได้แก่ `c`, `b` และ `a` ในท้ายที่สุด ตัวแปรทุกตัวจะมีค่าเดียวกัน -ก็... ถ้าอยากให้อ่านง่ายๆ ก็แบ่งกำหนดค่าทีละตัวในแต่ละบรรทัดดีกว่า +อีกครั้ง เพื่อความง่ายต่อการอ่าน เราควรแยกโค้ดแบบนี้ออกเป็นสองสามบรรทัด: ```js c = 2 + 2; b = c; a = c; ``` -แบบนี้จะอ่านง่าย มองปุ๊ปก็รู้ปั๊ปว่าตรงนี้ทำอะไร -## Modify-in-place +แบบนี้อ่านง่ายกว่า โดยเฉพาะเวลาที่เรากวาดตามองโค้ดอย่างเร็วๆ -เรามักจะต้องใช้ตัวดำเนินการกับตัวแปรและเก็บผลลัพธ์ใหม่ไว้ในตัวแปรเดียวกัน +## การปรับค่าในตัวแปรโดยตรง -ตัวอย่าง +เรามักต้องนำตัวดำเนินการไปใช้กับตัวแปรและเก็บผลลัพธ์ใหม่ลงในตัวแปรนั้นเอง + +ตัวอย่างเช่น: ```js let n = 2; n = n + 5; -n = n * 2; +n = n * 2; ``` -หรือสามารถทำให้สั้นลงได้โดยการใช้ `+=` และ `*=`: +เราสามารถเขียนโค้ดข้างต้นให้สั้นลงได้โดยใช้ตัวดำเนินการ `+=` และ `*=`: ```js run let n = 2; -n += 5; // ทีนี้ n = 7 (เหมือนกับ n = n + 5) -n *= 2; // ทีนี้ n = 14 (เหมือนกับ n = n * 2) +n += 5; // ตอนนี้ n = 7 (เหมือนกับการเขียน n = n + 5) +n *= 2; // ตอนนี้ n = 14 (เหมือนกับการเขียน n = n * 2) -alert( n ); // 14 +alert( n ); // 14 ``` -การทำแบบนี้เรียกว่าตัวดำเนินการแบบ "modify-and-assign" สามารถใช้ได้กับตัวดำเนินการทางคณิตศาสตร์ทุกตัวอย่าง: `/=`, `-=`, อื่นๆ +ตัวดำเนินการ "ปรับค่าและกำหนดค่า" แบบสั้นๆ นี้มีให้ใช้งานสำหรับตัวดำเนินการทางคณิตศาสตร์และระดับบิต (Bitwise) ทุกตัว เช่น `/=`, `-=` เป็นต้น -ตัวดำเนินการดังกล่าวมีศักดิ์เทียบกับ การกำหนดค่า (assignment) ดังนั้นจะทำงานก็ต่อเมื่อ จบการคำนวณหมดแล้ว +ตัวดำเนินการเหล่านี้มีลำดับความสำคัญเท่ากับการกำหนดค่าปกติ จึงทำงานหลังจากการคำนวณส่วนอื่นๆ เสร็จสิ้น: ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // คำนวณทางด้านขวาก่อน เหมือนกับการเขียน n *= 8 -alert( n ); // 16 (ด้านขวาจะทำงานก่อน 3 + 5 ก่อนแล้วค่อยคูณ 2, เหมือนกับ n *= 8) +alert( n ); // 16 ``` -## การเพิ่ม/การลด - - +## การเพิ่ม/ลดค่า -การเพิ่ม หรือ การลด ตัวเลขทีละหนึ่ง เป็นการดำเนินการทางตวัเลขที่พบบ่อยที่สุดในโปรแกรมมิ่ง +การเพิ่มหรือลดค่าตัวเลขทีละ 1 ถือเป็นหนึ่งในการดำเนินการทางตัวเลขที่ใช้บ่อยที่สุด -จนต้องมีสัญลักษณ์พิเศษขึ้นมา สำหรับการดำเนินการนี้โดยเฉพาะ: +ดังนั้น จึงมีตัวดำเนินการพิเศษสำหรับกรณีนี้: -- **Increment** จะใช้ `++` การเพิ่มค่าในตัวแปรทีละ 1: +- **Increment** `++` ใช้เพิ่มค่าตัวแปรขึ้นทีละ 1: ```js run no-beautify let counter = 2; - counter++; // ทำงานเหมือนกับ counter = counter + 1 แต่สั้นกว่า + counter++; // ทำงานเหมือนกับ counter = counter + 1 แต่เขียนสั้นกว่า alert( counter ); // 3 ``` -- **Decrement** จะใช้ `--` การลดค่าในตัวแปรทีละ 1: + +- **Decrement** `--` ใช้ลดค่าตัวแปรลงทีละ 1: ```js run no-beautify let counter = 2; - counter--; // ทำงานเหมือนกับ counter = counter - 1 แต่สั้นกว่า + counter--; // ทำงานเหมือนกับ counter = counter - 1 แต่เขียนสั้นกว่า alert( counter ); // 1 ``` ```warn -การเพิ่ม การลด จะใช้ได้กับตัวแปรเท่านั้น หากไม่เชื่อลอง `5++` ดูก็ได้ จะได้ error มาแทนผลลัพธ์ +Increment/decrement สามารถใช้ได้กับตัวแปรเท่านั้น หากพยายามใช้กับค่าตรงๆ เช่น `5++` จะทำให้เกิด error ``` -ตัวดำเนินการ `++` และ `--` สามารถใส่ไว้ข้างหน้าตัวแปร หรือข้างหลังตัวแปรก็ได้ +ตัวดำเนินการ `++` และ `--` สามารถวางไว้ด้านหน้าหรือด้านหลังตัวแปรก็ได้ -- ตัวดำเนินการอยู่ข้างหลังแบบนี้ เรียกว่า "postfix form": `counter++`. -- ตัวดำเนินการอยู่ข้างหน้าแบบนี้ เรียกว่า "prefix form": `++counter`. +- เมื่อตัวดำเนินการอยู่หลังตัวแปร เรียกว่ารูปแบบ "postfix": `counter++` +- รูปแบบ "prefix" คือเมื่อตัวดำเนินการอยู่หน้าตัวแปร: `++counter` -ทั้งสอง statements ทำเหมือนกัน ก็คือบวกตัวแปร `counter` ด้วย `1` +ทั้งสองแบบทำงานเหมือนกันคือเพิ่มค่า `counter` ขึ้น `1` -แล้วมีความแตกต่างอะไรไหม? ที แต่เราจะเห็นได้เฉพาะเวลาที่ค่าส่งกลับมาจาก `++/--` แล้ว +แล้วมีความแตกต่างกันไหม? มี แต่จะเห็นได้ก็ต่อเมื่อนำค่าที่คืนมาจาก `++/--` ไปใช้เท่านั้น -ช่วยให้กระจ่างอีกที อย่างที่เรารู้ ตัวดำเนินการทุกอย่างล้วนคืนค่ากลับ กาเพิ่ม การลดก็ไม่มีข้อยกเว้น โดยแบบ prefix form จะส่งค่าใหม่กลับมา แต่ postfix form จะส่งค่าเก่ากลับมา (ก่อนจะเพิ่มหรือลดไปหนึ่ง) +เรามาทำความเข้าใจให้ชัดเจนกัน อย่างที่ทราบกันว่าตัวดำเนินการทุกตัวจะคืนค่า Increment/decrement ก็เช่นกัน รูปแบบ prefix จะคืนค่าใหม่ ส่วนรูปแบบ postfix จะคืนค่าเดิมก่อนการเพิ่ม/ลดค่า -จะเห็นความแตกต่าง ได้จากตัวอย่าง: +ลองดูตัวอย่างนี้เพื่อให้เห็นความแตกต่าง: ```js run let counter = 1; @@ -346,9 +359,9 @@ let a = ++counter; // (*) alert(a); // *!*2*/!* ``` -ในบรรทัด `(*)`, *prefix* form `++counter` เพิ่ม `counter` ไปหนึ่ง และส่งค่าใหม่กลับ `2` ดังนั้น `alert` ก็เลยโชว์ `2`. +ในบรรทัด `(*)` รูปแบบ *prefix* `++counter` จะเพิ่มค่า `counter` ก่อนแล้วคืนค่าใหม่ `2` ดังนั้น `alert` จึงแสดงเลข `2` -ทีนี้มาดูแบบ postfix form: +ทีนี้ลองใช้รูปแบบ postfix ดูบ้าง: ```js run let counter = 1; @@ -357,34 +370,35 @@ let a = counter++; // (*) เปลี่ยนจาก ++counter เป็น alert(a); // *!*1*/!* ``` -ในบรรทัดที่เรากำกับ `(*)` ไว้ แบบ *postfix* `counter++` โดยเพิ่มค่าให้ตัวแปน `counter` ทีละหนึ่ง แต่มันส่งค่าเก่ากลับมาก่อนค่อยบวก ดังนั้น `alert` จึงแสดงเลข `1` +ในบรรทัด `(*)` รูปแบบ *postfix* `counter++` ก็เพิ่มค่า `counter` เหมือนกัน แต่จะคืนค่า *เดิม* ก่อนการเพิ่มค่า ดังนั้น `alert` จึงแสดงเลข `1` -มาสรุปกัน +สรุปได้ดังนี้: -- หากผลลัพธ์์จากการเพิ่มหรือการลดไม่ถูกใช้ เราจะไม่เห็นความแตกต่างนี้เลย: +- ถ้าไม่ได้นำผลลัพธ์ของ increment/decrement ไปใช้ต่อ ไม่ว่าจะใช้รูปแบบไหนก็ไม่ต่างกัน: ```js run let counter = 0; counter++; ++counter; - alert( counter ); // 2, ทั้งสองบรรทัดเพิ่มค่าให้ตัวแปรทีละหนึ่งเช่นกัน + alert( counter ); // 2, บรรทัดข้างบนให้ผลเหมือนกัน ``` -- หากเราต้องการใช้ค่าจากการเพิ่มในทันที ให้ใช้แบบ prefix: +- ถ้าต้องการเพิ่มค่า *และ* นำผลลัพธ์ของตัวดำเนินการไปใช้ทันที ต้องใช้รูปแบบ prefix: - ```js run + ```js run let counter = 0; alert( ++counter ); // 1 ``` -- หากเราต้องการใช้ค่าก่อนหน้านี้ แล้วเพิ่มค่าทีหลัง ให้ใช้แบบ postfix: + +- ถ้าต้องการเพิ่มค่า แต่นำค่าก่อนหน้าไปใช้ ต้องใช้รูปแบบ postfix: ```js run let counter = 0; - alert( counter++ ); // 0 + alert( counter++ ); // 0 ``` -````smart header="การเพิ่ม/การลด กับ operator ตัวอื่น" -operator `++/--` สามารถใช้ใน expressions ได้ด้วย โดยมันมีศักดิ์มากกว่า บวก ลบ คูณ และหาร (หมายความว่าจะ `++ หรือ --` ก่อนจะทำอย่างอื่น) +```smart header="Increment/decrement ท่ามกลางตัวดำเนินการอื่นๆ" +ตัวดำเนินการ `++/--` สามารถใช้ภายในนิพจน์ได้ด้วย ลำดับความสำคัญสูงกว่าการดำเนินการทางคณิตศาสตร์ส่วนใหญ่ ตัวอย่างเช่น: @@ -393,33 +407,32 @@ let counter = 1; alert( 2 * ++counter ); // 4 ``` -เทียบกับ: +เปรียบเทียบกับ: ```js run let counter = 1; -alert( 2 * counter++ ); // 2, เพราะว่า counter++ ส่งค่าเก่ากลับ +alert( 2 * counter++ ); // 2, เพราะ counter++ คืนค่า "เดิม" ``` -ท่าแบบนี้ในทางเทคนิคก็ทำได้ การทำหลายอย่างในบรรทัดเดียว ไม่ค่อยเป็นมิตรกับการอ่านเท่าไหร่ +ถึงแม้ในแง่เทคนิคจะใช้ได้ แต่การเขียนแบบนี้มักจะทำให้โค้ดอ่านเข้าใจยากขึ้น เพราะทำหลายอย่างในบรรทัดเดียว ซึ่งไม่ค่อยดีนัก -ขณะที่นั่งอ่านโค้ด สายตาจะสแกนจากบนลงล่าง เพราะฉะนั้นง่ายมากที่จะพลาด อ่านแค่ `counter++` อ๋อบรรทัดนี้ก็แค่เพิ่มไปหนึ่งนี่นา ฉะนั้นระวังให้ดี +ระหว่างอ่านโค้ด การสแกนตามแนวดิ่งอย่างรวดเร็วอาจมองข้าม `counter++` ไปได้ง่าย และก็ไม่ชัดเจนว่าค่าตัวแปรเพิ่มขึ้น -เราจึงอยากแนะนำให้เขียนแบบข้างล่างมากกว่า: +เราแนะนำให้เขียนแบบ "หนึ่งบรรทัด หนึ่งการกระทำ": ```js run let counter = 1; alert( 2 * counter ); counter++; ``` -```` -## Bitwise operators (ตัวดำเนินการแบบบิต) +## ตัวดำเนินการระดับบิต (Bitwise) -Bitwise operators จะรับข้อมูลชนิดใดๆก็ตามแล้วแปลงออกมาเป็นตัวเลขแบบไบนารี +ตัวดำเนินการระดับบิต (Bitwise) จะปฏิบัติต่ออาร์กิวเมนต์เป็นจำนวนเต็ม 32 บิต และดำเนินการในระดับการแสดงผลเป็นเลขฐานสอง -Bitwise operators ไม่ได้มีแค่ในจาวาสคริปต์เท่านั้น ภาษาอื่นๆก็มีตัว operator แบบนี้อยุ่ด้วย +ตัวดำเนินการเหล่านี้ไม่ได้เฉพาะเจาะจงกับ JavaScript แต่รองรับในภาษาโปรแกรมส่วนใหญ่ -หน้าตาของ operator ประเภทนี้: +ตัวดำเนินการมีดังนี้: - AND ( `&` ) - OR ( `|` ) @@ -429,15 +442,15 @@ Bitwise operators ไม่ได้มีแค่ในจาวาสคร - RIGHT SHIFT ( `>>` ) - ZERO-FILL RIGHT SHIFT ( `>>>` ) -ตามปกติเราแทบจะไม่ได้ใช้ operator เหล่านี้ เราจะได้ใช้ operator พวกนี้ ในสายที่เฉพาะมากๆอย่าง การเข้ารหัส (cryptography) หากสนใจสามารถอ่านเพิ่มเติมได้ที่ [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise) +ตัวดำเนินการเหล่านี้ใช้น้อยมาก เว้นแต่เมื่อต้องจัดการกับตัวเลขในระดับ bitwise โดยเฉพาะ อาจจะไม่ต้องใช้ในเร็วๆ นี้ เนื่องจากการพัฒนาเว็บแทบจะไม่ได้ใช้ แต่ในบางด้าน เช่น การเข้ารหัสลับ ก็ยังมีประโยชน์อยู่ อ่านเพิ่มเติมได้จากบท [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) บน MDN เมื่อจำเป็นต้องใช้ -## คอมมา (comma) +## ตัวดำเนินการเครื่องหมายจุลภาค (Comma) -เป็นอีกหนึ่ง operator ที่แทบจะไม่ได้ใช้ เราจะใช้เมื่ืออยากให้โค้ดสั้นลง แต่เราต้องเข้าใจจริงๆว่า มันมีลำดับการทำงานยังไง +ตัวดำเนินการเครื่องหมายจุลภาค `,` เป็นหนึ่งในตัวดำเนินการที่พบได้ยากและแปลกประหลาดที่สุด บางครั้งคนใช้เพื่อเขียนโค้ดให้สั้นลง เราจึงควรรู้จักไว้เพื่อให้เข้าใจว่าโค้ดกำลังทำอะไร -เราสามารถใช้คอมมาเพื่อดูผลลัพธ์ของ expression หลายๆตัวพร้อมๆกันได้ โดยแยก expression แต่ละตัวด้วยคอมมา แต่เรามันคืนผลลัพธ์เฉพาะตัวสุดท้ายเท่านั้น +ตัวดำเนินการเครื่องหมายจุลภาคช่วยให้ประเมินนิพจน์ได้หลายตัว โดยคั่นด้วยเครื่องหมายจุลภาค `,` แต่ละนิพจน์จะถูกประเมินผล แต่จะคืนเฉพาะผลลัพธ์ของนิพจน์ตัวสุดท้าย -ตัวอย่าง: +ตัวอย่างเช่น: ```js run *!* @@ -447,26 +460,25 @@ let a = (1 + 2, 3 + 4); alert( a ); // 7 (ผลลัพธ์จาก 3 + 4) ``` -expression แรกคือ `1 + 2` พอบวกกันเสร็จ ผลลัพธ์ก็จะถูกโยนทิ้งไป มาถึง expression ที่สอง `3 + 4` บวกกันเสร็จ ผลลัพธ์ก็จะส่งกลับมา +ในตัวอย่างนี้ นิพจน์แรก `1 + 2` ถูกประเมินผลแต่ทิ้งผลลัพธ์ไป จากนั้น `3 + 4` จึงถูกประเมินและคืนค่าเป็นผลลัพธ์ -```smart header="คอมมามีศักดิ์ต่ำสุดในบรรดา operator ทั้งหมด" +```smart header="เครื่องหมายจุลภาคมีลำดับความสำคัญต่ำมาก" +โปรดสังเกตว่าตัวดำเนินการเครื่องหมายจุลภาคมีระดับความสำคัญต่ำมาก ต่ำกว่า `=` ดังนั้นวงเล็บจึงสำคัญในตัวอย่างข้างต้น -โปรดจำไว้ว่าเครื่องหมายคอมมามีศักดิ์ต่ำสุด ต่ำกว่า `=` ดังนั้น การล้อมด้วยวงเล็บจะทำให้ได้ผลลัพธ์เป็นตัวหลัง - -เมื่อไม่มีวงเล็บ `a = 1 + 2, 3 + 4` ตัวทำงานจะเริ่มจาก `+` ก่อน จะได้เป็น `a = 3, 7` ต่อด้วย `=` จะกำหนด `a = 3` และจบการทำงาน ผลลัพธ์จะเหมือน `(a = 1 + 2), 3 + 4` +หากไม่ใส่วงเล็บ: `a = 1 + 2, 3 + 4` จะประเมิน `+` ก่อน รวมผลเป็น `a = 3, 7` จากนั้นตัวดำเนินการ `=` จะกำหนดค่า `a = 3` และเพิกเฉยต่อส่วนที่เหลือ เสมือนเขียนเป็น `(a = 1 + 2), 3 + 4` ``` -แล้วแบบนี้ ทำไมต้องจะต้องใช้คอมมาก็ในเมื่อคอมมาเอาแต่ผลลัพธ์ที่อยู่ท้ายสุด +แล้วทำไมเราถึงต้องการตัวดำเนินการที่ทิ้งทุกอย่างยกเว้นนิพจน์ตัวสุดท้าย? -แต่ก็มีหลายคนใช้คอมมา เพื่อจะทำหลาย action ในบรรทัดเดียว +บางครั้งคนใช้ในโครงสร้างที่ซับซ้อนกว่า เพื่อรวมหลายการกระทำไว้ในบรรทัดเดียว -ตัวอย่าง: +ตัวอย่างเช่น: ```js -// ใช้ 3 oparetor ในบรรทัดเดียว +// รวมสามการกระทำในบรรทัดเดียว for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { - ... + ... } ``` -ทริคแบบนี้ถูกใช้เผยแพน่ในบรรดา frameworks นั่นเป็นเหตุผลว่าทำไมถึงต้องพูดเรื่องนี้ แต่มันไม่ได้ช่วยให้โค้ดอ่านง่ายขึ้นเลย เราควรคิดดีแล้วก่อนที่จะอ่านมันลงไป +เทคนิคเช่นนี้พบในหลายเฟรมเวิร์กของ JavaScript นั่นจึงเป็นเหตุผลที่กล่าวถึง แต่โดยทั่วไปแล้วไม่ได้ช่วยให้โค้ดอ่านง่ายขึ้น ควรคิดให้ดีก่อนนำมาใช้ \ No newline at end of file diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md index 632b1cf4e..90432456d 100644 --- a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -10,12 +10,12 @@ null == "\n0\n" → false null === +"\n0\n" → false ``` -Some of the reasons: +เฉลย: -1. Obviously, true. -2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`. -3. Again, dictionary comparison, first char `"2"` is greater than the first char `"1"`. -4. Values `null` and `undefined` equal each other only. -5. Strict equality is strict. Different types from both sides lead to false. -6. Similar to `(4)`, `null` only equals `undefined`. -7. Strict equality of different types. +1. ได้ true แน่นอน +2. การเปรียบเทียบตามลำดับ Unicode ดังนั้น `"a"` น้อยกว่า `"p"` +3. การเปรียบเทียบตามลำดับ Unicode ดังนั้น `"2"` มากกว่า `"1"`. +4. ค่า `null` และ `undefined` เท่ากันเสมอ เมื่อเทียบด้วย `==` +5. Strict equality นั้นเข้มงวด มันจะตรวจสอบชนิดของข้อมูลด้วย หากชนิดไม่เหมือนกัน ก็ได้จะ false +6. เหมือนกับข้อ `(4)` `null` จะเท่ากับ `undefined` เท่านั้น +7. เหมือนกับข้อ `(5)` diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md index be7f75ddd..818807231 100644 --- a/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Comparisons +# การเปรียบเทียบ -What will be the result for these expressions? +มาเดาผลลัพธ์ที่เกิดจากนิพจน์ด้านล่างกัน ```js no-beautify 5 > 4 diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md index ead7922ff..6ff5b5716 100644 --- a/1-js/02-first-steps/09-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -1,216 +1,216 @@ -# Comparisons +# การเปรียบเทียบ -We know many comparison operators from maths. +เราได้รู้จักเครื่องหมายเปรียบเทียบมากมายจากคณิตศาสตร์ -In JavaScript they are written like this: +ใน JavaScript เขียนได้ดังนี้: -- Greater/less than: a > b, a < b. -- Greater/less than or equals: a >= b, a <= b. -- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment. -- Not equals. In maths the notation is , but in JavaScript it's written as a != b. +- มากกว่า/น้อยกว่า: a > b, a < b +- มากกว่าหรือเท่ากับ/น้อยกว่าหรือเท่ากับ: a >= b, a <= b +- เท่ากับ: `a == b` สังเกตว่าต้องใช้เครื่องหมายเท่ากับสองตัว `==` สำหรับการเช็คความเท่ากัน ส่วนเครื่องหมายเดียว `a = b` ใช้สำหรับการกำหนดค่า +- ไม่เท่ากับ: ในเชิงคณิตศาสตร์ใช้สัญลักษณ์ แต่ใน JavaScript เขียนเป็น a != b -In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities. +ในบทความนี้ เราจะเรียนรู้เพิ่มเติมเกี่ยวกับการเปรียบเทียบแบบต่างๆ วิธีที่ JavaScript เปรียบเทียบ รวมถึงข้อควรระวังสำคัญ -At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues. +ท้ายบทความมีเคล็ดลับดีๆ ในการหลีกเลี่ยงปัญหาที่มักพบใน JavaScript -## Boolean is the result +## ผลลัพธ์เป็น Boolean -All comparison operators return a boolean value: +ผลการเปรียบเทียบทั้งหมดจะให้ค่า boolean: -- `true` -- means "yes", "correct" or "the truth". -- `false` -- means "no", "wrong" or "not the truth". +- `true` หมายถึง "ใช่" "ถูกต้อง" หรือ "จริง" +- `false` หมายถึง "ไม่" "ไม่ถูกต้อง" หรือ "ไม่จริง" -For example: +ตัวอย่างเช่น: ```js run -alert( 2 > 1 ); // true (correct) -alert( 2 == 1 ); // false (wrong) -alert( 2 != 1 ); // true (correct) +alert( 2 > 1 ); // true (ถูก) +alert( 2 == 1 ); // false (ไม่ถูก) +alert( 2 != 1 ); // true (ถูก) ``` -A comparison result can be assigned to a variable, just like any value: +ผลการเปรียบเทียบสามารถเก็บในตัวแปรได้ เหมือนกับค่าอื่นๆ: ```js run -let result = 5 > 4; // assign the result of the comparison +let result = 5 > 4; // เก็บผลการเปรียบเทียบ alert( result ); // true ``` -## String comparison +## การเปรียบเทียบ string -To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order. +ในการเช็คว่า string อันหนึ่งมากกว่าอีกอันหรือไม่ JavaScript ใช้การเรียงลำดับแบบ "พจนานุกรม" หรือ "ตามตัวอักษร" -In other words, strings are compared letter-by-letter. +หรือพูดง่ายๆ คือเปรียบเทียบ string ทีละตัวอักษร -For example: +ตัวอย่างเช่น: ```js run alert( 'Z' > 'A' ); // true alert( 'Glow' > 'Glee' ); // true -alert( 'Bee' > 'Be' ); // true +alert( 'Bee' > 'Be' ); // true ``` -The algorithm to compare two strings is simple: +ขั้นตอนในการเปรียบเทียบ string สองอันมีดังนี้: -1. Compare the first character of both strings. -2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done. -3. Otherwise, if both strings' first characters are the same, compare the second characters the same way. -4. Repeat until the end of either string. -5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater. +1. เปรียบเทียบตัวอักษรตัวแรกของ string ทั้งสองอัน +2. ถ้าตัวอักษรตัวแรกจาก string แรกมากกว่า (หรือน้อยกว่า) string ที่สอง แสดงว่า string แรกมากกว่า (หรือน้อยกว่า) string ที่สอง จบการทำงาน +3. ถ้าตัวอักษรตัวแรกของ string ทั้งสองเหมือนกัน ให้เปรียบเทียบตัวอักษรที่สองแบบเดียวกัน +4. ทำซ้ำไปเรื่อยๆ จนกว่าจะเจอตัวอักษรที่แตกต่างกันหรือจนหมด string +5. ถ้าหมด string พร้อมกันที่ความยาวเท่ากัน แปลว่าเท่ากัน ไม่อย่างนั้น string ที่ยาวกว่าจะมากกว่า -In the first example above, the comparison `'Z' > 'A'` gets to a result at the first step. +ในตัวอย่างแรกข้างบน การเปรียบเทียบ `'Z' > 'A'` ให้ผลลัพธ์ตั้งแต่ขั้นตอนแรก -The second comparison `'Glow'` and `'Glee'` needs more steps as strings are compared character-by-character: +การเปรียบเทียบที่สองระหว่าง `'Glow'` กับ `'Glee'` ต้องใช้หลายขั้นตอนมากกว่า โดยเปรียบเทียบตัวอักษรทีละตัว: -1. `G` is the same as `G`. -2. `l` is the same as `l`. -3. `o` is greater than `e`. Stop here. The first string is greater. +1. `G` เท่ากับ `G` +2. `l` เท่ากับ `l` +3. `o` มากกว่า `e` หยุดตรงนี้ string แรกมากกว่า -```smart header="Not a real dictionary, but Unicode order" -The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same. +```smart header="ไม่ใช่พจนานุกรมจริงๆ แต่เป็นลำดับ Unicode" +ขั้นตอนการเปรียบเทียบข้างต้นคล้ายกับที่ใช้ในพจนานุกรมหรือสมุดโทรศัพท์ แต่ไม่เหมือนกันทีเดียว -For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? The lowercase `"a"`. Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter . +เช่น ตัวพิมพ์ใหญ่เล็กมีผล `"A"` ไม่เท่ากับ `"a"` ตัวไหนมากกว่า? `"a"` เพราะมีค่าดัชนีที่สูงกว่าในตารางเข้ารหัสภายใน (Unicode) ที่ JavaScript ใช้ เราจะกลับมาดูรายละเอียดและผลที่ตามมาในบท ``` -## Comparison of different types +## การเปรียบเทียบข้อมูลต่างชนิด -When comparing values of different types, JavaScript converts the values to numbers. +เมื่อเปรียบเทียบค่าที่มีชนิดต่างกัน JavaScript จะแปลงค่าเป็นตัวเลขก่อน -For example: +ตัวอย่างเช่น: ```js run -alert( '2' > 1 ); // true, string '2' becomes a number 2 -alert( '01' == 1 ); // true, string '01' becomes a number 1 +alert( '2' > 1 ); // true, string '2' จะถูกแปลงเป็นตัวเลข 2 +alert( '01' == 1 ); // true, string '01' จะถูกแปลงเป็นตัวเลข 1 ``` -For boolean values, `true` becomes `1` and `false` becomes `0`. +สำหรับค่า boolean `true` จะกลายเป็น `1` และ `false` จะกลายเป็น `0` -For example: +ตัวอย่างเช่น: ```js run -alert( true == 1 ); // true +alert( true == 1 ); // true alert( false == 0 ); // true ``` -````smart header="A funny consequence" -It is possible that at the same time: +````smart header="ผลลัพธ์ที่แปลกประหลาด" +เป็นไปได้ว่าในเวลาเดียวกัน: -- Two values are equal. -- One of them is `true` as a boolean and the other one is `false` as a boolean. +- สองค่านั้นเท่ากัน +- แต่ค่าหนึ่งเป็น `true` เมื่อแปลงเป็น boolean ส่วนอีกค่าเป็น `false` เมื่อแปลงเป็น boolean -For example: +ตัวอย่างเช่น: ```js run let a = 0; alert( Boolean(a) ); // false -let b = "0"; +let b = "0"; alert( Boolean(b) ); // true alert(a == b); // true! ``` -From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules. +จากมุมมองของ JavaScript ผลลัพธ์นี้ถือว่าปกติ การเช็คความเท่ากันแปลงค่าโดยใช้กฎตัวเลข (ทำให้ `"0"` เป็น `0`) ส่วนการแปลงเป็น `Boolean` โดยตรงใช้ชุดกฎที่ต่างออกไป ```` -## Strict equality +## ความเท่าเทียมกันอย่างเข้มงวด -A regular equality check `==` has a problem. It cannot differentiate `0` from `false`: +การตรวจสอบความเท่าเทียมกันแบบปกติด้วย `==` มีปัญหาคือไม่สามารถแยกแยะความแตกต่างระหว่าง `0` กับ `false` ได้: ```js run alert( 0 == false ); // true ``` -The same thing happens with an empty string: +เหตุการณ์เดียวกันนี้เกิดขึ้นกับ string ว่างด้วย: -```js run +```js run alert( '' == false ); // true ``` -This happens because operands of different types are converted to numbers by the equality operator `==`. An empty string, just like `false`, becomes a zero. +สิ่งนี้เกิดขึ้นเพราะเมื่อเปรียบเทียบข้อมูลคนละชนิดด้วยเครื่องหมาย `==` JavaScript จะแปลงชนิดข้อมูลเป็นตัวเลขก่อน ทั้ง string ว่างและ `false` ต่างกลายเป็นศูนย์ -What to do if we'd like to differentiate `0` from `false`? +แล้วถ้าเราอยากแยกแยะความแตกต่างระหว่าง `0` กับ `false` ล่ะ จะทำอย่างไร? -**A strict equality operator `===` checks the equality without type conversion.** +**เครื่องหมายเท่ากันอย่างเข้มงวด `===` จะเปรียบเทียบความเท่าเทียมกันโดยไม่แปลงชนิดข้อมูล** -In other words, if `a` and `b` are of different types, then `a === b` immediately returns `false` without an attempt to convert them. +อีกนัยหนึ่ง ถ้า `a` และ `b` เป็นคนละชนิดกัน `a === b` จะคืนค่า `false` ทันที โดยไม่พยายามแปลงชนิดข้อมูล -Let's try it: +ลองดูตัวอย่าง: ```js run -alert( 0 === false ); // false, because the types are different +alert( 0 === false ); // false เพราะเป็นคนละชนิดกัน ``` -There is also a "strict non-equality" operator `!==` analogous to `!=`. +นอกจากนี้ยังมีเครื่องหมาย "ไม่เท่ากันอย่างเข้มงวด" `!==` ที่คล้ายกับ `!=` ด้วย -The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors. +การใช้เครื่องหมายเท่ากันแบบเข้มงวดอาจเขียนยาวขึ้นหน่อย แต่จะทำให้เห็นชัดเจนว่าเกิดอะไรขึ้นและลดโอกาสเกิดข้อผิดพลาดได้มากกว่า -## Comparison with null and undefined +## การเปรียบเทียบกับ null และ undefined -There's a non-intuitive behavior when `null` or `undefined` are compared to other values. +การเปรียบเทียบที่มี `null` หรือ `undefined` เกี่ยวข้องอาจให้ผลที่ไม่ตรงกับความคาดหมายเสมอไป -For a strict equality check `===` -: These values are different, because each of them is a different type. +สำหรับการตรวจสอบความเท่ากันอย่างเข้มงวดด้วย `===`: +: ค่าเหล่านี้แตกต่างกัน เพราะแต่ละค่ามีประเภทข้อมูลต่างกัน - ```js run - alert( null === undefined ); // false - ``` +```js run +alert( null === undefined ); // false +``` -For a non-strict check `==` -: There's a special rule. These two are a "sweet couple": they equal each other (in the sense of `==`), but not any other value. +สำหรับการตรวจสอบความเท่ากันแบบไม่เข้มงวดด้วย `==`: +: มีกฎพิเศษ ค่าทั้งสองนี้เป็น "คู่รักในฝัน" คือเท่ากันในแง่ของ `==` แต่ไม่เท่ากับค่าอื่นใด - ```js run - alert( null == undefined ); // true - ``` +```js run +alert( null == undefined ); // true +``` -For maths and other comparisons `< > <= >=` -: `null/undefined` are converted to numbers: `null` becomes `0`, while `undefined` becomes `NaN`. +สำหรับการคำนวณทางคณิตศาสตร์และการเปรียบเทียบอื่นๆ `< > <= >=`: +: `null/undefined` จะถูกแปลงเป็นตัวเลข โดย `null` กลายเป็น `0` และ `undefined` กลายเป็น `NaN` -Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them. +ทีนี้มาดูเหตุการณ์แปลกๆ ที่เกิดขึ้นเมื่อใช้กฎเหล่านี้ และที่สำคัญกว่านั้น คือจะหลีกเลี่ยงกับดักเหล่านี้ได้อย่างไร -### Strange result: null vs 0 +### ผลลัพธ์ประหลาด: null เทียบกับ 0 -Let's compare `null` with a zero: +ลองเปรียบเทียบ `null` กับศูนย์ดู: ```js run alert( null > 0 ); // (1) false alert( null == 0 ); // (2) false -alert( null >= 0 ); // (3) *!*true*/!* +alert( null >= 0 ); // (3) *!*true*/!* ``` -Mathematically, that's strange. The last result states that "`null` is greater than or equal to zero", so in one of the comparisons above it must be `true`, but they are both false. +ทางคณิตศาสตร์แล้วแปลกมาก ผลลัพธ์สุดท้ายบอกว่า "`null` มากกว่าหรือเท่ากับศูนย์" ดังนั้นในการเปรียบเทียบข้างต้นต้องมีอย่างน้อยหนึ่งค่าเป็น `true` แต่กลับเป็น false ทั้งคู่ -The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, treating it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. +สาเหตุคือ การตรวจสอบความเท่ากันด้วย `==` และการเปรียบเทียบ `> < >= <=` ทำงานต่างกัน การเปรียบเทียบจะแปลง `null` เป็นตัวเลขและถือว่าเป็น `0` นั่นเป็นเหตุผลที่ (3) `null >= 0` เป็น true และ (1) `null > 0` เป็น false -On the other hand, the equality check `==` for `undefined` and `null` is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) `null == 0` is false. +ในทางกลับกัน การตรวจสอบความเท่ากันด้วย `==` ระหว่าง `undefined` กับ `null` ถูกกำหนดไว้ว่าจะเท่ากันเองเท่านั้นโดยไม่มีการแปลงประเภท และไม่เท่ากับค่าอื่นใด นั่นเป็นเหตุผลที่ (2) `null == 0` เป็น false -### An incomparable undefined +### undefined ไม่สามารถเปรียบเทียบได้ -The value `undefined` shouldn't be compared to other values: +เราไม่ควรนำค่า `undefined` ไปเปรียบเทียบกับค่าอื่นๆ: ```js run alert( undefined > 0 ); // false (1) -alert( undefined < 0 ); // false (2) +alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` -Why does it dislike zero so much? Always false! +ทำไม `undefined` ถึงเกลียดศูนย์ขนาดนั้น จนเป็น false ตลอด! -We get these results because: +เพราะว่า: -- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons. -- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value. +- การเปรียบเทียบ `(1)` และ `(2)` ให้ค่า `false` เพราะ `undefined` ถูกแปลงเป็น `NaN` ซึ่ง `NaN` เป็นค่าตัวเลขพิเศษที่ให้ผลเป็น `false` เสมอเมื่อเปรียบเทียบ +- การตรวจสอบความเท่ากัน `(3)` ให้ค่า `false` เพราะ `undefined` จะเท่ากับ `null` และ `undefined` เท่านั้น ไม่เท่ากับค่าอื่น -### Avoid problems +### หลีกเลี่ยงปัญหา -Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them: +ทำไมถึงยกตัวอย่างเหล่านี้? ต้องจำความพิเศษเหล่านี้ตลอดเวลาเลยหรือ? ไม่จำเป็นขนาดนั้น สิ่งที่ดูยุ่งยากเหล่านี้จะค่อยๆ คุ้นเคยไปเองตามเวลา แต่มีวิธีที่ดีกว่าในการหลีกเลี่ยงปัญหาพวกนี้: -- Treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. -- Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. +- ระมัดระวังเป็นพิเศษเมื่อเปรียบเทียบกับ `undefined/null` ยกเว้นการตรวจสอบความเท่ากันแบบเข้มงวดด้วย `===` +- อย่าใช้การเปรียบเทียบ `>= > < <=` กับตัวแปรที่อาจเป็น `null/undefined` เว้นแต่จะมั่นใจว่ากำลังทำอะไรอยู่ ถ้าตัวแปรอาจมีค่าเหล่านี้ได้ ให้ตรวจสอบ `null/undefined` แยกต่างหาก -## Summary +## สรุป -- Comparison operators return a boolean value. -- Strings are compared letter-by-letter in the "dictionary" order. -- When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). -- The values `null` and `undefined` equal `==` each other and do not equal any other value. -- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea. +- ตัวดำเนินการเปรียบเทียบให้ค่าเป็น boolean +- การเปรียบเทียบ string จะเปรียบเทียบทีละตัวอักษรตามลำดับ "พจนานุกรม" +- เมื่อเปรียบเทียบค่าต่างประเภทกัน ค่าจะถูกแปลงเป็นตัวเลข (ยกเว้นการตรวจสอบความเท่ากันอย่างเข้มงวด) +- ค่า `null` และ `undefined` เท่ากันเฉพาะเมื่อใช้ `==` และไม่เท่ากับค่าอื่นใดทั้งสิ้น +- ระมัดระวังเมื่อใช้การเปรียบเทียบ เช่น `>` หรือ `<` กับตัวแปรที่อาจเป็น `null/undefined` ได้ บางครั้งการตรวจสอบ `null/undefined` แยกต่างหากถือเป็นแนวทางที่ดี diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md index 51f1d4680..a0783893c 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md @@ -1,8 +1,8 @@ -**Yes, it will.** +**ใช่ alert จะโชว์** -Any string except an empty one (and `"0"` is not empty) becomes `true` in the logical context. +สตริงใดๆ ยกเว้นสตริงว่าง (และสตริง "0" ไม่ใช่สตริงเปล่า) จะกลายเป็น "จริง" ในบริบททางตรรกะ -We can run and check: +เราสามรถเรียกใช้เพื่อตรวจสอบได้: ```js run if ("0") { diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md index 5f16cda85..a838f144b 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# if (a string with zero) +# if (ศูนย์เป็นสตริง) -Will `alert` be shown? +หลังจากรันโค้ดนี้คิดว่า `alert` จะโชว์ขึ้นมาหรือไม่ ```js if ("0") { diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg index 25dc2744d..47b020aab 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg @@ -1 +1 @@ -BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript \ No newline at end of file +BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript \ No newline at end of file diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index a4d943245..1ef523413 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -2,11 +2,11 @@ importance: 2 --- -# The name of JavaScript +# ชื่ออันแท้จริงของ JavaScript -Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?' +ใช้รูปโยค `if..else` เขียนโค้ดที่ถามว่า: 'What is the "official" name of JavaScript?' -If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!" +หากผู้เยี่ยมชมป้อน "ECMAScript" ให้ส่งออกเป็น "Right!" มิฉะนั้น -- ให้ออกเป็น: "You don't know? ECMAScript!" ![](ifelse_task2.svg) diff --git a/1-js/02-first-steps/10-ifelse/3-sign/task.md b/1-js/02-first-steps/10-ifelse/3-sign/task.md index 0c5d0e008..8984bd5b8 100644 --- a/1-js/02-first-steps/10-ifelse/3-sign/task.md +++ b/1-js/02-first-steps/10-ifelse/3-sign/task.md @@ -2,14 +2,14 @@ importance: 2 --- -# Show the sign +# จงแสดงสัญญาณ -Using `if..else`, write the code which gets a number via `prompt` and then shows in `alert`: +ใช้ if..else เขียนโค้ดที่ได้รับตัวเลขผ่าน `prompt` แล้วแสดงใน `alert`: -- `1`, if the value is greater than zero, -- `-1`, if less than zero, -- `0`, if equals zero. +- แสดง `1` ถ้าค่ามากกว่าศูนย์ +- แสดง `-1`, ถ้าค่าน้อยกว่าศูนย์ +- แสดง `0` ถ้าค่าเท่ากับศูนย์ -In this task we assume that the input is always a number. +เราถือว่าอินพุตเป็นตัวเลขเสมอ [demo src="if_sign"] diff --git a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md index 6bdf8453e..de56d4277 100644 --- a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md +++ b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Rewrite 'if' into '?' +# เขียน 'if' ให้เป็น '?' -Rewrite this `if` using the conditional operator `'?'`: +เขียน `if` นี้ใหม่โดยใช้ตัวดำเนินการตามเงื่อนไข `'?'`: ```js let result; diff --git a/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md b/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md index 4f7d994a2..240ae0712 100644 --- a/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md +++ b/1-js/02-first-steps/10-ifelse/6-rewrite-if-else-question/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Rewrite 'if..else' into '?' +# เขียน 'if..else' ให้เป็น '?' -Rewrite `if..else` using multiple ternary operators `'?'`. +เขียน `if..else` นี้ใหม่โดยใช้ตัวดำเนินการตามเงื่อนไข `'?'` -For readability, it's recommended to split the code into multiple lines. +เพื่อให้โค้ดอ่านง่ายขึ้น ขอแนะนำให้แยกโค้ดออกเป็นหลายบรรทัด ```js let message; diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 7327243b1..8ca5eed91 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -1,239 +1,239 @@ -# Conditional branching: if, '?' +# การกระโดดแบบมีเงื่อนไข: if, '?' -Sometimes, we need to perform different actions based on different conditions. +บ่อยครั้งที่เราต้องทำสิ่งต่างๆ ขึ้นอยู่กับเงื่อนไข -To do that, we can use the `if` statement and the conditional operator `?`, that's also called a "question mark" operator. +เพื่อทำเช่นนั้น เราสามารถใช้คำสั่ง `if` และตัวดำเนินการแบบมีเงื่อนไข `?` ซึ่งมีอีกชื่อหนึ่งว่า "เครื่องหมายคำถาม" -## The "if" statement +## คำสั่ง "if" -The `if(...)` statement evaluates a condition in parentheses and, if the result is `true`, executes a block of code. +คำสั่ง `if(...)` จะประเมินเงื่อนไขในวงเล็บ ถ้าผลลัพธ์เป็น `true` จะดำเนินการโค้ดในบล็อก -For example: +ตัวอย่างเช่น: ```js run -let year = prompt('In which year was ECMAScript-2015 specification published?', ''); +let year = prompt('มาตรฐาน ECMAScript-2015 เผยแพร่ในปีใด?', ''); *!* -if (year == 2015) alert( 'You are right!' ); +if (year == 2015) alert('คุณตอบถูก!'); */!* ``` -In the example above, the condition is a simple equality check (`year == 2015`), but it can be much more complex. +ในตัวอย่างนี้ เงื่อนไขคือการตรวจสอบความเท่ากันแบบง่าย (`year == 2015`) แต่อาจซับซ้อนกว่านั้นได้มาก -If we want to execute more than one statement, we have to wrap our code block inside curly braces: +ถ้าต้องการรันคำสั่งมากกว่าหนึ่งคำสั่ง ต้องครอบโค้ดด้วยวงเล็บปีกกา: ```js if (year == 2015) { - alert( "That's correct!" ); - alert( "You're so smart!" ); -} + alert("ถูกต้อง!"); + alert("คุณเก่งมาก!"); +} ``` -We recommend wrapping your code block with curly braces `{}` every time you use an `if` statement, even if there is only one statement to execute. Doing so improves readability. +แนะนำให้ครอบโค้ดด้วยวงเล็บปีกกา `{}` ทุกครั้งที่ใช้ `if` แม้จะมีคำสั่งเดียวก็ตาม เพราะช่วยให้อ่านโค้ดง่ายขึ้นมาก -## Boolean conversion +## การแปลงเป็น boolean -The `if (…)` statement evaluates the expression in its parentheses and converts the result to a boolean. +คำสั่ง `if(...)` จะประเมินนิพจน์ในวงเล็บและแปลงผลลัพธ์เป็น boolean -Let's recall the conversion rules from the chapter : +ลองมาทบทวนกฎการแปลงประเภทจากบท กัน: -- A number `0`, an empty string `""`, `null`, `undefined`, and `NaN` all become `false`. Because of that they are called "falsy" values. -- Other values become `true`, so they are called "truthy". +- ตัวเลข `0`, string ว่าง `""`, `null`, `undefined` และ `NaN` จะกลายเป็น `false` ทั้งหมด เรียกค่าเหล่านี้ว่า "falsy" +- ค่าอื่นๆ จะกลายเป็น `true` เรียกว่า "truthy" -So, the code under this condition would never execute: +ดังนั้น โค้ดในเงื่อนไขนี้จะไม่ทำงาน: ```js -if (0) { // 0 is falsy +if (0) { // 0 เป็น falsy ... } ``` -...and inside this condition -- it always will: +...แต่เงื่อนไขนี้จะทำงานเสมอ: ```js -if (1) { // 1 is truthy +if (1) { // 1 เป็น truthy ... } ``` -We can also pass a pre-evaluated boolean value to `if`, like this: +เรายังสามารถส่งค่า boolean ที่ประเมินไว้แล้วเข้าไปใน `if` ได้ด้วย แบบนี้: -```js -let cond = (year == 2015); // equality evaluates to true or false +```js +let cond = (year == 2015); // ตรวจสอบความเท่ากัน ได้ค่า true หรือ false if (cond) { - ... + ... } ``` -## The "else" clause +## กรณี "else" -The `if` statement may contain an optional "else" block. It executes when the condition is false. +คำสั่ง `if` อาจมีบล็อก `else` เพิ่มเติมได้ ซึ่งจะทำงานเมื่อเงื่อนไขเป็น falsy (เท็จ) -For example: +ตัวอย่างเช่น: ```js run -let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); +let year = prompt('ECMAScript-2015 เผยแพร่ในปีใด?', ''); if (year == 2015) { - alert( 'You guessed it right!' ); + alert('คุณตอบถูกต้อง!'); } else { - alert( 'How can you be so wrong?' ); // any value except 2015 + alert('คุณจำผิดแล้ว'); // สำหรับค่าอื่นๆ ที่ไม่ใช่ 2015 } ``` -## Several conditions: "else if" +## หลายเงื่อนไข: "else if" -Sometimes, we'd like to test several variants of a condition. The `else if` clause lets us do that. +บางครั้งเราอยากทดสอบเงื่อนไขหลายรูปแบบ กรณี `else if` ช่วยให้เราทำแบบนั้นได้ -For example: +ตัวอย่างเช่น: ```js run -let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); +let year = prompt('ECMAScript-2015 เผยแพร่ในปีใด?', ''); if (year < 2015) { - alert( 'Too early...' ); + alert('เร็วไป...'); } else if (year > 2015) { - alert( 'Too late' ); + alert('ช้าไป'); } else { - alert( 'Exactly!' ); + alert('ถูกต้อง!'); } ``` -In the code above, JavaScript first checks `year < 2015`. If that is falsy, it goes to the next condition `year > 2015`. If that is also falsy, it shows the last `alert`. +ในโค้ดนี้ JavaScript ตรวจสอบ `year < 2015` ก่อน ถ้าเป็น falsy จะตรวจสอบเงื่อนไขถัดไปคือ `year > 2015` ถ้าเงื่อนไขนั้นก็เป็น falsy เช่นกัน ก็จะแสดง `alert` สุดท้าย -There can be more `else if` blocks. The final `else` is optional. +เราสามารถมีบล็อก `else if` ได้หลายบล็อก ส่วน `else` ตัวสุดท้ายเป็นตัวเลือกเสริม ไม่บังคับ -## Conditional operator '?' +## ตัวดำเนินการแบบมีเงื่อนไข '?' -Sometimes, we need to assign a variable depending on a condition. +บางครั้งเราต้องการกำหนดค่าให้ตัวแปรตามเงื่อนไขบางอย่าง -For instance: +ยกตัวอย่างเช่น: ```js run no-beautify let accessAllowed; -let age = prompt('How old are you?', ''); +let age = prompt('อายุเท่าไหร่?', ''); *!* if (age > 18) { accessAllowed = true; } else { - accessAllowed = false; + accessAllowed = false; } */!* alert(accessAllowed); ``` -The so-called "conditional" or "question mark" operator lets us do that in a shorter and simpler way. +ตัวดำเนินการที่เรียกว่า "แบบมีเงื่อนไข" หรือ "เครื่องหมายคำถาม" ช่วยให้เราเขียนโค้ดแบบนี้ได้สั้นและง่ายขึ้น -The operator is represented by a question mark `?`. Sometimes it's called "ternary", because the operator has three operands. It is actually the one and only operator in JavaScript which has that many. +ตัวดำเนินการนี้แทนด้วยเครื่องหมายคำถาม `?` บางครั้งเรียกว่า "ternary" เพราะมี 3 operand ซึ่งเป็นตัวดำเนินการเดียวใน JavaScript ที่มีจำนวน operand มากขนาดนี้ -The syntax is: +รูปแบบไวยากรณ์คือ: ```js let result = condition ? value1 : value2; ``` -The `condition` is evaluated: if it's truthy then `value1` is returned, otherwise -- `value2`. +ระบบจะประเมิน `condition` ก่อน — ถ้าเป็น truthy จะคืนค่า `value1` ถ้าไม่จะคืนค่า `value2` -For example: +ตัวอย่างเช่น: ```js let accessAllowed = (age > 18) ? true : false; ``` -Technically, we can omit the parentheses around `age > 18`. The question mark operator has a low precedence, so it executes after the comparison `>`. +ในทางเทคนิค ละวงเล็บรอบ `age > 18` ได้ เพราะตัวดำเนินการ `?` มีลำดับความสำคัญ (precedence) ต่ำกว่า `>` จึงรันทีหลัง -This example will do the same thing as the previous one: +ตัวอย่างนี้จะทำงานเหมือนกับตัวอย่างก่อนหน้า: ```js -// the comparison operator "age > 18" executes first anyway -// (no need to wrap it into parentheses) +// การเปรียบเทียบ "age > 18" รันก่อนอยู่แล้ว +// (ไม่ต้องครอบด้วยวงเล็บก็ได้) let accessAllowed = age > 18 ? true : false; ``` -But parentheses make the code more readable, so we recommend using them. +แต่การใส่วงเล็บช่วยให้โค้ดอ่านง่ายขึ้น จึงแนะนำให้ใส่ไว้ ````smart -In the example above, you can avoid using the question mark operator because the comparison itself returns `true/false`: +ในตัวอย่างข้างต้น จริงๆ แล้วไม่จำเป็นต้องใช้ ? ก็ได้นะ เพราะการเปรียบเทียบเองให้ค่า `true/false` อยู่แล้ว: ```js -// the same +// เหมือนกัน let accessAllowed = age > 18; -``` +``` ```` -## Multiple '?' +## เครื่องหมายคำถามหลายตัว '?' -A sequence of question mark operators `?` can return a value that depends on more than one condition. +การใช้เครื่องหมายคำถาม `?` ต่อเนื่องกันหลายตัว สามารถคืนค่าที่ขึ้นอยู่กับเงื่อนไขมากกว่าหนึ่งอย่างได้ -For instance: +ตัวอย่างเช่น: ```js run -let age = prompt('age?', 18); +let age = prompt('อายุเท่าไหร่?', 18); -let message = (age < 3) ? 'Hi, baby!' : - (age < 18) ? 'Hello!' : - (age < 100) ? 'Greetings!' : - 'What an unusual age!'; +let message = (age < 3) ? 'สวัสดีจ้าหนู!' : + (age < 18) ? 'สวัสดีจ้า!' : + (age < 100) ? 'สวัสดีครับ/ค่ะ!' : + 'อายุแปลกๆ นะ!'; -alert( message ); +alert( message ); ``` -It may be difficult at first to grasp what's going on. But after a closer look, we can see that it's just an ordinary sequence of tests: +อาจดูงงในตอนแรกนะ แต่ถ้ามองให้ดีจะเห็นว่าเป็นแค่การตรวจสอบเงื่อนไขทีละข้อตามลำดับ: -1. The first question mark checks whether `age < 3`. -2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon '":"', checking `age < 18`. -3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon '":"', checking `age < 100`. -4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon '":"', returning `'What an unusual age!'`. +1. `?` ตัวแรกตรวจสอบว่า `age < 3` หรือไม่ +2. ถ้าใช่ — คืนค่า `'สวัสดีจ้าหนู!'` ถ้าไม่ใช่ ตรวจสอบเงื่อนไขถัดจากโคลอน ":" คือ `age < 18` +3. ถ้าเงื่อนไขนั้นเป็นจริง — คืนค่า `'สวัสดีจ้า!'` ถ้าไม่จริง ก็ตรวจสอบเงื่อนไขถัดไป ":" คือ `age < 100` +4. ถ้าเป็นจริง — คืนค่า `'สวัสดีครับ/ค่ะ!'` ถ้าไม่ใช่ก็ไปที่โคลอนสุดท้าย แล้วคืนค่า `'อายุแปลกๆ นะ!'` -Here's how this looks using `if..else`: +นี่คือการเขียนโค้ดข้างต้นโดยใช้ `if..else`: ```js if (age < 3) { - message = 'Hi, baby!'; + message = 'สวัสดีจ้าหนู!'; } else if (age < 18) { - message = 'Hello!'; + message = 'สวัสดีจ้า!'; } else if (age < 100) { - message = 'Greetings!'; + message = 'สวัสดีครับ/ค่ะ!'; } else { - message = 'What an unusual age!'; + message = 'อายุแปลกๆ นะ!'; } ``` -## Non-traditional use of '?' +## การใช้เครื่องหมายคำถาม '?' แบบไม่เป็นไปตามแบบแผน -Sometimes the question mark `?` is used as a replacement for `if`: +บางครั้งมีการใช้เครื่องหมายคำถาม `?` แทนคำสั่ง `if`: ```js run no-beautify -let company = prompt('Which company created JavaScript?', ''); +let company = prompt('บริษัทใดเป็นผู้สร้าง JavaScript?', ''); *!* (company == 'Netscape') ? - alert('Right!') : alert('Wrong.'); + alert('ถูกต้อง!') : alert('ผิดแล้ว'); */!* ``` -Depending on the condition `company == 'Netscape'`, either the first or the second expression after the `?` gets executed and shows an alert. +เงื่อนไข `company == 'Netscape'` ถ้าเป็นจริงจะรันนิพจน์แรกหลัง `?` ถ้าเป็นเท็จจะรันนิพจน์ที่สอง ทั้งคู่แสดง alert ออกมา -We don't assign a result to a variable here. Instead, we execute different code depending on the condition. +สังเกตว่าเราไม่ได้กำหนดผลลัพธ์ให้กับตัวแปร แต่เรารันโค้ดที่แตกต่างกันไปตามเงื่อนไข -**It's not recommended to use the question mark operator in this way.** +**ไม่แนะนำให้ใช้เครื่องหมายคำถามในลักษณะนี้นะ** -The notation is shorter than the equivalent `if` statement, which appeals to some programmers. But it is less readable. +การเขียนแบบนี้สั้นกว่าใช้ `if` จึงมีนักพัฒนาบางคนชอบ แต่จริงๆ แล้วอ่านเข้าใจยากกว่า -Here is the same code using `if` for comparison: +เปรียบเทียบกับการใช้ `if` ในโค้ดเดียวกัน: ```js run no-beautify -let company = prompt('Which company created JavaScript?', ''); +let company = prompt('บริษัทใดเป็นผู้สร้าง JavaScript?', ''); -*!* +*!* if (company == 'Netscape') { - alert('Right!'); + alert('ถูกต้อง!'); } else { - alert('Wrong.'); + alert('ผิดแล้ว'); } */!* ``` -Our eyes scan the code vertically. Code blocks which span several lines are easier to understand than a long, horizontal instruction set. +สายตาเราอ่านโค้ดจากบนลงล่าง บล็อกที่มีหลายบรรทัดจึงเข้าใจง่ายกว่าคำสั่งยาวๆ บรรทัดเดียว -The purpose of the question mark operator `?` is to return one value or another depending on its condition. Please use it for exactly that. Use `if` when you need to execute different branches of code. +จุดประสงค์หลักของเครื่องหมาย `?` คือการคืนค่าอย่างใดอย่างหนึ่งตามเงื่อนไข ควรใช้เพื่อวัตถุประสงค์นั้นโดยเฉพาะ ส่วนถ้าต้องการรันคำสั่งแตกต่างกันตามเงื่อนไข ให้ใช้ `if` จะดีกว่า \ No newline at end of file diff --git a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md index 8869d32e6..0c73b435c 100644 --- a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md +++ b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/solution.md @@ -1,4 +1,4 @@ -The answer is `2`, that's the first truthy value. +คำตอบคือ `2` เพราะว่าเป็นค่า truthy ตัวแรก ```js run alert( null || 2 || undefined ); diff --git a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md index a7c9addfc..d48008d44 100644 --- a/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md +++ b/1-js/02-first-steps/11-logical-operators/1-alert-null-2-undefined/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# What's the result of OR? +# ทายผลลัพธ์ของ OR จากโค้ดต่อไปนี้ -What is the code below going to output? +คิดว่าอะไรจะแสดงที่ออกมาจากฟังก์ชั่น `alert` ```js alert( null || 2 || undefined ); diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md index f85b56366..fa3128b63 100644 --- a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md +++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md @@ -1,13 +1,13 @@ -The answer: first `1`, then `2`. +คำตอบคือ: `alert` แสดง `1` ก่อน ตามด้วย `2` ```js run alert( alert(1) || 2 || alert(3) ); ``` -The call to `alert` does not return a value. Or, in other words, it returns `undefined`. +การเรียกใช้ฟังก์ชั่น `alert` จะไม่คืนค่าอะไรออกมา หรือก็คือ มันคืน `undefined` มา -1. The first OR `||` evaluates its left operand `alert(1)`. That shows the first message with `1`. -2. The `alert` returns `undefined`, so OR goes on to the second operand searching for a truthy value. -3. The second operand `2` is truthy, so the execution is halted, `2` is returned and then shown by the outer alert. +1. OR `||` ตัวแรกประเมินฟังก์ชั่น `alert(1)` และแสดง `1` ออกมา +2. ฟังก์ชัน `alert` ส่ง `undefined` ซึ่งเป็นค่า falsy กลับดังนั้น OR จะไปประเมินตัวที่สองต่อเพื่อหาค่า truthy +3. ตัวที่สอง `2` เป็นค่า truthy สิ้นสุดการทำงาน เลข `2` ถูกส่งกลับ และแสดงออกมาผ่านฟังก์ชัน `alert` ตัวนอกก -There will be no `3`, because the evaluation does not reach `alert(3)`. +ไม่มีเลข `3` แสดงออกมาเพราะว่าตามการทำงานไปไม่ถึงฟังก์ชั่น `alert(3)` diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md index 3908fa2ec..6dee8a964 100644 --- a/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md +++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# What's the result of OR'ed alerts? +# ทายผลลัพธ์ของ OR จากโค้ดต่อไปนี้ -What will the code below output? +อะไรคือจะแสดงออกมาจากฟังก์ชั่น `alert` ```js alert( alert(1) || 2 || alert(3) ); diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md index 5c2455ef4..f9e11642a 100644 --- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md +++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md @@ -1,6 +1,6 @@ -The answer: `null`, because it's the first falsy value from the list. +คำตอบคือ `null` เพราะว่า มันเป็นค่าที่เป็น falsy แรกที่เจอ ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md index 043d431e4..c67bdb855 100644 --- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md +++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# What is the result of AND? +# ทายผลลัพธ์ของ AND จากโค้ดต่อไปนี้? -What is this code going to show? +คิดว่าอะไรจะแสดงที่ออกมาจากฟังก์ชั่น `alert` ```js alert( 1 && null && 2 ); diff --git a/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md b/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md index b6fb10d72..4053549a1 100644 --- a/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md +++ b/1-js/02-first-steps/11-logical-operators/4-alert-and/solution.md @@ -1,10 +1,10 @@ -The answer: `1`, and then `undefined`. +คำตอบคือ `1` ตามด้วย `undefined`. ```js run alert( alert(1) && alert(2) ); ``` -The call to `alert` returns `undefined` (it just shows a message, so there's no meaningful return). +เรียกฟังก์ชั่น `alert` จะส่ง `undefined` กลับ (ตัวฟังก์ชั่นมีหน้าที่เพียงแสดงข้อความ ดังนั้นจึงไม่จำเป็นส่งค่าใดๆกลับไป) -Because of that, `&&` evaluates the left operand (outputs `1`), and immediately stops, because `undefined` is a falsy value. And `&&` looks for a falsy value and returns it, so it's done. +เพราะเหตุนี้ `&&` จึงประเมินตัวถูกดำเนินการทางซ้าย (เอาท์พุท `1`) และหยุดทำงานทันที เพราะ `undefined` เป็นค่า falsy และ `&&` จะหาค่า falsy ตัวแรกที่เจอ และส่งค่านั้นกลับไป เป็นอันเสร็จสิ้นการทำงาน diff --git a/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md b/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md index 69f877b95..96de618de 100644 --- a/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md +++ b/1-js/02-first-steps/11-logical-operators/4-alert-and/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# What is the result of AND'ed alerts? +# ทายผลลัพธ์ของ AND จากโค้ดต่อไปนี้? -What will this code show? +คิดว่าอะไรจะแสดงที่ออกมาจากฟังก์ชั่น alert ```js alert( alert(1) && alert(2) ); diff --git a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md index 25e3568f8..00cc59b47 100644 --- a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md +++ b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/solution.md @@ -1,16 +1,16 @@ -The answer: `3`. +คำตออบคือ `3` ```js run alert( null || 2 && 3 || 4 ); ``` -The precedence of AND `&&` is higher than `||`, so it executes first. +AND `&&` จะทำงานก่อน `||` เสมอ -The result of `2 && 3 = 3`, so the expression becomes: +ดังนั้นผลลัพธ์ของ `2 && 3 = 3`จากนิพจน์ข้างต้นจึงได้ว่า ``` null || 3 || 4 ``` -Now the result is the first truthy value: `3`. +ตอนนี้ค่า truthy แรกที่เจอก็คือ `3` diff --git a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md index b18bb9c51..5881a1796 100644 --- a/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md +++ b/1-js/02-first-steps/11-logical-operators/5-alert-and-or/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# The result of OR AND OR +# ทายผลลัพธ์ของ OR AND OR จากโค้ดต่อไปนี้? -What will the result be? +คิดว่าอะไรจะแสดงที่ออกมาจากฟังก์ชั่น alert ```js alert( null || 2 && 3 || 4 ); diff --git a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md index fc9e336c1..17b0ffa71 100644 --- a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md +++ b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md @@ -2,8 +2,8 @@ importance: 3 --- -# Check the range between +# เขียน if ตรวจสอบช่วงตัวเลข -Write an `if` condition to check that `age` is between `14` and `90` inclusively. +เขียนเงื่อนไขแบบ `if` เพื่อตรวจสอบว่า `age` อยู่ระหว่าง `14` ถึง `90` หรือไม่ -"Inclusively" means that `age` can reach the edges `14` or `90`. +โดยรวมอายุ `14` และ `90` ด้วย diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md index 9b947d00f..b10acdc4d 100644 --- a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md +++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md @@ -2,8 +2,8 @@ importance: 3 --- -# Check the range outside +# เขียน if ตรวจสอบช่วงตัวเลข -Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively. +เขียนเงื่อนไขแบบ `if` เพื่อตรวจสอบว่า `age` อยู่ไม่ระหว่าง `14` และ `90` -Create two variants: the first one using NOT `!`, the second one -- without it. +เขียนสองเงื่อนไข: โดยตัวแรกใช้ NOT `!` และตัวที่สองไม่ใช้ NOT `!` diff --git a/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md b/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md index 210509758..b9aac2b42 100644 --- a/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md +++ b/1-js/02-first-steps/11-logical-operators/8-if-question/solution.md @@ -1,20 +1,20 @@ -The answer: the first and the third will execute. +คำตอบคือ "if" ตัวแรกและตัวที่สามจะทำงาน -Details: +คำอธิบาย: ```js run -// Runs. -// The result of -1 || 0 = -1, truthy +// คำสั่งนี้ทำงาน +// ผลลัพธ์คือ -1 || 0 = -1 เป็นค่า truthy if (-1 || 0) alert( 'first' ); -// Doesn't run -// -1 && 0 = 0, falsy +// คำสั่งนี้ไม่ทำงาน +// -1 && 0 = 0 เป็นค่า falsy if (-1 && 0) alert( 'second' ); -// Executes -// Operator && has a higher precedence than || -// so -1 && 1 executes first, giving us the chain: -// null || -1 && 1 -> null || 1 -> 1 +// คำสั่งนี้ทำงาน +// ตัวดำเนินการ && จะทำงานก่อน || เสมอ +// ดังนั้น -1 && 1 ทำงานเสร็จ ก็จะทำงานส่วนที่เหลือต่อ +// null || -1 && 1 จะเป็น null || 1 จะเป็น 1 if (null || -1 && 1) alert( 'third' ); ``` diff --git a/1-js/02-first-steps/11-logical-operators/8-if-question/task.md b/1-js/02-first-steps/11-logical-operators/8-if-question/task.md index 55987121b..2f31c0c2b 100644 --- a/1-js/02-first-steps/11-logical-operators/8-if-question/task.md +++ b/1-js/02-first-steps/11-logical-operators/8-if-question/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# A question about "if" +# ปัญหาของ "if" -Which of these `alert`s are going to execute? +จากคำสั่ง "if" ทั้งสาม `alert` ตัวไหนจะทำงานบ้าง -What will the results of the expressions be inside `if(...)`? +และผลลัพธ์สุดท้ายภายใน `if(...)` จะเป็นอะไร ```js if (-1 || 0) alert( 'first' ); diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg index ca3e0aead..d22b518a9 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg @@ -1 +1 @@ -BeginCanceledCanceledWelcome!I don't know youWrong passwordWho's there?Password?CancelCancelAdminTheMasterOtherOther \ No newline at end of file +BeginCanceledCanceledWelcome!I don't know youWrong passwordWho's there?Password?CancelCancelAdminTheMasterOtherOther \ No newline at end of file diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md index 604606259..128866c27 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md @@ -22,4 +22,4 @@ if (userName === 'Admin') { } ``` -Note the vertical indents inside the `if` blocks. They are technically not required, but make the code more readable. +สังเกตการระยะห่างแนวตั้งภายในบล็อก `if` ในทางเทคนิคไม่จำเป็นต้องมีก็ได้ โค้ดก็ทำงานเหมือนเดิม เราเติมช่องว่างเข้าไปเพื่อให้โค้ดอ่านง่ายขึ้น diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md index 290a52642..7e8f7e840 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md @@ -2,24 +2,24 @@ importance: 3 --- -# Check the login +# มาเริ่มสร้างฟอร์มล็อคอินผ่าน prompt -Write the code which asks for a login with `prompt`. +สร้างฟอร์มล็อคอินด้วยฟังก์ชั่น `prompt` -If the visitor enters `"Admin"`, then `prompt` for a password, if the input is an empty line or `key:Esc` -- show "Canceled", if it's another string -- then show "I don't know you". +หากผู้เยี่ยมชมป้อน `"Admin"` ให้ใช้ฟังก์ชั่น `prompt` ถามรหัสผ่าน หากป้อนสตริงว่าง หรือกด `key:Esc` ให้แสดงคำว่า "Canceled" และหากป้อนสตริงอื่นให้แสดงคำว่า "I don't know you" -The password is checked as follows: +กฎการตรวจสอบรหัสผ่านคือดังนี้: -- If it equals "TheMaster", then show "Welcome!", -- Another string -- show "Wrong password", -- For an empty string or cancelled input, show "Canceled" +- หากรหัสผ่านเท่ากับ "TheMaster" ให้แสดงคำว่า "Welcome!", +- หากไม่ใช่ให้แสดงคำว่า "Wrong password", +- หากป้อนสตริงว่าง หรือกด `key:Esc` ให้แสดงคำว่า "Canceled" -The schema: +Schema: ![](ifelse_task.svg) -Please use nested `if` blocks. Mind the overall readability of the code. +โปรดใช้บล็อค `if` ให้คำนึงถึงความอ่านง่ายของโค้ด -Hint: passing an empty input to a prompt returns an empty string `''`. Pressing `key:ESC` during a prompt returns `null`. +คำใบ้: หากไม่ใส่อะไรลงในช่องอินพุทมันจะส่งสตริงเปล่ากลับมา `''` หากกด `key:ESC` มันจะส่ง `null` กลับมา [demo] diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md index caa8cdfaf..67d9c45ae 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -1,47 +1,47 @@ -# Logical operators +# ตัวดำเนินการตรรกะ -There are three logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT). +ใน JavaScript มีตัวดำเนินการตรรกะ 4 ตัว ได้แก่ `||` (OR), `&&` (AND), `!` (NOT) และ `??` (Nullish Coalescing) ในที่นี้จะอธิบายเฉพาะ 3 ตัวแรก ส่วนตัว `??` จะอยู่ในบทความถัดไป -Although they are called "logical", they can be applied to values of any type, not only boolean. Their result can also be of any type. +แม้จะเรียกว่า "ตรรกะ" แต่ก็สามารถใช้กับค่าประเภทอื่นๆ นอกเหนือจาก boolean ได้ และผลลัพธ์ที่ออกมาก็เป็นประเภทอะไรก็ได้เช่นกัน -Let's see the details. +มาดูรายละเอียดกัน ## || (OR) -The "OR" operator is represented with two vertical line symbols: +เครื่องหมาย "OR" จะแทนด้วยสัญลักษณ์เส้นตั้งคู่ `||` แบบนี้: ```js result = a || b; ``` -In classical programming, the logical OR is meant to manipulate boolean values only. If any of its arguments are `true`, it returns `true`, otherwise it returns `false`. +โดยทั่วไปแล้ว OR ตรรกะมีไว้ใช้กับค่า boolean เท่านั้น ถ้ามี argument ตัวใดเป็น `true` ก็จะคืนค่า `true` ถ้าไม่มีเลยจะคืนค่า `false` -In JavaScript, the operator is a little bit trickier and more powerful. But first, let's see what happens with boolean values. +ใน JavaScript ตัวดำเนินการ OR มีพลังมากกว่านั้นอีกหน่อย ก่อนอื่น ลองมาดูพฤติกรรมกับค่า boolean ก่อน -There are four possible logical combinations: +จะมีเคสที่เป็นไปได้ 4 แบบ: ```js run alert( true || true ); // true alert( false || true ); // true alert( true || false ); // true -alert( false || false ); // false +alert( false || false ); // false ``` -As we can see, the result is always `true` except for the case when both operands are `false`. +จะเห็นว่าผลลัพธ์จะเป็น `true` เสมอ ยกเว้นกรณีที่ operand ทั้งสองข้างเป็น `false` -If an operand is not a boolean, it's converted to a boolean for the evaluation. +ถ้า operand ไม่ใช่ boolean ค่านั้นจะถูกแปลงเป็น boolean ก่อนเพื่อประเมินผล -For instance, the number `1` is treated as `true`, the number `0` as `false`: +เช่น ตัวเลข `1` จะถือเป็น `true` ส่วนตัวเลข `0` จะเป็น `false`: ```js run -if (1 || 0) { // works just like if( true || false ) - alert( 'truthy!' ); +if (1 || 0) { // จะทำงานเหมือน if (true || false) + alert('truthy!'); } ``` -Most of the time, OR `||` is used in an `if` statement to test if *any* of the given conditions is `true`. +ส่วนใหญ่แล้ว OR `||` จะใช้ในคำสั่ง `if` เพื่อตรวจว่ามีเงื่อนไขใดเป็น `true` บ้าง -For example: +ตัวอย่างเช่น: ```js run let hour = 9; @@ -49,102 +49,100 @@ let hour = 9; *!* if (hour < 10 || hour > 18) { */!* - alert( 'The office is closed.' ); + alert('ออฟฟิศปิดแล้ว'); } ``` -We can pass more conditions: +เราสามารถเพิ่มเงื่อนไขได้อีก: ```js run let hour = 12; let isWeekend = true; if (hour < 10 || hour > 18 || isWeekend) { - alert( 'The office is closed.' ); // it is the weekend + alert('ออฟฟิศปิดแล้ว'); // เพราะเป็นวันหยุดสุดสัปดาห์ } ``` -## OR "||" finds the first truthy value +## OR "||" จะหาค่า truthy ตัวแรก [#or-finds-the-first-truthy-value] -The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript. +ตรรกะที่อธิบายไว้ข้างต้นค่อนข้างเป็นแบบดั้งเดิม ทีนี้มาดูความสามารถ "พิเศษ" ของ JavaScript กัน -The extended algorithm works as follows. - -Given multiple OR'ed values: +อัลกอริทึมแบบขยายทำงานดังนี้ เมื่อมีหลายค่ามา OR กัน: ```js result = value1 || value2 || value3; ``` -The OR `||` operator does the following: +ตัวดำเนินการ OR `||` จะทำดังนี้: -- Evaluates operands from left to right. -- For each operand, converts it to boolean. If the result is `true`, stops and returns the original value of that operand. -- If all operands have been evaluated (i.e. all were `false`), returns the last operand. +- ประเมิน operand จากซ้ายไปขวา +- สำหรับแต่ละ operand จะแปลงเป็น boolean ถ้าผลลัพธ์เป็น `true` จะหยุดและคืนค่าเดิมของ operand นั้นทันที +- ถ้าประเมิน operand ทั้งหมดแล้ว (คือทุกตัวเป็น `false`) จะคืนค่า operand ตัวสุดท้าย -A value is returned in its original form, without the conversion. +ค่าที่คืนออกมาจะเป็นค่าต้นฉบับ โดยไม่มีการแปลงใดๆ -In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found. +พูดง่ายๆ คือ การเชื่อม OR `||` หลายตัวจะคืนค่า truthy ตัวแรกที่เจอ หรือถ้าไม่มีเลยก็คืนตัวสุดท้าย -For instance: +ตัวอย่างเช่น: ```js run -alert( 1 || 0 ); // 1 (1 is truthy) +alert( 1 || 0 ); // 1 (1 เป็น truthy) -alert( null || 1 ); // 1 (1 is the first truthy value) -alert( null || 0 || 1 ); // 1 (the first truthy value) +alert( null || 1 ); // 1 (1 เป็นค่า truthy ตัวแรก) +alert( null || 0 || 1 ); // 1 (ค่า truthy ตัวแรก) -alert( undefined || null || 0 ); // 0 (all falsy, returns the last value) +alert( undefined || null || 0 ); // 0 (ทั้งหมดเป็น falsy จึงคืนค่าตัวสุดท้าย) ``` -This leads to some interesting usage compared to a "pure, classical, boolean-only OR". - -1. **Getting the first truthy value from a list of variables or expressions.** +พฤติกรรมนี้เปิดโอกาสให้ใช้ OR ได้หลากหลายกว่าแค่การตรรกะ boolean ธรรมดา - For instance, we have `firstName`, `lastName` and `nickName` variables, all optional (i.e. can be undefined or have falsy values). +1. **การหาค่า truthy ตัวแรกจาก list ของตัวแปรหรือนิพจน์ต่างๆ** - Let's use OR `||` to choose the one that has the data and show it (or `"Anonymous"` if nothing set): + สมมติเรามีตัวแปร `firstName`, `lastName` และ `nickName` ซึ่งทุกตัวเป็น optional อาจเป็น undefined หรือ falsy ได้ - ```js run - let firstName = ""; - let lastName = ""; - let nickName = "SuperCoder"; + ใช้ OR `||` เพื่อเลือกค่าแรกที่มีข้อมูล (หรือแสดง `"Anonymous"` ถ้าไม่มีเลย): - *!* - alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder - */!* - ``` + ```js run + let firstName = ""; + let lastName = ""; + let nickName = "SuperCoder"; - If all variables were falsy, `"Anonymous"` would show up. + *!* + alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder + */!* + ``` -2. **Short-circuit evaluation.** + ถ้าทุกตัวแปรเป็น falsy ก็จะแสดง `"Anonymous"` - Another feature of OR `||` operator is the so-called "short-circuit" evaluation. +2. **การประเมินแบบตัดตอนสั้น (short-circuit)** - It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument. + อีกความสามารถหนึ่งของ OR `||` เรียกว่า "short-circuit evaluation" - That importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call. + หมายความว่า `||` จะประมวลผลอาร์กิวเมนต์ไปเรื่อยๆ จนเจอค่า truthy ตัวแรก แล้วคืนค่านั้นทันที โดยไม่แตะอาร์กิวเมนต์ที่เหลืออีกเลย - In the example below, only the second message is printed: + ความสำคัญของเรื่องนี้จะเห็นชัดขึ้น ถ้า operand ไม่ใช่แค่ค่าธรรมดา แต่เป็นนิพจน์ที่มี side-effect เช่น การกำหนดค่าตัวแปรหรือเรียกฟังก์ชัน - ```js run no-beautify - *!*true*/!* || alert("not printed"); - *!*false*/!* || alert("printed"); - ``` + ในตัวอย่างด้านล่าง จะพิมพ์แค่ข้อความที่สองเท่านั้น: + + ```js run no-beautify + *!*true*/!* || alert("จะไม่พิมพ์"); + *!*false*/!* || alert("จะพิมพ์"); + ``` - In the first line, the OR `||` operator stops the evaluation immediately upon seeing `true`, so the `alert` isn't run. + บรรทัดแรก OR `||` หยุดทันทีเมื่อเจอ `true` ทำให้ `alert` ไม่ถูกเรียกเลย - Sometimes, people use this feature to execute commands only if the condition on the left part is falsy. + บางครั้งก็มีคนใช้เทคนิคนี้เพื่อรันคำสั่งก็ต่อเมื่อเงื่อนไขทางซ้ายเป็น falsy ## && (AND) -The AND operator is represented with two ampersands `&&`: +เครื่องหมาย AND ใช้สัญลักษณ์ & สองตัวติดกัน `&&` แบบนี้: ```js result = a && b; ``` -In classical programming, AND returns `true` if both operands are truthy and `false` otherwise: +โดยปกติในการเขียนโปรแกรม AND จะคืนค่า `true` ถ้า operand ทั้งสองเป็น truthy และคืน `false` ในกรณีอื่นๆ: ```js run alert( true && true ); // true @@ -153,137 +151,135 @@ alert( true && false ); // false alert( false && false ); // false ``` -An example with `if`: +ตัวอย่างการใช้กับ `if`: ```js run let hour = 12; let minute = 30; if (hour == 12 && minute == 30) { - alert( 'The time is 12:30' ); -} + alert( 'ขณะนี้เวลา 12:30 น.' ); +} ``` -Just as with OR, any value is allowed as an operand of AND: +เหมือนกับ OR นั่นคือ operand ของ AND จะเป็นค่าอะไรก็ได้: ```js run -if (1 && 0) { // evaluated as true && false - alert( "won't work, because the result is falsy" ); +if (1 && 0) { // ประเมินเป็น true && false + alert( "จะไม่ทำงาน เพราะผลลัพธ์เป็น falsy" ); } ``` +## AND "&&" จะหาค่า falsy ตัวแรก -## AND "&&" finds the first falsy value - -Given multiple AND'ed values: +ถ้ามีหลายค่ามา AND กัน: ```js result = value1 && value2 && value3; ``` -The AND `&&` operator does the following: +ตัวดำเนินการ AND `&&` จะทำงานดังนี้: -- Evaluates operands from left to right. -- For each operand, converts it to a boolean. If the result is `false`, stops and returns the original value of that operand. -- If all operands have been evaluated (i.e. all were truthy), returns the last operand. +- ประเมิน operand จากซ้ายไปขวา +- สำหรับแต่ละ operand จะแปลงเป็น boolean ถ้าผลลัพธ์เป็น `false` จะหยุดและคืนค่าเดิมของ operand นั้นทันที +- ถ้าประเมิน operand ทั้งหมดแล้ว (คือทุกตัวเป็น truthy) จะคืนค่า operand ตัวสุดท้าย -In other words, AND returns the first falsy value or the last value if none were found. +อีกนัยหนึ่ง AND จะคืนค่า falsy ตัวแรกที่เจอ หรือถ้าไม่เจอเลยจะคืนค่าตัวสุดท้าย -The rules above are similar to OR. The difference is that AND returns the first *falsy* value while OR returns the first *truthy* one. +กฎข้างต้นคล้ายกับ OR ต่างกันตรงที่ AND คืนค่า *falsy* ตัวแรก ส่วน OR คืนค่า *truthy* ตัวแรก -Examples: +ตัวอย่าง: ```js run -// if the first operand is truthy, -// AND returns the second operand: +// ถ้า operand ตัวแรกเป็น truthy +// AND จะคืนค่า operand ตัวที่สอง: alert( 1 && 0 ); // 0 alert( 1 && 5 ); // 5 -// if the first operand is falsy, -// AND returns it. The second operand is ignored +// ถ้า operand ตัวแรกเป็น falsy +// AND จะคืนค่านั้นเลย โดยไม่สนใจ operand ตัวที่สอง alert( null && 5 ); // null -alert( 0 && "no matter what" ); // 0 +alert( 0 && "ไม่สำคัญ" ); // 0 ``` -We can also pass several values in a row. See how the first falsy one is returned: +ลองส่งหลายค่าต่อกันดู สังเกตว่าค่า falsy ตัวแรกจะถูกคืนออกมา: ```js run alert( 1 && 2 && null && 3 ); // null ``` -When all values are truthy, the last value is returned: +ถ้าทุกค่าเป็น truthy จะได้ค่าตัวสุดท้ายกลับมา: ```js run -alert( 1 && 2 && 3 ); // 3, the last one +alert( 1 && 2 && 3 ); // 3, ตัวสุดท้าย ``` -````smart header="Precedence of AND `&&` is higher than OR `||`" -The precedence of AND `&&` operator is higher than OR `||`. +````smart header="AND `&&` มีลำดับความสำคัญสูงกว่า OR `||`" +ตัวดำเนินการ AND `&&` จะมี precedence สูงกว่า OR `||` -So the code `a && b || c && d` is essentially the same as if the `&&` expressions were in parentheses: `(a && b) || (c && d)`. +ดังนั้นโค้ด `a && b || c && d` ก็เหมือนกับการเขียนนิพจน์ `&&` อยู่ในวงเล็บ: `(a && b) || (c && d)` ```` -````warn header="Don't replace `if` with `||` or `&&`" -Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". +````warn header="อย่าใช้ `||` หรือ `&&` แทนคำสั่ง `if`" +บางครั้งมีคนใช้ตัวดำเนินการ AND `&&` เพื่อ "เขียน `if` ให้สั้นลง" -For instance: +เช่น: ```js run let x = 1; -(x > 0) && alert( 'Greater than zero!' ); +(x > 0) && alert( 'มากกว่าศูนย์!' ); ``` -The action in the right part of `&&` would execute only if the evaluation reaches it. That is, only if `(x > 0)` is true. +ส่วนทางขวาของ `&&` จะรันก็ต่อเมื่อการประเมินทำไปถึงจุดนั้น นั่นคือเมื่อ `(x > 0)` เป็น true -So we basically have an analogue for: +ดังนั้นโค้ดนี้ก็เหมือนกับการเขียนแบบนี้: ```js run let x = 1; -if (x > 0) alert( 'Greater than zero!' ); +if (x > 0) alert( 'มากกว่าศูนย์!' ); ``` -Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND. +การใช้ `&&` ดูสั้นกว่า แต่ `if` ชัดเจนและอ่านง่ายกว่า แนะนำให้ใช้แต่ละอย่างตามวัตถุประสงค์ที่ถูกออกแบบมา: ใช้ `if` สำหรับเงื่อนไข และใช้ `&&` สำหรับตรรกะ AND ```` - ## ! (NOT) -The boolean NOT operator is represented with an exclamation sign `!`. +ตัวดำเนินการตรรกะ NOT ใช้สัญลักษณ์อัศเจรีย์ `!` -The syntax is pretty simple: +รูปแบบการใช้งานค่อนข้างง่าย: ```js result = !value; ``` -The operator accepts a single argument and does the following: +โดยตัวดำเนินการจะรับ operand เดียว และทำงานดังนี้: -1. Converts the operand to boolean type: `true/false`. -2. Returns the inverse value. +1. แปลง operand ให้เป็นประเภท boolean: `true/false` +2. คืนค่าตรงข้าม (invert) -For instance: +ตัวอย่างเช่น: ```js run alert( !true ); // false alert( !0 ); // true ``` -A double NOT `!!` is sometimes used for converting a value to boolean type: +การใช้ NOT สองตัวติดกัน `!!` บางครั้งใช้เพื่อแปลงค่าให้เป็นประเภท boolean: ```js run alert( !!"non-empty string" ); // true -alert( !!null ); // false +alert( !!null ); // false ``` -That is, the first NOT converts the value to boolean and returns the inverse, and the second NOT inverses it again. In the end, we have a plain value-to-boolean conversion. +NOT ตัวแรกแปลงค่าเป็น boolean และคืนค่าตรงข้าม จากนั้น NOT ตัวที่สอง invert อีกรอบ ผลที่ได้จึงเป็นการแปลงค่าเป็น boolean อย่างตรงไปตรงมา -There's a little more verbose way to do the same thing -- a built-in `Boolean` function: +อีกวิธีที่อ่านง่ายกว่าคือใช้ฟังก์ชัน `Boolean` ในตัวเลย: ```js run alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false ``` -The precedence of NOT `!` is the highest of all logical operators, so it always executes first, before `&&` or `||`. +NOT `!` มีลำดับความสำคัญ (precedence) สูงสุดในบรรดาตัวดำเนินการตรรกะทั้งหมด จึงถูกประมวลผลก่อนเสมอ ก่อน `&&` และ `||` \ No newline at end of file diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md index 55e0c2067..ef8da7f54 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -1,93 +1,94 @@ -# Nullish coalescing operator '??' +# ตัวดำเนินการรวม Nullish '??' [recent browser="new"] -Here, in this article, we'll say that an expression is "defined" when it's neither `null` nor `undefined`. +ตัวดำเนินการรวม nullish เขียนด้วยเครื่องหมายคำถามสองตัวติดกัน `??` -The nullish coalescing operator is written as two question marks `??`. +ในบทความนี้เราจะนิยามศัพท์เฉพาะไว้ก่อน เนื่องจาก `null` และ `undefined` ถูกจัดการในลักษณะคล้ายกัน เพื่อให้กระชับ เราจะเรียกว่าค่าหนึ่ง "ถูกกำหนด" เมื่อไม่ใช่ทั้ง `null` และ `undefined` -The result of `a ?? b` is: -- if `a` is defined, then `a`, -- if `a` isn't defined, then `b`. +ผลลัพธ์ของ `a ?? b` จะเป็น: +- ถ้า `a` ถูกกำหนด ผลลัพธ์คือ `a` +- ถ้า `a` ไม่ถูกกำหนด ผลลัพธ์คือ `b` +พูดง่ายๆ ก็คือ `??` คืนอาร์กิวเมนต์ตัวแรกถ้าไม่ใช่ `null/undefined` มิฉะนั้นคืนตัวที่สอง -In other words, `??` returns the first argument if it's not `null/undefined`. Otherwise, the second one. +ตัวดำเนินการรวม nullish ไม่ใช่เรื่องใหม่ เป็นเพียงไวยากรณ์ที่สะดวกในการหาค่าแรกที่ "ถูกกำหนด" จากสองค่า -The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two. - -We can rewrite `result = a ?? b` using the operators that we already know, like this: +`result = a ?? b` สามารถเขียนใหม่โดยใช้ตัวดำเนินการที่คุ้นเคยแล้วได้ดังนี้: ```js result = (a !== null && a !== undefined) ? a : b; ``` -The common use case for `??` is to provide a default value for a potentially undefined variable. +ตอนนี้น่าจะชัดเจนแล้วว่า `??` ทำอะไร มาดูกันว่ามีประโยชน์อย่างไรบ้าง + +กรณีใช้งานทั่วไปของ `??` คือการกำหนดค่าเริ่มต้น (default) -For example, here we show `Anonymous` if `user` isn't defined: +ตัวอย่างเช่น โค้ดนี้จะแสดง `user` ถ้าค่าไม่ใช่ `null/undefined` ไม่อย่างนั้นจะแสดง `Anonymous`: ```js run let user; -alert(user ?? "Anonymous"); // Anonymous +alert(user ?? "Anonymous"); // Anonymous (user เป็น undefined) ``` -Of course, if `user` had any value except `null/undefined`, then we would see it instead: +นี่คือตัวอย่างที่กำหนด `user` เป็นชื่อ: ```js run let user = "John"; -alert(user ?? "Anonymous"); // John +alert(user ?? "Anonymous"); // John (user ไม่ใช่ null/undefined) ``` -We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`. +นอกจากนี้ยังใช้ `??` ต่อกันหลายตัวได้ด้วย เพื่อเลือกค่าแรกจากรายการที่ไม่ใช่ `null/undefined` -Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be undefined, if the user decided not to enter a value. +สมมติว่ามีข้อมูลผู้ใช้อยู่ในตัวแปร `firstName`, `lastName` หรือ `nickName` ซึ่งอาจไม่มีค่าถ้าผู้ใช้ไม่ได้กรอก -We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are undefined. +เราต้องการแสดงชื่อจากตัวแปรเหล่านี้ตัวใดตัวหนึ่ง หรือแสดง "Anonymous" ถ้าทุกตัวเป็น `null/undefined` -Let's use the `??` operator for that: +ลองใช้ `??` ดู: ```js run let firstName = null; let lastName = null; let nickName = "Supercoder"; -// shows the first defined value: +// แสดงค่าแรกที่ถูกกำหนด: *!* alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder -*/!* +*/!* ``` -## Comparison with || +## เปรียบเทียบกับ || -The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value). +ตัวดำเนินการ OR `||` สามารถใช้ได้ในทำนองเดียวกับ `??` ตามที่อธิบายไว้ใน[บทก่อนหน้า](info:logical-operators#or-finds-the-first-truthy-value) -For example, in the code above we could replace `??` with `||` and still get the same result: +เช่น ในโค้ดข้างต้น เราสามารถแทนที่ `??` ด้วย `||` โดยยังได้ผลลัพธ์เหมือนเดิม: ```js run let firstName = null; let lastName = null; let nickName = "Supercoder"; -// shows the first truthy value: +// แสดงค่า truthy ตัวแรก: *!* alert(firstName || lastName || nickName || "Anonymous"); // Supercoder */!* ``` -The OR `||` operator exists since the beginning of JavaScript, so developers were using it for such purposes for a long time. +ย้อนไปในอดีต ตัวดำเนินการ OR `||` มีมาก่อนตั้งแต่ JavaScript เริ่มต้น นักพัฒนาจึงใช้เพื่อวัตถุประสงค์แบบนี้มานาน -On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`. +ส่วนตัวดำเนินการรวม nullish `??` เพิ่งเข้ามาใน JavaScript ไม่นานมานี้ เพราะคนไม่ค่อยพอใจกับ `||` นัก -The important difference between them is that: -- `||` returns the first *truthy* value. -- `??` returns the first *defined* value. +ความแตกต่างสำคัญระหว่างสองตัวนี้คือ: +- `||` คืนค่า *truthy* ตัวแรก +- `??` คืนค่าแรกที่ *ถูกกำหนด* -In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result. +กล่าวอีกอย่าง `||` ไม่ได้แยกความแตกต่างระหว่าง `false`, `0`, string ว่าง `""` และ `null/undefined` ถือว่าทั้งหมดเป็นค่า falsy เหมือนกัน ถ้าตัวใดตัวหนึ่งเป็น argument ตัวแรกของ `||` เราจะได้ argument ตัวที่สองเป็นผลลัพธ์ -In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set. +แต่ในทางปฏิบัติ เราอาจต้องการใช้ค่าเริ่มต้นก็ต่อเมื่อตัวแปรเป็น `null/undefined` นั่นคือเมื่อยังไม่มีค่า/ไม่ได้ถูกกำหนด -For example, consider this: +ตัวอย่างเช่น พิจารณาโค้ดนี้: ```js run let height = 0; @@ -96,71 +97,73 @@ alert(height || 100); // 100 alert(height ?? 100); // 0 ``` -- The `height || 100` checks `height` for being a falsy value, and it really is. - - so the result is the second argument, `100`. -- The `height ?? 100` checks `height` for being `null/undefined`, and it's not, - - so the result is `height` "as is", that is `0`. +- `height || 100` ตรวจสอบว่า `height` เป็นค่า falsy หรือไม่ ซึ่ง `0` ก็ถือเป็น falsy จริงๆ + - ดังนั้นผลลัพธ์ของ `||` คือ argument ตัวที่สอง `100` +- `height ?? 100` ตรวจสอบว่า `height` เป็น `null/undefined` หรือไม่ ซึ่งไม่ใช่ + - ดังนั้นผลลัพธ์คือ `height` "ตามที่เป็น" คือ `0` + +ในทางปฏิบัติ `height` เป็นศูนย์มักเป็นค่าที่ถูกต้อง ไม่ควรนำค่าเริ่มต้นมาแทนที่ ดังนั้น `??` จึงให้ผลลัพธ์ที่ถูกต้องพอดี -If the zero height is a valid value, that shouldn't be replaced with the default, then `??` does just the right thing. +## ลำดับความสำคัญ -## Precedence +ตัวดำเนินการ `??` มีลำดับความสำคัญเท่ากับ `||` ทั้งคู่มีค่าเท่ากับ `3` ในตาราง [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table) -The precedence of the `??` operator is rather low: `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). So `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. +นั่นหมายความว่า เช่นเดียวกับ `||` ตัวดำเนินการรวม nullish `??` จะประเมินผลก่อน `=` และ `?` แต่หลังการดำเนินการอื่นๆ ส่วนใหญ่ เช่น `+`, `*` -So if we'd like to choose a value with `??` in an expression with other operators, consider adding parentheses: +ดังนั้นในนิพจน์แบบนี้ เราอาจต้องใส่วงเล็บเพิ่ม: ```js run let height = null; let width = null; -// important: use parentheses +// สำคัญ: ใช้วงเล็บ let area = (height ?? 100) * (width ?? 50); alert(area); // 5000 ``` -Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results. +ไม่เช่นนั้น ถ้าละวงเล็บ เนื่องจาก `*` มีลำดับความสำคัญสูงกว่า `??` จะประเมินผลก่อน ทำให้ได้ผลลัพธ์ไม่ถูกต้อง ```js -// without parentheses +// ไม่มีวงเล็บ let area = height ?? 100 * width ?? 50; -// ...works the same as this (probably not what we want): +// ...ทำงานแบบนี้ (ไม่ใช่สิ่งที่เราต้องการ): let area = height ?? (100 * width) ?? 50; ``` -### Using ?? with && or || +### การใช้ ?? ร่วมกับ && หรือ || -Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses. +ด้วยเหตุผลด้านความปลอดภัย JavaScript ไม่อนุญาตให้ใช้ `??` ร่วมกับตัวดำเนินการ `&&` และ `||` นอกจากจะระบุลำดับความสำคัญชัดเจนด้วยวงเล็บ -The code below triggers a syntax error: +โค้ดด้านล่างจะทำให้เกิด syntax error: ```js run let x = 1 && 2 ?? 3; // Syntax error ``` -The limitation is surely debatable, but it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch to `??` from `||`. +ข้อจำกัดนี้อาจโต้แย้งได้ แต่ถูกเพิ่มเข้าไปในข้อกำหนดของภาษา เพื่อป้องกันข้อผิดพลาดที่อาจเกิดขึ้นเมื่อคนเริ่มเปลี่ยนจาก `||` มาใช้ `??` -Use explicit parentheses to work around it: +ใช้วงเล็บให้ชัดเจนเพื่อแก้ปัญหา: ```js run *!* -let x = (1 && 2) ?? 3; // Works +let x = (1 && 2) ?? 3; // ใช้ได้ */!* alert(x); // 2 ``` -## Summary +## สรุป -- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list. +- ตัวดำเนินการรวม nullish `??` ให้วิธีที่กระชับในการเลือกค่าแรกที่ "ถูกกำหนด" จาก list - It's used to assign default values to variables: + มักใช้เพื่อกำหนดค่าเริ่มต้นให้ตัวแปร: - ```js - // set height=100, if height is null or undefined - height = height ?? 100; - ``` + ```js + // กำหนด height=100 ถ้า height เป็น null หรือ undefined + height = height ?? 100; + ``` -- The operator `??` has a very low precedence, only a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression. -- It's forbidden to use it with `||` or `&&` without explicit parentheses. +- ตัวดำเนินการ `??` มีลำดับความสำคัญต่ำมาก สูงกว่า `?` และ `=` เพียงเล็กน้อย ดังนั้นควรใส่วงเล็บเมื่อใช้ในนิพจน์ +- ห้ามใช้ร่วมกับ `||` หรือ `&&` โดยไม่ใส่วงเล็บให้ชัดเจน \ No newline at end of file diff --git a/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md index 43ee4aad3..ffc37a316 100644 --- a/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md +++ b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md @@ -1,4 +1,4 @@ -The answer: `1`. +คำตอบก็คือ `1`. ```js run let i = 3; @@ -8,18 +8,18 @@ while (i) { } ``` -Every loop iteration decreases `i` by `1`. The check `while(i)` stops the loop when `i = 0`. +การวนซ้ำแต่ละครั้งค่าของตัวแปร `i` จะลดลงทีละ `1` ฉะนั้น `while (i)` จะหยุดเมื่อ `i = 0` -Hence, the steps of the loop form the following sequence ("loop unrolled"): +ดังนั้นขั้นตอนการทำงานจึงเป็นไปตามด้านล่างนี้ ("loop unrolled") ```js let i = 3; -alert(i--); // shows 3, decreases i to 2 +alert(i--); // แสดง 3 ลด i เป็น 2 -alert(i--) // shows 2, decreases i to 1 +alert(i--) // แสดง 2 ลด i เป็น 1 -alert(i--) // shows 1, decreases i to 0 +alert(i--) // แสดง 1 ลด i เป็น 0 -// done, while(i) check stops the loop +// จบการทำงาน ``` diff --git a/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md index 3b847dfa2..bb5c9b337 100644 --- a/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md +++ b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# Last loop value +# ทายเลขตัวสุดท้าย -What is the last value alerted by this code? Why? +คิดว่าเลขตัวสุดท้ายที่จะแสดงผลจากฟังก์ชัน `alert` คือเลขอะไร ```js let i = 3; diff --git a/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md index 495359876..6611b0348 100644 --- a/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md +++ b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md @@ -1,30 +1,30 @@ -The task demonstrates how postfix/prefix forms can lead to different results when used in comparisons. +วิธีนี้จะช่วยให้เห็นว่าการเติม postfix/prefix สามารถให้ผลลัพธ์ที่แตกต่างกันได้ หากใช้ในการเปรียบเทียบ -1. **From 1 to 4** +1. **จาก 1 ไป 4** ```js run let i = 0; while (++i < 5) alert( i ); ``` - The first value is `i = 1`, because `++i` first increments `i` and then returns the new value. So the first comparison is `1 < 5` and the `alert` shows `1`. + ค่าแรกคือ `i = 1`, การ prefix `++i` คือบวกเพิ่มทีละหนึ่งก่อนค่อยส่งค่า**ใหม่**กลับ ดังนั้นเมื่อเทียบเงื่อนไขก็จะเป็น `1 < 5` ฟังก์ชัน `alert` จึงแสดงเลข `1` - Then follow `2, 3, 4…` -- the values show up one after another. The comparison always uses the incremented value, because `++` is before the variable. + จากนั้นตามด้วย `2, 3, 4…` ค่าจะแสดงเพิ่มขึ้นทีละ 1 ในขณะเดียวกัน - Finally, `i = 4` is incremented to `5`, the comparison `while(5 < 5)` fails, and the loop stops. So `5` is not shown. -2. **From 1 to 5** + จนไปถึง `i = 4` ค่าถูกเพิ่มไปที่ `5` เมื่อเทียบเปรียบจะได้ `while(5 < 5)` ซึ่งเป็นเท็จ ลูบจะหยุดทำงาน เลข `5` จึงไม่แสดงออกมา +2. **จาก 1 ไป 5** ```js run let i = 0; while (i++ < 5) alert( i ); ``` - The first value is again `i = 1`. The postfix form of `i++` increments `i` and then returns the *old* value, so the comparison `i++ < 5` will use `i = 0` (contrary to `++i < 5`). + ค่าแรกคือ `i = 1` การ postfix `i++` คือบวก `i` เพิ่มทีละหนึ่งแต่กลับค่า**เก่า**กลับ ดังนั้นเมื่อเทียบเงื่อนไข `i++ < 5` ก็จะเป็น `i = 0` (ตรงข้ามกับ `++i < 5`) - But the `alert` call is separate. It's another statement which executes after the increment and the comparison. So it gets the current `i = 1`. + แต่ฟังก์ชัน `alert` ถูกเรียกแยกตะหาก มันเป็นอีกคำสั่งหนึ่งซึ่งถูกเรียกหลังจากที่ตัวแปร `i` ถูกบวกค่าเพิ่มเข้าไปแล้ว จึงทำให้ตอนนี้ตัวแปร `i = 1` - Then follow `2, 3, 4…` + จากนั้นก็ตามด้วย `2, 3, 4…` - Let's stop on `i = 4`. The prefix form `++i` would increment it and use `5` in the comparison. But here we have the postfix form `i++`. So it increments `i` to `5`, but returns the old value. Hence the comparison is actually `while(4 < 5)` -- true, and the control goes on to `alert`. + มาหยุดตรงที่ตอน `i = 4` ไปก่อน เราเห็นจากข้างบนแล้วว่าการเติม prefix `++i` เพิ่มค่าให้ `i` ทีละหนึ่งซึ่งจะทำให้ใช้เลข `5` เป็นตัวเทียบเปรียบ แต่สำหรับการเติม postfix `i++` มันก็ทำงานแบบเดียวกันคือตัวแปร `i` เป็น `5` แต่มันจะส่งค่าเก่ากลับไป ดังนั้นเมื่อเปรียบเทียบจึงกลายเป็น `while(4 < 5)` -- ซึ่งเงื่อนไขเป็นจริง ทำให้ลูบทำงานต่อ - The value `i = 5` is the last one, because on the next step `while(5 < 5)` is false. + จนวนกลับมาอีกรอบทีนี้ `i = 5` แล้ว เงื่อนไข `while(5 < 5)` จึงเป็นเท็จ ลูบจึงหยุดทำงาน diff --git a/1-js/02-first-steps/13-while-for/2-which-value-while/task.md b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md index 298213237..3d082b10f 100644 --- a/1-js/02-first-steps/13-while-for/2-which-value-while/task.md +++ b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md @@ -2,19 +2,19 @@ importance: 4 --- -# Which values does the while loop show? +# ทายกันว่าจะมีเลขอะไรออกมาบ้าง -For every loop iteration, write down which value it outputs and then compare it with the solution. +ลองหยิบกระดาศขึ้นมาหนึ่งแผ่น เขียนตัวเลขที่คิดว่าจะแสดงผลออกมาจากโค้ดด้านล่าง และตอบคำถามด้านล่าง -Both loops `alert` the same values, or not? +ทั้งสองลูบแสดงผลลัพธ์เดียวกันหรือไม่ -1. The prefix form `++i`: +1. เติม prefix `++i`: ```js let i = 0; while (++i < 5) alert( i ); ``` -2. The postfix form `i++` +2. เติม postfix `i++` ```js let i = 0; diff --git a/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md index e2e28e75b..f997c4d2b 100644 --- a/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md +++ b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md @@ -1,4 +1,4 @@ -**The answer: from `0` to `4` in both cases.** +**คำตอบ: แสดงจาก `0` ถึง `4` ทั้งสองกรณี** ```js run for (let i = 0; i < 5; ++i) alert( i ); @@ -6,12 +6,12 @@ for (let i = 0; i < 5; ++i) alert( i ); for (let i = 0; i < 5; i++) alert( i ); ``` -That can be easily deducted from the algorithm of `for`: +มาดูวิธีการทำงานของลูบ `for` กัน: -1. Execute once `i = 0` before everything (begin). -2. Check the condition `i < 5` -3. If `true` -- execute the loop body `alert(i)`, and then `i++` +1. มันทำงานเริ่มจากให้ `i = 0` ก่อน +2. จากนั่นก็ไปตรวจสอบเงื่อนไข `i < 5` +3. ถ้าเป็น `true` -- โค้ดที่อยู่ข้างในลูบจะทำงาน `alert(i)` และตามด้วย `i++` -The increment `i++` is separated from the condition check (2). That's just another statement. +จะเห็นว่าการเพิ่มทีละหนึ่ง `i++` ทำงานทีหลังการตรวจสอบเงื่อนไข (2). มันเป็นอีกหนึ่งคำสั่ง -The value returned by the increment is not used here, so there's no difference between `i++` and `++i`. +ดังนั้นค่าที่คืนมาจากการเพิ่มละหนึ่งไม่ว่าแบบ `postfix` หรือ `prefix` ไม่ได้ถูกใช้ ดังนั้นจึงไม่มีความแตกต่างกันระหว่าง `i++` และ `++i` diff --git a/1-js/02-first-steps/13-while-for/3-which-value-for/task.md b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md index bfefa63f5..3ee1947e1 100644 --- a/1-js/02-first-steps/13-while-for/3-which-value-for/task.md +++ b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md @@ -2,18 +2,18 @@ importance: 4 --- -# Which values get shown by the "for" loop? +# ทายผลลัพธ์อะไรที่จะแสดงออกจากลูบ "for" -For each loop write down which values it is going to show. Then compare with the answer. +ลองมาเขียนผลลัพธ์ที่จะแสดงออกมาในแต่ละรอบการทำงานกัน -Both loops `alert` same values or not? +ทั้งสองลูบฟังก์ชัน `alert` จะแสดงค่าออกมาเหมือนกันหรือไม่ -1. The postfix form: +1. แบบ postfix: ```js for (let i = 0; i < 5; i++) alert( i ); ``` -2. The prefix form: +2. แบบ prefix: ```js for (let i = 0; i < 5; ++i) alert( i ); diff --git a/1-js/02-first-steps/13-while-for/4-for-even/solution.md b/1-js/02-first-steps/13-while-for/4-for-even/solution.md index e8e66bb47..b1c4a0e6c 100644 --- a/1-js/02-first-steps/13-while-for/4-for-even/solution.md +++ b/1-js/02-first-steps/13-while-for/4-for-even/solution.md @@ -8,4 +8,4 @@ for (let i = 2; i <= 10; i++) { } ``` -We use the "modulo" operator `%` to get the remainder and check for the evenness here. +เราใช้ตัวดำเนินการ "modulo" หรือ `%` เพื่อดูเศษที่เหลือจากการหาร ในทีนี้คือ สองหาร `i` เหลือเศษเป็น `0` หรือไม่ หากเป็นจริงฟังก์ชัน `alert` จะทำงาน diff --git a/1-js/02-first-steps/13-while-for/4-for-even/task.md b/1-js/02-first-steps/13-while-for/4-for-even/task.md index ff34e7e40..7f9ac2e15 100644 --- a/1-js/02-first-steps/13-while-for/4-for-even/task.md +++ b/1-js/02-first-steps/13-while-for/4-for-even/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Output even numbers in the loop +# มาลองเขียนลูบที่แสดงผลเฉพาะเลขคู่กัน -Use the `for` loop to output even numbers from `2` to `10`. +มาลองเขียนลูบ `for` ที่แสดงผลจากเลข `2` ไป `10` กัน [demo] diff --git a/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md index 0c69d9c2d..745013bf0 100644 --- a/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md +++ b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Replace "for" with "while" +# มาลองเปลี่ยนจากลูบ "for" เป็นลูบ "while" กัน -Rewrite the code changing the `for` loop to `while` without altering its behavior (the output should stay same). +มาลองเขียนโค้ดใหม่จากลูบ "for" ด้านล่างให้มาเป็นลูบ "while" กัน โดยที่ผลลัพธ์ควรเหมือนเดิม ```js run for (let i = 0; i < 3; i++) { diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md index c7de5f09b..5ef93f4b8 100644 --- a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md @@ -7,9 +7,9 @@ do { } while (num <= 100 && num); ``` -The loop `do..while` repeats while both checks are truthy: +เราใช้ลูบ `do..while` เพื่อให้มันทำงานจนกว่าเงื่อนไขจะเป็นเท็จ -1. The check for `num <= 100` -- that is, the entered value is still not greater than `100`. -2. The check `&& num` is false when `num` is `null` or an empty string. Then the `while` loop stops too. +1. ตรวจสอบว่า `num <= 100` +2. ตรวจสอบอีกชั้นด้วย `&& num` หาก `num` เป็น falsy value เช่น `null` หรือสตริงเปล่า ลูบ `while` ก็จะหยุดทำงาน -P.S. If `num` is `null` then `num <= 100` is `true`, so without the 2nd check the loop wouldn't stop if the user clicks CANCEL. Both checks are required. +ปล. หากตัวแปร `num` เป็น `null` เมื่อเทียบว่า `num <= 100` จะเป็นจริง `true` นั่นก็เพราะว่า หากเป็นการเปรียบเทียบ `null` จะถูกแปลงเป็นตัวเลขซึ่งก็คือศูนย์ ดูคุ้นๆไหม ดังนั้นหากเราไม่มีการตรวจสอบ 2 ชั้น ลูบจะไม่หยุดทำงานจนกว่าผู้ใช้จะกด CANCEL เราจึงจำเป็นต้องมีการตรวจสอบชั้น 2 ด้วย diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md index 0788ee76e..d488cce56 100644 --- a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md @@ -2,12 +2,12 @@ importance: 5 --- -# Repeat until the input is correct +# มาลองเขียนลูบที่ถามผู้ใช้ไปเรื่อยๆจนกว่าจะป้อนข้อความที่ถูกต้องกัน -Write a loop which prompts for a number greater than `100`. If the visitor enters another number -- ask them to input again. +เขียนลูบที่ให้ผู้ใช้ป้อนตัวเลขมากกว่า `100` หากผู้ใช้ป้อนตัวเลขที่ต่ำกว่าก็ให้ถามผู้ใช้ไปเรื่อยๆจนกว่าจะป้อนถูก -The loop must ask for a number until either the visitor enters a number greater than `100` or cancels the input/enters an empty line. +ลูบจะต้องวนซ้ำไปเรื่อยๆจนกว่าผู้ใช้จะป้อนตัวเลขมากกว่า `100` หรือยกเลิกการป้อนข้อมูลหรือป้อนบรรทัดว่าง -Here we can assume that the visitor only inputs numbers. There's no need to implement a special handling for a non-numeric input in this task. +เราจะให้ผู้ใช้ป้อนเฉพาะตัวเลขเท่านั้น ดังนั้นไม่จำเป็นต้องเขียนแปลงชนิดของข้อมูลอื่นๆให้เป็นตัวเลข [demo] diff --git a/1-js/02-first-steps/13-while-for/7-list-primes/solution.md b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md index b4b64b6fa..1a233404b 100644 --- a/1-js/02-first-steps/13-while-for/7-list-primes/solution.md +++ b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md @@ -1,6 +1,6 @@ -There are many algorithms for this task. +สำหรับการบ้านนี้เราสามารถทำได้หลากหลายวิธี -Let's use a nested loop: +ลองมาเริ่มจากใช้ลูบซ้อนลูบก่อน: ```js For each i in the interval { @@ -10,7 +10,7 @@ For each i in the interval { } ``` -The code using a label: +ลองใช้ label: ```js run let n = 10; @@ -19,11 +19,11 @@ nextPrime: for (let i = 2; i <= n; i++) { // for each i... for (let j = 2; j < i; j++) { // look for a divisor.. - if (i % j == 0) continue nextPrime; // not a prime, go next i + if (i % j == 0) continue nextPrime; // หากไม่เป็นจำนวนเฉพาะ ไป i ตัวต่อไป } - alert( i ); // a prime + alert( i ); // ตรงนี้จะได้จำนวนเฉพาะ } ``` -There's a lot of space to optimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. +มีวิธีมากมายที่ช่วยเพิ่มประสิทธิภาพให้กับโค้ดชุดนี้ เมื่อ `n` ใหญ่ขึ้น เช่น เราสามารถมองหาตัวหารจาก `2` ถึงรากที่สองของ `i` แต่อย่างไรก็ตามหาก `n` ของเราใหญ่มากๆ เราก็จำเป้นที่จะต้องใช้วิธีการที่ซับซ้อนมากขึ้น เพื่อให้โปรแกรมทำงานได้มีประสิทธิภาพสูงสุด โดยอาศัยหลักการทางคณิตศาสตร์และอัลกอรึทึ่มเข้าช่วยเช่น [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) และอื่นๆ diff --git a/1-js/02-first-steps/13-while-for/7-list-primes/task.md b/1-js/02-first-steps/13-while-for/7-list-primes/task.md index 6344b9f6f..f0a7a00dd 100644 --- a/1-js/02-first-steps/13-while-for/7-list-primes/task.md +++ b/1-js/02-first-steps/13-while-for/7-list-primes/task.md @@ -2,16 +2,16 @@ importance: 3 --- -# Output prime numbers +# แสดงจำนวนเฉพาะ (prime numbers) -An integer number greater than `1` is called a [prime](https://en.wikipedia.org/wiki/Prime_number) if it cannot be divided without a remainder by anything except `1` and itself. +เราจะเรียกจำนวนเต็มที่มากกว่า `1` และไม่สามารถหารเลขอื่นลงตัวได้ (ไม่มีเศษ) ยกเว้น `1` กับตัวมันเองว่า[จำนวนเฉพาะ](https://en.wikipedia.org/wiki/Prime_number) -In other words, `n > 1` is a prime if it can't be evenly divided by anything except `1` and `n`. +หรือก็คือเมื่อ `n > 1` จะเป็นจำนวนเฉพาะหาก `n` ไม่สามารถหารเลขอื่นลงตัวได้ ยกเว้น `1` กับ `n` -For example, `5` is a prime, because it cannot be divided without a remainder by `2`, `3` and `4`. +ตัวอย่าง `5` เป็นจำนวนเฉพาะ เพราะว่า `5` ไม่สามารถหาร `2`, `3` และ `4` ลงตัวได้ -**Write the code which outputs prime numbers in the interval from `2` to `n`.** +**จงเขียนโค้ดที่แสดงจำนวนเฉพาะจาก `2` ถึง `n`** -For `n = 10` the result will be `2,3,5,7`. +หาก `n = 10` ผลลัพธ์ที่ได้ควรเป็น `2,3,5,7`. -P.S. The code should work for any `n`, not be hard-tuned for any fixed value. +ปล. โค้ดที่เขียนควรใช้ได้กับ `n` ที่เป็นเลขจำนวนเต็มใดๆก็ได้ diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md index b3e3953b8..ad58df196 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -1,66 +1,78 @@ -# Loops: while and for +# ลูป: while และ for -We often need to repeat actions. +บ่อยครั้งที่เราต้องทำสิ่งเดิมซ้ำๆ -For example, outputting goods from a list one after another or just running the same code for each number from 1 to 10. +ตัวอย่างเช่น แสดงผลสินค้าจากรายการทีละชิ้น หรืออาจเป็นแค่การรันโค้ดเดิมซ้ำๆ สำหรับทุกตัวเลขตั้งแต่ 1 ถึง 10 -*Loops* are a way to repeat the same code multiple times. +*ลูป* คือวิธีการทำให้โค้ดทำงานซ้ำหลายรอบ -## The "while" loop +````smart header="ลูป for..of และ for..in" +ข้อความสำหรับผู้อ่านระดับสูงเล็กน้อย -The `while` loop has the following syntax: +บทความนี้จะกล่าวถึงเฉพาะลูปพื้นฐาน ได้แก่ `while`, `do..while` และ `for(..;..;..)` + +หากคุณมาที่บทความนี้เพื่อหาข้อมูลเกี่ยวกับลูปประเภทอื่นๆ สามารถดูได้จาก: + +- [for..in](info:object#forin) สำหรับการวนลูปเพื่อเข้าถึงพร็อพเพอร์ตี้ของออบเจ็กต์ +- [for..of](info:array#loops) และ [iterable](info:iterable) สำหรับการวนลูปอาร์เรย์และออบเจ็กต์ที่สามารถวนซ้ำได้ (iterable objects) + +หากไม่ใช่ กรุณาอ่านต่อไปได้เลยครับ +```` + +## ลูป "while" + +ลูป `while` มีไวยากรณ์ดังนี้: ```js while (condition) { - // code - // so-called "loop body" + // โค้ด + // เรียกว่า "loop body" } ``` -While the `condition` is truthy, the `code` from the loop body is executed. +ตราบใดที่ `condition` เป็นจริง โค้ดใน loop body ก็จะทำงาน -For instance, the loop below outputs `i` while `i < 3`: +ยกตัวอย่างเช่น ลูปด้านล่างจะแสดงค่า `i` ตราบเท่าที่ `i < 3`: ```js run let i = 0; -while (i < 3) { // shows 0, then 1, then 2 +while (i < 3) { // แสดง 0, แล้ว 1, แล้ว 2 alert( i ); i++; } ``` -A single execution of the loop body is called *an iteration*. The loop in the example above makes three iterations. +การที่ loop body ทำงานหนึ่งรอบเรียกว่า *การวนซ้ำ (iteration)* ลูปในตัวอย่างข้างต้นวนซ้ำทั้งหมดสามรอบ -If `i++` was missing from the example above, the loop would repeat (in theory) forever. In practice, the browser provides ways to stop such loops, and in server-side JavaScript, we can kill the process. +ถ้าไม่มีคำสั่ง `i++` ในตัวอย่างข้างต้น ลูปจะวนซ้ำไปเรื่อยๆ ในทางทฤษฎี แต่ในทางปฏิบัติ เบราว์เซอร์มีวิธีหยุดลูปแบบนี้ได้ และใน JavaScript ฝั่งเซิร์ฟเวอร์ก็สามารถยุติกระบวนการได้เช่นกัน -Any expression or variable can be a loop condition, not just comparisons: the condition is evaluated and converted to a boolean by `while`. +เงื่อนไขลูปเป็นนิพจน์หรือตัวแปรอะไรก็ได้ ไม่จำกัดแค่การเปรียบเทียบ — `while` จะประเมินเงื่อนไขและแปลงเป็นบูลีนเอง -For instance, a shorter way to write `while (i != 0)` is `while (i)`: +ตัวอย่างเช่น วิธีที่สั้นกว่าในการเขียน `while (i != 0)` คือ `while (i)`: ```js run let i = 3; *!* -while (i) { // when i becomes 0, the condition becomes falsy, and the loop stops +while (i) { // เมื่อ i เป็น 0, เงื่อนไขจะเป็นเท็จ และลูปจะหยุด */!* alert( i ); i--; } ``` -````smart header="Curly braces are not required for a single-line body" -If the loop body has a single statement, we can omit the curly braces `{…}`: +````smart header="ไม่จำเป็นต้องใช้วงเล็บปีกกาสำหรับ loop body ที่มีคำสั่งเดียว" +หาก loop body มีเพียงคำสั่งเดียว เราสามารถละวงเล็บปีกกา `{…}` ได้: ```js run let i = 3; *!* while (i) alert(i--); */!* -``` ```` -## The "do..while" loop +## ลูป "do..while" -The condition check can be moved *below* the loop body using the `do..while` syntax: +เราสามารถย้ายการตรวจสอบเงื่อนไขไปอยู่*ด้านล่าง*ของ loop body ได้ด้วยการใช้ไวยากรณ์ `do..while`: ```js do { @@ -68,9 +80,9 @@ do { } while (condition); ``` -The loop will first execute the body, then check the condition, and, while it's truthy, execute it again and again. +ลูปจะทำงาน body ก่อน แล้วค่อยตรวจสอบเงื่อนไข หากเงื่อนไขเป็นจริงก็วนซ้ำต่อไปเรื่อยๆ -For example: +ตัวอย่างเช่น: ```js run let i = 0; @@ -80,13 +92,13 @@ do { } while (i < 3); ``` -This form of syntax should only be used when you want the body of the loop to execute **at least once** regardless of the condition being truthy. Usually, the other form is preferred: `while(…) {…}`. +ไวยากรณ์ในรูปแบบนี้ควรใช้เฉพาะเมื่อต้องการให้ body ของลูปทำงาน**อย่างน้อยหนึ่งครั้ง** โดยไม่คำนึงว่าเงื่อนไขจะเป็นจริงหรือไม่ โดยทั่วไปแล้วมักจะนิยมใช้รูปแบบอื่นมากกว่า เช่น `while(…) {…}` -## The "for" loop +## ลูป "for" -The `for` loop is more complex, but it's also the most commonly used loop. +ลูป `for` มีความซับซ้อนกว่า แต่ก็เป็นลูปที่ใช้บ่อยที่สุดเช่นกัน -It looks like this: +รูปแบบเป็นดังนี้: ```js for (begin; condition; step) { @@ -94,24 +106,24 @@ for (begin; condition; step) { } ``` -Let's learn the meaning of these parts by example. The loop below runs `alert(i)` for `i` from `0` up to (but not including) `3`: +มาทำความเข้าใจแต่ละส่วนผ่านตัวอย่างกัน ลูปด้านล่างจะรัน `alert(i)` สำหรับ `i` ตั้งแต่ `0` จนถึง (แต่ไม่รวม) `3`: ```js run -for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2 +for (let i = 0; i < 3; i++) { // แสดง 0, แล้ว 1, แล้ว 2 alert(i); } ``` -Let's examine the `for` statement part-by-part: +มาดูประโยค `for` ทีละส่วนกัน: -| part | | | +| ส่วน | | | |-------|----------|----------------------------------------------------------------------------| -| begin | `i = 0` | Executes once upon entering the loop. | -| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | -| body | `alert(i)`| Runs again and again while the condition is truthy. | -| step| `i++` | Executes after the body on each iteration. | +| begin | `let i = 0` | ทำงานครั้งเดียวเมื่อเริ่มเข้าลูป | +| condition | `i < 3`| ตรวจสอบก่อนการวนลูปทุกรอบ ถ้าเป็นเท็จ ลูปจะหยุด | +| body | `alert(i)`| ทำซ้ำไปเรื่อยๆ ตราบเท่าที่เงื่อนไขเป็นจริง | +| step| `i++` | ทำงานหลัง body ในแต่ละรอบ | -The general loop algorithm works like this: +ขั้นตอนการทำงานของลูปโดยทั่วไปเป็นดังนี้: ``` Run begin @@ -121,68 +133,65 @@ Run begin → ... ``` -That is, `begin` executes once, and then it iterates: after each `condition` test, `body` and `step` are executed. +กล่าวคือ `begin` ทำงานแค่ครั้งเดียว จากนั้นก็เข้าสู่ลูป: หลังตรวจสอบ `condition` แต่ละครั้ง `body` และ `step` จะทำงานตามลำดับ -If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper. +ถ้าเพิ่งเริ่มเรียนเรื่องลูป ลองย้อนกลับไปดูตัวอย่างแล้วเขียนไล่ทีละขั้นตอนบนกระดาษ — ช่วยให้เข้าใจได้ดีขึ้นมากเลย -Here's exactly what happens in our case: +ต่อไปนี้คือสิ่งที่เกิดขึ้นจริงๆ ในกรณีของเรา: ```js // for (let i = 0; i < 3; i++) alert(i) -// run begin +// รัน begin let i = 0 -// if condition → run body and run step +// ถ้า condition → รัน body และ step if (i < 3) { alert(i); i++ } -// if condition → run body and run step +// ถ้า condition → รัน body และ step if (i < 3) { alert(i); i++ } -// if condition → run body and run step +// ถ้า condition → รัน body และ step if (i < 3) { alert(i); i++ } -// ...finish, because now i == 3 +// ...จบ เพราะตอนนี้ i == 3 ``` -````smart header="Inline variable declaration" -Here, the "counter" variable `i` is declared right in the loop. This is called an "inline" variable declaration. Such variables are visible only inside the loop. +```smart header="การประกาศตัวแปรอินไลน์" +ในที่นี้ ตัวแปร "ตัวนับ" `i` ถูกประกาศภายในลูปโดยตรง เรียกว่าการประกาศตัวแปรแบบ "อินไลน์" ตัวแปรแบบนี้จะมองเห็นได้เฉพาะภายในลูปเท่านั้น ```js run for (*!*let*/!* i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } -alert(i); // error, no such variable +alert(i); // error, ไม่มีตัวแปรนี้ ``` -Instead of defining a variable, we could use an existing one: +แทนที่จะประกาศตัวแปรใหม่ เราสามารถใช้ตัวแปรที่มีอยู่แล้วก็ได้: ```js run let i = 0; -for (i = 0; i < 3; i++) { // use an existing variable +for (i = 0; i < 3; i++) { // ใช้ตัวแปรที่มีอยู่ alert(i); // 0, 1, 2 } -alert(i); // 3, visible, because declared outside of the loop +alert(i); // 3, มองเห็นได้ เพราะประกาศไว้นอกลูป ``` -```` - - -### Skipping parts +### การข้ามบางส่วน -Any part of `for` can be skipped. +ส่วนใดๆ ของ `for` ก็ข้ามได้ -For example, we can omit `begin` if we don't need to do anything at the loop start. +ตัวอย่างเช่น เราสามารถละส่วน `begin` ได้ถ้าไม่จำเป็นต้องทำอะไรตอนเริ่มลูป -Like here: +แบบนี้: ```js run -let i = 0; // we have i already declared and assigned +let i = 0; // เรามีการประกาศและกำหนดค่า i อยู่แล้ว -for (; i < 3; i++) { // no need for "begin" +for (; i < 3; i++) { // ไม่ต้องมีส่วน "begin" alert( i ); // 0, 1, 2 } ``` -We can also remove the `step` part: +เราสามารถเอาส่วน `step` ออกได้ด้วย: ```js run let i = 0; @@ -192,25 +201,25 @@ for (; i < 3;) { } ``` -This makes the loop identical to `while (i < 3)`. +ลูปแบบนี้จะเหมือนกับ `while (i < 3)` -We can actually remove everything, creating an infinite loop: +จริงๆ แล้วเราสามารถเอาทุกอย่างออกได้ เพื่อสร้างลูปไม่รู้จบ: ```js for (;;) { - // repeats without limits + // วนซ้ำไม่มีที่สิ้นสุด } ``` -Please note that the two `for` semicolons `;` must be present. Otherwise, there would be a syntax error. +โปรดสังเกตว่าจะต้องมีอัฒภาค `;` ทั้งสองตัวของ `for` อยู่เสมอ ไม่เช่นนั้นจะเกิด syntax error -## Breaking the loop +## การออกจากลูป -Normally, a loop exits when its condition becomes falsy. +โดยปกติแล้ว ลูปจะจบลงเมื่อเงื่อนไขเป็นเท็จ -But we can force the exit at any time using the special `break` directive. +แต่เราสามารถบังคับให้ออกจากลูปได้ทุกเมื่อด้วยการใช้คำสั่งพิเศษ `break` -For example, the loop below asks the user for a series of numbers, "breaking" when no number is entered: +ยกตัวอย่างเช่น ลูปด้านล่างจะถามผู้ใช้ให้ป้อนตัวเลขเป็นชุด และจะ "break" เมื่อไม่มีการป้อนตัวเลข: ```js run let sum = 0; @@ -229,32 +238,32 @@ while (true) { alert( 'Sum: ' + sum ); ``` -The `break` directive is activated at the line `(*)` if the user enters an empty line or cancels the input. It stops the loop immediately, passing control to the first line after the loop. Namely, `alert`. +คำสั่ง `break` จะทำงานที่บรรทัด `(*)` ถ้าผู้ใช้ป้อนบรรทัดว่างหรือกด cancel — จะหยุดลูปทันทีและส่งการควบคุมไปที่บรรทัดแรกหลังลูป นั่นคือ `alert` -The combination "infinite loop + `break` as needed" is great for situations when a loop's condition must be checked not in the beginning or end of the loop, but in the middle or even in several places of its body. +การใช้ "ลูปไม่รู้จบ + `break` เมื่อต้องการ" นั้นเหมาะมากสำหรับสถานการณ์ที่ต้องตรวจสอบเงื่อนไขของลูป ไม่ใช่ที่จุดเริ่มต้นหรือจุดสิ้นสุดของลูป แต่อยู่ตรงกลางหรือแม้กระทั่งหลายๆ จุดในตัวลูปเอง -## Continue to the next iteration [#continue] +## การข้ามไปยังรอบถัดไป [#continue] -The `continue` directive is a "lighter version" of `break`. It doesn't stop the whole loop. Instead, it stops the current iteration and forces the loop to start a new one (if the condition allows). +คำสั่ง `continue` เป็น "เวอร์ชันที่เบากว่า" ของ `break` มันไม่ได้หยุดลูปทั้งหมด แต่จะหยุดเฉพาะรอบปัจจุบันและบังคับให้ลูปเริ่มรอบใหม่แทน (ถ้าเงื่อนไขอนุญาต) -We can use it if we're done with the current iteration and would like to move on to the next one. +ใช้ได้เมื่อเราจัดการรอบปัจจุบันเสร็จแล้วและอยากข้ามไปรอบถัดไปเลย -The loop below uses `continue` to output only odd values: +ลูปด้านล่างใช้ `continue` เพื่อแสดงผลเฉพาะค่าที่เป็นเลขคี่: ```js run no-beautify for (let i = 0; i < 10; i++) { - // if true, skip the remaining part of the body + // ถ้าเป็นจริง ข้ามส่วนที่เหลือของ body *!*if (i % 2 == 0) continue;*/!* - alert(i); // 1, then 3, 5, 7, 9 + alert(i); // 1, แล้วก็ 3, 5, 7, 9 } ``` -For even values of `i`, the `continue` directive stops executing the body and passes control to the next iteration of `for` (with the next number). So the `alert` is only called for odd values. +เมื่อ `i` เป็นเลขคู่ `continue` จะหยุด body แล้วส่งการควบคุมไปยังรอบถัดไปของ `for` (พร้อมตัวเลขถัดไป) ทำให้ `alert` แสดงผลเฉพาะค่าเลขคี่เท่านั้น -````smart header="The `continue` directive helps decrease nesting" -A loop that shows odd values could look like this: +````smart header="คำสั่ง `continue` ช่วยลดความซับซ้อนของโค้ด" +ลูปที่แสดงค่าที่เป็นเลขคี่อาจเขียนได้แบบนี้: ```js run for (let i = 0; i < 10; i++) { @@ -266,15 +275,15 @@ for (let i = 0; i < 10; i++) { } ``` -From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`. +ในแง่เทคนิค โค้ดนี้เหมือนกับตัวอย่างด้านบนทุกประการ ห่อโค้ดไว้ในบล็อก `if` แทนการใช้ `continue` ก็ได้ -But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability. +แต่ผลข้างเคียงคือ โค้ดจะซ้อนลึกขึ้นอีกหนึ่งชั้น (การเรียก `alert` อยู่ภายในวงเล็บปีกกา) ถ้าโค้ดภายใน `if` ยาวกว่าสองสามบรรทัด อาจทำให้อ่านโค้ดโดยรวมลำบากขึ้น ```` -````warn header="No `break/continue` to the right side of '?'" -Please note that syntax constructs that are not expressions cannot be used with the ternary operator `?`. In particular, directives such as `break/continue` aren't allowed there. +````warn header="ไม่มี `break/continue` ที่ด้านขวาของ '?'" +สังเกตว่า โครงสร้างทางไวยากรณ์ที่ไม่ใช่นิพจน์จะใช้กับ ternary operator `?` ไม่ได้ โดยเฉพาะ `break/continue` ไม่อนุญาตให้ใช้ในตำแหน่งนั้น -For example, if we take this code: +ตัวอย่างเช่น ถ้าเรามีโค้ดแบบนี้: ```js if (i > 5) { @@ -284,23 +293,22 @@ if (i > 5) { } ``` -...and rewrite it using a question mark: - +...แล้วเขียนใหม่โดยใช้เครื่องหมายคำถาม: ```js no-beautify -(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here +(i > 5) ? alert(i) : *!*continue*/!*; // continue ไม่ได้รับอนุญาตที่นี่ ``` -...it stops working: there's a syntax error. +...โค้ดจะหยุดทำงาน: เกิด syntax error ขึ้น -This is just another reason not to use the question mark operator `?` instead of `if`. +นี่เป็นอีกเหตุผลที่ไม่ควรใช้ ternary operator `?` แทน `if` ```` -## Labels for break/continue +## Labels สำหรับ break/continue -Sometimes we need to break out from multiple nested loops at once. +บางครั้งเราอาจต้องออกจากลูปที่ซ้อนกันหลายชั้นในคราวเดียว -For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(2,2)`: +ตัวอย่างเช่น ในโค้ดด้านล่าง เราวนลูปด้วย `i` และ `j` เพื่อถามหาค่าที่พิกัด `(i, j)` ตั้งแต่ `(0,0)` ถึง `(2,2)`: ```js run no-beautify for (let i = 0; i < 3; i++) { @@ -309,25 +317,26 @@ for (let i = 0; i < 3; i++) { let input = prompt(`Value at coords (${i},${j})`, ''); - // what if we want to exit from here to Done (below)? + // จะทำอย่างไรถ้าเราต้องการออกจากที่นี่ไปสู่ Done (ด้านล่าง)? } } alert('Done!'); ``` -We need a way to stop the process if the user cancels the input. +จำเป็นต้องมีวิธีหยุดกระบวนการนี้เมื่อผู้ใช้ยกเลิกการป้อนข้อมูล + +การใช้ `break` ธรรมดาหลัง `input` จะออกจากลูปชั้นในเท่านั้น — ยังไม่เพียงพอ ตรงนี้แหละที่ labels เข้ามาช่วย! -The ordinary `break` after `input` would only break the inner loop. That's not sufficient--labels, come to the rescue! +*label* คือตัวระบุที่มีเครื่องหมายโคลอนอยู่ข้างหน้าลูป: -A *label* is an identifier with a colon before a loop: ```js labelName: for (...) { ... } ``` -The `break ` statement in the loop below breaks out to the label: +คำสั่ง `break ` ในลูปด้านล่างจะออกไปสู่ label: ```js run no-beautify *!*outer:*/!* for (let i = 0; i < 3; i++) { @@ -336,51 +345,65 @@ The `break ` statement in the loop below breaks out to the label: let input = prompt(`Value at coords (${i},${j})`, ''); - // if an empty string or canceled, then break out of both loops + // ถ้าเป็นสตริงว่างหรือกด cancel ให้ออกจากลูปทั้งสอง if (!input) *!*break outer*/!*; // (*) - // do something with the value... + // ทำอะไรบางอย่างกับค่าที่ได้... } } + alert('Done!'); ``` -In the code above, `break outer` looks upwards for the label named `outer` and breaks out of that loop. +ในโค้ดด้านบน `break outer` จะมองหา label ชื่อ `outer` ขึ้นไป แล้วออกจากลูปนั้น -So the control goes straight from `(*)` to `alert('Done!')`. +ดังนั้นการควบคุมจะข้ามจาก `(*)` ไปสู่ `alert('Done!')` โดยตรง -We can also move the label onto a separate line: +เราสามารถย้าย label ไปอยู่บนบรรทัดแยกต่างหากได้ด้วย: ```js no-beautify outer: for (let i = 0; i < 3; i++) { ... } ``` -The `continue` directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop. +คำสั่ง `continue` ก็ใช้ร่วมกับ label ได้เช่นกัน ในกรณีนี้ การทำงานจะข้ามไปยังรอบถัดไปของลูปที่มี label กำกับ -````warn header="Labels do not allow to \"jump\" anywhere" -Labels do not allow us to jump into an arbitrary place in the code. +````warn header="Labels ไม่อนุญาตให้ \"กระโดด\" ไปที่ใดก็ได้" +Labels ไม่ได้อนุญาตให้กระโดดไปยังตำแหน่งใดๆ ก็ได้ในโค้ด + +ตัวอย่างเช่น เราไม่สามารถทำแบบนี้ได้: -For example, it is impossible to do this: ```js -break label; // doesn't jumps to the label below +break label; // กระโดดไปสู่ label ด้านล่าง (ใช้ไม่ได้) label: for (...) ``` -A call to `break/continue` is only possible from inside a loop and the label must be somewhere above the directive. +คำสั่ง `break` ต้องอยู่ภายในบล็อกโค้ด ในทางเทคนิค บล็อกโค้ดใดๆ ที่มี label กำกับก็ใช้ได้ เช่น: + +```js +label: { + // ... + break label; // ใช้ได้ + // ... +} +``` + +...อย่างไรก็ตาม ในทางปฏิบัติ 99.9% ของเวลา `break` จะอยู่ภายในลูป เหมือนที่เห็นในตัวอย่างด้านบน + +ส่วน `continue` จะใช้ได้เฉพาะภายในลูปเท่านั้น ```` -## Summary +## สรุป -We covered 3 types of loops: +เราได้ครอบคลุมลูป 3 ประเภท: -- `while` -- The condition is checked before each iteration. -- `do..while` -- The condition is checked after each iteration. -- `for (;;)` -- The condition is checked before each iteration, additional settings available. +- `while` -- ตรวจสอบเงื่อนไขก่อนการวนลูปแต่ละรอบ +- `do..while` -- ตรวจสอบเงื่อนไขหลังการวนลูปแต่ละรอบ +- `for (;;)` -- ตรวจสอบเงื่อนไขก่อนการวนลูปแต่ละรอบ สามารถกำหนดค่าเริ่มต้นและปรับแต่งเพิ่มเติมได้ -To make an "infinite" loop, usually the `while(true)` construct is used. Such a loop, just like any other, can be stopped with the `break` directive. +ในการสร้างลูป "ไม่มีที่สิ้นสุด" มักใช้โครงสร้าง `while(true)` ลูปแบบนี้ เช่นเดียวกับลูปอื่นๆ หยุดได้ด้วยคำสั่ง `break` -If we don't want to do anything in the current iteration and would like to forward to the next one, we can use the `continue` directive. +ถ้าไม่อยากทำอะไรในรอบปัจจุบันและต้องการข้ามไปรอบถัดไปเลย ใช้คำสั่ง `continue` ได้ -`break/continue` support labels before the loop. A label is the only way for `break/continue` to escape a nested loop to go to an outer one. +`break/continue` รองรับการใช้ label ที่อยู่ข้างหน้าลูป — label คือวิธีเดียวที่ทำให้ `break/continue` ออกจากลูปซ้อนไปยังลูปชั้นนอกได้ diff --git a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md index d3e397434..91c1626c5 100644 --- a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md +++ b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md @@ -1,6 +1,6 @@ -To precisely match the functionality of `switch`, the `if` must use a strict comparison `'==='`. +เพื่อที่จะให้ตรงกับการทำงานของคำสั่ง `switch`, คำสั่ง `if` จึงต้องให้ `'==='` -For given strings though, a simple `'=='` works too. +ตามสตริงที่ให้มา `'=='` ก็ใช้ได้งานตามที่คาดหวังเช่นกัน ```js no-beautify if(browser == 'Edge') { @@ -15,6 +15,6 @@ if(browser == 'Edge') { } ``` -Please note: the construct `browser == 'Chrome' || browser == 'Firefox' …` is split into multiple lines for better readability. +โปรดทราบ: รูปประโยค `browser == 'Chrome' || browser == 'Firefox' …` ควรแบ่งเป็นหลายๆบรรทัดเพื่อให้อ่านง่ายขึ้น -But the `switch` construct is still cleaner and more descriptive. +แต่อย่างไรก็ตาม ในกรณีนี้คำสั่ง `switch` ก็ดูสะอาดและเข้าใจง่ายกว่า diff --git a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md index f4dc0e5f1..20b050482 100644 --- a/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md +++ b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Rewrite the "switch" into an "if" +# ลองมาแปลงคำสั่งจาก "switch" เป็น "if" -Write the code using `if..else` which would correspond to the following `switch`: +เขียนโค้ดโดยใช้ `if..else` โดยให้สอดคล้องกับ `switch` ด้านล่าง: ```js switch (browser) { diff --git a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md index ed87dd94b..55fc36243 100644 --- a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md +++ b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md @@ -1,4 +1,4 @@ -The first two checks turn into two `case`. The third check is split into two cases: +ขั้นแรกมาเปลี่ยน if ตัวสองแรกเป็น `case` กันก่อน เพราะว่า if ตัวที่สามเราต้องใช้แยกออกมาเป็นสอง `case`: ```js run let a = +prompt('a?', ''); @@ -21,6 +21,6 @@ switch (a) { } ``` -Please note: the `break` at the bottom is not required. But we put it to make the code future-proof. +โปรดจำจงไว้: คำสั่ง `break` ที่บรรทัดล่างสุดไม่จำเป็นต้องใส่ก็ได้ แต่ทางเราเขียนไว้เผื่ออนาคต -In the future, there is a chance that we'd want to add one more `case`, for example `case 4`. And if we forget to add a break before it, at the end of `case 3`, there will be an error. So that's a kind of self-insurance. +เพราะว่าอนาคต มีโอกาสที่เราจะเพิ่ม `case` ขึ้นมาอีก ตัวอย่างเช่น `case 4` และเราลืมใส่คำสั่ง `break` ไว้ท้าย `case 3` ซึ่งจะทำให้เกิดผลลัพธ์ที่ไม่คาดหวัง ดังนั้นเพื่อรับประกันว่าโค้ดทุกอย่างยังคงทำงานอย่างถูกต้อง เราจึงใส่ไว้นั่นเอง diff --git a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md index ec99d098d..697867b5b 100644 --- a/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md +++ b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md @@ -2,9 +2,9 @@ importance: 4 --- -# Rewrite "if" into "switch" +# ลองมาแปลงคำสั่งจาก "if" เป็น "switch" -Rewrite the code below using a single `switch` statement: +ลองเขียนโค้ดด้านล่างใหม่โดยใช้คำสั่ง `switch` กัน: ```js run let a = +prompt('a?', ''); diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md index 314c6cef8..ed5b7b824 100644 --- a/1-js/02-first-steps/14-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -1,22 +1,22 @@ -# The "switch" statement +# คำสั่ง "switch" -A `switch` statement can replace multiple `if` checks. +คำสั่ง `switch` สามารถใช้แทนการตรวจสอบด้วย `if` หลายๆ ครั้งได้ -It gives a more descriptive way to compare a value with multiple variants. +เป็นวิธีเปรียบเทียบค่ากับตัวเลือกหลายๆ ตัวที่อ่านเข้าใจง่ายกว่า -## The syntax +## ไวยากรณ์ -The `switch` has one or more `case` blocks and an optional default. +`switch` จะมีหนึ่งหรือหลายบล็อก `case` และมี `default` เป็นตัวเลือกเสริม -It looks like this: +มีรูปแบบดังนี้: ```js no-beautify switch(x) { - case 'value1': // if (x === 'value1') + case 'value1': // ถ้า (x === 'value1') ... [break] - case 'value2': // if (x === 'value2') + case 'value2': // ถ้า (x === 'value2') ... [break] @@ -26,71 +26,71 @@ switch(x) { } ``` -- The value of `x` is checked for a strict equality to the value from the first `case` (that is, `value1`) then to the second (`value2`) and so on. -- If the equality is found, `switch` starts to execute the code starting from the corresponding `case`, until the nearest `break` (or until the end of `switch`). -- If no case is matched then the `default` code is executed (if it exists). +- `switch` จะตรวจสอบความเท่ากันอย่างเข้มงวดระหว่าง `x` กับค่าของ `case` แรก (คือ `value1`) จากนั้นเป็น `case` ที่สอง (`value2`) และต่อไปเรื่อยๆ +- ถ้าพบ `case` ที่ตรงกัน จะเริ่มรันโค้ดจาก `case` นั้น จนกว่าจะเจอ `break` ที่ใกล้ที่สุด (หรือจนจบ `switch`) +- ถ้าไม่ตรงกับ `case` ใดเลย โค้ดใน `default` จะทำงานแทน (ถ้ามี) -## An example +## ตัวอย่าง -An example of `switch` (the executed code is highlighted): +ตัวอย่างของ `switch` (โค้ดส่วนที่ทำงานจะถูกไฮไลต์ไว้): ```js run let a = 2 + 2; switch (a) { case 3: - alert( 'Too small' ); + alert( 'เล็กไป' ); break; *!* case 4: - alert( 'Exactly!' ); + alert( 'ถูกต้อง!' ); break; */!* case 5: - alert( 'Too large' ); + alert( 'ใหญ่ไป' ); break; default: - alert( "I don't know such values" ); + alert( "ไม่รู้จักค่านี้" ); } ``` -Here the `switch` starts to compare `a` from the first `case` variant that is `3`. The match fails. +`switch` จะเริ่มเปรียบเทียบ `a` กับ `case` แรกคือ `3` ซึ่งไม่ตรงกัน -Then `4`. That's a match, so the execution starts from `case 4` until the nearest `break`. +จากนั้นเป็น `4` — ตรงกัน! โค้ดจึงเริ่มรันจาก `case 4` จนกว่าจะเจอ `break` -**If there is no `break` then the execution continues with the next `case` without any checks.** +**ถ้าไม่มี `break` โค้ดจะรันต่อไปยัง `case` ถัดไปเลยโดยไม่ตรวจสอบอะไร** -An example without `break`: +ตัวอย่างที่ไม่มี `break`: ```js run let a = 2 + 2; switch (a) { case 3: - alert( 'Too small' ); + alert( 'เล็กไป' ); *!* case 4: - alert( 'Exactly!' ); + alert( 'ถูกต้อง!' ); case 5: - alert( 'Too big' ); - default: - alert( "I don't know such values" ); + alert( 'ใหญ่ไป' ); + default: + alert( "ไม่รู้จักค่านี้" ); */!* } ``` -In the example above we'll see sequential execution of three `alert`s: +ในตัวอย่างข้างบน จะเห็น `alert` สามอันทำงานต่อเนื่องกัน: ```js -alert( 'Exactly!' ); -alert( 'Too big' ); -alert( "I don't know such values" ); +alert( 'ถูกต้อง!' ); +alert( 'ใหญ่ไป' ); +alert( "ไม่รู้จักค่านี้" ); ``` -````smart header="Any expression can be a `switch/case` argument" -Both `switch` and `case` allow arbitrary expressions. +````smart header="นิพจน์อะไรก็ใช้เป็นอาร์กิวเมนต์ของ `switch/case` ได้" +ทั้ง `switch` และ `case` สามารถใช้นิพจน์อะไรก็ได้ -For example: +เช่น: ```js run let a = "1"; @@ -99,74 +99,74 @@ let b = 0; switch (+a) { *!* case b + 1: - alert("this runs, because +a is 1, exactly equals b+1"); + alert("นี่ทำงาน เพราะ +a เป็น 1 ซึ่งเท่ากับ b+1"); break; */!* default: - alert("this doesn't run"); + alert("นี่ไม่ทำงาน"); } ``` -Here `+a` gives `1`, that's compared with `b + 1` in `case`, and the corresponding code is executed. +`+a` ให้ค่า `1` ซึ่งตรงกับ `b + 1` ใน `case` โค้ดในบล็อกนั้นจึงทำงาน ```` -## Grouping of "case" +## การจัดกลุ่ม "case" -Several variants of `case` which share the same code can be grouped. +`case` หลายตัวที่รันโค้ดเดียวกัน สามารถจัดกลุ่มรวมกันได้ -For example, if we want the same code to run for `case 3` and `case 5`: +ตัวอย่างเช่น ถ้าเราต้องการให้โค้ดเดียวกันทำงานทั้งใน `case 3` และ `case 5`: ```js run no-beautify let a = 3; switch (a) { case 4: - alert('Right!'); + alert('ถูกต้อง!'); break; *!* - case 3: // (*) grouped two cases + case 3: // (*) จัดกลุ่มสอง case ไว้ด้วยกัน case 5: - alert('Wrong!'); - alert("Why don't you take a math class?"); + alert('ผิด!'); + alert("ลองไปเรียนคณิตหน่อยไหม"); break; */!* default: - alert('The result is strange. Really.'); + alert('ผลลัพธ์แปลกมากเลย'); } ``` -Now both `3` and `5` show the same message. +ตอนนี้ทั้ง `3` และ `5` จะแสดงข้อความเดียวกัน -The ability to "group" cases is a side-effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`. +ความสามารถ "จัดกลุ่ม" `case` แบบนี้เป็นผลข้างเคียงตามธรรมชาติจากพฤติกรรมของ `switch/case` เมื่อไม่มี `break` นั่นเอง — `case 3` จะเริ่มรันโค้ดจากบรรทัด `(*)` และไหลต่อผ่าน `case 5` ไปเรื่อยๆ เพราะไม่มี `break` คั่น -## Type matters +## ชนิดข้อมูลมีความสำคัญ -Let's emphasize that the equality check is always strict. The values must be of the same type to match. +จุดสำคัญที่ต้องจำไว้คือ การตรวจสอบความเท่ากันใน `switch` เป็นแบบเข้มงวด (`===`) เสมอ ค่าต้องมีชนิดข้อมูลเหมือนกันด้วยจึงจะตรงกัน -For example, let's consider the code: +ตัวอย่างเช่น ลองพิจารณาโค้ดนี้: ```js run -let arg = prompt("Enter a value?"); +let arg = prompt("ใส่ค่ามา"); switch (arg) { case '0': case '1': - alert( 'One or zero' ); + alert( 'ศูนย์หรือหนึ่ง' ); break; case '2': - alert( 'Two' ); + alert( 'สอง' ); break; case 3: - alert( 'Never executes!' ); + alert( 'จะไม่มีทางรันเลย!' ); break; default: - alert( 'An unknown value' ); + alert( 'ค่าที่ไม่รู้จัก' ); } ``` -1. For `0`, `1`, the first `alert` runs. -2. For `2` the second `alert` runs. -3. But for `3`, the result of the `prompt` is a string `"3"`, which is not strictly equal `===` to the number `3`. So we've got a dead code in `case 3`! The `default` variant will execute. +1. สำหรับ `0`, `1` จะแสดง `alert` แรก +2. สำหรับ `2` จะแสดง `alert` ที่สอง +3. แต่สำหรับ `3` ผลลัพธ์จาก `prompt` จะเป็น string `"3"` ซึ่งไม่เท่ากันอย่างเข้มงวด `===` กับตัวเลข `3` โค้ดใน `case 3` จึงไม่มีทางทำงานได้เลย — `default` จะทำงานแทน \ No newline at end of file diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md index e41c80418..3843f917d 100644 --- a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md @@ -1 +1 @@ -No difference. \ No newline at end of file +ไม่มีความแตกต่าง diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md index 4f69a5c8c..0ba7985e2 100644 --- a/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md @@ -2,11 +2,11 @@ importance: 4 --- -# Is "else" required? +# จำเป็นต้องมี "else" หรือไม่? -The following function returns `true` if the parameter `age` is greater than `18`. +ฟังก์ชั่นด้านล่างจะส่งคืนค่า `true` หากพารามิเตอร์์ `age` มีค่ามากกว่า `18` -Otherwise it asks for a confirmation and returns its result: +มิเช่นนั้น ฟังก์ชั่น `confirm` จะทำงานแล้ว ส่งคืนผลลัพธ์ที่ผู้ใช้กรอกมา ```js function checkAge(age) { @@ -21,7 +21,7 @@ function checkAge(age) { } ``` -Will the function work differently if `else` is removed? +ฟังก์ชั่นจะทำงานต่างออกไปหรือไม่หากไม่มี `else` ```js function checkAge(age) { @@ -35,4 +35,4 @@ function checkAge(age) { } ``` -Is there any difference in the behavior of these two variants? +อะไรคือความต่างกันของทั้งสองตัว diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index c8ee9618f..e48502642 100644 --- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md @@ -14,4 +14,4 @@ function checkAge(age) { } ``` -Note that the parentheses around `age > 18` are not required here. They exist for better readabilty. +Note that the parentheses around `age > 18` are not required here. They exist for better readability. diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index b12d0b9e7..a08d82988 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -1,64 +1,64 @@ -# Functions +# ฟังก์ชัน -Quite often we need to perform a similar action in many places of the script. +บ่อยครั้งที่เราต้องทำสิ่งที่คล้ายๆ กันในหลายๆ จุดของสคริปต์ -For example, we need to show a nice-looking message when a visitor logs in, logs out and maybe somewhere else. +เช่น เราอาจต้องแสดงข้อความที่ดูดีเมื่อผู้ใช้ล็อกอิน ล็อกเอาท์ หรือที่อื่นๆ -Functions are the main "building blocks" of the program. They allow the code to be called many times without repetition. +ฟังก์ชันคือ "บล็อกสร้าง" หลักของโปรแกรม ช่วยให้เรียกใช้โค้ดได้หลายครั้งโดยไม่ต้องเขียนซ้ำ -We've already seen examples of built-in functions, like `alert(message)`, `prompt(message, default)` and `confirm(question)`. But we can create functions of our own as well. +เราเคยเห็นตัวอย่างฟังก์ชันที่มีมาในตัวแล้ว เช่น `alert(message)`, `prompt(message, default)` และ `confirm(question)` แต่เราก็สามารถสร้างฟังก์ชันของตัวเองได้ด้วย -## Function Declaration +## การประกาศฟังก์ชัน -To create a function we can use a *function declaration*. +ในการสร้างฟังก์ชัน เราสามารถใช้ *การประกาศฟังก์ชัน* -It looks like this: +ซึ่งมีรูปแบบดังนี้: ```js function showMessage() { - alert( 'Hello everyone!' ); + alert( 'สวัสดีทุกคน!' ); } ``` -The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above) and finally the code of the function, also named "the function body", between curly braces. +ใช้คีย์เวิร์ด `function` ก่อน ตามด้วย*ชื่อฟังก์ชัน* แล้วก็รายการ *พารามิเตอร์* ในวงเล็บ (คั่นด้วยเครื่องหมายจุลภาค ในตัวอย่างข้างบนจะเว้นว่าง เดี๋ยวจะมีตัวอย่างอีกทีหลัง) สุดท้ายคือโค้ดของฟังก์ชันระหว่างปีกกาปิดเปิด ที่เรียกว่า "ตัวฟังก์ชัน" หรือ "function body" ```js -function name(parameters) { - ...body... +function name(parameter1, parameter2, ... parameterN) { + // ตัวฟังก์ชัน } ``` -Our new function can be called by its name: `showMessage()`. +เรียกใช้ฟังก์ชันนี้ได้ด้วยชื่อของมัน เช่น: `showMessage()` -For instance: +ดังตัวอย่าง: ```js run function showMessage() { - alert( 'Hello everyone!' ); + alert( 'สวัสดีทุกคน!' ); } *!* showMessage(); showMessage(); -*/!* +*/!* ``` -The call `showMessage()` executes the code of the function. Here we will see the message two times. +เมื่อเรียก `showMessage()` ก็จะรันโค้ดในตัวฟังก์ชัน ในที่นี้เราจะเห็นข้อความถูกแสดงสองครั้ง -This example clearly demonstrates one of the main purposes of functions: to avoid code duplication. +ตัวอย่างนี้แสดงให้เห็นวัตถุประสงค์หลักอย่างหนึ่งของฟังก์ชัน นั่นคือเพื่อหลีกเลี่ยงการเขียนโค้ดซ้ำ -If we ever need to change the message or the way it is shown, it's enough to modify the code in one place: the function which outputs it. +ถ้าต้องการเปลี่ยนข้อความหรือวิธีแสดงผล ก็แค่แก้โค้ดในที่เดียว นั่นคือในตัวฟังก์ชันที่รับหน้าที่แสดงผล -## Local variables +## ตัวแปรภายในฟังก์ชัน (Local variables) -A variable declared inside a function is only visible inside that function. +ตัวแปรที่ประกาศภายในฟังก์ชัน จะมองเห็นได้เฉพาะภายในฟังก์ชันนั้นเท่านั้น -For example: +ตัวอย่างเช่น: ```js run function showMessage() { *!* - let message = "Hello, I'm JavaScript!"; // local variable + let message = "Hello, I'm JavaScript!"; // ตัวแปรภายในฟังก์ชัน */!* alert( message ); @@ -66,12 +66,12 @@ function showMessage() { showMessage(); // Hello, I'm JavaScript! -alert( message ); // <-- Error! The variable is local to the function +alert( message ); // <-- Error! ตัวแปรอยู่ภายในฟังก์ชัน ไม่สามารถเข้าถึงจากภายนอกได้ ``` -## Outer variables +## ตัวแปรภายนอก (Outer variables) -A function can access an outer variable as well, for example: +ฟังก์ชันสามารถเข้าถึงตัวแปรภายนอกได้ด้วย เช่น: ```js run no-beautify let *!*userName*/!* = 'John'; @@ -84,84 +84,81 @@ function showMessage() { showMessage(); // Hello, John ``` -The function has full access to the outer variable. It can modify it as well. +ฟังก์ชันสามารถเข้าถึงตัวแปรภายนอกได้อย่างเต็มที่ และยังสามารถแก้ไขค่าได้ด้วย -For instance: +ตัวอย่างเช่น: ```js run let *!*userName*/!* = 'John'; function showMessage() { - *!*userName*/!* = "Bob"; // (1) changed the outer variable + *!*userName*/!* = "Bob"; // (1) เปลี่ยนค่าตัวแปรภายนอก let message = 'Hello, ' + *!*userName*/!*; alert(message); } -alert( userName ); // *!*John*/!* before the function call +alert( userName ); // *!*John*/!* ก่อนเรียกฟังก์ชัน showMessage(); -alert( userName ); // *!*Bob*/!*, the value was modified by the function +alert( userName ); // *!*Bob*/!*, ค่าถูกเปลี่ยนโดยฟังก์ชัน ``` -The outer variable is only used if there's no local one. +ตัวแปรภายนอกจะถูกใช้ก็ต่อเมื่อไม่มีตัวแปรภายในฟังก์ชันที่ชื่อเดียวกัน -If a same-named variable is declared inside the function then it *shadows* the outer one. For instance, in the code below the function uses the local `userName`. The outer one is ignored: +ถ้ามีการประกาศตัวแปรชื่อซ้ำกันภายในฟังก์ชัน ตัวแปรนั้นจะ *บดบัง* ตัวแปรภายนอก เช่น ในโค้ดด้านล่าง ฟังก์ชันจะใช้ `userName` ของตัวเอง ส่วนตัวภายนอกจะถูกมองข้าม: ```js run let userName = 'John'; function showMessage() { *!* - let userName = "Bob"; // declare a local variable + let userName = "Bob"; // ประกาศตัวแปรภายในฟังก์ชัน */!* let message = 'Hello, ' + userName; // *!*Bob*/!* alert(message); } -// the function will create and use its own userName +// ฟังก์ชันจะสร้างและใช้ userName ของตัวเอง showMessage(); -alert( userName ); // *!*John*/!*, unchanged, the function did not access the outer variable +alert( userName ); // *!*John*/!*, ไม่เปลี่ยนแปลง เพราะฟังก์ชันไม่ได้เข้าถึงตัวแปรภายนอก ``` -```smart header="Global variables" -Variables declared outside of any function, such as the outer `userName` in the code above, are called *global*. +```smart header="ตัวแปรโกลบอล (Global variables)" +ตัวแปรที่ประกาศนอกฟังก์ชันใดๆ เช่น `userName` ภายนอกในตัวอย่างข้างต้น เรียกว่า *ตัวแปรโกลบอล* -Global variables are visible from any function (unless shadowed by locals). +ตัวแปรโกลบอลจะมองเห็นได้จากทุกฟังก์ชัน (ยกเว้นจะถูกบดบังโดยตัวแปรภายในฟังก์ชัน) -It's a good practice to minimize the use of global variables. Modern code has few or no globals. Most variables reside in their functions. Sometimes though, they can be useful to store project-level data. +เป็นแนวทางปฏิบัติที่ดีที่จะลดการใช้ตัวแปรโกลบอล โค้ดสมัยใหม่มักมีตัวแปรโกลบอลน้อยมากหรือไม่มีเลย ตัวแปรส่วนใหญ่จะอยู่ภายในฟังก์ชันของตัวเอง อย่างไรก็ตาม บางครั้งตัวแปรโกลบอลก็มีประโยชน์ในการเก็บข้อมูลระดับโปรเจกต์ ``` -## Parameters +## พารามิเตอร์ (Parameters) -We can pass arbitrary data to functions using parameters (also called *function arguments*) . +เราสามารถส่งผ่านข้อมูลใดๆ ไปยังฟังก์ชันโดยใช้พารามิเตอร์ได้ -In the example below, the function has two parameters: `from` and `text`. +ในตัวอย่างด้านล่าง ฟังก์ชันมีพารามิเตอร์สองตัวคือ `from` และ `text` ```js run -function showMessage(*!*from, text*/!*) { // arguments: from, text +function showMessage(*!*from, text*/!*) { // พารามิเตอร์: from, text alert(from + ': ' + text); } -*!* -showMessage('Ann', 'Hello!'); // Ann: Hello! (*) -showMessage('Ann', "What's up?"); // Ann: What's up? (**) -*/!* +*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) +*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) ``` -When the function is called in lines `(*)` and `(**)`, the given values are copied to local variables `from` and `text`. Then the function uses them. - -Here's one more example: we have a variable `from` and pass it to the function. Please note: the function changes `from`, but the change is not seen outside, because a function always gets a copy of the value: +เมื่อฟังก์ชันถูกเรียกในบรรทัด `(*)` และ `(**)` ค่าที่ส่งเข้าไปจะถูกคัดลอกไปยังตัวแปรภายในฟังก์ชัน `from` และ `text` จากนั้นฟังก์ชันก็จะใช้ตัวแปรเหล่านั้น +นี่คืออีกตัวอย่าง: เรามีตัวแปร `from` และส่งผ่านมันเข้าไปในฟังก์ชัน สังเกตว่า ฟังก์ชันเปลี่ยนค่า `from` แต่การเปลี่ยนแปลงนั้นจะไม่ปรากฏภายนอก เพราะฟังก์ชันจะได้รับสำเนาของค่าเสมอ: ```js run function showMessage(from, text) { *!* - from = '*' + from + '*'; // make "from" look nicer + from = '*' + from + '*'; // ทำให้ "from" ดูสวยงามขึ้น */!* alert( from + ': ' + text ); @@ -171,23 +168,34 @@ let from = "Ann"; showMessage(from, "Hello"); // *Ann*: Hello -// the value of "from" is the same, the function modified a local copy -alert( from ); // Ann +// ค่าของ "from" ยังเหมือนเดิม ฟังก์ชันแก้ไขสำเนาในตัวเอง +alert( from ); // Ann ``` -## Default values +เมื่อค่าถูกส่งผ่านเข้าไปเป็นพารามิเตอร์ของฟังก์ชัน เราเรียกค่านั้นว่า *อาร์กิวเมนต์ (argument)* + +พูดง่ายๆ เพื่อให้เข้าใจคำศัพท์ชัดเจน: + +- พารามิเตอร์ คือตัวแปรที่ระบุในวงเล็บตอนประกาศฟังก์ชัน (เป็นคำศัพท์ช่วงประกาศ) +- อาร์กิวเมนต์ คือค่าที่ส่งผ่านเข้าไปในฟังก์ชันตอนเรียกใช้ (เป็นคำศัพท์ช่วงเรียกใช้) -If a parameter is not provided, then its value becomes `undefined`. +เราประกาศฟังก์ชันโดยระบุพารามิเตอร์ไว้ จากนั้นเรียกใช้โดยส่งอาร์กิวเมนต์เข้าไป -For instance, the aforementioned function `showMessage(from, text)` can be called with a single argument: +ในตัวอย่างข้างบน เราอาจพูดว่า: "ฟังก์ชัน `showMessage` ถูกประกาศด้วยพารามิเตอร์สองตัว จากนั้นถูกเรียกใช้ด้วยอาร์กิวเมนต์สองตัวคือ `from` และ `"Hello"`" + +## ค่าเริ่มต้น (Default values) + +หากฟังก์ชันถูกเรียกใช้แต่ไม่ได้ส่งอาร์กิวเมนต์มาด้วย ค่าที่สอดคล้องกันจะเป็น `undefined` + +ยกตัวอย่างเช่น ฟังก์ชัน `showMessage(from, text)` ที่กล่าวถึงก่อนหน้า สามารถเรียกใช้ด้วยอาร์กิวเมนต์เพียงตัวเดียวได้: ```js showMessage("Ann"); ``` -That's not an error. Such a call would output `"*Ann*: undefined"`. There's no `text`, so it's assumed that `text === undefined`. +นี่ไม่ใช่ข้อผิดพลาด การเรียกแบบนี้จะแสดงผลเป็น `"*Ann*: undefined"` เนื่องจากไม่ได้ส่งค่าให้กับพารามิเตอร์ `text` จึงมีค่าเป็น `undefined` -If we want to use a "default" `text` in this case, then we can specify it after `=`: +เราสามารถระบุค่าที่เรียกว่า "ค่าเริ่มต้น" (ใช้เมื่อไม่ได้ส่งค่ามา) สำหรับพารามิเตอร์ในการประกาศฟังก์ชันได้ โดยใช้ `=`: ```js run function showMessage(from, *!*text = "no text given"*/!*) { @@ -197,71 +205,112 @@ function showMessage(from, *!*text = "no text given"*/!*) { showMessage("Ann"); // Ann: no text given ``` -Now if the `text` parameter is not passed, it will get the value `"no text given"` +ตอนนี้ถ้าไม่ได้ส่งพารามิเตอร์ `text` มา ค่าของมันจะเป็น `"no text given"` + +ค่าเริ่มต้นจะถูกนำมาใช้ด้วย ถ้าพารามิเตอร์มีอยู่แต่ค่าเป็น `undefined` อย่างเคร่งครัด เช่นนี้: + +```js +showMessage("Ann", undefined); // Ann: no text given +``` -Here `"no text given"` is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible: +ในที่นี้ `"no text given"` เป็น string แต่ค่าเริ่มต้นสามารถเป็นนิพจน์ที่ซับซ้อนกว่านั้นได้ด้วย โดยจะประเมินค่าและกำหนดให้เฉพาะเมื่อไม่ได้ส่งพารามิเตอร์มาเท่านั้น เช่นนี้ก็ได้: ```js run function showMessage(from, text = anotherFunction()) { - // anotherFunction() only executed if no text given - // its result becomes the value of text + // anotherFunction() จะถูกเรียกใช้เฉพาะเมื่อไม่ได้ส่ง text มา + // ผลลัพธ์ของมันจะกลายเป็นค่าของ text } ``` -```smart header="Evaluation of default parameters" -In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. +```smart header="การประเมินค่าพารามิเตอร์เริ่มต้น" +ใน JavaScript พารามิเตอร์เริ่มต้นจะถูกประเมินค่าทุกครั้งที่เรียกใช้ฟังก์ชันโดยไม่ส่งพารามิเตอร์ที่เกี่ยวข้อง + +ในตัวอย่างข้างต้น `anotherFunction()` จะไม่ถูกเรียกใช้เลย ถ้าส่งพารามิเตอร์ `text` มา -In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter. +ในทางกลับกัน จะถูกเรียกใช้แยกต่างหากทุกครั้งที่ไม่ได้ส่ง `text` มา ``` -### Alternative default parameters +````smart header="พารามิเตอร์เริ่มต้นในโค้ด JavaScript เก่า" +เมื่อหลายปีก่อน JavaScript ไม่รองรับไวยากรณ์ของพารามิเตอร์เริ่มต้น ดังนั้นคนจึงใช้วิธีอื่นในการระบุค่าเริ่มต้น -Sometimes it makes sense to set default values for parameters not in the function declaration, but at a later stage, during its execution. +ปัจจุบันเราอาจพบเจอสิ่งเหล่านี้ในสคริปต์เก่าๆ -To check for an omitted parameter, we can compare it with `undefined`: +เช่น การตรวจสอบ `undefined` อย่างชัดเจน: + +```js +function showMessage(from, text) { +*!* + if (text === undefined) { + text = 'no text given'; + } +*/!* + + alert( from + ": " + text ); +} +``` + +...หรือใช้ตัวดำเนินการ `||`: + +```js +function showMessage(from, text) { + // ถ้าค่าของ text เป็น falsy ให้กำหนดค่าเริ่มต้น + // ซึ่งสมมติว่า text == "" เหมือนกับไม่มี text เลย + text = text || 'no text given'; + ... +} +``` +```` + +### พารามิเตอร์เริ่มต้นทางเลือก + +บางครั้งการกำหนดค่าเริ่มต้นให้พารามิเตอร์ในภายหลัง หลังจากประกาศฟังก์ชันไปแล้ว ก็มีเหตุผลเช่นกัน + +เราสามารถตรวจสอบว่ามีการส่งพารามิเตอร์มาหรือไม่ระหว่างการทำงานของฟังก์ชัน โดยเปรียบเทียบกับ `undefined`: ```js run function showMessage(text) { + // ... + *!* - if (text === undefined) { - text = 'empty message'; + if (text === undefined) { // ถ้าไม่มีพารามิเตอร์ส่งมา + text = 'ข้อความว่าง'; } */!* alert(text); } -showMessage(); // empty message +showMessage(); // ข้อความว่าง ``` -...Or we could use the `||` operator: +...หรือเราสามารถใช้ตัวดำเนินการ `||`: ```js -// if text parameter is omitted or "" is passed, set it to 'empty' function showMessage(text) { + // ถ้า text เป็น undefined หรือ falsy อื่นๆ ให้กำหนดเป็น 'empty' text = text || 'empty'; - ... + ... } ``` -Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when falsy values, such as `0`, are considered regular: +JavaScript เอนจินสมัยใหม่รองรับ[ตัวดำเนินการรวม nullish](info:nullish-coalescing-operator) `??` ซึ่งจะเหมาะกว่าเมื่อต้องการให้ค่า falsy ส่วนใหญ่ เช่น `0` ถือเป็นค่า "ปกติ": ```js run -// if there's no "count" parameter, show "unknown" function showCount(count) { + // ถ้า count เป็น undefined หรือ null ให้แสดง "unknown" alert(count ?? "unknown"); } showCount(0); // 0 -showCount(null); // unknown +showCount(null); // unknown showCount(); // unknown ``` -## Returning a value +## การคืนค่า (Returning a value) -A function can return a value back into the calling code as the result. +ฟังก์ชันสามารถคืนค่ากลับไปยังโค้ดที่เรียกใช้เป็นผลลัพธ์ได้ -The simplest example would be a function that sums two values: +ตัวอย่างง่ายๆ คือฟังก์ชันที่รวมผลบวกของสองค่า: ```js run no-beautify function sum(a, b) { @@ -272,9 +321,9 @@ let result = sum(1, 2); alert( result ); // 3 ``` -The directive `return` can be in any place of the function. When the execution reaches it, the function stops, and the value is returned to the calling code (assigned to `result` above). +คำสั่ง `return` สามารถอยู่ที่ไหนก็ได้ในฟังก์ชัน เมื่อโปรแกรมทำงานมาถึง `return` ฟังก์ชันจะหยุดและคืนค่าไปยังโค้ดที่เรียกใช้ (กำหนดให้กับ `result` ในตัวอย่างข้างต้น) -There may be many occurrences of `return` in a single function. For instance: +ในฟังก์ชันเดียวอาจมีหลายจุดที่ใช้ `return` ได้ เช่น: ```js run function checkAge(age) { @@ -284,23 +333,23 @@ function checkAge(age) { */!* } else { *!* - return confirm('Do you have permission from your parents?'); + return confirm('คุณได้รับอนุญาตจากผู้ปกครองหรือไม่?'); */!* } } -let age = prompt('How old are you?', 18); +let age = prompt('คุณอายุเท่าไหร่?', 18); if ( checkAge(age) ) { - alert( 'Access granted' ); + alert('อนุญาตให้เข้าใช้งาน'); } else { - alert( 'Access denied' ); + alert('ไม่อนุญาตให้เข้าใช้งาน'); } ``` -It is possible to use `return` without a value. That causes the function to exit immediately. +เราสามารถใช้ `return` โดยไม่มีค่าก็ได้ ซึ่งจะทำให้ฟังก์ชันจบการทำงานทันที -For example: +ตัวอย่างเช่น: ```js function showMovie(age) { @@ -310,25 +359,25 @@ function showMovie(age) { */!* } - alert( "Showing you the movie" ); // (*) + alert("กำลังแสดงภาพยนตร์ให้ชม"); // (*) // ... } ``` -In the code above, if `checkAge(age)` returns `false`, then `showMovie` won't proceed to the `alert`. +ในโค้ดข้างต้น ถ้า `checkAge(age)` คืนค่า `false` ฟังก์ชัน `showMovie` จะไม่ทำงานต่อไปที่ `alert` -````smart header="A function with an empty `return` or without it returns `undefined`" -If a function does not return a value, it is the same as if it returns `undefined`: +````smart header="ฟังก์ชันที่มี `return` ว่างหรือไม่มี `return` จะคืนค่า `undefined`" +ถ้าฟังก์ชันไม่มีการคืนค่า จะเหมือนกับการคืนค่า `undefined`: ```js run -function doNothing() { /* empty */ } +function doNothing() { /* ว่าง */ } alert( doNothing() === undefined ); // true ``` -An empty `return` is also the same as `return undefined`: +`return` ว่างก็เหมือนกับ `return undefined`: -```js run +```js run function doNothing() { return; } @@ -337,23 +386,23 @@ alert( doNothing() === undefined ); // true ``` ```` -````warn header="Never add a newline between `return` and the value" -For a long expression in `return`, it might be tempting to put it on a separate line, like this: +````warn header="อย่าขึ้นบรรทัดใหม่ระหว่าง `return` กับค่าที่คืน" +สำหรับนิพจน์ยาวๆ ใน `return` อาจเย้ายวนใจให้แยกเป็นบรรทัดใหม่ แบบนี้: ```js return (some + long + expression + or + whatever * f(a) + f(b)) ``` -That doesn't work, because JavaScript assumes a semicolon after `return`. That'll work the same as: +นั่นจะไม่ทำงาน เพราะ JavaScript จะสันนิษฐานว่ามีเครื่องหมายอัฒภาคหลัง `return` ซึ่งจะทำงานเหมือนกับ: -```js +```js return*!*;*/!* (some + long + expression + or + whatever * f(a) + f(b)) ``` -So, it effectively becomes an empty return. +ดังนั้นมันจึงกลายเป็น return เปล่าๆ นั่นเอง -If we want the returned expression to wrap across multiple lines, we should start it at the same line as `return`. Or at least put the opening parentheses there as follows: +ถ้าเราต้องการให้นิพจน์ที่คืนค่าขึ้นบรรทัดใหม่ เราควรเริ่มต้นในบรรทัดเดียวกับ `return` หรืออย่างน้อยควรใส่วงเล็บเปิดเอาไว้ที่นั่น แบบนี้: ```js return ( @@ -362,67 +411,68 @@ return ( whatever * f(a) + f(b) ) ``` -And it will work just as we expect it to. + +แค่นี้ก็จะทำงานตามที่คาดหวังได้ ```` -## Naming a function [#function-naming] +## การตั้งชื่อฟังก์ชัน [#function-naming] -Functions are actions. So their name is usually a verb. It should be brief, as accurate as possible and describe what the function does, so that someone reading the code gets an indication of what the function does. +ฟังก์ชันคือการกระทำ ดังนั้นชื่อของมันมักจะเป็นคำกริยา ชื่อควรสั้นกระชับ ตรงประเด็นที่สุดเท่าที่จะเป็นไปได้ และบ่งบอกว่าฟังก์ชันทำอะไร เพื่อให้คนที่อ่านโค้ดเข้าใจได้ทันทีว่าฟังก์ชันนั้นทำหน้าที่อะไร -It is a widespread practice to start a function with a verbal prefix which vaguely describes the action. There must be an agreement within the team on the meaning of the prefixes. +เป็นแนวปฏิบัติที่แพร่หลายในการขึ้นต้นชื่อฟังก์ชันด้วยคำกริยาเพื่ออธิบายการกระทำคร่าวๆ ทีมงานควรตกลงกันเรื่องความหมายของคำนำหน้าเหล่านี้ให้ชัดเจน -For instance, functions that start with `"show"` usually show something. +ตัวอย่างเช่น ฟังก์ชันที่ขึ้นต้นด้วย `"show"` มักจะแสดงบางอย่าง -Function starting with... +ฟังก์ชันที่ขึ้นต้นด้วย... -- `"get…"` -- return a value, -- `"calc…"` -- calculate something, -- `"create…"` -- create something, -- `"check…"` -- check something and return a boolean, etc. +- `"get…"` -- คืนค่าบางอย่าง +- `"calc…"` -- คำนวณบางอย่าง +- `"create…"` -- สร้างบางอย่าง +- `"check…"` -- ตรวจสอบบางอย่างและคืนค่าบูลีน ฯลฯ -Examples of such names: +ตัวอย่างชื่อฟังก์ชันเหล่านี้: ```js no-beautify -showMessage(..) // shows a message -getAge(..) // returns the age (gets it somehow) -calcSum(..) // calculates a sum and returns the result -createForm(..) // creates a form (and usually returns it) -checkPermission(..) // checks a permission, returns true/false +showMessage(..) // แสดงข้อความ +getAge(..) // คืนค่าอายุ (ได้ค่ามาด้วยวิธีใดวิธีหนึ่ง) +calcSum(..) // คำนวณผลรวมและคืนผลลัพธ์ +createForm(..) // สร้างฟอร์ม (และมักจะคืนค่าฟอร์มนั้น) +checkPermission(..) // ตรวจสอบสิทธิ์ คืนค่า true/false ``` -With prefixes in place, a glance at a function name gives an understanding what kind of work it does and what kind of value it returns. +เมื่อใช้คำนำหน้าแล้ว แค่มองชื่อฟังก์ชันก็พอจะเข้าใจได้ว่าทำงานแบบไหนและคืนค่าประเภทใด -```smart header="One function -- one action" -A function should do exactly what is suggested by its name, no more. +```smart header="หนึ่งฟังก์ชัน -- หนึ่งการกระทำ" +ฟังก์ชันควรทำในสิ่งที่ชื่อบอกไว้ ไม่มากไปกว่านั้น -Two independent actions usually deserve two functions, even if they are usually called together (in that case we can make a 3rd function that calls those two). +การกระทำสองอย่างที่เป็นอิสระจากกัน มักสมควรแยกเป็นสองฟังก์ชัน แม้ว่าปกติจะถูกเรียกใช้ด้วยกัน (ในกรณีนั้นเราอาจสร้างฟังก์ชันที่สามเพื่อเรียกใช้ทั้งสองฟังก์ชันนั้น) -A few examples of breaking this rule: +ตัวอย่างที่ละเมิดกฎข้อนี้: -- `getAge` -- would be bad if it shows an `alert` with the age (should only get). -- `createForm` -- would be bad if it modifies the document, adding a form to it (should only create it and return). -- `checkPermission` -- would be bad if it displays the `access granted/denied` message (should only perform the check and return the result). +- `getAge` -- จะไม่ดีถ้าแสดง `alert` บอกอายุด้วย (ควรแค่ดึงค่าอายุเท่านั้น) +- `createForm` -- จะไม่ดีถ้าแก้ไขเอกสารด้วยการเพิ่มฟอร์มเข้าไป (ควรแค่สร้างฟอร์มและคืนค่า) +- `checkPermission` -- จะไม่ดีถ้าแสดงข้อความ `อนุญาต/ไม่อนุญาตให้เข้าถึง` (ควรแค่ตรวจสอบและคืนผลลัพธ์) -These examples assume common meanings of prefixes. You and your team are free to agree on other meanings, but usually they're not much different. In any case, you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge. +ตัวอย่างเหล่านี้ใช้ความหมายทั่วไปของคำนำหน้า ทีมสามารถตกลงกันได้ว่าจะใช้ความหมายใด แต่โดยปกติมักไม่ค่อยต่างกันมาก สิ่งสำคัญคือทุกคนในทีมต้องเข้าใจชัดเจนว่าคำนำหน้าแต่ละตัวหมายความว่าอะไร ฟังก์ชันที่ใช้คำนำหน้าเดียวกันควรปฏิบัติตามกฎเดียวกัน ``` -```smart header="Ultrashort function names" -Functions that are used *very often* sometimes have ultrashort names. +```smart header="ชื่อฟังก์ชันที่สั้นมากๆ" +ฟังก์ชันที่ถูกใช้ *บ่อยมากๆ* บางครั้งอาจมีชื่อที่สั้นมากๆ -For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`. +เช่น เฟรมเวิร์ค [jQuery](https://jquery.com/) กำหนดฟังก์ชันชื่อ `$` ส่วนไลบรารี [Lodash](https://lodash.com/) มีฟังก์ชันหลักชื่อ `_` -These are exceptions. Generally functions names should be concise and descriptive. +แต่สิ่งเหล่านี้เป็นข้อยกเว้น โดยทั่วไปแล้วชื่อฟังก์ชันควรกระชับและบ่งบอกความหมายได้ชัดเจน ``` -## Functions == Comments +## ฟังก์ชัน == คอมเมนต์ -Functions should be short and do exactly one thing. If that thing is big, maybe it's worth it to split the function into a few smaller functions. Sometimes following this rule may not be that easy, but it's definitely a good thing. +ฟังก์ชันควรสั้นและทำเพียงหนึ่งสิ่งอย่างชัดเจน ถ้าสิ่งนั้นใหญ่เกินไป ก็อาจคุ้มที่จะแยกออกเป็นฟังก์ชันเล็กๆ สองสามอัน บางครั้งอาจไม่ง่ายนักที่จะทำตามกฎนี้ แต่เป็นสิ่งที่ดีแน่นอน -A separate function is not only easier to test and debug -- its very existence is a great comment! +ฟังก์ชันที่แยกออกมา นอกจากจะทดสอบและดีบักได้ง่ายกว่าแล้ว การมีอยู่ของมันยังทำหน้าที่เป็นคอมเมนต์ได้ด้วย! -For instance, compare the two functions `showPrimes(n)` below. Each one outputs [prime numbers](https://en.wikipedia.org/wiki/Prime_number) up to `n`. +ยกตัวอย่างเช่น เปรียบเทียบฟังก์ชัน `showPrimes(n)` สองอันด้านล่าง แต่ละอันแสดงผล [จำนวนเฉพาะ](https://en.wikipedia.org/wiki/Prime_number) จนถึง `n` -The first variant uses a label: +แบบแรกใช้ label: ```js function showPrimes(n) { @@ -432,12 +482,12 @@ function showPrimes(n) { if (i % j == 0) continue nextPrime; } - alert( i ); // a prime + alert( i ); // จำนวนเฉพาะ } } ``` -The second variant uses an additional function `isPrime(n)` to test for primality: +แบบที่สองใช้ฟังก์ชันเสริม `isPrime(n)` เพื่อตรวจสอบว่าเป็นจำนวนเฉพาะหรือไม่: ```js function showPrimes(n) { @@ -445,7 +495,7 @@ function showPrimes(n) { for (let i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* - alert(i); // a prime + alert(i); // จำนวนเฉพาะ } } @@ -457,32 +507,32 @@ function isPrime(n) { } ``` -The second variant is easier to understand, isn't it? Instead of the code piece we see a name of the action (`isPrime`). Sometimes people refer to such code as *self-describing*. +แบบที่สองเข้าใจง่ายกว่าใช่ไหม? แทนที่จะเห็นส่วนโค้ด เราเห็นชื่อการกระทำ (`isPrime`) บางครั้งผู้คนเรียกโค้ดแบบนี้ว่า *อธิบายตัวเอง* -So, functions can be created even if we don't intend to reuse them. They structure the code and make it readable. +ดังนั้นเราจึงสร้างฟังก์ชันได้แม้ไม่ได้ตั้งใจจะนำมาใช้ซ้ำ เพราะมันช่วยจัดโครงสร้างโค้ดและทำให้อ่านง่ายขึ้น -## Summary +## สรุป -A function declaration looks like this: +การประกาศฟังก์ชันมีหน้าตาแบบนี้: ```js function name(parameters, delimited, by, comma) { /* code */ -} +} ``` -- Values passed to a function as parameters are copied to its local variables. -- A function may access outer variables. But it works only from inside out. The code outside of the function doesn't see its local variables. -- A function can return a value. If it doesn't, then its result is `undefined`. +- ค่าที่ส่งเข้าไปในฟังก์ชันผ่านพารามิเตอร์จะถูกคัดลอกไปเป็นตัวแปรภายในฟังก์ชัน +- ฟังก์ชันสามารถเข้าถึงตัวแปรภายนอกได้ แต่ทำได้แค่ทิศทางเดียวคือจากในออกนอก โค้ดภายนอกฟังก์ชันไม่สามารถเห็นตัวแปรภายในฟังก์ชันได้ +- ฟังก์ชันสามารถคืนค่าได้ ถ้าไม่คืนค่า ผลลัพธ์จะเป็น `undefined` -To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables. +เพื่อให้โค้ดสะอาดและเข้าใจง่าย แนะนำให้ใช้ตัวแปรภายในฟังก์ชันและพารามิเตอร์เป็นหลัก ไม่พึ่งตัวแปรภายนอก -It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side-effect. +ฟังก์ชันที่รับพารามิเตอร์ ทำงานกับข้อมูลนั้น และคืนผลลัพธ์ออกมา — จะเข้าใจง่ายกว่าฟังก์ชันที่ไม่รับพารามิเตอร์แต่แอบไปแก้ตัวแปรภายนอก ซึ่งถือเป็นผลข้างเคียงที่ไม่พึงประสงค์ -Function naming: +การตั้งชื่อฟังก์ชัน: -- A name should clearly describe what the function does. When we see a function call in the code, a good name instantly gives us an understanding what it does and returns. -- A function is an action, so function names are usually verbal. -- There exist many well-known function prefixes like `create…`, `show…`, `get…`, `check…` and so on. Use them to hint what a function does. +- ชื่อควรบอกชัดเจนว่าฟังก์ชันทำอะไร เมื่อเห็นการเรียกฟังก์ชันในโค้ด ชื่อที่ดีจะทำให้เข้าใจทันทีว่าทำอะไรและคืนค่าอะไร +- ฟังก์ชันคือการกระทำ ดังนั้นชื่อฟังก์ชันมักเป็นคำกริยา +- มีคำนำหน้าฟังก์ชันที่ใช้กันแพร่หลาย เช่น `create…`, `show…`, `get…`, `check…` ให้ใช้เป็นสัญญาณบอกว่าฟังก์ชันทำอะไร -Functions are the main building blocks of scripts. Now we've covered the basics, so we actually can start creating and using them. But that's only the beginning of the path. We are going to return to them many times, going more deeply into their advanced features. +ฟังก์ชันเป็นบล็อกสำคัญในการสร้างสคริปต์ ตอนนี้เราได้เรียนรู้พื้นฐานแล้ว ก็พร้อมเริ่มสร้างและใช้งานฟังก์ชันได้เลย — แต่นี่เป็นเพียงจุดเริ่มต้น เรายังจะกลับมาที่ฟังก์ชันอีกหลายครั้งเพื่อศึกษาฟีเจอร์ขั้นสูงให้ลึกซึ้งยิ่งขึ้น \ No newline at end of file diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md index a8ccd6c6c..6c417f922 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -1,8 +1,8 @@ -# Function expressions +# นิพจน์ฟังก์ชัน -In JavaScript, a function is not a "magical language structure", but a special kind of value. +ใน JavaScript ฟังก์ชันไม่ใช่ "โครงสร้างพิเศษทางภาษา" แต่เป็นค่าชนิดหนึ่งที่มีลักษณะเฉพาะตัว -The syntax that we used before is called a *Function Declaration*: +รูปแบบที่เราใช้ก่อนหน้านี้เรียกว่า *การประกาศฟังก์ชัน* (Function Declaration): ```js function sayHi() { @@ -10,9 +10,11 @@ function sayHi() { } ``` -There is another syntax for creating a function that is called a *Function Expression*. +อีกวิธีหนึ่งในการสร้างฟังก์ชันเรียกว่า *นิพจน์ฟังก์ชัน* (Function Expression) -It looks like this: +วิธีนี้ช่วยให้สร้างฟังก์ชันใหม่ได้ในทุกๆ นิพจน์ (expression) + +ตัวอย่างเช่น: ```js let sayHi = function() { @@ -20,11 +22,21 @@ let sayHi = function() { }; ``` -Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable `sayHi`. +ตัวแปร `sayHi` ได้รับค่าเป็นฟังก์ชันใหม่ที่สร้างขึ้นด้วย `function() { alert("Hello"); }` + +เนื่องจากการสร้างฟังก์ชันเกิดขึ้นในบริบทของนิพจน์การกำหนดค่า (ทางขวาของเครื่องหมาย `=`) จึงเรียกว่านิพจน์ฟังก์ชัน + +สังเกตว่าไม่มีชื่อหลังคำสำคัญ `function` — สำหรับนิพจน์ฟังก์ชันนั้น การละเว้นชื่อเป็นเรื่องปกติ + +เราได้กำหนดค่าให้กับตัวแปรทันที ดังนั้นตัวอย่างโค้ดทั้งสองจึงมีความหมายเดียวกันคือ "สร้างฟังก์ชันและเก็บลงในตัวแปร `sayHi`" + +ในสถานการณ์ที่ซับซ้อนขึ้นซึ่งจะเจอในภายหลัง ฟังก์ชันอาจถูกสร้างและเรียกใช้ทันที หรือกำหนดเวลาไว้ใช้ทีหลัง โดยไม่เก็บไว้ที่ใดเลย จึงไม่มีชื่อ (anonymous) + +## ฟังก์ชันคือค่า -The meaning of these code samples is the same: "create a function and put it into the variable `sayHi`". +ขอย้ำอีกครั้ง: ไม่ว่าฟังก์ชันจะถูกสร้างมาอย่างไร ก็คือค่าชนิดหนึ่งทั้งนั้น ตัวอย่างทั้งสองด้านบนต่างเก็บฟังก์ชันไว้ในตัวแปร `sayHi` -We can even print out that value using `alert`: +เราสามารถแสดงค่านั้นผ่าน `alert` ได้ด้วย: ```js run function sayHi() { @@ -32,53 +44,52 @@ function sayHi() { } *!* -alert( sayHi ); // shows the function code +alert( sayHi ); // แสดงโค้ดฟังก์ชัน */!* ``` -Please note that the last line does not run the function, because there are no parentheses after `sayHi`. There are programming languages where any mention of a function name causes its execution, but JavaScript is not like that. +สังเกตว่าบรรทัดสุดท้ายไม่ได้เรียกใช้ฟังก์ชัน เพราะไม่มีวงเล็บหลัง `sayHi` มีภาษาโปรแกรมบางภาษาที่เพียงแค่กล่าวถึงชื่อฟังก์ชันก็จะเรียกใช้ทันที แต่ JavaScript ไม่เป็นเช่นนั้น -In JavaScript, a function is a value, so we can deal with it as a value. The code above shows its string representation, which is the source code. +ใน JavaScript ฟังก์ชันเป็นค่าประเภทหนึ่ง เราจึงจัดการกับมันได้เหมือนกับค่าประเภทอื่นๆ โค้ดด้านบนแสดงผลลัพธ์เป็นสตริง ซึ่งก็คือซอร์สโค้ดของฟังก์ชันนั้นเอง -Surely, a function is a special value, in the sense that we can call it like `sayHi()`. +แน่นอนว่าฟังก์ชันเป็นค่าพิเศษ ตรงที่เราเรียกใช้งานได้ด้วย `sayHi()` -But it's still a value. So we can work with it like with other kinds of values. +แต่ยังคงเป็นค่าอยู่ดี จึงทำงานกับมันได้เหมือนกับค่าประเภทอื่นๆ -We can copy a function to another variable: +เราสามารถคัดลอกฟังก์ชันไปยังตัวแปรอื่นได้: ```js run no-beautify -function sayHi() { // (1) create +function sayHi() { // (1) สร้าง alert( "Hello" ); } -let func = sayHi; // (2) copy +let func = sayHi; // (2) คัดลอก -func(); // Hello // (3) run the copy (it works)! -sayHi(); // Hello // this still works too (why wouldn't it) +func(); // Hello // (3) เรียกใช้สำเนา (ใช้ได้)! +sayHi(); // Hello // ตัวต้นฉบับยังใช้ได้เหมือนเดิม (แน่นอน) ``` -Here's what happens above in detail: +นี่คือสิ่งที่เกิดขึ้นโดยละเอียด: -1. The Function Declaration `(1)` creates the function and puts it into the variable named `sayHi`. -2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. -3. Now the function can be called as both `sayHi()` and `func()`. +1. การประกาศฟังก์ชัน `(1)` สร้างฟังก์ชันและเก็บไว้ในตัวแปรชื่อ `sayHi` +2. บรรทัด `(2)` คัดลอกไปยังตัวแปร `func` สังเกตอีกครั้ง: ไม่มีวงเล็บหลัง `sayHi` ถ้ามี `func = sayHi()` จะเขียน *ผลลัพธ์จากการเรียกใช้* `sayHi()` ลงใน `func` ไม่ใช่ *ฟังก์ชัน* `sayHi` เอง +3. ตอนนี้เรียกฟังก์ชันได้ทั้งในชื่อ `sayHi()` และ `func()` -Note that we could also have used a Function Expression to declare `sayHi`, in the first line: +เราสามารถใช้นิพจน์ฟังก์ชันเพื่อประกาศ `sayHi` ในบรรทัดแรกก็ได้: ```js -let sayHi = function() { +let sayHi = function() { // (1) สร้าง alert( "Hello" ); }; -let func = sayHi; +let func = sayHi; //(2) // ... ``` -Everything would work the same. - +ทุกอย่างจะทำงานเหมือนเดิม -````smart header="Why is there a semicolon at the end?" -You might wonder, why does Function Expression have a semicolon `;` at the end, but Function Declaration does not: +```smart header="ทำไมต้องมีเครื่องหมายอัฒภาคตามหลัง?" +อาจสงสัยว่าทำไมนิพจน์ฟังก์ชันถึงต้องมีเครื่องหมายอัฒภาค `;` ท้ายประโยค แต่การประกาศฟังก์ชันไม่ต้องมี: ```js function sayHi() { @@ -90,27 +101,26 @@ let sayHi = function() { }*!*;*/!* ``` -The answer is simple: -- There's no need for `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc. -- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block, but rather an assignment. The semicolon `;` is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement. -```` +คำตอบคือ: นิพจน์ฟังก์ชันถูกสร้างขึ้นในรูปแบบ `function(…) {…}` ภายในประโยคการกำหนดค่า: `let sayHi = …;` ซึ่งควรมีเครื่องหมายอัฒภาค `;` ท้ายประโยค — ไม่ใช่ส่วนหนึ่งของไวยากรณ์ฟังก์ชัน + +เครื่องหมายอัฒภาคอยู่ในการกำหนดค่าทั่วไปอยู่แล้ว เช่น `let sayHi = 5;` และก็อยู่ในการกำหนดค่าฟังก์ชันด้วยเช่นกัน -## Callback functions +## ฟังก์ชันคอลแบ็ก -Let's look at more examples of passing functions as values and using function expressions. +ทีนี้มาดูตัวอย่างเพิ่มเติมเกี่ยวกับการส่งฟังก์ชันเป็นค่าและการใช้นิพจน์ฟังก์ชันกัน -We'll write a function `ask(question, yes, no)` with three parameters: +เราจะเขียนฟังก์ชัน `ask(question, yes, no)` ที่มีสามพารามิเตอร์: `question` -: Text of the question +: ข้อความของคำถาม `yes` -: Function to run if the answer is "Yes" +: ฟังก์ชันที่จะรันหากคำตอบคือ "ใช่" `no` -: Function to run if the answer is "No" +: ฟังก์ชันที่จะรันหากคำตอบคือ "ไม่" -The function should ask the `question` and, depending on the user's answer, call `yes()` or `no()`: +ฟังก์ชันควรถาม `question` และเรียกใช้ `yes()` หรือ `no()` ตามคำตอบของผู้ใช้: ```js run *!* @@ -128,17 +138,17 @@ function showCancel() { alert( "You canceled the execution." ); } -// usage: functions showOk, showCancel are passed as arguments to ask +// การใช้งาน: ส่งฟังก์ชัน showOk, showCancel เป็นอาร์กิวเมนต์ให้ ask ask("Do you agree?", showOk, showCancel); ``` -In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such function usually draws a nice-looking question window. But that's another story. +ในทางปฏิบัติ ฟังก์ชันแบบนี้มีประโยชน์มาก ความแตกต่างหลักระหว่าง `ask` ในชีวิตจริงกับตัวอย่างข้างต้นคือ ฟังก์ชันจริงจะใช้วิธีโต้ตอบกับผู้ใช้ที่ซับซ้อนกว่า `confirm` ธรรมดา — ในเบราว์เซอร์มักสร้างหน้าต่างคำถามที่สวยงาม แต่นั่นเป็นอีกเรื่องหนึ่ง -**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.** +**อาร์กิวเมนต์ `showOk` และ `showCancel` ของ `ask` เรียกว่า *ฟังก์ชันคอลแบ็ก* หรือ *คอลแบ็ก*** -The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer. +แนวคิดคือ เราส่งฟังก์ชันไป และคาดหวังว่าจะถูก "เรียกกลับ (called back)" ในภายหลังหากจำเป็น ในกรณีของเรา `showOk` เป็นคอลแบ็กสำหรับคำตอบ "ใช่" และ `showCancel` สำหรับคำตอบ "ไม่" -We can use Function Expressions to write the same function much shorter: +เราสามารถใช้นิพจน์ฟังก์ชันเพื่อเขียนฟังก์ชันเทียบเท่าที่สั้นกว่าได้: ```js run no-beautify function ask(question, yes, no) { @@ -155,59 +165,58 @@ ask( */!* ``` -Here, functions are declared right inside the `ask(...)` call. They have no name, and so are called *anonymous*. Such functions are not accessible outside of `ask` (because they are not assigned to variables), but that's just what we want here. +ฟังก์ชันถูกประกาศขึ้นภายในการเรียก `ask(...)` โดยตรง โดยไม่มีชื่อ จึงเรียกว่า *anonymous (ไม่มีชื่อ)* ฟังก์ชันเหล่านี้ไม่สามารถเข้าถึงได้จากภายนอก `ask` (เพราะไม่ได้กำหนดให้กับตัวแปร) แต่นั่นแหละคือสิ่งที่เราต้องการ -Such code appears in our scripts very naturally, it's in the spirit of JavaScript. +โค้ดแบบนี้เป็นเรื่องธรรมดามากในสคริปต์ JavaScript — เป็นสไตล์ที่อยู่ในจิตวิญญาณของภาษานี้เลย -```smart header="A function is a value representing an \"action\"" -Regular values like strings or numbers represent the *data*. +```smart header="ฟังก์ชันคือค่าที่แทนการ \"กระทำ\"" +ค่าปกติ เช่น สตริงหรือตัวเลข แสดงถึง *ข้อมูล* -A function can be perceived as an *action*. +สามารถมองฟังก์ชันเป็น *การกระทำ* ได้ -We can pass it between variables and run when we want. +เราสามารถส่งผ่านมันไประหว่างตัวแปร และสั่งให้ทำงานเมื่อใดก็ได้ที่ต้องการ ``` +## นิพจน์ฟังก์ชัน (Function Expression) กับ การประกาศฟังก์ชัน (Function Declaration) -## Function Expression vs Function Declaration +มาสรุปความแตกต่างสำคัญระหว่างการประกาศฟังก์ชันและนิพจน์ฟังก์ชันกัน -Let's formulate the key differences between Function Declarations and Expressions. +เริ่มจากด้านไวยากรณ์ก่อน — จะแยกแยะทั้งสองแบบในโค้ดได้อย่างไร -First, the syntax: how to differentiate between them in the code. - -- *Function Declaration:* a function, declared as a separate statement, in the main code flow. +- *การประกาศฟังก์ชัน:* ฟังก์ชันที่ประกาศเป็นประโยคแยกต่างหาก ในเนื้อหาหลักของโค้ด: ```js - // Function Declaration + // การประกาศฟังก์ชัน function sum(a, b) { return a + b; } ``` -- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created at the right side of the "assignment expression" `=`: +- *นิพจน์ฟังก์ชัน:* ฟังก์ชันที่ถูกสร้างภายในนิพจน์ หรือภายในโครงสร้างไวยากรณ์อื่นๆ ในที่นี้ ฟังก์ชันถูกสร้างขึ้นทางด้านขวาของ "นิพจน์การกำหนดค่า" `=`: ```js - // Function Expression + // นิพจน์ฟังก์ชัน let sum = function(a, b) { return a + b; }; ``` -The more subtle difference is *when* a function is created by the JavaScript engine. +ความแตกต่างที่ลึกกว่านั้นคือ *เวลาที่* JavaScript engine สร้างฟังก์ชันขึ้นมา -**A Function Expression is created when the execution reaches it and is usable only from that moment.** +**นิพจน์ฟังก์ชัน ถูกสร้างเมื่อการประมวลผลมาถึงมัน และใช้งานได้ตั้งแต่จุดนั้นเป็นต้นไป** -Once the execution flow passes to the right side of the assignment `let sum = function…` -- here we go, the function is created and can be used (assigned, called, etc. ) from now on. +เมื่อลำดับการทำงานมาถึงทางด้านขวาของการกำหนดค่า `let sum = function…` — ฟังก์ชันก็จะถูกสร้างขึ้น และใช้งานได้ตั้งแต่จุดนั้นเป็นต้นไป (กำหนดค่า เรียกใช้ ฯลฯ) -Function Declarations are different. +การประกาศฟังก์ชันนั้นแตกต่างออกไป -**A Function Declaration can be called earlier than it is defined.** +**การประกาศฟังก์ชันสามารถเรียกใช้ได้ก่อนที่จะมีการประกาศ** -For example, a global Function Declaration is visible in the whole script, no matter where it is. +ยกตัวอย่างเช่น การประกาศฟังก์ชันระดับโกลบอลจะมองเห็นได้ทั่วทั้งสคริปต์ ไม่ว่าจะอยู่ตรงไหนก็ตาม -That's due to internal algorithms. When JavaScript prepares to run the script, it first looks for global Function Declarations in it and creates the functions. We can think of it as an "initialization stage". +เหตุผลอยู่ที่อัลกอริทึมภายใน: เมื่อ JavaScript เตรียมจะรันสคริปต์ จะมองหาการประกาศฟังก์ชันระดับโกลบอลทั้งหมดก่อน แล้วสร้างขึ้นมาล่วงหน้า — เราอาจมองว่าเป็น "ขั้นตอนการเตรียมการ" -And after all Function Declarations are processed, the code is executed. So it has access to these functions. +หลังจากประมวลผลการประกาศฟังก์ชันทั้งหมดแล้ว จึงค่อยประมวลผลโค้ดตามปกติ ดังนั้นโค้ดจึงเข้าถึงฟังก์ชันเหล่านั้นได้ -For example, this works: +ตัวอย่างเช่น โค้ดนี้สามารถทำงานได้: ```js run refresh untrusted *!* @@ -219,34 +228,34 @@ function sayHi(name) { } ``` -The Function Declaration `sayHi` is created when JavaScript is preparing to start the script and is visible everywhere in it. +การประกาศฟังก์ชัน `sayHi` ถูกสร้างขึ้นตอนที่ JavaScript กำลังเตรียมเริ่มสคริปต์ จึงมองเห็นได้ทั่วทั้งสคริปต์ -...If it were a Function Expression, then it wouldn't work: +...แต่ถ้าเป็นนิพจน์ฟังก์ชัน จะใช้งานไม่ได้: ```js run refresh untrusted *!* sayHi("John"); // error! */!* -let sayHi = function(name) { // (*) no magic any more +let sayHi = function(name) { // (*) ไม่มีเวทมนต์อีกต่อไป alert( `Hello, ${name}` ); }; ``` -Function Expressions are created when the execution reaches them. That would happen only in the line `(*)`. Too late. +นิพจน์ฟังก์ชันจะถูกสร้างก็ต่อเมื่อลำดับการทำงานมาถึงมัน ซึ่งเกิดขึ้นที่บรรทัด `(*)` เท่านั้น — ช้าไปแล้ว -Another special feature of Function Declarations is their block scope. +อีกฟีเจอร์พิเศษของการประกาศฟังก์ชันคือขอบเขตแบบบล็อก -**In strict mode, when a Function Declaration is within a code block, it's visible everywhere inside that block. But not outside of it.** +**ในโหมดเข้มงวด เมื่อการประกาศฟังก์ชันอยู่ภายในบล็อกโค้ด มันจะมองเห็นได้ทั่วภายในบล็อกนั้น แต่มองไม่เห็นภายนอกบล็อก** -For instance, let's imagine that we need to declare a function `welcome()` depending on the `age` variable that we get during runtime. And then we plan to use it some time later. +สมมติว่าเราต้องการประกาศฟังก์ชัน `welcome()` โดยขึ้นอยู่กับค่าตัวแปร `age` ที่ได้ระหว่าง runtime และวางแผนจะใช้มันในภายหลัง -If we use Function Declaration, it won't work as intended: +หากใช้การประกาศฟังก์ชัน จะไม่ทำงานตามที่ตั้งใจ: ```js run let age = prompt("What is your age?", 18); -// conditionally declare a function +// ประกาศฟังก์ชันตามเงื่อนไข if (age < 18) { function welcome() { @@ -261,52 +270,50 @@ if (age < 18) { } -// ...use it later +// ...ใช้ในภายหลัง *!* welcome(); // Error: welcome is not defined */!* ``` -That's because a Function Declaration is only visible inside the code block in which it resides. - -Here's another example: +นี่คืออีกตัวอย่างหนึ่งที่แสดงให้เห็น: ```js run -let age = 16; // take 16 as an example +let age = 16; // ใช้ 16 เป็นตัวอย่าง if (age < 18) { *!* - welcome(); // \ (runs) + welcome(); // \ (ทำงานได้) */!* // | - function welcome() { // | - alert("Hello!"); // | Function Declaration is available - } // | everywhere in the block where it's declared + function welcome() { // | + alert("Hello!"); // | การประกาศฟังก์ชันมองเห็นได้ + } // | ทั่วทั้งบล็อกที่ประกาศ // | *!* - welcome(); // / (runs) + welcome(); // / (ทำงานได้) */!* } else { - function welcome() { + function welcome() { alert("Greetings!"); } } -// Here we're out of curly braces, -// so we can not see Function Declarations made inside of them. +// ออกจากวงเล็บปีกกาแล้ว +// จึงมองไม่เห็นการประกาศฟังก์ชันที่อยู่ข้างใน *!* welcome(); // Error: welcome is not defined */!* ``` -What can we do to make `welcome` visible outside of `if`? +เราจะทำอย่างไรเพื่อให้ `welcome` มองเห็นได้จากภายนอก `if`? -The correct approach would be to use a Function Expression and assign `welcome` to the variable that is declared outside of `if` and has the proper visibility. +วิธีที่ถูกต้องคือใช้นิพจน์ฟังก์ชันและกำหนดค่า `welcome` ให้กับตัวแปรที่ประกาศไว้นอก `if` ซึ่งมีการมองเห็นที่ครอบคลุมพอ -This code works as intended: +โค้ดนี้ทำงานตามที่ตั้งใจไว้: ```js run let age = prompt("What is your age?", 18); @@ -328,11 +335,11 @@ if (age < 18) { } *!* -welcome(); // ok now +welcome(); // ตอนนี้ใช้ได้แล้ว */!* ``` -Or we could simplify it even further using a question mark operator `?`: +หรือจะทำให้กระชับขึ้นอีกโดยใช้ตัวดำเนินการ `?`: ```js run let age = prompt("What is your age?", 18); @@ -342,27 +349,27 @@ let welcome = (age < 18) ? function() { alert("Greetings!"); }; *!* -welcome(); // ok now +welcome(); // ตอนนี้ใช้ได้แล้ว */!* ``` -```smart header="When to choose Function Declaration versus Function Expression?" -As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared. +```smart header="เมื่อไหร่ควรเลือกใช้การประกาศฟังก์ชัน หรือนิพจน์ฟังก์ชัน?" +โดยหลักการทั่วไป เมื่อต้องการประกาศฟังก์ชัน ให้พิจารณาใช้ไวยากรณ์แบบการประกาศฟังก์ชันก่อนเป็นอันดับแรก เพราะให้อิสระในการจัดวางโค้ดมากกว่า — เรียกใช้ฟังก์ชันก่อนที่จะมีการประกาศได้เลย -That's also better for readability, as it's easier to look up `function f(…) {…}` in the code than `let f = function(…) {…};`. Function Declarations are more "eye-catching". +นอกจากนี้ยังอ่านง่ายกว่าด้วย เพราะมองหา `function f(…) {…}` ในโค้ดได้ง่ายกว่า `let f = function(…) {…};` — การประกาศฟังก์ชันนั้น "โดดเด่นกว่า" -...But if a Function Declaration does not suit us for some reason, or we need a conditional declaration (we've just seen an example), then Function Expression should be used. +...แต่ถ้าการประกาศฟังก์ชันไม่เหมาะด้วยเหตุผลบางอย่าง หรือต้องการประกาศแบบมีเงื่อนไข (เราเพิ่งเห็นตัวอย่างไปแล้ว) ให้ใช้นิพจน์ฟังก์ชันแทน ``` -## Summary +## สรุป -- Functions are values. They can be assigned, copied or declared in any place of the code. -- If the function is declared as a separate statement in the main code flow, that's called a "Function Declaration". -- If the function is created as a part of an expression, it's called a "Function Expression". -- Function Declarations are processed before the code block is executed. They are visible everywhere in the block. -- Function Expressions are created when the execution flow reaches them. +- ฟังก์ชันคือค่า กำหนด คัดลอก หรือประกาศไว้ที่ใดก็ได้ในโค้ด +- ถ้าฟังก์ชันถูกประกาศเป็นประโยคแยกต่างหากในลำดับการทำงานหลัก เรียกว่า "การประกาศฟังก์ชัน" +- ถ้าฟังก์ชันถูกสร้างขึ้นเป็นส่วนหนึ่งของนิพจน์ เรียกว่า "นิพจน์ฟังก์ชัน" +- การประกาศฟังก์ชัน (Function Declaration) จะถูกประมวลผลก่อนที่บล็อกโค้ดจะเริ่มทำงาน จึงมองเห็นได้ทั่วทั้งบล็อก +- นิพจน์ฟังก์ชัน (Function Expression) จะถูกสร้างขึ้นเมื่อลำดับการทำงานมาถึงจุดที่มันอยู่ -In most cases when we need to declare a function, a Function Declaration is preferable, because it is visible prior to the declaration itself. That gives us more flexibility in code organization, and is usually more readable. +ในกรณีส่วนใหญ่ที่ต้องการประกาศฟังก์ชัน การประกาศฟังก์ชัน (Function Declaration) เป็นตัวเลือกที่เหมาะกว่า เพราะมองเห็นได้ก่อนการประกาศจริง ให้ความยืดหยุ่นในการจัดวางโค้ด และมักอ่านเข้าใจง่ายกว่า -So we should use a Function Expression only when a Function Declaration is not fit for the task. We've seen a couple of examples of that in this chapter, and will see more in the future. +ใช้นิพจน์ฟังก์ชันเฉพาะเมื่อการประกาศฟังก์ชันไม่เหมาะกับงาน — เราได้เห็นตัวอย่างไปบ้างแล้วในบทนี้ และจะพบเพิ่มเติมในอนาคต diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index 3ea112473..041db18bc 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,7 +1,7 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index 2f44db27e..e18c08a83 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -5,7 +5,7 @@ Replace Function Expressions with arrow functions in the code below: ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md index e0fb5bda5..876db0c49 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/article.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -1,29 +1,29 @@ -# Arrow functions, the basics +# ฟังก์ชันลูกศร (Arrow functions) ข้อมูลพื้นฐาน -There's another very simple and concise syntax for creating functions, that's often better than Function Expressions. +มีไวยากรณ์ที่เรียบง่ายและกระชับอีกแบบหนึ่งในการสร้างฟังก์ชัน ซึ่งมักจะดีกว่า Function Expression -It's called "arrow functions", because it looks like this: +เรียกว่า "ฟังก์ชันลูกศร" เพราะหน้าตาเป็นแบบนี้: ```js -let func = (arg1, arg2, ...argN) => expression +let func = (arg1, arg2, ..., argN) => expression; ``` -...This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result. +นี่จะสร้างฟังก์ชัน `func` ที่รับอาร์กิวเมนต์ `arg1..argN` จากนั้นประเมินค่า `expression` ทางด้านขวาด้วยอาร์กิวเมนต์เหล่านั้น แล้วคืนค่าผลลัพธ์ -In other words, it's the shorter version of: +พูดง่ายๆ ก็คือ เป็นเวอร์ชันสั้นกว่าของ: ```js -let func = function(arg1, arg2, ...argN) { +let func = function(arg1, arg2, ..., argN) { return expression; }; ``` -Let's see a concrete example: +มาดูตัวอย่างที่เป็นรูปธรรมกัน: ```js run let sum = (a, b) => a + b; -/* This arrow function is a shorter form of: +/* ฟังก์ชันลูกศรนี้เป็นรูปแบบสั้นกว่าของ: let sum = function(a, b) { return a + b; @@ -33,22 +33,22 @@ let sum = function(a, b) { alert( sum(1, 2) ); // 3 ``` -As you can, see `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result. +จะเห็นว่า `(a, b) => a + b` หมายถึงฟังก์ชันที่รับอาร์กิวเมนต์สองตัวชื่อ `a` และ `b` เมื่อรัน จะประเมินนิพจน์ `a + b` แล้วคืนค่าผลลัพธ์ -- If we have only one argument, then parentheses around parameters can be omitted, making that even shorter. +- ถ้ามีอาร์กิวเมนต์เพียงตัวเดียว วงเล็บล้อมรอบพารามิเตอร์สามารถละได้ ทำให้สั้นลงไปอีก - For example: + ยกตัวอย่างเช่น: ```js run *!* let double = n => n * 2; - // roughly the same as: let double = function(n) { return n * 2 } + // คล้ายๆ กับ: let double = function(n) { return n * 2 } */!* alert( double(3) ); // 6 ``` -- If there are no arguments, parentheses will be empty (but they should be present): +- ถ้าไม่มีอาร์กิวเมนต์เลย วงเล็บจะว่างเปล่า แต่ต้องใส่ไว้: ```js run let sayHi = () => alert("Hello!"); @@ -56,56 +56,56 @@ As you can, see `(a, b) => a + b` means a function that accepts two arguments na sayHi(); ``` -Arrow functions can be used in the same way as Function Expressions. +ฟังก์ชันลูกศรสามารถใช้ได้ในลักษณะเดียวกับ Function Expression -For instance, to dynamically create a function: +ยกตัวอย่างเช่น สำหรับการสร้างฟังก์ชันแบบไดนามิก: ```js run let age = prompt("What is your age?", 18); let welcome = (age < 18) ? - () => alert('Hello') : + () => alert('Hello!') : () => alert("Greetings!"); welcome(); ``` -Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure. +ฟังก์ชันลูกศรอาจดูไม่คุ้นตาและอ่านยากในตอนแรก แต่พอตาชินกับโครงสร้างแล้ว ก็จะเปลี่ยนไปอย่างรวดเร็ว -They are very convenient for simple one-line actions, when we're just too lazy to write many words. +สะดวกมากสำหรับงานง่ายๆ บรรทัดเดียว ในยามที่ขี้เกียจเขียนโค้ดยาวๆ -## Multiline arrow functions +## ฟังก์ชันลูกศรแบบหลายบรรทัด -The examples above took arguments from the left of `=>` and evaluated the right-side expression with them. +ฟังก์ชันลูกศรที่เห็นมาจนถึงตอนนี้ค่อนข้างเรียบง่าย — รับอาร์กิวเมนต์จากด้านซ้ายของ `=>` ประเมินค่า แล้วคืนค่านิพจน์ทางขวา -Sometimes we need something a little bit more complex, like multiple expressions or statements. It is also possible, but we should enclose them in curly braces. Then use a normal `return` within them. +บางครั้งต้องการฟังก์ชันที่ซับซ้อนกว่านั้น ที่มีหลายนิพจน์และคำสั่ง ในกรณีนี้ครอบด้วยวงเล็บปีกกาได้เลย ความแตกต่างหลักคือต้องมีคำสั่ง `return` ภายใน เพื่อคืนค่า (เหมือนฟังก์ชันปกติ) -Like this: +ตัวอย่างเช่น: ```js run -let sum = (a, b) => { // the curly brace opens a multiline function +let sum = (a, b) => { // วงเล็บปีกกาเปิดฟังก์ชันแบบหลายบรรทัด let result = a + b; *!* - return result; // if we use curly braces, then we need an explicit "return" + return result; // ถ้าเราใช้วงเล็บปีกกา ต้องใส่ "return" อย่างชัดเจน */!* }; alert( sum(1, 2) ); // 3 ``` -```smart header="More to come" -Here we praised arrow functions for brevity. But that's not all! +```smart header="ยังมีอีกมากมาย" +ที่ผ่านมาเราชื่นชมฟังก์ชันลูกศรในเรื่องความกระชับ แต่นั่นไม่ใช่ทั้งหมด! -Arrow functions have other interesting features. +ฟังก์ชันลูกศรยังมีฟีเจอร์ที่น่าสนใจอื่นๆ อีก -To study them in-depth, we first need to get to know some other aspects of JavaScript, so we'll return to arrow functions later in the chapter . +เพื่อศึกษาสิ่งเหล่านั้นอย่างลึกซึ้ง เราจำเป็นต้องมีความรู้เกี่ยวกับแง่มุมอื่นๆ ของ JavaScript ก่อน ดังนั้นเราจะกลับมาพูดถึงฟังก์ชันลูกศรอีกครั้งในบท -For now, we can already use arrow functions for one-line actions and callbacks. +สำหรับตอนนี้ เราสามารถใช้ฟังก์ชันลูกศรกับการทำงานแบบบรรทัดเดียวและฟังก์ชันคอลแบ็กได้แล้ว ``` -## Summary +## สรุป -Arrow functions are handy for one-liners. They come in two flavors: +ฟังก์ชันลูกศรใช้สะดวกสำหรับงานง่ายๆ โดยเฉพาะแบบบรรทัดเดียว มีสองรูปแบบ: -1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result. -2. With curly braces: `(...args) => { body }` -- brackets allow us to write multiple statements inside the function, but we need an explicit `return` to return something. +1. ไม่มีวงเล็บปีกกา: `(...args) => expression` -- ทางขวาเป็นนิพจน์: ฟังก์ชันจะประเมินค่าและคืนค่าผลลัพธ์ สามารถละวงเล็บได้ถ้ามีอาร์กิวเมนต์เพียงตัวเดียว เช่น `n => n*2` +2. มีวงเล็บปีกกา: `(...args) => { body }` -- วงเล็บปีกกาช่วยให้เขียนหลายคำสั่งภายในฟังก์ชันได้ แต่ต้องใส่ `return` อย่างชัดเจนเพื่อคืนค่าออกมา diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md index 97d0457bb..cdc9df054 100644 --- a/1-js/02-first-steps/18-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -1,23 +1,23 @@ -# JavaScript specials +# ความพิเศษของ JavaScript -This chapter briefly recaps the features of JavaScript that we've learned by now, paying special attention to subtle moments. +บทนี้รวบรวมสิ่งที่เราเรียนมาจนถึงตอนนี้ไว้แบบกระชับ พร้อมเน้นจุดที่ควรระวังเป็นพิเศษ -## Code structure +## โครงสร้างโค้ด -Statements are delimited with a semicolon: +คำสั่งจะถูกคั่นด้วยเครื่องหมายอัฒภาค (semicolon): ```js run no-beautify alert('Hello'); alert('World'); ``` -Usually, a line-break is also treated as a delimiter, so that would also work: +โดยทั่วไปแล้ว การขึ้นบรรทัดใหม่ก็ถือเป็นตัวคั่นเช่นกัน ดังนั้นแบบนี้ก็ใช้ได้เช่นกัน: ```js run no-beautify alert('Hello') alert('World') ``` -That's called "automatic semicolon insertion". Sometimes it doesn't work, for instance: +เรียกว่า "การแทรกเครื่องหมายอัฒภาคอัตโนมัติ" แต่บางครั้งก็ไม่ทำงาน ยกตัวอย่างเช่น: ```js run alert("There will be an error after this message") @@ -25,27 +25,27 @@ alert("There will be an error after this message") [1, 2].forEach(alert) ``` -Most codestyle guides agree that we should put a semicolon after each statement. +คู่มือรูปแบบการเขียนโค้ดส่วนใหญ่เห็นพ้องกันว่า เราควรใส่เครื่องหมายอัฒภาคหลังแต่ละคำสั่ง -Semicolons are not required after code blocks `{...}` and syntax constructs with them like loops: +ไม่จำเป็นต้องใช้เครื่องหมายอัฒภาคหลังบล็อกโค้ด `{...}` และโครงสร้างทางไวยากรณ์ที่มีบล็อกเหล่านั้น เช่น ลูป: ```js function f() { - // no semicolon needed after function declaration + // ไม่ต้องใส่เครื่องหมายอัฒภาคหลังการประกาศฟังก์ชัน } for(;;) { - // no semicolon needed after the loop + // ไม่ต้องใส่เครื่องหมายอัฒภาคหลังลูป } ``` -...But even if we can put an "extra" semicolon somewhere, that's not an error. It will be ignored. +...แต่แม้ว่าเราจะใส่เครื่องหมายอัฒภาค "เพิ่มเติม" ไว้ที่ใดสักแห่ง ก็ไม่ถือเป็นข้อผิดพลาด — JavaScript จะข้ามมันไปเฉยๆ -More in: . +อ่านเพิ่มเติมได้ที่: -## Strict mode +## โหมดเข้มงวด (Strict mode) -To fully enable all features of modern JavaScript, we should start scripts with `"use strict"`. +เพื่อเปิดใช้งานคุณลักษณะทั้งหมดของ JavaScript สมัยใหม่อย่างเต็มที่ เราควรเริ่มต้นสคริปต์ด้วย `"use strict"` ```js 'use strict'; @@ -53,68 +53,68 @@ To fully enable all features of modern JavaScript, we should start scripts with ... ``` -The directive must be at the top of a script or at the beginning of a function body. +คำสั่งนี้จะต้องอยู่ที่ด้านบนสุดของสคริปต์ หรือที่จุดเริ่มต้นของ function body -Without `"use strict"`, everything still works, but some features behave in the old-fashion, "compatible" way. We'd generally prefer the modern behavior. +หากไม่มี `"use strict"` ทุกอย่างก็ยังคงทำงานได้ แต่คุณลักษณะบางอย่างจะทำงานในแบบเก่าๆ "แบบเข้ากันได้" โดยทั่วไปเรามักจะต้องการพฤติกรรมแบบสมัยใหม่มากกว่า -Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly. +คุณลักษณะสมัยใหม่บางอย่างของภาษา (เช่น คลาส ซึ่งเราจะศึกษาในอนาคต) จะเปิดใช้งานโหมดเข้มงวดโดยอัตโนมัติ -More in: . +อ่านเพิ่มเติมได้ที่: -## Variables +## ตัวแปร -Can be declared using: +สามารถประกาศตัวแปรได้โดยใช้: - `let` -- `const` (constant, can't be changed) -- `var` (old-style, will see later) +- `const` (ค่าคงที่ ไม่สามารถเปลี่ยนแปลงได้) +- `var` (แบบเก่า จะได้เห็นในภายหลัง) -A variable name can include: -- Letters and digits, but the first character may not be a digit. -- Characters `$` and `_` are normal, on par with letters. -- Non-Latin alphabets and hieroglyphs are also allowed, but commonly not used. +ชื่อตัวแปรสามารถประกอบด้วย: +- ตัวอักษรและตัวเลข แต่ตัวแรกจะต้องไม่ใช่ตัวเลข +- อักขระ `$` และ `_` เป็นอักขระปกติ เทียบเท่ากับตัวอักษร +- ตัวอักษรที่ไม่ใช่ละตินและอักษรอียิปต์โบราณก็สามารถใช้ได้ แต่ไม่ค่อยนิยมใช้ -Variables are dynamically typed. They can store any value: +ตัวแปรมีการกำหนดชนิดข้อมูลแบบไดนามิก สามารถเก็บค่าใดๆ ก็ได้: ```js let x = 5; x = "John"; ``` -There are 8 data types: +มีชนิดข้อมูลทั้งหมด 8 ชนิด: -- `number` for both floating-point and integer numbers, -- `bigint` for integer numbers of arbitrary length, -- `string` for strings, -- `boolean` for logical values: `true/false`, -- `null` -- a type with a single value `null`, meaning "empty" or "does not exist", -- `undefined` -- a type with a single value `undefined`, meaning "not assigned", -- `object` and `symbol` -- for complex data structures and unique identifiers, we haven't learnt them yet. +- `number` สำหรับตัวเลขทั้งจำนวนจริงและจำนวนเต็ม +- `bigint` สำหรับจำนวนเต็มที่มีความยาวตามต้องการ +- `string` สำหรับข้อความ +- `boolean` สำหรับค่าตรรกะ: `true/false` +- `null` -- ชนิดข้อมูลที่มีค่าเดียวคือ `null` หมายถึง "ว่างเปล่า" หรือ "ไม่มีอยู่" +- `undefined` -- ชนิดข้อมูลที่มีค่าเดียวคือ `undefined` หมายถึง "ยังไม่ได้กำหนดค่า" +- `object` และ `symbol` -- สำหรับโครงสร้างข้อมูลที่ซับซ้อน และตัวระบุที่ไม่ซ้ำกัน เรายังไม่ได้เรียนรู้เกี่ยวกับสิ่งเหล่านี้ -The `typeof` operator returns the type for a value, with two exceptions: +ตัวดำเนินการ `typeof` จะคืนค่าชนิดข้อมูลของค่านั้นๆ โดยมีข้อยกเว้นสองกรณี: ```js -typeof null == "object" // error in the language -typeof function(){} == "function" // functions are treated specially +typeof null == "object" // ข้อผิดพลาดในภาษา +typeof function(){} == "function" // ฟังก์ชันมีการจัดการพิเศษ ``` -More in: and . +อ่านเพิ่มเติมได้ที่: และ -## Interaction +## การโต้ตอบ -We're using a browser as a working environment, so basic UI functions will be: +เนื่องจากเรากำลังใช้เบราว์เซอร์เป็นสภาพแวดล้อมในการทำงาน ฟังก์ชัน UI พื้นฐานจึงประกอบด้วย: -[`prompt(question, [default])`](mdn:api/Window/prompt) -: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel". +[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) +: ถาม `question` และคืนค่าสิ่งที่ผู้ใช้ป้อนเข้ามา หรือ `null` หากผู้ใช้คลิก "ยกเลิก" -[`confirm(question)`](mdn:api/Window/confirm) -: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. +[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +: ถาม `question` และแนะนำให้เลือกระหว่าง ตกลง และ ยกเลิก โดยคืนค่าการเลือกเป็น `true/false` -[`alert(message)`](mdn:api/Window/alert) -: Output a `message`. +[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) +: แสดงผล `message` -All these functions are *modal*, they pause the code execution and prevent the visitor from interacting with the page until they answer. +ฟังก์ชันเหล่านี้ทั้งหมดเป็นแบบ *modal* หมายความว่าจะหยุดการทำงานของโค้ด และป้องกันไม่ให้ผู้ใช้โต้ตอบกับหน้าเว็บจนกว่าจะตอบคำถาม -For instance: +ตัวอย่างเช่น: ```js run let userName = prompt("Your name?", "Alice"); @@ -124,61 +124,61 @@ alert( "Visitor: " + userName ); // Alice alert( "Tea wanted: " + isTeaWanted ); // true ``` -More in: . +อ่านเพิ่มเติมได้ที่: -## Operators +## ตัวดำเนินการ (Operators) -JavaScript supports the following operators: +JavaScript รองรับตัวดำเนินการดังต่อไปนี้: -Arithmetical -: Regular: `* + - /`, also `%` for the remainder and `**` for power of a number. +ทางคณิตศาสตร์ +: ปกติ: `* + - /` รวมถึง `%` สำหรับการหารเอาเศษ และ `**` สำหรับการยกกำลัง - The binary plus `+` concatenates strings. And if any of the operands is a string, the other one is converted to string too: + ตัวดำเนินการบวก `+` แบบสองตัวถูกกระทำจะเชื่อมต่อสตริง และถ้าตัวใดตัวหนึ่งเป็นสตริง อีกตัวก็จะถูกแปลงเป็นสตริงด้วยเช่นกัน: ```js run alert( '1' + 2 ); // '12', string alert( 1 + '2' ); // '12', string ``` -Assignments -: There is a simple assignment: `a = b` and combined ones like `a *= 2`. +การกำหนดค่า +: มีการกำหนดค่าธรรมดา: `a = b` และแบบรวมต่างๆ เช่น `a *= 2` -Bitwise -: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Guide/Expressions_and_Operators#Bitwise) when they are needed. +การดำเนินการระดับบิต +: ตัวดำเนินการระดับบิตจะทำงานกับจำนวนเต็ม 32 บิตที่ระดับบิตต่ำสุด: ดูที่ [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) เมื่อต้องการ -Conditional -: The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. +แบบมีเงื่อนไข +: ตัวดำเนินการเพียงตัวเดียวที่มีสามพารามิเตอร์: `cond ? resultA : resultB` ถ้า `cond` เป็น truthy จะคืนค่า `resultA` ไม่เช่นนั้นจะคืน `resultB` -Logical operators -: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped (not necessary `true`/`false`). Logical NOT `!` converts the operand to boolean type and returns the inverse value. +ตัวดำเนินการทางตรรกะ +: AND ตรรกะ `&&` และ OR ตรรกะ `||` ประเมินแบบลัดวงจรและคืนค่าตรงที่หยุด (ไม่จำเป็นต้องเป็น `true/false`) ส่วน NOT ตรรกะ `!` แปลงตัวถูกกระทำเป็น boolean แล้วคืนค่าตรงข้าม -Nullish coalescing operator -: The `??` operator provides a way to choose a defined value from a list of variables. The result of `a ?? b` is `a` unless it's `null/undefined`, then `b`. +ตัวดำเนินการ Nullish coalescing +: ตัวดำเนินการ `??` ใช้เลือกค่าที่กำหนดแล้วจากรายการตัวแปร ผลลัพธ์ของ `a ?? b` คือ `a` เว้นแต่ `a` จะเป็น `null/undefined` จึงได้ `b` แทน -Comparisons -: Equality check `==` for values of different types converts them to a number (except `null` and `undefined` that equal each other and nothing else), so these are equal: +การเปรียบเทียบ +: การตรวจสอบความเท่ากัน `==` สำหรับค่าที่มีชนิดต่างกันจะแปลงเป็นตัวเลข (ยกเว้น `null` และ `undefined` ซึ่งเท่ากันและไม่เท่ากับอะไรอื่น) ดังนั้นพวกนี้จึงเท่ากัน: ```js run alert( 0 == false ); // true alert( 0 == '' ); // true ``` - Other comparisons convert to a number as well. + การเปรียบเทียบอื่นๆ ก็จะแปลงเป็นตัวเลขด้วยเช่นกัน - The strict equality operator `===` doesn't do the conversion: different types always mean different values for it. + ตัวดำเนินการเปรียบเทียบความเท่ากันแบบเข้มงวด `===` จะไม่แปลงค่า: ชนิดที่ต่างกันเสมอไปจะถือว่าค่าไม่เท่ากัน - Values `null` and `undefined` are special: they equal `==` each other and don't equal anything else. + ค่า `null` และ `undefined` พิเศษกว่าชนิดอื่น: เท่ากัน `==` กันเองและไม่เท่ากับอะไรอื่นเลย - Greater/less comparisons compare strings character-by-character, other types are converted to a number. + การเปรียบเทียบมากกว่า/น้อยกว่าจะเปรียบเทียบสตริงทีละอักขระ ส่วนชนิดอื่นจะแปลงเป็นตัวเลข -Other operators -: There are few others, like a comma operator. +ตัวดำเนินการอื่นๆ +: มีอีกไม่กี่ตัว เช่น comma operator -More in: , , , . +อ่านเพิ่มเติมได้ที่: , , , -## Loops +## ลูป -- We covered 3 types of loops: +- เราได้ครอบคลุมลูป 3 ประเภท: ```js // 1 @@ -197,25 +197,25 @@ More in: , , , . +รายละเอียดใน: -Later we'll study more types of loops to deal with objects. +ในภายหลังเราจะศึกษาลูปชนิดอื่นๆ เพิ่มเติมเพื่อจัดการกับออบเจ็กต์ -## The "switch" construct +## โครงสร้าง "switch" -The "switch" construct can replace multiple `if` checks. It uses `===` (strict equality) for comparisons. +โครงสร้าง "switch" ใช้แทนการตรวจสอบ `if` หลายครั้งได้ โดยใช้ `===` (เปรียบเทียบแบบเข้มงวด) ในการจับคู่ค่า -For instance: +ตัวอย่างเช่น: ```js run let age = prompt('Your age?', 18); switch (age) { case 18: - alert("Won't work"); // the result of prompt is a string, not a number + alert("Won't work"); // ผลลัพธ์จาก prompt เป็นสตริง ไม่ใช่ตัวเลข break; case "18": @@ -227,13 +227,13 @@ switch (age) { } ``` -Details in: . +รายละเอียดใน: -## Functions +## ฟังก์ชัน -We covered three ways to create a function in JavaScript: +เราได้ครอบคลุมวิธีการสร้างฟังก์ชันใน JavaScript สามแบบ: -1. Function Declaration: the function in the main code flow +1. Function Declaration: ฟังก์ชันในลำดับการทำงานหลักของโค้ด ```js function sum(a, b) { @@ -243,7 +243,7 @@ We covered three ways to create a function in JavaScript: } ``` -2. Function Expression: the function in the context of an expression +2. Function Expression: ฟังก์ชันในบริบทของนิพจน์ ```js let sum = function(a, b) { @@ -256,29 +256,28 @@ We covered three ways to create a function in JavaScript: 3. Arrow functions: ```js - // expression at the right side + // นิพจน์ทางด้านขวา let sum = (a, b) => a + b; - // or multi-line syntax with { ... }, need return here: + // หรือใช้ไวยากรณ์แบบหลายบรรทัดด้วย { ... }, ต้องใส่ return ที่นี่: let sum = (a, b) => { // ... return a + b; } - // without arguments + // ไม่มีอาร์กิวเมนต์ let sayHi = () => alert("Hello"); - // with a single argument + // มีอาร์กิวเมนต์เดียว let double = n => n * 2; ``` +- ฟังก์ชันอาจมีตัวแปรภายใน (local variables): คือตัวแปรที่ประกาศไว้ใน body หรือในรายการพารามิเตอร์ ใช้ได้เฉพาะภายในฟังก์ชันนั้นเท่านั้น +- พารามิเตอร์สามารถกำหนดค่าเริ่มต้นได้: `function sum(a = 1, b = 2) {...}` +- ฟังก์ชันจะคืนค่าอะไรบางอย่างเสมอ หากไม่มีประโยค `return` ผลลัพธ์จะเป็น `undefined` -- Functions may have local variables: those declared inside its body. Such variables are only visible inside the function. -- Parameters can have default values: `function sum(a = 1, b = 2) {...}`. -- Functions always return something. If there's no `return` statement, then the result is `undefined`. +รายละเอียด: ดูที่ , -Details: see , . +## ในอนาคตยังมีอีกมาก -## More to come - -That was a brief list of JavaScript features. As of now we've studied only basics. Further in the tutorial you'll find more specials and advanced features of JavaScript. +นี่เป็นเพียงรายการโดยย่อ — ตอนนี้เราเรียนแค่พื้นฐานเท่านั้น ในบทต่อๆ ไปจะได้พบกับฟีเจอร์ที่พิเศษและลึกซึ้งยิ่งขึ้นอีกมากมาย \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index ee7dea4c4..efd01b292 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,196 +1,195 @@ -# Debugging in Chrome +# การดีบักใน Browser -Before writing more complex code, let's talk about debugging. +ก่อนที่จะเขียนโค้ดที่ซับซ้อนกว่านี้ มาคุยกันเรื่องการดีบักกันสักหน่อย -[Debugging](https://en.wikipedia.org/wiki/Debugging) is the process of finding and fixing errors within a script. All modern browsers and most other environments support debugging tools -- a special UI in developer tools that makes debugging much easier. It also allows to trace the code step by step to see what exactly is going on. +[Debugging](https://en.wikipedia.org/wiki/Debugging) คือกระบวนการค้นหาและแก้ไขข้อผิดพลาดในสคริปต์ เบราว์เซอร์สมัยใหม่ทุกตัวรวมถึงสภาพแวดล้อมส่วนใหญ่ต่างมีเครื่องมือสำหรับดีบักมาให้ — เป็น UI พิเศษในเครื่องมือนักพัฒนาที่ช่วยให้ดีบักง่ายขึ้นมาก แถมยังติดตามการทำงานของโค้ดได้ทีละขั้นตอน เพื่อดูว่าเกิดอะไรขึ้นกันแน่ -We'll be using Chrome here, because it has enough features, most other browsers have a similar process. +บทความนี้จะใช้ Chrome เป็นหลัก เพราะมีฟีเจอร์ครบถ้วน ส่วนเบราว์เซอร์อื่นๆ ก็มีขั้นตอนคล้ายๆ กัน -## The "Sources" panel +## แผง "Sources" -Your Chrome version may look a little bit different, but it still should be obvious what's there. +Chrome เวอร์ชันที่ใช้อาจมีหน้าตาแตกต่างกันไปบ้าง แต่ก็น่าจะพอเห็นได้ชัดว่ามีอะไรอยู่ตรงไหน -- Open the [example page](debugging/index.html) in Chrome. -- Turn on developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). -- Select the `Sources` panel. +- เปิด[หน้าตัวอย่าง](debugging/index.html) ใน Chrome +- เปิดเครื่องมือนักพัฒนาด้วย `key:F12` (Mac: `key:Cmd+Opt+I`) +- เลือกแผง `Sources` -Here's what you should see if you are doing it for the first time: +ถ้าเป็นครั้งแรกที่ทำ จะเห็นหน้าตาแบบนี้: ![](chrome-open-sources.svg) -The toggler button opens the tab with files. +ปุ่มสลับ จะเปิดแท็บที่แสดงรายการไฟล์ -Let's click it and select `hello.js` in the tree view. Here's what should show up: +ลองคลิกแล้วเลือก `hello.js` ในมุมมองแบบต้นไม้ จะเห็นหน้าจอแบบนี้: ![](chrome-tabs.svg) -The Sources panel has 3 parts: +แผง Sources ประกอบด้วย 3 ส่วน: -1. The **File Navigator** pane lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. -2. The **Code Editor** pane shows the source code. -3. The **JavaScript Debugging** pane is for debugging, we'll explore it soon. +1. บานหน้าต่าง **File Navigator** แสดงรายการไฟล์ HTML, JavaScript, CSS และอื่นๆ รวมถึงรูปภาพที่แนบมากับหน้าเว็บ ส่วนขยายของ Chrome ก็อาจปรากฏที่นี่เช่นกัน +2. บานหน้าต่าง **Code Editor** แสดงซอร์สโค้ด +3. บานหน้าต่าง **JavaScript Debugging** ใช้สำหรับดีบัก — เดี๋ยวเราจะมาดูกันในอีกสักครู่ -Now you could click the same toggler again to hide the resources list and give the code some space. +ตอนนี้ลองคลิกปุ่มสลับ อีกครั้งเพื่อซ่อนรายการไฟล์และเพิ่มพื้นที่ให้โค้ดได้ ## Console -If we press `key:Esc`, then a console opens below. We can type commands there and press `key:Enter` to execute. +ถ้ากด `key:Esc` จะมีคอนโซลเปิดขึ้นมาด้านล่าง พิมพ์คำสั่งลงไปแล้วกด `key:Enter` เพื่อรัน -After a statement is executed, its result is shown below. +พอคำสั่งทำงานเสร็จ ผลลัพธ์ก็จะแสดงอยู่ด้านล่าง -For example, here `1+2` results in `3`, and `hello("debugger")` returns nothing, so the result is `undefined`: +เช่น `1+2` ให้ผลลัพธ์เป็น `3` ส่วนการเรียกฟังก์ชัน `hello("debugger")` ไม่ได้คืนค่าอะไร ผลลัพธ์จึงเป็น `undefined`: ![](chrome-sources-console.svg) ## Breakpoints -Let's examine what's going on within the code of the [example page](debugging/index.html). In `hello.js`, click at line number `4`. Yes, right on the `4` digit, not on the code. +มาดูกันว่าเกิดอะไรขึ้นในโค้ดของ[หน้าตัวอย่าง](debugging/index.html)บ้าง ใน `hello.js` ให้คลิกที่เลขบรรทัด `4` — ใช่ คลิกที่ตัวเลข `4` เลย ไม่ใช่ที่โค้ด -Congratulations! You've set a breakpoint. Please also click on the number for line `8`. +ยินดีด้วย! ตั้ง breakpoint ได้แล้ว ลองคลิกที่เลขบรรทัด `8` ด้วยนะ -It should look like this (blue is where you should click): +หน้าตาควรเป็นแบบนี้ (สีน้ำเงินคือตำแหน่งที่ควรคลิก): ![](chrome-sources-breakpoint.svg) -A *breakpoint* is a point of code where the debugger will automatically pause the JavaScript execution. +*breakpoint* คือจุดในโค้ดที่ตัวดีบักเกอร์จะหยุดการทำงานของ JavaScript โดยอัตโนมัติ -While the code is paused, we can examine current variables, execute commands in the console etc. In other words, we can debug it. +ระหว่างที่โค้ดหยุดอยู่ เราตรวจสอบค่าตัวแปร รันคำสั่งในคอนโซล หรือทำอะไรอื่นๆ ก็ได้ — พูดง่ายๆ ก็คือ ดีบักโค้ดได้เลยตรงนั้น -We can always find a list of breakpoints in the right panel. That's useful when we have many breakpoints in various files. It allows us to: -- Quickly jump to the breakpoint in the code (by clicking on it in the right panel). -- Temporarily disable the breakpoint by unchecking it. -- Remove the breakpoint by right-clicking and selecting Remove. -- ...And so on. +รายการ breakpoint ทั้งหมดจะแสดงอยู่ที่แผงด้านขวาเสมอ ตรงนี้มีประโยชน์มากเวลามี breakpoint หลายจุดกระจายอยู่ในหลายไฟล์ เพราะช่วยให้เรา: +- กระโดดไปยัง breakpoint ในโค้ดได้ทันที (แค่คลิกที่รายการในแผงด้านขวา) +- ปิด breakpoint ชั่วคราวโดยเอาเครื่องหมายถูกออก +- ลบ breakpoint โดยคลิกขวาแล้วเลือก Remove +- ...และอื่นๆ -```smart header="Conditional breakpoints" -*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression is truthy. +```smart header="Breakpoint แบบมีเงื่อนไข" +*คลิกขวา* ที่เลขบรรทัดจะสร้าง breakpoint *แบบมีเงื่อนไข* ได้ breakpoint แบบนี้จะทำงานก็ต่อเมื่อนิพจน์ที่กำหนดไว้ (ต้องใส่เองตอนสร้าง) มีค่าเป็นจริงเท่านั้น -That's handy when we need to stop only for a certain variable value or for certain function parameters. +วิธีนี้มีประโยชน์มากเวลาอยากหยุดเฉพาะตอนที่ตัวแปรบางตัว หรือพารามิเตอร์ของฟังก์ชันมีค่าตามที่ต้องการ ``` -## Debugger command +## คำสั่ง "debugger" -We can also pause the code by using the `debugger` command in it, like this: +นอกจาก breakpoint แล้ว เรายังหยุดโค้ดได้โดยใช้คำสั่ง `debugger` ในโค้ดโดยตรง แบบนี้: ```js function hello(name) { let phrase = `Hello, ${name}!`; *!* - debugger; // <-- the debugger stops here + debugger; // <-- ตัวดีบักเกอร์จะหยุดที่ตรงนี้ */!* say(phrase); } ``` -That's very convenient when we are in a code editor and don't want to switch to the browser and look up the script in developer tools to set the breakpoint. +คำสั่งนี้จะทำงานก็ต่อเมื่อเครื่องมือนักพัฒนาเปิดอยู่เท่านั้น ไม่งั้นเบราว์เซอร์จะข้ามไปเฉยๆ +## หยุดแล้วสำรวจดูรอบๆ -## Pause and look around +ในตัวอย่างของเรา `hello()` ถูกเรียกตอนโหลดหน้าเว็บ ดังนั้นวิธีที่ง่ายที่สุดในการเปิดใช้ตัวดีบักเกอร์ (หลังตั้ง breakpoint แล้ว) ก็คือโหลดหน้าเว็บใหม่ — กด `key:F5` (Windows, Linux) หรือ `key:Cmd+R` (Mac) เลย -In our example, `hello()` is called during the page load, so the easiest way to activate the debugger (after we've set the breakpoints) is to reload the page. So let's press `key:F5` (Windows, Linux) or `key:Cmd+R` (Mac). - -As the breakpoint is set, the execution pauses at the 4th line: +เนื่องจากตั้ง breakpoint ไว้แล้ว โปรแกรมจะหยุดที่บรรทัดที่ 4: ![](chrome-sources-debugger-pause.svg) -Please open the informational dropdowns to the right (labeled with arrows). They allow you to examine the current code state: +ลองเปิดดรอปดาวน์แสดงข้อมูลทางด้านขวา (ที่มีลูกศรชี้อยู่) จะได้ตรวจสอบสถานะของโค้ดในปัจจุบัน: -1. **`Watch` -- shows current values for any expressions.** +1. **`Watch` — แสดงค่าปัจจุบันของนิพจน์ต่างๆ** - You can click the plus `+` and input an expression. The debugger will show its value at any moment, automatically recalculating it in the process of execution. + คลิกเครื่องหมายบวก `+` แล้วใส่นิพจน์เข้าไป ตัวดีบักเกอร์จะแสดงค่าของนิพจน์นั้น และคำนวณใหม่ให้อัตโนมัติระหว่างรันโค้ด -2. **`Call Stack` -- shows the nested calls chain.** +2. **`Call Stack` — แสดงลำดับการเรียกฟังก์ชันที่ซ้อนกัน** - At the current moment the debugger is inside `hello()` call, called by a script in `index.html` (no function there, so it's called "anonymous"). + ตอนนี้ตัวดีบักเกอร์อยู่ในฟังก์ชัน `hello()` ซึ่งถูกเรียกจากสคริปต์ใน `index.html` (ไม่มีฟังก์ชันตรงนั้น จึงเรียกว่า "anonymous") - If you click on a stack item (e.g. "anonymous"), the debugger jumps to the corresponding code, and all its variables can be examined as well. -3. **`Scope` -- current variables.** + ถ้าคลิกที่รายการใน call stack (เช่น "anonymous") ตัวดีบักเกอร์จะกระโดดไปยังโค้ดส่วนนั้น และตรวจสอบค่าตัวแปรทั้งหมดที่นั่นได้เช่นกัน +3. **`Scope` — ตัวแปรในสโคปปัจจุบัน** - `Local` shows local function variables. You can also see their values highlighted right over the source. + `Local` แสดงตัวแปรภายในฟังก์ชัน ค่าของตัวแปรเหล่านี้จะถูกไฮไลต์ไว้เหนือโค้ดด้วย - `Global` has global variables (out of any functions). + `Global` แสดงตัวแปรระดับ global (ที่อยู่นอกฟังก์ชันใดๆ) - There's also `this` keyword there that we didn't study yet, but we'll do that soon. + นอกจากนี้ยังมีคีย์เวิร์ด `this` ที่เรายังไม่ได้เรียน แต่เดี๋ยวจะได้เรียนกันเร็วๆ นี้แหละ -## Tracing the execution +## การติดตามการทำงานของโค้ด -Now it's time to *trace* the script. +ตอนนี้ถึงเวลา *ติดตาม* การทำงานของสคริปต์แล้ว -There are buttons for it at the top of the right panel. Let's engage them. +มีปุ่มสำหรับติดตามอยู่ด้านบนของแผงทางขวา มาลองใช้กัน - -- "Resume": continue the execution, hotkey `key:F8`. -: Resumes the execution. If there are no additional breakpoints, then the execution just continues and the debugger loses control. + — "Resume": รันต่อ, ปุ่มลัด `key:F8` +: รันโค้ดต่อไป ถ้าไม่มี breakpoint เพิ่มเติม โค้ดจะรันต่อจนจบ และตัวดีบักเกอร์จะปล่อยการควบคุม - Here's what we can see after a click on it: + นี่คือสิ่งที่เห็นหลังคลิกปุ่มนี้: ![](chrome-sources-debugger-trace-1.svg) - The execution has resumed, reached another breakpoint inside `say()` and paused there. Take a look at the "Call Stack" at the right. It has increased by one more call. We're inside `say()` now. + โค้ดรันต่อไปจนถึง breakpoint อีกจุดในฟังก์ชัน `say()` แล้วหยุดอยู่ตรงนั้น ลองดูที่ "Call Stack" ทางขวา จะเห็นว่ามีการเรียกเพิ่มขึ้นมาอีกชั้น — ตอนนี้เราอยู่ในฟังก์ชัน `say()` แล้ว - -- "Step": run the next command, hotkey `key:F9`. -: Run the next statement. If we click it now, `alert` will be shown. + — "Step": รันคำสั่งถัดไป, ปุ่มลัด `key:F9` +: รันคำสั่งถัดไป ถ้าคลิกตอนนี้ `alert` จะปรากฏขึ้น - Clicking this again and again will step through all script statements one by one. + คลิกซ้ำๆ จะไล่ผ่านคำสั่งในสคริปต์ไปทีละบรรทัด - -- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`. -: Similar to the previous the "Step" command, but behaves differently if the next statement is a function call. That is: not a built-in, like `alert`, but a function of our own. + — "Step over": รันคำสั่งถัดไป แต่ *ไม่เข้าไปในฟังก์ชัน*, ปุ่มลัด `key:F10` +: คล้ายกับ "Step" แต่ทำงานต่างกันถ้าคำสั่งถัดไปเป็นการเรียกฟังก์ชัน (ไม่ใช่ฟังก์ชันในตัวอย่าง `alert` แต่เป็นฟังก์ชันที่เราเขียนเอง) - The "Step" command goes into it and pauses the execution at its first line, while "Step over" executes the nested function call invisibly, skipping the function internals. + ถ้าเทียบกัน — "Step" จะเข้าไปในฟังก์ชันที่เรียก แล้วหยุดที่บรรทัดแรกของฟังก์ชันนั้น ส่วน "Step over" จะรันฟังก์ชันนั้นจนจบโดยไม่แวะเข้าไปดูข้างใน - The execution is then paused immediately after that function. + แล้วค่อยหยุดที่บรรทัดถัดไปหลังจบการเรียกฟังก์ชัน - That's good if we're not interested to see what happens inside the function call. + เหมาะสำหรับเวลาที่ไม่สนใจว่าข้างในฟังก์ชันนั้นทำอะไร - -- "Step into", hotkey `key:F11`. -: That's similar to "Step", but behaves differently in case of asynchronous function calls. If you're only starting to learn JavaScript, then you can ignore the difference, as we don't have asynchronous calls yet. + — "Step into", ปุ่มลัด `key:F11` +: คล้ายกับ "Step" แต่ทำงานต่างกันกรณีเรียกฟังก์ชันแบบ async ถ้าเพิ่งเริ่มเรียน JavaScript ข้ามส่วนนี้ไปก่อนได้เลย เพราะยังไม่มีการเรียกแบบ async - For the future, just note that "Step" command ignores async actions, such as `setTimeout` (scheduled function call), that execute later. The "Step into" goes into their code, waiting for them if necessary. See [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) for more details. + แค่จำไว้ว่า "Step" จะข้ามการทำงานแบบ async เช่น `setTimeout` (ฟังก์ชันที่ตั้งเวลาเรียกทีหลัง) ส่วน "Step into" จะเข้าไปในโค้ดส่วนนั้นและรอจนเสร็จถ้าจำเป็น อ่านรายละเอียดเพิ่มเติมได้ใน [คู่มือ DevTools](https://developers.google.com/web/updates/2018/01/devtools#async) - -- "Step out": continue the execution till the end of the current function, hotkey `key:Shift+F11`. -: Continue the execution and stop it at the very last line of the current function. That's handy when we accidentally entered a nested call using , but it does not interest us, and we want to continue to its end as soon as possible. + — "Step out": รันต่อจนจบฟังก์ชันปัจจุบัน, ปุ่มลัด `key:Shift+F11` +: รันโค้ดต่อไปจนถึงบรรทัดสุดท้ายของฟังก์ชันปัจจุบัน เหมาะสำหรับเวลาที่เผลอกด เข้าไปในฟังก์ชันซ้อน แต่ไม่ได้อยากดูข้างใน อยากให้จบไวๆ - -- enable/disable all breakpoints. -: That button does not move the execution. Just a mass on/off for breakpoints. + — เปิด/ปิด breakpoint ทั้งหมด +: ปุ่มนี้ไม่ได้เลื่อนการทำงานของโค้ด แค่เปิด/ปิด breakpoint ทั้งหมดพร้อมกัน - -- enable/disable automatic pause in case of an error. -: When enabled, and the developer tools is open, a script error automatically pauses the execution. Then we can analyze variables to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. + — เปิด/ปิดการหยุดอัตโนมัติเมื่อเกิด error +: เมื่อเปิดไว้ ถ้าเครื่องมือนักพัฒนากำลังเปิดอยู่แล้วสคริปต์เกิด error ระหว่างรัน โค้ดจะหยุดทันที เราก็ตรวจสอบค่าตัวแปรต่างๆ ในตัวดีบักเกอร์ได้เลยว่ามีอะไรผิดปกติ ดังนั้นถ้าสคริปต์พังเพราะ error ก็แค่เปิดตัวดีบักเกอร์ เปิดตัวเลือกนี้ แล้วโหลดหน้าเว็บใหม่ — จะได้เห็นว่าโค้ดหยุดที่ตรงไหนและสถานะตอนนั้นเป็นยังไง -```smart header="Continue to here" -Right click on a line of code opens the context menu with a great option called "Continue to here". +```smart header="ข้ามไปยังบรรทัดที่ต้องการ (Continue to here)" +คลิกขวาที่บรรทัดโค้ดจะเปิดเมนูบริบทขึ้นมา ซึ่งมีตัวเลือกที่เจ๋งมากคือ "Continue to here" -That's handy when we want to move multiple steps forward to the line, but we're too lazy to set a breakpoint. +มีประโยชน์มากเวลาอยากข้ามไปหลายบรรทัดรวดเดียว แต่ขี้เกียจตั้ง breakpoint ``` -## Logging +## การเก็บ Log -To output something to console from our code, there's `console.log` function. +ถ้าอยากแสดงผลบางอย่างไปที่คอนโซลจากโค้ดของเรา ก็ใช้ฟังก์ชัน `console.log` ได้ -For instance, this outputs values from `0` to `4` to console: +เช่น โค้ดนี้จะแสดงค่าตั้งแต่ `0` ถึง `4` ไปที่คอนโซล: ```js run -// open console to see +// เปิดคอนโซลเพื่อดูผลลัพธ์ for (let i = 0; i < 5; i++) { console.log("value,", i); } ``` -Regular users don't see that output, it is in the console. To see it, either open the Console panel of developer tools or press `key:Esc` while in another panel: that opens the console at the bottom. +ผู้ใช้ทั่วไปจะไม่เห็นผลลัพธ์นี้เพราะแสดงอยู่ในคอนโซล ถ้าต้องการดู ให้เปิดแผง Console ในเครื่องมือนักพัฒนา หรือกด `key:Esc` ขณะอยู่ในแผงอื่น ก็จะเปิดคอนโซลขึ้นมาด้านล่าง -If we have enough logging in our code, then we can see what's going on from the records, without the debugger. +ถ้าใส่ log ไว้ในโค้ดมากพอ ก็จะติดตามได้ว่าเกิดอะไรขึ้นจากข้อมูลที่บันทึกไว้ โดยไม่ต้องใช้ตัวดีบักเกอร์เลย -## Summary +## สรุป -As we can see, there are three main ways to pause a script: -1. A breakpoint. -2. The `debugger` statements. -3. An error (if dev tools are open and the button is "on"). +จะเห็นว่ามีสามวิธีหลักๆ ในการหยุดสคริปต์: +1. Breakpoint +2. คำสั่ง `debugger` +3. Error (ถ้าเครื่องมือนักพัฒนาเปิดอยู่และปุ่ม อยู่ในสถานะ "เปิด") -When paused, we can debug - examine variables and trace the code to see where the execution goes wrong. +เมื่อโค้ดหยุดแล้ว เราก็ดีบักได้ — ตรวจสอบค่าตัวแปรและติดตามการทำงานของโค้ดเพื่อหาจุดที่ผิดพลาด -There are many more options in developer tools than covered here. The full manual is at . +เครื่องมือนักพัฒนายังมีตัวเลือกอีกมากมายนอกเหนือจากที่กล่าวมา อ่านคู่มือฉบับเต็มได้ที่ -The information from this chapter is enough to begin debugging, but later, especially if you do a lot of browser stuff, please go there and look through more advanced capabilities of developer tools. +เนื้อหาในบทนี้เพียงพอสำหรับเริ่มต้นดีบักแล้ว แต่ถ้าต้องทำงานกับเบราว์เซอร์บ่อยๆ แนะนำให้ไปศึกษาความสามารถขั้นสูงของเครื่องมือนักพัฒนาเพิ่มเติม -Oh, and also you can click at various places of dev tools and just see what's showing up. That's probably the fastest route to learn dev tools. Don't forget about the right click and context menus! +อ้อ แล้วก็ลองคลิกไปตามส่วนต่างๆ ของเครื่องมือนักพัฒนาแล้วสังเกตดูว่ามีอะไรบ้าง นี่น่าจะเป็นวิธีที่เร็วที่สุดในการเรียนรู้การใช้งาน อย่าลืมลองคลิกขวาเพื่อเปิดเมนูบริบทด้วยนะ! diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg index 1f7d21288..5fc6dce3a 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg @@ -1 +1 @@ -open sources \ No newline at end of file +open sources \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg index 6fb4332f1..63bf4966e 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg @@ -1 +1 @@ -here's the listbreakpoints \ No newline at end of file +here's the listbreakpoints \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg index 25284d055..3fe5f124f 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg index 40d9515ab..0147c2e0a 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg @@ -1 +1 @@ -213see the outer call detailswatch expressionscurrent variables \ No newline at end of file +213see the outer call detailswatch expressionscurrent variables \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg index 0d5bde9c4..9fa1b3b8c 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg @@ -1 +1 @@ -nested calls \ No newline at end of file +nested calls \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg index 352fbcb7c..016708256 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg @@ -1 +1 @@ -213 \ No newline at end of file +213 \ No newline at end of file diff --git a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md index 4facc8b29..e09f389a7 100644 --- a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md +++ b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md @@ -1,29 +1,28 @@ -You could note the following: +ลองสังเกตจุดที่มีปัญหา: ```js no-beautify -function pow(x,n) // <- no space between arguments -{ // <- figure bracket on a separate line - let result=1; // <- no spaces before or after = - for(let i=0;i -Now let's discuss the rules and reasons for them in detail. +ทีนี้มาคุยถึงกฎเหล่านี้พร้อมเหตุผลกันอย่างละเอียดเลย -```warn header="There are no \"you must\" rules" -Nothing is set in stone here. These are style preferences, not religious dogmas. +```warn header="ไม่มีกฎ \"ต้องทำ\" ตายตัว" +ไม่มีอะไรที่ตายตัวที่นี่ ข้อเสนอแนะเหล่านี้เป็นแค่รสนิยมในการจัดรูปแบบโค้ด ไม่ใช่หลักคำสอนทางศาสนา ``` -### Curly Braces +### วงเล็บปีกกา -In most JavaScript projects curly braces are written in "Egyptian" style with the opening brace on the same line as the corresponding keyword -- not on a new line. There should also be a space before the opening bracket, like this: +ในโปรเจ็กต์ JavaScript ส่วนใหญ่ วงเล็บปีกกาจะเขียนในสไตล์ "อียิปต์" คือวงเปิดอยู่บรรทัดเดียวกับคีย์เวิร์ด — ไม่ได้ขึ้นบรรทัดใหม่ และมีช่องว่างก่อนวงเปิดด้วย แบบนี้: ```js if (condition) { - // do this - // ...and that - // ...and that + // ทำอันนี้ + // ...แล้วก็อันนั้น + // ...แล้วก็อันนู่น } ``` -A single-line construct, such as `if (condition) doSomething()`, is an important edge case. Should we use braces at all? - -Here are the annotated variants so you can judge their readability for yourself: - -1. 😠 Beginners sometimes do that. Bad! Curly braces are not needed: - ```js - if (n < 0) *!*{*/!*alert(`Power ${n} is not supported`);*!*}*/!* - ``` -2. 😠 Split to a separate line without braces. Never do that, easy to make an error when adding new lines: - ```js - if (n < 0) - alert(`Power ${n} is not supported`); - ``` -3. 😏 One line without braces - acceptable, if it's short: - ```js - if (n < 0) alert(`Power ${n} is not supported`); - ``` -4. 😃 The best variant: - ```js - if (n < 0) { - alert(`Power ${n} is not supported`); - } - ``` - -For a very brief code, one line is allowed, e.g. `if (cond) return null`. But a code block (the last variant) is usually more readable. - -### Line Length - -No one likes to read a long horizontal line of code. It's best practice to split them. - -For example: +กรณีพิเศษที่น่าสนใจคือประโยคบรรทัดเดียว เช่น `if (condition) doSomething()` — แบบนี้ควรใส่วงเล็บปีกกาหรือเปล่า? + +ลองดูตัวเลือกต่างๆ พร้อมคำอธิบาย แล้วตัดสินด้วยตัวเองว่าแบบไหนอ่านง่ายกว่า: + +1. 😠 มือใหม่บางคนทำแบบนี้ ไม่ดีเลย! ไม่จำเป็นต้องใช้วงเล็บปีกกา: + ```js + if (n < 0) *!*{*/!*alert(`เลขยกกำลัง ${n} ไม่รองรับ`);*!*}*/!* + ``` +2. 😠 แยกเป็นบรรทัดใหม่โดยไม่ใช้วงเล็บปีกกา อย่าทำแบบนี้เลย เพราะจะผิดพลาดง่ายเวลาเพิ่มบรรทัดใหม่: + ```js + if (n < 0) + alert(`เลขยกกำลัง ${n} ไม่รองรับ`); + ``` +3. 😏 เขียนบรรทัดเดียวโดยไม่ใช้วงเล็บปีกกา - ยอมรับได้ถ้าสั้น: + ```js + if (n < 0) alert(`เลขยกกำลัง ${n} ไม่รองรับ`); + ``` +4. 😃 ตัวเลือกที่ดีที่สุด: + ```js + if (n < 0) { + alert(`เลขยกกำลัง ${n} ไม่รองรับ`); + } + ``` + +สำหรับโค้ดที่สั้นมากๆ อนุญาตให้เขียนบรรทัดเดียวได้ เช่น `if (cond) return null` แต่โดยทั่วไปแล้วการเขียนเป็นบล็อกโค้ด (ตัวเลือกสุดท้าย) จะอ่านเข้าใจง่ายกว่า + +### ความยาวบรรทัด + +ไม่มีใครชอบอ่านบรรทัดโค้ดที่ยาวเหยียด แนวปฏิบัติที่ดีคือตัดแบ่งให้สั้นลง + +ยกตัวอย่างเช่น: ```js -// backtick quotes ` allow to split the string into multiple lines +// เครื่องหมาย backtick ` ทำให้แบ่งสตริงเป็นหลายบรรทัดได้ let str = ` - ECMA International's TC39 is a group of JavaScript developers, - implementers, academics, and more, collaborating with the community - to maintain and evolve the definition of JavaScript. + ECMA International's TC39 เป็นกลุ่มนักพัฒนา JavaScript, + ผู้ทำระบบ, นักวิชาการ และอีกมากมาย ที่ร่วมกันดูแลรักษากับชุมชน + เพื่อปรับปรุงและพัฒนานิยามของ JavaScript `; ``` -And, for `if` statements: +และสำหรับประโยค `if`: ```js if ( id === 123 && - moonPhase === 'Waning Gibbous' && + moonPhase === 'Waning Gibbous' && zodiacSign === 'Libra' ) { letTheSorceryBegin(); } ``` -The maximum line length should be agreed upon at the team-level. It's usually 80 or 120 characters. +ความยาวสูงสุดของบรรทัดควรตกลงกันในระดับทีมงาน ปกติแล้วมักเป็น 80 หรือ 120 ตัวอักษร -### Indents +### การเยื้องบรรทัด -There are two types of indents: +การเยื้องบรรทัดมี 2 แบบ: -- **Horizontal indents: 2 or 4 spaces.** +- **การเยื้องในแนวนอน: เว้นวรรค 2 หรือ 4 ช่อง** - A horizontal indentation is made using either 2 or 4 spaces or the horizontal tab symbol (key `key:Tab`). Which one to choose is an old holy war. Spaces are more common nowadays. + ใช้เว้นวรรค 2 หรือ 4 ช่อง หรือใช้แท็บ (ปุ่ม `key:Tab`) ก็ได้ เรื่องนี้เป็นประเด็นถกเถียงกันมานาน แต่ปัจจุบันนิยมใช้เว้นวรรคมากกว่า - One advantage of spaces over tabs is that spaces allow more flexible configurations of indents than the tab symbol. + ข้อดีของเว้นวรรคคือ กำหนดระยะเยื้องได้ยืดหยุ่นกว่าแท็บ - For instance, we can align the arguments with the opening bracket, like this: + เช่น จัดตำแหน่งพารามิเตอร์ให้ตรงกับวงเล็บเปิดได้แบบนี้: ```js no-beautify show(parameters, - aligned, // 5 spaces padding at the left + aligned, // เว้นวรรค 5 ช่องจากทางซ้าย one, after, another @@ -129,9 +129,9 @@ There are two types of indents: } ``` -- **Vertical indents: empty lines for splitting code into logical blocks.** +- **การเว้นบรรทัดในแนวตั้ง: เว้นบรรทัดว่างเพื่อแบ่งโค้ดเป็นกลุ่มย่อยตามลำดับขั้นตอน** - Even a single function can often be divided into logical blocks. In the example below, the initialization of variables, the main loop and returning the result are split vertically: + แม้แต่ฟังก์ชันเดียวก็มักแบ่งเป็นกลุ่มย่อยได้ ตัวอย่างด้านล่างนี้ แยกส่วนกำหนดค่าเริ่มต้น, ลูปหลัก และการคืนค่า ออกจากกันด้วยบรรทัดว่าง: ```js function pow(x, n) { @@ -145,51 +145,51 @@ There are two types of indents: } ``` - Insert an extra newline where it helps to make the code more readable. There should not be more than nine lines of code without a vertical indentation. + เพิ่มบรรทัดว่างในจุดที่ช่วยให้อ่านโค้ดเข้าใจง่ายขึ้น ไม่ควรมีโค้ดยาวเกิน 9 บรรทัดโดยไม่มีการเว้นบรรทัดในแนวตั้ง -### Semicolons +### เครื่องหมายอัฒภาค (semicolon) -A semicolon should be present after each statement, even if it could possibly be skipped. +ควรมีอัฒภาคปิดท้ายแต่ละประโยคเสมอ แม้ในบางกรณีจะละเว้นได้ก็ตาม -There are languages where a semicolon is truly optional and it is rarely used. In JavaScript, though, there are cases where a line break is not interpreted as a semicolon, leaving the code vulnerable to errors. See more about that in the chapter . +บางภาษามีอัฒภาคเป็นตัวเลือกล้วนๆ แทบไม่เห็นใช้เลย แต่ใน JavaScript มีบางกรณีที่ตัวขึ้นบรรทัดใหม่ไม่ได้ถูกตีความเป็นอัฒภาค ทำให้โค้ดเสี่ยงเกิดข้อผิดพลาดได้ อ่านเพิ่มเติมในบท -If you're an experienced JavaScript programmer, you may choose a no-semicolon code style like [StandardJS](https://standardjs.com/). Otherwise, it's best to use semicolons to avoid possible pitfalls. The majority of developers put semicolons. +ถ้าเป็นโปรแกรมเมอร์ JavaScript ที่มีประสบการณ์สูง อาจเลือกใช้สไตล์ไม่ใส่อัฒภาค เช่น [StandardJS](https://standardjs.com/) ก็ได้ แต่ถ้ายังไม่มั่นใจ แนะนำให้ใส่อัฒภาคเสมอจะปลอดภัยกว่า นักพัฒนาส่วนใหญ่ก็ใส่อัฒภาคกันเป็นปกติอยู่แล้ว -### Nesting Levels +### ระดับความลึกของการซ้อนโค้ด -Try to avoid nesting code too many levels deep. +พยายามหลีกเลี่ยงการซ้อนโค้ดลึกเกินไปหลายชั้น -For example, in the loop, it's sometimes a good idea to use the [`continue`](info:while-for#continue) directive to avoid extra nesting. +ยกตัวอย่างเช่น ในลูป บางครั้งการใช้คำสั่ง [`continue`](info:while-for#continue) เพื่อข้ามการซ้อนโค้ดเพิ่มเติมเป็นวิธีที่ดี -For example, instead of adding a nested `if` conditional like this: +เช่น แทนที่จะเพิ่มเงื่อนไข `if` ซ้อนกันแบบนี้: ```js for (let i = 0; i < 10; i++) { if (cond) { - ... // <- one more nesting level + ... // <- ซ้อนลึกลงไปอีกชั้น } } ``` -We can write: +เขียนเป็นแบบนี้แทนได้: ```js for (let i = 0; i < 10; i++) { if (!cond) *!*continue*/!*; - ... // <- no extra nesting level + ... // <- ไม่ต้องซ้อนลึกลงไปอีกชั้น } ``` -A similar thing can be done with `if/else` and `return`. +ใช้เทคนิคเดียวกันนี้กับ `if/else` และ `return` ก็ได้ -For example, two constructs below are identical. +ตัวอย่างเช่น โครงสร้างสองแบบด้านล่างนี้ให้ผลลัพธ์เหมือนกัน -Option 1: +แบบที่ 1: ```js function pow(x, n) { if (n < 0) { - alert("Negative 'n' not supported"); + alert("ไม่รองรับ 'n' ติดลบ"); } else { let result = 1; @@ -202,12 +202,12 @@ function pow(x, n) { } ``` -Option 2: +แบบที่ 2: ```js function pow(x, n) { if (n < 0) { - alert("Negative 'n' not supported"); + alert("ไม่รองรับ 'n' ติดลบ"); return; } @@ -221,102 +221,104 @@ function pow(x, n) { } ``` -The second one is more readable because the "special case" of `n < 0` is handled early on. Once the check is done we can move on to the "main" code flow without the need for additional nesting. +แบบที่สองอ่านเข้าใจง่ายกว่า เพราะจัดการ "กรณีพิเศษ" ที่ `n < 0` ตั้งแต่เนิ่นๆ พอตรวจสอบผ่านแล้ว ก็ไปต่อที่โค้ดหลักได้เลยโดยไม่ต้องซ้อนลึกลงไปอีก -## Function Placement +## การจัดวางตำแหน่งฟังก์ชัน -If you are writing several "helper" functions and the code that uses them, there are three ways to organize the functions. +ถ้ากำลังเขียนฟังก์ชัน "ผู้ช่วย" หลายตัว พร้อมกับโค้ดที่เรียกใช้ มีวิธีจัดวาง 3 แบบ -1. Declare the functions *above* the code that uses them: +1. ประกาศฟังก์ชัน *ก่อน* โค้ดที่เรียกใช้: - ```js - // *!*function declarations*/!* - function createElement() { - ... - } + ```js + // *!*ประกาศฟังก์ชัน*/!* + function createElement() { + ... + } - function setHandler(elem) { - ... - } + function setHandler(elem) { + ... + } - function walkAround() { - ... - } + function walkAround() { + ... + } - // *!*the code which uses them*/!* - let elem = createElement(); - setHandler(elem); - walkAround(); - ``` -2. Code first, then functions + // *!*โค้ดที่เรียกใช้ฟังก์ชัน*/!* + let elem = createElement(); + setHandler(elem); + walkAround(); + ``` - ```js - // *!*the code which uses the functions*/!* - let elem = createElement(); - setHandler(elem); - walkAround(); - - // --- *!*helper functions*/!* --- - function createElement() { - ... - } +2. เขียนโค้ดที่เรียกใช้ฟังก์ชันก่อน จากนั้นตามด้วยฟังก์ชัน - function setHandler(elem) { - ... - } + ```js + // *!*โค้ดที่เรียกใช้ฟังก์ชัน*/!* + let elem = createElement(); + setHandler(elem); + walkAround(); - function walkAround() { - ... - } - ``` -3. Mixed: a function is declared where it's first used. + // --- *!*ฟังก์ชันผู้ช่วย*/!* --- + function createElement() { + ... + } + + function setHandler(elem) { + ... + } + + function walkAround() { + ... + } + ``` + +3. ผสมกันระหว่างสองแบบข้างต้น: ประกาศฟังก์ชันในตำแหน่งที่มีการเรียกใช้ครั้งแรก -Most of time, the second variant is preferred. +โดยส่วนใหญ่แล้ว นิยมใช้แบบที่ 2 มากกว่า -That's because when reading code, we first want to know *what it does*. If the code goes first, then it becomes clear from the start. Then, maybe we won't need to read the functions at all, especially if their names are descriptive of what they actually do. +เพราะเวลาอ่านโค้ด เราอยากรู้ก่อนว่า *โค้ดทำอะไร* ถ้าเขียนโค้ดหลักไว้ข้างบนสุด ความตั้งใจจะชัดเจนตั้งแต่แรก บางทีอาจไม่ต้องอ่านฟังก์ชันเลยก็ได้ — โดยเฉพาะถ้าชื่อฟังก์ชันบ่งบอกหน้าที่ได้ดีอยู่แล้ว -## Style Guides +## คู่มือสไตล์การเขียนโค้ด -A style guide contains general rules about "how to write" code, e.g. which quotes to use, how many spaces to indent, the maximal line length, etc. A lot of minor things. +คู่มือสไตล์รวบรวมกฎทั่วไปเกี่ยวกับ "วิธีเขียน" โค้ด เช่น ใช้เครื่องหมายคำพูดแบบไหน เว้นย่อหน้ากี่ช่อง บรรทัดยาวได้สูงสุดเท่าไร ฯลฯ — เป็นรายละเอียดเล็กๆ น้อยๆ แต่มีมากมาย -When all members of a team use the same style guide, the code looks uniform, regardless of which team member wrote it. +เมื่อสมาชิกทุกคนในทีมใช้คู่มือสไตล์ชุดเดียวกัน โค้ดจะมีรูปแบบที่เป็นเอกภาพ ไม่ว่าจะเขียนโดยสมาชิกคนไหนก็ตาม -Of course, a team can always write their own style guide, but usually there's no need to. There are many existing guides to choose from. +แน่นอนว่า ทีมสามารถเขียนคู่มือสไตล์เป็นของตัวเองได้เสมอ แต่โดยทั่วไปแล้วมักไม่จำเป็น เพราะมีคู่มือสำเร็จรูปให้เลือกใช้อยู่แล้วมากมาย -Some popular choices: +ตัวเลือกยอดนิยมบางส่วน ได้แก่: - [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) - [StandardJS](https://standardjs.com/) -- (plus many more) +- (และอีกมากมาย) -If you're a novice developer, start with the cheat sheet at the beginning of this chapter. Then you can browse other style guides to pick up more ideas and decide which one you like best. +สำหรับมือใหม่ แนะนำให้เริ่มจากตารางสรุปที่ต้นบทนี้ก่อน จากนั้นค่อยๆ ศึกษาคู่มือสไตล์อื่นๆ เพื่อเก็บไอเดียเพิ่มเติม แล้วเลือกสไตล์ที่ถูกใจที่สุด -## Automated Linters +## เครื่องมือตรวจสอบโค้ดอัตโนมัติ (Automated Linters) -Linters are tools that can automatically check the style of your code and make improving suggestions. +Linter คือเครื่องมือที่ช่วยตรวจสอบสไตล์โค้ดให้โดยอัตโนมัติ และเสนอแนะจุดที่ควรปรับปรุงแก้ไข -The great thing about them is that style-checking can also find some bugs, like typos in variable or function names. Because of this feature, using a linter is recommended even if you don't want to stick to one particular "code style". +ข้อดีคือ การตรวจสอบสไตล์ยังช่วยค้นพบบั๊กบางอย่างได้ด้วย เช่น สะกดชื่อตัวแปรหรือฟังก์ชันผิด ด้วยเหตุนี้จึงแนะนำให้ใช้ linter แม้จะไม่ได้อยากยึดติดกับ "สไตล์โค้ด" แบบใดแบบหนึ่งโดยเฉพาะก็ตาม -Here are some well-known linting tools: +ต่อไปนี้คือเครื่องมือ linting ที่มีชื่อเสียง: -- [JSLint](http://www.jslint.com/) -- one of the first linters. -- [JSHint](http://www.jshint.com/) -- more settings than JSLint. -- [ESLint](http://eslint.org/) -- probably the newest one. +- [JSLint](https://www.jslint.com/) — หนึ่งใน linter รุ่นแรกๆ +- [JSHint](https://jshint.com/) — มีตัวเลือกการตั้งค่ามากกว่า JSLint +- [ESLint](https://eslint.org/) — น่าจะเป็นตัวล่าสุด -All of them can do the job. The author uses [ESLint](http://eslint.org/). +ทั้งหมดนี้ใช้งานได้เหมือนกัน ผู้เขียนเองใช้ [ESLint](https://eslint.org/) -Most linters are integrated with many popular editors: just enable the plugin in the editor and configure the style. +Linter ส่วนใหญ่เชื่อมต่อกับ editor ยอดนิยมได้ แค่เปิดใช้ปลั๊กอินแล้วตั้งค่าสไตล์ที่ต้องการก็เสร็จ -For instance, for ESLint you should do the following: +ยกตัวอย่างสำหรับ ESLint ให้ทำตามขั้นตอนต่อไปนี้: -1. Install [Node.js](https://nodejs.org/). -2. Install ESLint with the command `npm install -g eslint` (npm is a JavaScript package installer). -3. Create a config file named `.eslintrc` in the root of your JavaScript project (in the folder that contains all your files). -4. Install/enable the plugin for your editor that integrates with ESLint. The majority of editors have one. +1. ติดตั้ง [Node.js](https://nodejs.org/) +2. ติดตั้ง ESLint ด้วยคำสั่ง `npm install -g eslint` (npm คือตัวจัดการแพ็คเกจของ JavaScript) +3. สร้างไฟล์กำหนดค่าชื่อ `.eslintrc` ไว้ที่ root ของโปรเจ็กต์ JavaScript (ในโฟลเดอร์ที่เก็บไฟล์ทั้งหมด) +4. ติดตั้ง/เปิดใช้ปลั๊กอิน ESLint ใน editor ที่ใช้ — editor ส่วนใหญ่จะมีปลั๊กอินนี้ให้อยู่แล้ว -Here's an example of an `.eslintrc` file: +ตัวอย่างไฟล์ `.eslintrc` เป็นดังนี้: ```js { @@ -328,21 +330,21 @@ Here's an example of an `.eslintrc` file: }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` -Here the directive `"extends"` denotes that the configuration is based on the "eslint:recommended" set of settings. After that, we specify our own. +ตรงนี้ `"extends"` หมายถึงใช้ค่าพื้นฐานจากชุด "eslint:recommended" แล้วค่อยปรับแต่งเพิ่มเติมเอง -It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. +นอกจากนี้ยังดาวน์โหลดชุดกฎสไตล์จากเว็บมาต่อยอดได้ด้วย ดูรายละเอียดการติดตั้งได้ที่ -Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint. +IDE บางตัวก็มี linter มาในตัวแล้ว ใช้สะดวกดีแต่ปรับแต่งได้ไม่มากเท่า ESLint -## Summary +## สรุป -All syntax rules described in this chapter (and in the style guides referenced) aim to increase the readability of your code. All of them are debatable. +กฎไวยากรณ์ทั้งหมดที่กล่าวถึงในบทนี้ (รวมถึงในคู่มือสไตล์ต่างๆ) ล้วนมีเป้าหมายเดียวกันคือทำให้โค้ดอ่านเข้าใจง่ายขึ้น และทุกกฎก็ถกเถียงกันได้ -When we think about writing "better" code, the questions we should ask ourselves are: "What makes the code more readable and easier to understand?" and "What can help us avoid errors?" These are the main things to keep in mind when choosing and debating code styles. +เวลาคิดจะเขียนโค้ดให้ "ดีขึ้น" ลองถามตัวเองว่า "อะไรช่วยให้โค้ดอ่านง่ายขึ้น?" และ "อะไรช่วยให้หลีกเลี่ยงข้อผิดพลาดได้?" — นี่คือหัวใจสำคัญในการเลือกสไตล์โค้ด -Reading popular style guides will allow you to keep up to date with the latest ideas about code style trends and best practices. +การอ่านคู่มือสไตล์ยอดนิยมจะช่วยให้อัพเดตเทรนด์และแนวปฏิบัติที่ดีที่สุดอยู่เสมอ \ No newline at end of file diff --git a/1-js/03-code-quality/02-coding-style/code-style.svg b/1-js/03-code-quality/02-coding-style/code-style.svg index bd62691c6..739d9f1ed 100644 --- a/1-js/03-code-quality/02-coding-style/code-style.svg +++ b/1-js/03-code-quality/02-coding-style/code-style.svg @@ -1 +1 @@ -2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between parametersA space between parameters \ No newline at end of file +2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between argumentsA space between parameters \ No newline at end of file diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 0d11c6c52..d200c4999 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -1,38 +1,38 @@ -# Comments +# คอมเมนต์ -As we know from the chapter , comments can be single-line: starting with `//` and multiline: `/* ... */`. +จากบท เรารู้แล้วว่าคอมเมนต์เขียนได้สองแบบ — แบบบรรทัดเดียวด้วย `//` หรือแบบหลายบรรทัดด้วย `/* ... */` -We normally use them to describe how and why the code works. +ปกติแล้วเราใช้คอมเมนต์เพื่ออธิบายว่าโค้ดทำงานอย่างไร และทำไมถึงเขียนแบบนั้น -At first sight, commenting might be obvious, but novices in programming often use them wrongly. +ฟังดูเหมือนเรื่องง่ายใช่ไหม? แต่มือใหม่มักใช้คอมเมนต์ผิดวิธีอยู่บ่อยๆ -## Bad comments +## คอมเมนต์ที่ไม่ดี -Novices tend to use comments to explain "what is going on in the code". Like this: +มือใหม่มักใช้คอมเมนต์เพื่ออธิบาย "สิ่งที่กำลังเกิดขึ้นในโค้ด" แบบนี้: ```js -// This code will do this thing (...) and that thing (...) -// ...and who knows what else... +// โค้ดนี้จะทำสิ่งนี้ (...) และสิ่งนั้น (...) +// ...และใครจะรู้ว่ามันยังทำอะไรอีก... very; complex; code; ``` -But in good code, the amount of such "explanatory" comments should be minimal. Seriously, the code should be easy to understand without them. +แต่ในโค้ดที่ดี คอมเมนต์แบบ "อธิบาย" นี้ควรมีน้อยที่สุด โค้ดควรอ่านเข้าใจได้เองโดยไม่ต้องพึ่งคอมเมนต์ -There's a great rule about that: "if the code is so unclear that it requires a comment, then maybe it should be rewritten instead". +มีกฎที่ดีมากเกี่ยวกับเรื่องนี้ — "ถ้าโค้ดไม่ชัดเจนจนต้องใช้คอมเมนต์ บางทีควรเขียนโค้ดใหม่แทนจะดีกว่า" -### Recipe: factor out functions +### เคล็ดลับ: แยกโค้ดออกมาเป็นฟังก์ชัน -Sometimes it's beneficial to replace a code piece with a function, like here: +บางครั้งการแยกส่วนหนึ่งของโค้ดออกมาเป็นฟังก์ชันก็ช่วยได้ ลองดูตัวอย่างนี้: ```js function showPrimes(n) { nextPrime: for (let i = 2; i < n; i++) { - + *!* - // check if i is a prime number + // ตรวจสอบว่า i เป็นจำนวนเฉพาะหรือไม่ for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } @@ -43,8 +43,7 @@ function showPrimes(n) { } ``` -The better variant, with a factored out function `isPrime`: - +ถ้าแยกออกมาเป็นฟังก์ชัน `isPrime` จะดีกว่า: ```js function showPrimes(n) { @@ -52,7 +51,7 @@ function showPrimes(n) { for (let i = 2; i < n; i++) { *!*if (!isPrime(i)) continue;*/!* - alert(i); + alert(i); } } @@ -65,21 +64,21 @@ function isPrime(n) { } ``` -Now we can understand the code easily. The function itself becomes the comment. Such code is called *self-descriptive*. +แบบนี้อ่านเข้าใจง่ายขึ้นเลยใช่ไหม? ตัวฟังก์ชันเองก็ทำหน้าที่เป็นคอมเมนต์ไปในตัว โค้ดแบบนี้เรียกว่า *อธิบายตัวเอง (self-descriptive)* -### Recipe: create functions +### เคล็ดลับ: สร้างฟังก์ชัน -And if we have a long "code sheet" like this: +หากเรามี "แผ่นโค้ด" ยาวๆ แบบนี้: ```js -// here we add whiskey +// ตรงนี้เติมวิสกี้ for(let i = 0; i < 10; i++) { let drop = getWhiskey(); smell(drop); add(drop, glass); } -// here we add juice +// ตรงนี้เติมน้ำผลไม้ for(let t = 0; t < 3; t++) { let tomato = getTomato(); examine(tomato); @@ -87,10 +86,10 @@ for(let t = 0; t < 3; t++) { add(juice, glass); } -// ... +// ... ``` -Then it might be a better variant to refactor it into functions like: +การ refactor ให้เป็นฟังก์ชันจะดีกว่า: ```js addWhiskey(glass); @@ -108,73 +107,73 @@ function addJuice(container) { let tomato = getTomato(); //... } -} +} ``` -Once again, functions themselves tell what's going on. There's nothing to comment. And also the code structure is better when split. It's clear what every function does, what it takes and what it returns. +เช่นเดียวกัน ตัวฟังก์ชันเองบอกอยู่แล้วว่ากำลังทำอะไร ไม่ต้องใช้คอมเมนต์อธิบาย แถมโครงสร้างโค้ดก็ดีขึ้นด้วย — มองเห็นชัดเจนว่าแต่ละฟังก์ชันทำอะไร รับอะไร และคืนค่าอะไร -In reality, we can't totally avoid "explanatory" comments. There are complex algorithms. And there are smart "tweaks" for purposes of optimization. But generally we should try to keep the code simple and self-descriptive. +แน่นอนว่าในความเป็นจริง เราหลีกเลี่ยงคอมเมนต์แบบ "อธิบาย" ไม่ได้ทั้งหมด บางทีมีอัลกอริธึมที่ซับซ้อน หรือมี "กลเม็ด" เพื่อเพิ่มประสิทธิภาพโค้ด แต่โดยทั่วไปแล้ว ควรพยายามเขียนโค้ดให้เรียบง่ายและอธิบายตัวเองได้มากที่สุด -## Good comments +## คอมเมนต์ที่ดี -So, explanatory comments are usually bad. Which comments are good? +ดังนั้น คอมเมนต์ที่เป็นคำอธิบายมักจะไม่ดี แล้วคอมเมนต์แบบไหนถึงจะดีล่ะ? -Describe the architecture -: Provide a high-level overview of components, how they interact, what's the control flow in various situations... In short -- the bird's eye view of the code. There's a special language [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) to build high-level architecture diagrams explaining the code. Definitely worth studying. +อธิบายสถาปัตยกรรม +: ให้ภาพรวมของส่วนประกอบต่างๆ ว่าทำงานร่วมกันอย่างไร ลำดับการทำงานในแต่ละสถานการณ์เป็นอย่างไร — พูดง่ายๆ คือมองโค้ดจากมุมสูง มีภาษาอย่าง [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) ที่ใช้สร้างแผนภาพสถาปัตยกรรมเพื่ออธิบายโค้ด ซึ่งคุ้มค่าแก่การศึกษามาก -Document function parameters and usage -: There's a special syntax [JSDoc](http://en.wikipedia.org/wiki/JSDoc) to document a function: usage, parameters, returned value. +บันทึกพารามิเตอร์และการใช้งานฟังก์ชัน +: มีไวยากรณ์พิเศษอย่าง [JSDoc](http://en.wikipedia.org/wiki/JSDoc) สำหรับบันทึกข้อมูลฟังก์ชัน — วิธีใช้งาน พารามิเตอร์ และค่าที่คืนกลับมา -For instance: +เช่น: ```js /** - * Returns x raised to the n-th power. + * คืนค่า x ยกกำลัง n * - * @param {number} x The number to raise. - * @param {number} n The power, must be a natural number. - * @return {number} x raised to the n-th power. + * @param {number} x เลขที่ต้องการยกกำลัง + * @param {number} n เลขชี้กำลัง ต้องเป็นจำนวนนับ + * @return {number} x ยกกำลัง n */ function pow(x, n) { ... } ``` -Such comments allow us to understand the purpose of the function and use it the right way without looking in its code. +คอมเมนต์แบบนี้ช่วยให้เข้าใจจุดประสงค์ของฟังก์ชันและใช้งานได้ถูกต้อง โดยไม่ต้องไปอ่านโค้ดข้างใน -By the way, many editors like [WebStorm](https://www.jetbrains.com/webstorm/) can understand them as well and use them to provide autocomplete and some automatic code-checking. +นอกจากนี้ เครื่องมือแก้ไขโค้ดหลายตัวอย่าง [WebStorm](https://www.jetbrains.com/webstorm/) ก็อ่านคอมเมนต์เหล่านี้ได้ แล้วนำไปช่วยเติมโค้ดอัตโนมัติและตรวจสอบโค้ดให้ด้วย -Also, there are tools like [JSDoc 3](https://github.com/jsdoc3/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +ยังมีเครื่องมืออย่าง [JSDoc 3](https://github.com/jsdoc/jsdoc) ที่สร้างเอกสาร HTML จากคอมเมนต์ได้โดยตรง อ่านรายละเอียดเพิ่มเติมเกี่ยวกับ JSDoc ได้ที่ -Why is the task solved this way? -: What's written is important. But what's *not* written may be even more important to understand what's going on. Why is the task solved exactly this way? The code gives no answer. +ทำไมปัญหาถึงแก้ด้วยวิธีนี้? +: สิ่งที่เขียนไว้นั้นสำคัญ แต่สิ่งที่ *ไม่ได้* เขียนอาจสำคัญยิ่งกว่า — ทำไมถึงเลือกแก้ปัญหาด้วยวิธีนี้? โค้ดไม่ได้บอกคำตอบ - If there are many ways to solve the task, why this one? Especially when it's not the most obvious one. + ถ้ามีหลายวิธีแก้ปัญหา ทำไมถึงเลือกวิธีนี้? โดยเฉพาะเมื่อไม่ใช่วิธีที่ตรงไปตรงมาที่สุด - Without such comments the following situation is possible: - 1. You (or your colleague) open the code written some time ago, and see that it's "suboptimal". - 2. You think: "How stupid I was then, and how much smarter I'm now", and rewrite using the "more obvious and correct" variant. - 3. ...The urge to rewrite was good. But in the process you see that the "more obvious" solution is actually lacking. You even dimly remember why, because you already tried it long ago. You revert to the correct variant, but the time was wasted. + ถ้าไม่มีคอมเมนต์อธิบาย สถานการณ์แบบนี้อาจเกิดขึ้นได้: + 1. เรา (หรือเพื่อนร่วมงาน) เปิดโค้ดที่เขียนไว้เมื่อสักพัก แล้วเห็นว่า "ไม่เหมาะสมเท่าไร" + 2. คิดว่า "ตอนนั้นโง่จัง ตอนนี้ฉลาดขึ้นแล้ว" แล้วก็เขียนใหม่ด้วยวิธีที่ "ตรงไปตรงมาและถูกต้องกว่า" + 3. ...ความอยากเขียนใหม่เป็นเรื่องดี แต่ระหว่างทำก็พบว่าวิธีที่ "ตรงไปตรงมา" จริงๆ แล้วไม่ครบถ้วน พอจำได้ลางๆ ว่าทำไมถึงเป็นแบบเดิม เพราะเคยลองมาแล้วนานมาแล้ว สุดท้ายก็เปลี่ยนกลับไปใช้วิธีเดิม — แต่เสียเวลาไปเปล่าๆ - Comments that explain the solution are very important. They help to continue development the right way. + คอมเมนต์ที่อธิบายเหตุผลของวิธีแก้ปัญหาจึงสำคัญมาก ช่วยให้พัฒนาต่อไปในทิศทางที่ถูกต้อง -Any subtle features of the code? Where they are used? -: If the code has anything subtle and counter-intuitive, it's definitely worth commenting. +โค้ดมีจุดละเอียดอ่อนหรือเทคนิคพิเศษซ่อนอยู่ไหม? ใช้ตรงไหนบ้าง? +: ถ้าโค้ดมีอะไรที่ละเอียดอ่อนหรือขัดกับสัญชาตญาณ ก็ควรเขียนคอมเมนต์ไว้ให้ชัดเจน -## Summary +## สรุป -An important sign of a good developer is comments: their presence and even their absence. +สัญญาณสำคัญของนักพัฒนาที่ดีคือคอมเมนต์ — ทั้งการมีและการไม่มี -Good comments allow us to maintain the code well, come back to it after a delay and use it more effectively. +คอมเมนต์ที่ดีช่วยให้ดูแลโค้ดได้ดี กลับมาอ่านทีหลังก็ยังเข้าใจ และใช้งานได้อย่างถูกต้อง -**Comment this:** +**ควรเขียนคอมเมนต์เรื่อง:** -- Overall architecture, high-level view. -- Function usage. -- Important solutions, especially when not immediately obvious. +- สถาปัตยกรรมโดยรวม มุมมองภาพกว้าง +- วิธีใช้งานฟังก์ชัน +- วิธีแก้ปัญหาที่สำคัญ โดยเฉพาะเมื่อเหตุผลไม่ได้ชัดเจนในทันที -**Avoid comments:** +**หลีกเลี่ยงคอมเมนต์ที่:** -- That tell "how code works" and "what it does". -- Put them in only if it's impossible to make the code so simple and self-descriptive that it doesn't require them. +- บอกว่า "โค้ดทำงานอย่างไร" หรือ "ทำอะไร" +- ใส่คอมเมนต์ก็ต่อเมื่อไม่สามารถทำให้โค้ดเรียบง่ายและอธิบายตัวเองได้จริงๆ เท่านั้น -Comments are also used for auto-documenting tools like JSDoc3: they read them and generate HTML-docs (or docs in another format). +คอมเมนต์ยังใช้กับเครื่องมือสร้างเอกสารอัตโนมัติอย่าง JSDoc3 ได้ด้วย — เครื่องมือจะอ่านคอมเมนต์แล้วสร้างเอกสาร HTML (หรือรูปแบบอื่น) ให้เลย \ No newline at end of file diff --git a/1-js/03-code-quality/04-ninja-code/article.md b/1-js/03-code-quality/04-ninja-code/article.md index 96fdf4143..9a8885407 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,185 +1,180 @@ -# Ninja code +# โค้ดนินจา - -```quote author="Confucius (Analects)" -Learning without thought is labor lost; thought without learning is perilous. +```quote author="ขงจื๊อ (วจนะ)" +เรียนไร้คิดเหนื่อยเปล่าดุจดั่งฝัน คิดไร้เรียนอันตรายใช่น้อยเลย ``` -Programmer ninjas of the past used these tricks to sharpen the mind of code maintainers. - -Code review gurus look for them in test tasks. +โปรแกรมเมอร์นินจาในอดีตใช้กลเม็ดเหล่านี้เพื่อลับสมองของผู้ดูแลโค้ด -Novice developers sometimes use them even better than programmer ninjas. +ผู้เชี่ยวชาญการรีวิวโค้ดมองหากลเม็ดเหล่านี้ในการทดสอบงาน -Read them carefully and find out who you are -- a ninja, a novice, or maybe a code reviewer? +บางครั้งนักพัฒนามือใหม่ใช้กลเม็ดเหล่านี้ได้ดีกว่าโปรแกรมเมอร์นินจาเสียอีก +ลองอ่านดูให้ดีแล้วถามตัวเองว่าเราเป็นใคร — นินจา, มือใหม่, หรืออาจเป็นผู้รีวิวโค้ด? -```warn header="Irony detected" -Many try to follow ninja paths. Few succeed. +```warn header="พบการเสียดสี" +มากมายมุ่งวิถีนินจา น้อยนักล่วงพ้นถึงจุดหมาย ``` +## ความกระชับคือหัวใจของปัญญา -## Brevity is the soul of wit +เขียนโค้ดให้สั้นที่สุดเท่าที่จะทำได้ โชว์ความฉลาดให้โลกเห็น -Make the code as short as possible. Show how smart you are. +ปล่อยให้ฟีเจอร์ลึกลับของภาษาเป็นตัวนำทาง -Let subtle language features guide you. - -For instance, take a look at this ternary operator `'?'`: +เช่น ลองดู ternary operator `'?'` นี้: ```js -// taken from a well-known javascript library +// มาจากไลบรารี JavaScript ที่มีชื่อเสียง i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ``` -Cool, right? If you write like that, a developer who comes across this line and tries to understand what is the value of `i` is going to have a merry time. Then come to you, seeking for an answer. +เจ๋งใช่ไหม? ถ้าเขียนแบบนี้ นักพัฒนาที่เจอบรรทัดนี้แล้วพยายามไล่ดูว่า `i` มีค่าเท่าไหร่ คงจะสนุกกันน่าดู จากนั้นก็ต้องมาถามเราเพื่อขอคำตอบ -Tell them that shorter is always better. Initiate them into the paths of ninja. +บอกไปเลยว่าสั้นกว่าดีกว่าเสมอ แล้วชี้ทางพวกเขาสู่วิถีแห่งนินจา -## One-letter variables +## ตัวแปรตัวอักษรเดียว -```quote author="Laozi (Tao Te Ching)" -The Dao hides in wordlessness. Only the Dao is well begun and well -completed. +```quote author="เล่าจื๊อ (เต๋าเต๋อจิง)" +เต๋าซ่อนในความเงียบไร้คำ เริ่มต้นและจบลงอย่างสมบูรณ์ ``` -Another way to code shorter is to use single-letter variable names everywhere. Like `a`, `b` or `c`. +อีกวิธีหนึ่งที่จะเขียนโค้ดให้สั้นลงคือการใช้ชื่อตัวแปรเพียงตัวอักษรเดียวทุกที่ เช่น `a`, `b` หรือ `c` -A short variable disappears in the code like a real ninja in the forest. No one will be able to find it using "search" of the editor. And even if someone does, they won't be able to "decipher" what the name `a` or `b` means. +ตัวแปรที่สั้นจะหายไปในโค้ดเหมือนนินจาหายเข้าป่า ไม่มีใครหาเจอด้วยฟีเจอร์ "ค้นหา" ของ editor ถึงจะเจอก็ "ถอดรหัส" ไม่ออกว่า `a` หรือ `b` หมายถึงอะไร -...But there's an exception. A real ninja will never use `i` as the counter in a `"for"` loop. Anywhere, but not here. Look around, there are many more exotic letters. For instance, `x` or `y`. +...แต่มีข้อยกเว้น นินจาตัวจริงจะไม่มีวันใช้ `i` เป็นตัวนับในลูป `"for"` ที่ไหนก็ใช้ได้ แต่ไม่ใช่ตรงนี้ ลองมองหารอบๆ ดูสิ มีตัวอักษรแปลกๆ อีกเพียบ เช่น `x` หรือ `y` -An exotic variable as a loop counter is especially cool if the loop body takes 1-2 pages (make it longer if you can). Then if someone looks deep inside the loop, they won't be able to quickly figure out that the variable named `x` is the loop counter. +ใช้ตัวแปรแปลกตาเป็นตัวนับลูปนี่เจ๋งสุดๆ โดยเฉพาะถ้าภายในลูปยาว 1-2 หน้า (ยิ่งยาวยิ่งดี) เพราะถ้ามีใครมาไล่ดูโค้ดข้างในลูป จะเดาไม่ออกเลยว่าตัวแปรชื่อ `x` คือตัวนับลูปนั่นเอง -## Use abbreviations -If the team rules forbid the use of one-letter and vague names -- shorten them, make abbreviations. +## ใช้คำย่อ -Like this: +หากกฎของทีมห้ามใช้ชื่อที่เป็นตัวอักษรเดียวหรือคลุมเครือ — ก็ย่อซะ สร้างคำย่อขึ้นมา -- `list` -> `lst`. -- `userAgent` -> `ua`. -- `browser` -> `brsr`. -- ...etc +แบบนี้: -Only the one with truly good intuition will be able to understand such names. Try to shorten everything. Only a worthy person should be able to uphold the development of your code. +- `list` -> `lst` +- `userAgent` -> `ua` +- `browser` -> `brsr` +- ...ฯลฯ -## Soar high. Be abstract. +มีเพียงคนที่สัญชาตญาณดีเยี่ยมเท่านั้นถึงจะเข้าใจชื่อแบบนี้ได้ พยายามย่อทุกอย่าง มีแต่คนที่คู่ควรเท่านั้นที่จะดูแลโค้ดของเราต่อได้ -```quote author="Laozi (Tao Te Ching)" -The great square is cornerless
-The great vessel is last complete,
-The great note is rarified sound,
-The great image has no form. -``` +## โบยบินสูงส่ง ดั่งนามธรรม -While choosing a name try to use the most abstract word. Like `obj`, `data`, `value`, `item`, `elem` and so on. +```quote author="เล่าจื๊อ (เต๋าเต๋อจิง)" +สี่เหลี่ยมใหญ่ไร้มุมแหลมคม +ภาชนะยิ่งใหญ่เสร็จสิ้นท้ายสุด +เสียงอันไพเราะคือความเบาบาง +ภาพอันยิ่งใหญ่ไร้รูปทรง +``` -- **The ideal name for a variable is `data`.** Use it everywhere you can. Indeed, every variable holds *data*, right? +ตอนตั้งชื่อ พยายามเลือกคำที่เป็นนามธรรมที่สุด เช่น `obj`, `data`, `value`, `item`, `elem` เป็นต้น - ...But what to do if `data` is already taken? Try `value`, it's also universal. After all, a variable eventually gets a *value*. +- **`data` คือชื่อตัวแปรในอุดมคติ** ใช้ทุกที่ที่ทำได้เลย เพราะตัวแปรทุกตัวก็เก็บ *ข้อมูล* อยู่แล้วจริงไหม? -- **Name a variable by its type: `str`, `num`...** + ...แต่ถ้า `data` ถูกจองไปแล้วล่ะ? ลองใช้ `value` แทน เพราะก็เป็นคำสากลเหมือนกัน ท้ายที่สุดตัวแปรก็ต้องมี *ค่า* อยู่ดี - Give them a try. A young initiate may wonder -- are such names really useful for a ninja? Indeed, they are! +- **ตั้งชื่อตัวแปรตามชนิดข้อมูล: `str`, `num`...** - Sure, the variable name still means something. It says what's inside the variable: a string, a number or something else. But when an outsider tries to understand the code, they'll be surprised to see that there's actually no information at all! And will ultimately fail to alter your well-thought code. + ลองใช้ดู มือใหม่อาจสงสัยว่าชื่อแบบนั้นมีประโยชน์จริงหรือสำหรับนินจา? แน่นอน มีประโยชน์จริงๆ! - The value type is easy to find out by debugging. But what's the meaning of the variable? Which string/number does it store? + ชื่อตัวแปรก็ยังพอบอกอะไรได้บ้าง — บอกว่าข้างในเก็บอะไร: สตริง ตัวเลข หรืออย่างอื่น แต่พอคนนอกมาอ่านโค้ด จะพบว่าชื่อแบบนี้ไม่ได้ให้ข้อมูลอะไรเลย! แล้วก็จะแก้โค้ดที่เราคิดมาอย่างดีไม่ได้สักที - There's just no way to figure out without a good meditation! + ชนิดข้อมูลหาได้ง่ายจากการดีบัก แต่ความหมายของตัวแปรล่ะ? เก็บสตริง/ตัวเลขอะไรกันแน่? -- **...But what if there are no more such names?** Just add a number: `data1, item2, elem5`... + ไม่มีทางรู้ได้เลย นอกจากจะทำสมาธิให้ดี! -## Attention test +- **...แล้วถ้าชื่อแบบนั้นไม่เหลือแล้วล่ะ?** ก็แค่เติมตัวเลขต่อท้าย: `data1, item2, elem5`... -Only a truly attentive programmer should be able to understand your code. But how to check that? +## ทดสอบความใส่ใจ -**One of the ways -- use similar variable names, like `date` and `data`.** +มีแค่โปรแกรมเมอร์ที่ใส่ใจจริงๆ เท่านั้นถึงจะอ่านโค้ดของเราออก แต่จะทดสอบได้ยังไงล่ะ? -Mix them where you can. +**วิธีหนึ่งคือใช้ชื่อตัวแปรที่คล้ายกัน เช่น `date` กับ `data`** -A quick read of such code becomes impossible. And when there's a typo... Ummm... We're stuck for long, time to drink tea. +ใช้สลับกันไปมาตามใจชอบเลย +แค่นี้ก็อ่านโค้ดผ่านๆ ไม่ได้แล้ว แล้วถ้าดันมีพิมพ์ผิดอีก... อืม... คงได้ติดอยู่นาน ถึงเวลาพักไปชงชากันแล้ว -## Smart synonyms +## คำพ้องความหมายสุดเจ๋ง -```quote author="Laozi (Tao Te Ching)" -The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name. +```quote author="เล่าจื๊อ (เต๋าเต๋อจิง)" +เต๋าที่กล่าวได้มิใช่เต๋านิรันดร์ นามที่เอ่ยได้มิใช่นามอมตะ ``` -Using *similar* names for *same* things makes life more interesting and shows your creativity to the public. +การใช้ชื่อ *คล้ายๆ กัน* กับสิ่งที่ *เหมือนๆ กัน* ทำให้ชีวิตสนุกขึ้นและยังโชว์ความคิดสร้างสรรค์ให้คนอื่นเห็นด้วย -For instance, consider function prefixes. If a function shows a message on the screen -- start it with `display…`, like `displayMessage`. And then if another function shows on the screen something else, like a user name, start it with `show…` (like `showName`). +เช่น คำนำหน้าฟังก์ชัน ถ้าฟังก์ชันแสดงข้อความบนจอ ให้ขึ้นต้นด้วย `display...` อย่าง `displayMessage` แล้วถ้ามีอีกฟังก์ชันที่แสดงอย่างอื่นบนจอ เช่นชื่อผู้ใช้ ก็ขึ้นต้นด้วย `show...` แทน (อย่าง `showName`) -Insinuate that there's a subtle difference between such functions, while there is none. +ทำให้ดูเหมือนมีความต่างอย่างละเอียดอ่อน ทั้งที่จริงๆ แล้วไม่ได้ต่างกันเลย -Make a pact with fellow ninjas of the team: if John starts "showing" functions with `display...` in his code, then Peter could use `render..`, and Ann -- `paint...`. Note how much more interesting and diverse the code became. +นัดแนะกับเพื่อนนินจาในทีมเลย — ถ้าจอห์นใช้ `display...` ในโค้ดของเขา ปีเตอร์ก็ใช้ `render...` ส่วนแอนก็ `paint...` ดูสิว่าโค้ดจะน่าสนใจและหลากหลายขึ้นแค่ไหน -...And now the hat trick! +...แล้วนี่คือไม้เด็ด! -For two functions with important differences -- use the same prefix! +สำหรับสองฟังก์ชันที่แตกต่างกันชัดเจน ให้ใช้คำนำหน้าเดียวกัน! -For instance, the function `printPage(page)` will use a printer. And the function `printText(text)` will put the text on-screen. Let an unfamiliar reader think well over similarly named function `printMessage`: "Where does it put the message? To a printer or on the screen?". To make it really shine, `printMessage(message)` should output it in the new window! +เช่น ฟังก์ชัน `printPage(page)` จะส่งไปเครื่องพิมพ์ ส่วน `printText(text)` จะแสดงข้อความบนจอ ให้คนอ่านที่ไม่คุ้นเคยได้คิดหนักกับ `printMessage` ว่า "ส่งข้อความไปไหนนะ เครื่องพิมพ์หรือบนจอ?" จะเจ๋งขึ้นไปอีกถ้า `printMessage(message)` ดันไปเปิดหน้าต่างใหม่ขึ้นมา! -## Reuse names +## ใช้ชื่อซ้ำ -```quote author="Laozi (Tao Te Ching)" -Once the whole is divided, the parts
-need names.
-There are already enough names.
-One must know when to stop. +```quote author="เล่าจื๊อ (เต๋าเต๋อจิง)" +เมื่อแบ่งสรรพสิ่งออกเป็นส่วนย่อย +แต่ละส่วนล้วนต้องการชื่อเรียกขาน +นามทั้งหลายมีอยู่มากมายแล้ว +รู้จักพอคือคุณูปการอันล้ำค่า ``` -Add a new variable only when absolutely necessary. +อย่าเพิ่มตัวแปรใหม่ถ้าไม่จำเป็นจริงๆ -Instead, reuse existing names. Just write new values into them. +แทนที่จะทำแบบนั้น เอาชื่อที่มีอยู่แล้วมาใช้ซ้ำ แค่ยัดค่าใหม่ลงไปทับค่าเดิม -In a function try to use only variables passed as parameters. +ภายในฟังก์ชัน พยายามใช้เฉพาะตัวแปรที่ส่งเข้ามาเป็นพารามิเตอร์เท่านั้น -That would make it really hard to identify what's exactly in the variable *now*. And also where it comes from. The purpose is to develop the intuition and memory of a person reading the code. A person with weak intuition would have to analyze the code line-by-line and track the changes through every code branch. +วิธีนี้จะทำให้แทบเป็นไปไม่ได้เลยที่จะรู้ว่า *ตอนนี้* ตัวแปรเก็บอะไรอยู่ และมาจากไหน จุดประสงค์คือฝึกสัญชาตญาณและความจำของคนอ่านโค้ด ใครที่สัญชาตญาณไม่ดีก็ต้องไล่วิเคราะห์โค้ดทีละบรรทัด ติดตามการเปลี่ยนแปลงข้ามทุกสาขาของโค้ดไป -**An advanced variant of the approach is to covertly (!) replace the value with something alike in the middle of a loop or a function.** +**รูปแบบขั้นสูงของวิธีนี้คือการแอบ (!) เปลี่ยนค่าเป็นอย่างอื่นที่คล้ายๆ กันระหว่างวนลูปหรือทำงานในฟังก์ชัน** -For instance: +ตัวอย่างเช่น: ```js function ninjaFunction(elem) { - // 20 lines of code working with elem + // 20 บรรทัดโค้ดที่ทำงานกับ elem elem = clone(elem); - // 20 more lines, now working with the clone of the elem! + // อีก 20 บรรทัด ตอนนี้ทำงานกับ clone ของ elem! } ``` -A fellow programmer who wants to work with `elem` in the second half of the function will be surprised... Only during the debugging, after examining the code they will find out that they're working with a clone! - -Seen in code regularly. Deadly effective even against an experienced ninja. +โปรแกรมเมอร์ที่ต้องการทำงานกับ `elem` ในครึ่งหลังของฟังก์ชันจะงงแน่ๆ... ต้องดีบักไล่ดูโค้ดถึงจะรู้ว่าตัวเองกำลังทำงานกับตัวโคลนอยู่! -## Underscores for fun +เจอแบบนี้ในโค้ดบ่อยมาก ได้ผลดีถึงตายแม้กับนินจาจอมเก๋า -Put underscores `_` and `__` before variable names. Like `_name` or `__value`. It would be great if only you knew their meaning. Or, better, add them just for fun, without particular meaning at all. Or different meanings in different places. +## ใช้ underscore ให้สนุก -You kill two rabbits with one shot. First, the code becomes longer and less readable, and the second, a fellow developer may spend a long time trying to figure out what the underscores mean. +ใส่ underscore `_` และ `__` ข้างหน้าชื่อตัวแปร เช่น `_name` หรือ `__value` ถ้ามีแค่เราที่รู้ความหมายก็ยิ่งดี หรือดีกว่านั้น — ใส่ไปเพื่อความสนุกเฉยๆ โดยไม่มีความหมายอะไรเลย หรือจะให้ความหมายต่างกันในแต่ละที่ก็ได้ -A smart ninja puts underscores at one spot of code and evades them at other places. That makes the code even more fragile and increases the probability of future errors. +แค่นี้ก็ยิงปืนนัดเดียวได้นกสองตัว หนึ่ง โค้ดจะยาวขึ้นและอ่านยากขึ้น สอง เพื่อนนักพัฒนาอาจเสียเวลาไปเยอะกับการพยายามหาว่า underscore หมายถึงอะไร -## Show your love +นินจาเก๋าจะวาง underscore ไว้ตรงนึง แล้วไม่ใส่อีกตรงนึง แค่นี้ก็ยิ่งทำให้โค้ดเปราะบางและเพิ่มโอกาสเกิดบั๊กในอนาคตแล้ว -Let everyone see how magnificent your entities are! Names like `superElement`, `megaFrame` and `niceItem` will definitely enlighten a reader. +## แสดงความรัก -Indeed, from one hand, something is written: `super..`, `mega..`, `nice..` But from the other hand -- that brings no details. A reader may decide to look for a hidden meaning and meditate for an hour or two of their paid working time. +ให้ทุกคนได้เห็นความยิ่งใหญ่ของ entity ต่างๆ ที่เราสร้าง! ชื่ออย่าง `superElement`, `megaFrame` และ `niceItem` จะทำให้ผู้อ่านตาสว่างแน่นอน +ก็จริงอยู่ว่ามีอะไรบางอย่างเขียนไว้: `super..`, `mega..`, `nice..` แต่อีกแง่หนึ่ง ก็ไม่ได้ให้รายละเอียดอะไรเลย ผู้อ่านอาจต้องนั่งค้นหาความหมายที่ซ่อนอยู่ นั่งสมาธิสักชั่วโมงสองชั่วโมงในเวลางานที่ได้ค่าจ้าง -## Overlap outer variables +## ใช้ชื่อซ้ำกับตัวแปรด้านนอก -```quote author="Guan Yin Zi" -When in the light, can't see anything in the darkness.
-When in the darkness, can see everything in the light. +```quote author="กวนอินจื้อ" +ท่ามกลางแสงสว่าง มองไม่เห็นสิ่งใดในความมืด
+ในความมืด กลับมองเห็นทุกสิ่งในแสงสว่าง ``` -Use same names for variables inside and outside a function. As simple. No efforts to invent new names. +ใช้ชื่อเดียวกันสำหรับตัวแปรทั้งในและนอกฟังก์ชัน แค่นี้แหละ ไม่ต้องพยายามคิดชื่อใหม่ ```js let *!*user*/!* = authenticateUser(); @@ -187,54 +182,52 @@ let *!*user*/!* = authenticateUser(); function render() { let *!*user*/!* = anotherValue(); ... - ...many lines... + ...หลายบรรทัด... ... - ... // <-- a programmer wants to work with user here and... + ... // <-- โปรแกรมเมอร์อยากทำงานกับ user ตรงนี้และ... ... } ``` -A programmer who jumps inside the `render` will probably fail to notice that there's a local `user` shadowing the outer one. - -Then they'll try to work with `user` assuming that it's the external variable, the result of `authenticateUser()`... The trap is sprung! Hello, debugger... - +โปรแกรมเมอร์ที่กระโดดเข้ามาใน `render` อาจไม่ทันสังเกตว่ามี `user` ตัวใหม่ในฟังก์ชันกำลังบดบัง `user` ตัวนอกอยู่ -## Side-effects everywhere! +แล้วก็จะพยายามทำงานกับ `user` โดยคิดว่าเป็นตัวแปรจากภายนอก ผลลัพธ์จาก `authenticateUser()`... กับดักถูกวางแล้ว! สวัสดี debugger... -There are functions that look like they don't change anything. Like `isReady()`, `checkPermission()`, `findTags()`... They are assumed to carry out calculations, find and return the data, without changing anything outside of them. In other words, without "side-effects". +## ผลข้างเคียงได้ทุกที่! -**A really beautiful trick is to add a "useful" action to them, besides the main task.** +มีฟังก์ชันบางตัวที่ดูเหมือนไม่ได้เปลี่ยนแปลงอะไร เช่น `isReady()`, `checkPermission()`, `findTags()`... ปกติจะคาดหวังว่าฟังก์ชันพวกนี้แค่คำนวณ ค้นหา และคืนข้อมูล โดยไม่แตะต้องสิ่งใดภายนอก — พูดอีกอย่างก็คือ ไม่มี "ผลข้างเคียง" -An expression of dazed surprise on the face of your colleague when they see a function named `is..`, `check..` or `find...` changing something -- will definitely broaden your boundaries of reason. +**กลเม็ดสุดเจ๋งคือแอบใส่การกระทำที่ "มีประโยชน์" เข้าไป นอกเหนือจากงานหลัก** -**Another way to surprise is to return a non-standard result.** +สีหน้างงงวยของเพื่อนร่วมงาน เมื่อเห็นฟังก์ชันชื่อ `is..`, `check..` หรือ `find...` ที่ดันไปเปลี่ยนแปลงอะไรบางอย่าง จะช่วยขยายขอบเขตแห่งปัญญาได้แน่นอน -Show your original thinking! Let the call of `checkPermission` return not `true/false`, but a complex object with the results of the check. +**อีกวิธีที่จะสร้างความประหลาดใจคือคืนค่าผลลัพธ์แบบไม่ปกติ** -Those developers who try to write `if (checkPermission(..))`, will wonder why it doesn't work. Tell them: "Read the docs!". And give this article. +โชว์ความคิดริเริ่มเลย! ให้ `checkPermission` คืนค่าไม่ใช่แค่ `true/false` แต่เป็นออบเจ็กต์ที่ซับซ้อนพร้อมผลการตรวจสอบ +นักพัฒนาที่พยายามเขียน `if (checkPermission(..))` จะสงสัยว่าทำไมถึงใช้ไม่ได้ บอกไปเลยว่า "อ่าน docs สิ!" แล้วก็ส่งลิงก์บทความนี้ให้ -## Powerful functions! +## ฟังก์ชันอันทรงพลัง! -```quote author="Laozi (Tao Te Ching)" -The great Tao flows everywhere,
-both to the left and to the right. +```quote author="เล่าจื๊อ (เต๋าเต๋อจิง)" +มหาเต๋าไหลทั่วทุกหนแห่ง +ทั้งซ้ายขวาล้วนซึมซาบ ``` -Don't limit the function by what's written in its name. Be broader. +อย่าจำกัดฟังก์ชันด้วยสิ่งที่เขียนไว้ในชื่อ จงมองให้กว้างไกลกว่านั้น -For instance, a function `validateEmail(email)` could (besides checking the email for correctness) show an error message and ask to re-enter the email. +เช่น ฟังก์ชัน `validateEmail(email)` นอกจากตรวจสอบความถูกต้องของอีเมลแล้ว ก็อาจแสดง error และขอให้กรอกอีเมลใหม่ด้วย -Additional actions should not be obvious from the function name. A true ninja coder will make them not obvious from the code as well. +สิ่งที่ฟังก์ชันทำเพิ่มเติมไม่ควรเดาได้จากชื่อ นินจาตัวจริงจะทำให้มองไม่ออกจากโค้ดเช่นกัน -**Joining several actions into one protects your code from reuse.** +**การยัดหลายอย่างไว้ในฟังก์ชันเดียวจะปกป้องโค้ดของเราจากการถูกนำไปใช้ซ้ำ** -Imagine, another developer wants only to check the email, and not output any message. Your function `validateEmail(email)` that does both will not suit them. So they won't break your meditation by asking anything about it. +สมมติว่ามีนักพัฒนาอีกคนต้องการแค่เช็คอีเมลเฉยๆ โดยไม่ต้องแสดงข้อความ ฟังก์ชัน `validateEmail(email)` ของเราที่ทำงานทั้งสองอย่างจะไม่เหมาะกับเขา แบบนี้เขาก็จะไม่มารบกวนสมาธิด้วยการมาถามนู่นนี่ -## Summary +## สรุป -All "pieces of advice" above are from the real code... Sometimes, written by experienced developers. Maybe even more experienced than you are ;) +"เคล็ดลับ" ข้างต้นทั้งหมดมาจากโค้ดของจริง... บางครั้งเขียนโดยนักพัฒนาผู้มากประสบการณ์ บางทีอาจจะมากกว่าเราด้วยซ้ำ ;) -- Follow some of them, and your code will become full of surprises. -- Follow many of them, and your code will become truly yours, no one would want to change it. -- Follow all, and your code will become a valuable lesson for young developers looking for enlightenment. +- ทำตามบางข้อ โค้ดของเราก็จะเต็มไปด้วยเรื่องน่าประหลาดใจ +- ทำตามหลายๆ ข้อ โค้ดจะกลายเป็นของเราโดยแท้จริง ไม่มีใครอยากแตะต้อง +- ทำตามทุกข้อ โค้ดของเราจะกลายเป็นบทเรียนอันล้ำค่าสำหรับนักพัฒนาหน้าใหม่ผู้แสวงหาการรู้แจ้ง diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 68ffcae4d..77d421ef3 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -1,192 +1,193 @@ -# Automated testing with Mocha +# การทดสอบอัตโนมัติด้วย Mocha -Automated testing will be used in further tasks, and it's also widely used in real projects. +ในงานต่อๆ ไปเราจะใช้การทดสอบอัตโนมัติ ซึ่งเป็นสิ่งที่ใช้กันแพร่หลายในโปรเจ็กต์จริงด้วย -## Why we need tests? +## ทำไมต้องมีการทดสอบ? -When we write a function, we can usually imagine what it should do: which parameters give which results. +เวลาเขียนฟังก์ชัน เรามักจินตนาการได้อยู่แล้วว่าควรจะทำงานอย่างไร — พารามิเตอร์แบบไหนให้ผลลัพธ์แบบไหน -During development, we can check the function by running it and comparing the outcome with the expected one. For instance, we can do it in the console. +ระหว่างพัฒนา เราตรวจสอบฟังก์ชันได้โดยการรันและเปรียบเทียบผลลัพธ์กับสิ่งที่คาดหวังไว้ เช่น อาจทำในคอนโซล -If something is wrong -- then we fix the code, run again, check the result -- and so on till it works. +ถ้ามีอะไรผิดพลาด ก็แก้โค้ด รันใหม่ เช็คผลลัพธ์ วนไปแบบนี้จนกว่าจะใช้ได้ -But such manual "re-runs" are imperfect. +แต่การ "รันซ้ำ" ด้วยมือแบบนั้นยังไม่สมบูรณ์ -**When testing a code by manual re-runs, it's easy to miss something.** +**เมื่อทดสอบโค้ดโดยการรันซ้ำเอง มักจะมีบางอย่างหลุดไปได้ง่าย** -For instance, we're creating a function `f`. Wrote some code, testing: `f(1)` works, but `f(2)` doesn't work. We fix the code and now `f(2)` works. Looks complete? But we forgot to re-test `f(1)`. That may lead to an error. +เช่น สมมติเรากำลังเขียนฟังก์ชัน `f` เขียนโค้ดนิดหน่อย ลองทดสอบดู — `f(1)` ใช้ได้ แต่ `f(2)` ไม่ทำงาน เราแก้โค้ดแล้วตอนนี้ `f(2)` ก็ใช้ได้แล้ว ดูเหมือนจะครบแล้วใช่ไหม? แต่เราลืมย้อนกลับไปเทสต์ `f(1)` ใหม่ ซึ่งอาจมี error โผล่ขึ้นมาก็ได้ -That's very typical. When we develop something, we keep a lot of possible use cases in mind. But it's hard to expect a programmer to check all of them manually after every change. So it becomes easy to fix one thing and break another one. +นี่เป็นเรื่องปกติมาก ตอนพัฒนาอะไรสักอย่าง มักมีหลายกรณีที่ต้องคำนึงถึง แต่จะให้โปรแกรมเมอร์มานั่งเช็คทุกกรณีด้วยตัวเองทุกครั้งที่มีการเปลี่ยนแปลงนั้นเป็นเรื่องยาก เลยกลายเป็นว่าแก้อย่างหนึ่ง แล้วไปทำให้อีกอย่างพังได้ง่ายๆ -**Automated testing means that tests are written separately, in addition to the code. They run our functions in various ways and compare results with the expected.** +**การทดสอบอัตโนมัติหมายถึงการเขียนเทสต์แยกออกมาต่างหาก เป็นส่วนเสริมของโค้ด เทสต์เหล่านั้นจะรันฟังก์ชันของเราในหลากหลายวิธี และเปรียบเทียบผลลัพธ์กับสิ่งที่คาดหวัง** ## Behavior Driven Development (BDD) -Let's start with a technique named [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) or, in short, BDD. +มาเริ่มกันด้วยเทคนิคที่เรียกว่า [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) หรือเรียกสั้นๆ ว่า BDD -**BDD is three things in one: tests AND documentation AND examples.** +**BDD คือ 3 สิ่งในหนึ่งเดียว ทั้งเทสต์ เอกสาร และตัวอย่างการใช้งาน** -To understand BDD, we'll examine a practical case of development. +เพื่อทำความเข้าใจ BDD ลองมาดูตัวอย่างกรณีศึกษาจริงๆ กัน -## Development of "pow": the spec +## การพัฒนา "pow": ข้อกำหนด -Let's say we want to make a function `pow(x, n)` that raises `x` to an integer power `n`. We assume that `n≥0`. +สมมติว่าเราอยากเขียนฟังก์ชัน `pow(x, n)` ที่คืนค่า `x` ยกกำลัง `n` โดยที่ `n` เป็นจำนวนเต็มที่ `n≥0` -That task is just an example: there's the `**` operator in JavaScript that can do that, but here we concentrate on the development flow that can be applied to more complex tasks as well. +จริงๆ แล้วนี่เป็นแค่ตัวอย่าง เพราะ JavaScript มีตัวดำเนินการ `**` ที่ทำแบบนี้ได้อยู่แล้ว แต่ตรงนี้เราจะเน้นที่กระบวนการพัฒนา ซึ่งนำไปใช้กับงานที่ซับซ้อนกว่านี้ได้ด้วย -Before creating the code of `pow`, we can imagine what the function should do and describe it. +ก่อนจะเขียนโค้ดของ `pow` เราจินตนาการก่อนได้ว่าฟังก์ชันควรทำอะไร แล้วเขียนบรรยายไว้ -Such description is called a *specification* or, in short, a spec, and contains descriptions of use cases together with tests for them, like this: +คำบรรยายแบบนี้เรียกว่า *ข้อกำหนด (specification)* หรือเรียกสั้นๆ ว่า spec — ข้างในจะมีรายละเอียดกรณีการใช้งานต่างๆ พร้อมเทสต์สำหรับแต่ละกรณี ดังนี้ ```js describe("pow", function() { - it("raises to n-th power", function() { + it("ยกกำลัง n", function() { assert.equal(pow(2, 3), 8); }); }); ``` -A spec has three main building blocks that you can see above: +spec มีองค์ประกอบหลัก 3 ส่วนด้วยกัน -`describe("title", function() { ... })` -: What functionality we're describing. In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks. +`describe("ชื่อหัวข้อ", function() { ... })` +: บอกว่าเรากำลังอธิบายฟังก์ชันอะไร ในกรณีนี้คือ `pow` — ใช้จัดกลุ่มบล็อก `it` ที่เป็น "ตัวทำงาน" -`it("use case description", function() { ... })` -: In the title of `it` we *in a human-readable way* describe the particular use case, and the second argument is a function that tests it. +`it("คำอธิบายกรณีการใช้งาน", function() { ... })` +: ในชื่อของ `it` จะบรรยาย *ในรูปแบบที่คนอ่านแล้วเข้าใจ* ถึงกรณีการใช้งานเฉพาะ และอาร์กิวเมนต์ตัวที่สองก็จะเป็นฟังก์ชันที่ทดสอบกรณีดังกล่าว `assert.equal(value1, value2)` -: The code inside `it` block, if the implementation is correct, should execute without errors. +: โค้ดภายในบล็อก `it` ถ้าอิมพลีเมนต์ถูกต้อง ควรรันได้โดยไม่เกิด error - Functions `assert.*` are used to check whether `pow` works as expected. Right here we're using one of them -- `assert.equal`, it compares arguments and yields an error if they are not equal. Here it checks that the result of `pow(2, 3)` equals `8`. There are other types of comparisons and checks, that we'll add later. + ฟังก์ชัน `assert.*` ใช้เช็คว่า `pow` ทำงานได้ตามที่คาดหวังหรือไม่ ตรงนี้เราใช้ `assert.equal` ซึ่งจะเปรียบเทียบอาร์กิวเมนต์สองตัว แล้วโยน error ถ้าไม่เท่ากัน — ในที่นี้คือเช็คว่า `pow(2, 3)` ได้ `8` รึเปล่า นอกจากนี้ยังมีฟังก์ชันตรวจสอบแบบอื่นๆ อีก ซึ่งเราจะเพิ่มภายหลัง -The specification can be executed, and it will run the test specified in `it` block. We'll see that later. +spec นี้รันได้เลย โดยจะไปรันเทสต์ที่อยู่ในบล็อก `it` — เดี๋ยวจะมาดูกันทีหลัง -## The development flow +## ขั้นตอนการพัฒนา -The flow of development usually looks like this: +ขั้นตอนการพัฒนามักจะเป็นแบบนี้: -1. An initial spec is written, with tests for the most basic functionality. -2. An initial implementation is created. -3. To check whether it works, we run the testing framework [Mocha](http://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. -4. Now we have a working initial implementation with tests. -5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail. -6. Go to 3, update the implementation till tests give no errors. -7. Repeat steps 3-6 till the functionality is ready. +1. เขียน spec เบื้องต้น พร้อมเทสต์สำหรับฟีเจอร์พื้นฐานที่สุด +2. เขียนอิมพลีเมนต์เบื้องต้น +3. รันเทสต์ผ่านเฟรมเวิร์ก [Mocha](https://mochajs.org/) (จะอธิบายเพิ่มเร็วๆ นี้) เพื่อเช็คว่าใช้ได้ไหม — ถ้ายังไม่ครบถ้วนก็จะเห็น error แล้วค่อยทยอยแก้จนทุกอย่างผ่าน +4. ตอนนี้เรามีอิมพลีเมนต์เบื้องต้นที่ใช้ได้จริงพร้อมเทสต์แล้ว +5. เพิ่มกรณีการใช้งานเข้าไปใน spec — ซึ่งอิมพลีเมนต์อาจยังไม่รองรับ เทสต์จึงเริ่มล้มเหลว +6. กลับไปทำข้อ 3 — ปรับแก้อิมพลีเมนต์จนเทสต์ผ่านหมด +7. ทำข้อ 3-6 ซ้ำไปเรื่อยๆ จนทุกฟีเจอร์เสร็จสมบูรณ์ -So, the development is *iterative*. We write the spec, implement it, make sure tests pass, then write more tests, make sure they work etc. At the end we have both a working implementation and tests for it. +การพัฒนาจึงเป็นแบบ *วนซ้ำ (iterative)* — เขียน spec, อิมพลีเมนต์, เทสต์ให้ผ่าน แล้วก็เพิ่มเทสต์ วนไปเรื่อยๆ สุดท้ายเราจะได้ทั้งอิมพลีเมนต์ที่ใช้งานจริงได้ พร้อมกับเทสต์ครบถ้วน -Let's see this development flow in our practical case. +ทีนี้มาดูกระบวนการนี้ในกรณีตัวอย่างของเรากัน -The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +ขั้นตอนแรกเสร็จแล้ว — เรามี spec เบื้องต้นของ `pow` แล้ว ก่อนจะลงมือเขียนอิมพลีเมนต์ ลองใช้ไลบรารี JavaScript เพื่อรันเทสต์ดูก่อน แค่ให้เห็นว่าระบบทำงานอยู่ (ตอนนี้เทสต์ทั้งหมดจะล้มเหลวแน่นอน) -## The spec in action +## ข้อกำหนดที่ใช้งานจริง -Here in the tutorial we'll be using the following JavaScript libraries for tests: +ในบทนี้เราจะใช้ไลบรารี JavaScript ต่อไปนี้สำหรับการทดสอบ: -- [Mocha](http://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. -- [Chai](http://chaijs.com) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. -- [Sinon](http://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. +- [Mocha](https://mochajs.org/) — เฟรมเวิร์กหลัก ให้ฟังก์ชันทดสอบทั่วไปอย่าง `describe` และ `it` รวมถึงฟังก์ชันสำหรับรันเทสต์ +- [Chai](https://www.chaijs.com/) — ไลบรารีที่มี assertion หลากหลายแบบ ตอนนี้เราต้องการแค่ `assert.equal` +- [Sinon](https://sinonjs.org/) — ไลบรารีสำหรับ spy ฟังก์ชัน จำลองการทำงานของฟังก์ชันในตัว และอื่นๆ เราจะใช้ในภายหลัง -These libraries are suitable for both in-browser and server-side testing. Here we'll consider the browser variant. +ไลบรารีเหล่านี้ใช้ทดสอบได้ทั้งฝั่งเบราว์เซอร์และเซิร์ฟเวอร์ แต่ที่นี่จะพูดถึงกรณีบนเบราว์เซอร์ -The full HTML page with these frameworks and `pow` spec: +หน้า HTML แบบเต็มที่รวมเฟรมเวิร์กเหล่านี้พร้อม spec ของ `pow`: ```html src="index.html" ``` -The page can be divided into five parts: +หน้านี้แบ่งได้เป็น 5 ส่วน: -1. The `` -- add third-party libraries and styles for tests. -2. The ` ``` -If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason. +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` ### A module code is evaluated only the first time when imported -If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers. +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. + +The one-time evaluation has important consequences, that we should be aware of. -That has important consequences. Let's look at them using examples: +Let's see a couple of examples. First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: @@ -133,9 +146,11 @@ import `./alert.js`; // Module is evaluated! import `./alert.js`; // (shows nothing) ``` -In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it. +The second import shows nothing, because the module has already been evaluated. -Now, a more advanced example. +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. + +Now, let's consider a deeper example. Let's say, a module exports an object: @@ -160,54 +175,67 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* -// Both 1.js and 2.js imported the same object +// Both 1.js and 2.js reference the same admin object // Changes made in 1.js are visible in 2.js */!* ``` -So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. + +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that. -Such behavior allows us to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready. +**Such behavior is actually very convenient, because it allows us to *configure* modules.** -For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -Another module can also see `admin.name`: +...Now the module `admin.js` is configured. -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +Further importers can call it, and it correctly shows the current user: -alert(admin.name); // *!*Pete*/!* +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta The object `import.meta` contains the information about the current module. -Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML: +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: ```html run height=0 ``` @@ -233,7 +261,7 @@ Compare it to non-module scripts, where `this` is a global object: There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. -You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. +You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. ### Module scripts are deferred @@ -244,7 +272,7 @@ In other words: - module scripts wait until the HTML document is fully ready (even if they are tiny and load faster than HTML), and then run. - relative order of scripts is maintained: scripts that go first in the document, execute first. -As a side-effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. +As a side effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. For instance: @@ -260,7 +288,7 @@ Compare to regular script below: diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 4bd41a168..1b5649c69 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -46,7 +46,7 @@ Also, we can put `export` separately. Here we first declare, and then export: -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -93,25 +93,14 @@ At first sight, "import everything" seems such a cool thing, short to write, why Well, there are few reasons. -1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff. - - Let's say, we added a 3rd-party library `say.js` to our project with many functions: - ```js - // 📁 say.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` +1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. +2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. - Now if we only use one of `say.js` functions in our project: - ```js - // 📁 main.js - import {sayHi} from './say.js'; - ``` - ...Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking". +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. -2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. -3. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +``` ## Import "as" @@ -224,7 +213,7 @@ Without `default`, such an export would give an error: export class { // Error! (non-default export needs a name) constructor() {} } -``` +``` ### The "default" name @@ -321,12 +310,12 @@ export {default as User} from './user.js'; // re-export default Why would that be needed? Let's see a practical use case. -Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages), and many modules are just "helpers", for internal use in other package modules. +Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules. The file structure could be like this: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -337,13 +326,19 @@ auth/ ... ``` -We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this: +We'd like to expose the package functionality via a single entry point. + +In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`. + +Like this: ```js import {login, logout} from 'auth/index.js' ``` -The idea is that outsiders, developers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. +The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package. + +The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. As the actual exported functionality is scattered among the package, we can import it into `auth/index.js` and export from it: @@ -366,19 +361,21 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// import login/logout and immediately export them +// re-export login/logout export {login, logout} from './helpers.js'; -// import default as User and export it +// re-export the default export as User export {default as User} from './user.js'; ... ``` +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. + ### Re-exporting the default export The default export needs separate handling when re-exporting. -Let's say we have `user.js`, and we'd like to re-export class `User` from it: +Let's say we have `user.js` with the `export default class User` and would like to re-export it: ```js // 📁 user.js @@ -387,19 +384,21 @@ export default class User { } ``` -1. `export User from './user.js'` won't work. What can go wrong?... But that's a syntax error! +We can come across two problems with it: + +1. `export User from './user.js'` won't work. That would lead to a syntax error. - To re-export the default export, we have to write `export {default as User}`, as in the example above. + To re-export the default export, we have to write `export {default as User}`, as in the example above. 2. `export * from './user.js'` re-exports only named exports, but ignores the default one. - If we'd like to re-export both named and the default export, then two statements are needed: + If we'd like to re-export both named and default exports, then two statements are needed: ```js export * from './user.js'; // to re-export named exports export {default} from './user.js'; // to re-export the default export ``` -Such oddities of re-exporting the default export are one of the reasons why some developers don't like them. +Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones. ## Summary @@ -418,14 +417,14 @@ You can check yourself by reading them and recalling what they mean: Import: -- Named exports from module: +- Importing named exports: - `import {x [as y], ...} from "module"` -- Default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` -- Everything: +- Import all: - `import * as obj from "module"` -- Import the module (its code runs), but do not assign it to a variable: +- Import the module (its code runs), but do not assign any of its exports to variables: - `import "module"` We can put `import/export` statements at the top or at the bottom of a script, that doesn't matter. diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md index 357a57313..9db69cb2f 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md @@ -19,5 +19,5 @@ function wrap(target) { user = wrap(user); alert(user.name); // John -alert(user.age); // ReferenceError: Property doesn't exist "age" +alert(user.age); // ReferenceError: Property doesn't exist: "age" ``` diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md index d7093c0c3..47985e1a7 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md @@ -27,6 +27,6 @@ user = wrap(user); alert(user.name); // John *!* -alert(user.age); // ReferenceError: Property doesn't exist "age" +alert(user.age); // ReferenceError: Property doesn't exist: "age" */!* ``` diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index 0711fd33a..1f84912e5 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -39,7 +39,7 @@ As there are no traps, all operations on `proxy` are forwarded to `target`. As we can see, without any traps, `proxy` is a transparent wrapper around `target`. -![](proxy.svg) +![](proxy.svg) `Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`. @@ -67,7 +67,7 @@ For every internal method, there's a trap in this table: the name of the method | `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) | | `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) | | `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | -| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` | +| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` | ```warn header="Invariants" JavaScript enforces some invariants -- conditions that must be fulfilled by internal methods and traps. @@ -335,7 +335,7 @@ let user = { _password: "secret" }; -alert(user._password); // secret +alert(user._password); // secret ``` Let's use proxies to prevent any access to properties starting with `_`. @@ -376,7 +376,7 @@ user = new Proxy(user, { }, *!* deleteProperty(target, prop) { // to intercept property deletion -*/!* +*/!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { @@ -437,7 +437,7 @@ user = { ``` -A call to `user.checkPassword()` call gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. +A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps. @@ -963,9 +963,13 @@ revoke(); alert(proxy.data); // Error ``` -A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. The target object can be garbage-collected after that. +A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. + +Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope. -We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object: +We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`. + +Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy: ```js run *!* @@ -980,15 +984,13 @@ let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); -// ..later in our code.. +// ..somewhere else in our code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked) ``` -The benefit of such an approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needed. - We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. ## References diff --git a/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg b/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg index a5e158400..3fba64606 100644 --- a/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg +++ b/1-js/99-js-misc/01-proxy/proxy-inherit-admin.svg @@ -1 +1 @@ -_name: "Guest" name: getter_name: "Admin"user (proxied)original useradmin[[Prototype]] \ No newline at end of file +_name: "Guest" name: getter_name: "Admin"user (proxied)original useradmin[[Prototype]] \ No newline at end of file diff --git a/1-js/99-js-misc/01-proxy/proxy-inherit.svg b/1-js/99-js-misc/01-proxy/proxy-inherit.svg index 510dcef1b..6c34c0f4e 100644 --- a/1-js/99-js-misc/01-proxy/proxy-inherit.svg +++ b/1-js/99-js-misc/01-proxy/proxy-inherit.svg @@ -1 +1 @@ -_name: "Guest" name: getteruser (proxied)original user \ No newline at end of file +_name: "Guest" name: getteruser (proxied)original user \ No newline at end of file diff --git a/1-js/99-js-misc/01-proxy/proxy.svg b/1-js/99-js-misc/01-proxy/proxy.svg index 76a41670c..6b2224cfd 100644 --- a/1-js/99-js-misc/01-proxy/proxy.svg +++ b/1-js/99-js-misc/01-proxy/proxy.svg @@ -1 +1 @@ -test: 5proxytargetget proxy.test5 \ No newline at end of file +test: 5proxytargetget proxy.test5 \ No newline at end of file diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md index bb308847c..d71ac23f8 100644 --- a/1-js/99-js-misc/03-currying-partials/article.md +++ b/1-js/99-js-misc/03-currying-partials/article.md @@ -155,7 +155,7 @@ function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { - return function pass(...args2) { // (2) + return function(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } @@ -164,18 +164,10 @@ function curried(...args) { When we run it, there are two `if` execution branches: -1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it. -2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result. +1. If passed `args` count is the same or more than the original function has in its definition (`func.length`) , then just pass the call to it using `func.apply`. +2. Otherwise, get a partial: we don't call `func` just yet. Instead, another wrapper is returned, that will re-apply `curried` providing previous arguments together with the new ones. -For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`. - -For the call `curried(1)(2)(3)`: - -1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`. -2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. As the argument count is still less than 3, `curry` returns `pass`. -3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function. - -If that's still not obvious, just trace the calls sequence in your mind or on paper. +Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result. ```smart header="Fixed-length functions only" The currying requires the function to have a fixed number of arguments. diff --git a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md index 31ea4ff88..e4ee78748 100644 --- a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md +++ b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md @@ -5,7 +5,7 @@ Here's the explanations. 2. The same, parentheses do not change the order of operations here, the dot is first anyway. -3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines: +3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines: ```js no-beautify f = obj.go; // calculate the expression @@ -14,7 +14,7 @@ Here's the explanations. Here `f()` is executed as a function, without `this`. -4. The similar thing as `(3)`, to the left of the dot `.` we have an expression. +4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression. To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type. diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index c680c17f9..894db8fc6 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -4,7 +4,7 @@ ```warn header="In-depth language feature" This article covers an advanced topic, to understand certain edge-cases better. -It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood. +It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. ``` A dynamically evaluated method call can lose `this`. @@ -59,7 +59,7 @@ If we put these operations on separate lines, then `this` will be lost for sure: let user = { name: "John", hi() { alert(this.name); } -} +}; *!* // split getting and calling the method in two lines @@ -87,13 +87,13 @@ The result of a property access `user.hi` is not a function, but a value of Refe (user, "hi", true) ``` -When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case). +When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`user` in this case). Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`. Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`. -So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). +So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). ## Summary diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 000000000..4f144f824 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```warn header="Advanced knowledge" +The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, the length of such symbols is `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare Chinese character +``` + +That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! + +We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. + +Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 000000000..777bf703c --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

+ +To increase page loading speed, it would be reasonable to download and display photo thumbnails in *compressed* quality. +But, to create a collage from selected photos, download and use them in *full-size* quality. + +Below, we can see, that the intrinsic size of the thumbnails is 240x240 pixels. +The size was chosen on purpose to increase loading speed. +Moreover, we do not need full-size photos in preview mode. + +![](weakref-finalizationregistry-demo-02.png) + +
+Let's assume, that we need to create a collage of 4 photos: we select them, and then click the "Create collage" button. +At this stage, the already known to us weakRefCache function checks whether the required image is in the cache. +If not, it downloads it from the cloud and puts it in the cache for further use. +This happens for each selected image: +

+ +![](weakref-finalizationregistry-demo-03.gif) + +
+ +Paying attention to the output in the console, you can see, which of the photos were downloaded from the cloud - this is indicated by FETCHED_IMAGE. +Since this is the first attempt to create a collage, this means, that at this stage the "weak cache" was still empty, and all the photos were downloaded from the cloud and put in it. + +But, along with the process of downloading images, there is also a process of memory cleanup by the garbage collector. +This means, that the object stored in the cache, which we refer to, using a weak reference, is deleted by the garbage collector. +And our finalizer executes successfully, thereby deleting the key, by which the image was stored in the cache. +CLEANED_IMAGE notifies us about it: + +![](weakref-finalizationregistry-demo-04.jpg) + +
+Next, we realize that we do not like the resulting collage, and decide to change one of the images and create a new one. +To do this, just deselect the unnecessary image, select another one, and click the "Create collage" button again: +

+ +![](weakref-finalizationregistry-demo-05.gif) + +
+But this time not all images were downloaded from the network, and one of them was taken from the weak cache: the CACHED_IMAGE message tells us about it. +This means that at the time of collage creation, the garbage collector had not yet deleted our image, and we boldly took it from the cache, +thereby reducing the number of network requests and speeding up the overall time of the collage creation process: +

+ +![](weakref-finalizationregistry-demo-06.jpg) + +
+Let's "play around" a little more, by replacing one of the images again and creating a new collage: +

+ +![](weakref-finalizationregistry-demo-07.gif) + +
+This time the result is even more impressive. Of the 4 images selected, 3 of them were taken from the weak cache, and only one had to be downloaded from the network. +The reduction in network load was about 75%. Impressive, isn't it? +

+ +![](weakref-finalizationregistry-demo-08.jpg) + +
+ +Of course, it is important to remember, that such behavior is not guaranteed, and depends on the specific implementation and operation of the garbage collector. + +Based on this, a completely logical question immediately arises: why do not we use an ordinary cache, where we can manage its entities ourselves, instead of relying on the garbage collector? +That's right, in the vast majority of cases there is no need to use `WeakRef` and `FinalizationRegistry`. + +Here, we simply demonstrated an alternative implementation of similar functionality, using a non-trivial approach with interesting language features. +Still, we cannot rely on this example, if we need a constant and predictable result. + +You can [open this example in the sandbox](sandbox:weakref-finalizationregistry). + +## Summary + +`WeakRef` - designed to create weak references to objects, allowing them to be deleted from memory by the garbage collector if there are no longer strong references to them. +This is beneficial for addressing excessive memory usage and optimizing the utilization of system resources in applications. + +`FinalizationRegistry` - is a tool for registering callbacks, that are executed when objects that are no longer strongly referenced, are destroyed. +This allows releasing resources associated with the object or performing other necessary operations before deleting the object from memory. \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png new file mode 100644 index 000000000..021637342 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css new file mode 100644 index 000000000..f6df812d0 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css @@ -0,0 +1,49 @@ +.app { + display: flex; + flex-direction: column; + gap: 16px; +} + +.start-messages { + width: fit-content; +} + +.window { + width: 100%; + border: 2px solid #464154; + overflow: hidden; +} + +.window__header { + position: sticky; + padding: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #736e7e; +} + +.window__title { + margin: 0; + font-size: 24px; + font-weight: 700; + color: white; + letter-spacing: 1px; +} + +.window__button { + padding: 4px; + background: #4f495c; + outline: none; + border: 2px solid #464154; + color: white; + font-size: 16px; + cursor: pointer; +} + +.window__body { + height: 250px; + padding: 16px; + overflow: scroll; + background-color: #736e7e33; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html new file mode 100644 index 000000000..7f93af4c7 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html @@ -0,0 +1,28 @@ + + + + + + + WeakRef DOM Logger + + + + +
+ +
+
+

Messages:

+ +
+
+ No messages. +
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 000000000..ea55b4478 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 000000000..2a507dbcd --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 000000000..6cc199a12 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 000000000..949a14f9f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 000000000..1177d6580 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 000000000..e738f8e7e --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png new file mode 100644 index 000000000..fc33a023a Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 000000000..7d8bb01e8 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 000000000..b81966dda Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 000000000..ba60f1e86 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 000000000..d34bda4d7 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 000000000..b2655540f Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 000000000..51f874518 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 000000000..5f98aec14 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 000000000..e6c9e3960 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 000000000..7ce52f927 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +
+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 000000000..983b34d9a --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 56b568833..eedc28fb3 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,10 +1,10 @@ # Browser environment, specs -The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms. +The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. -A platform may be a browser, or a web-server or another *host*, even a "smart" coffee machine, if it can run JavaScript. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*. +A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*. -A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. +A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. Here's a bird's-eye view of what we have when JavaScript runs in a web browser: @@ -15,9 +15,9 @@ There's a "root" object called `window`. It has two roles: 1. First, it is a global object for JavaScript code, as described in the chapter . 2. Second, it represents the "browser window" and provides methods to control it. -For instance, here we use it as a global object: +For instance, we can use it as a global object: -```js run +```js run global function sayHi() { alert("Hello"); } @@ -26,17 +26,17 @@ function sayHi() { window.sayHi(); ``` -And here we use it as a browser window, to see the window height: +And we can use it as a browser window, to show the window height: ```js run alert(window.innerHeight); // inner window height ``` -There are more window-specific methods and properties, we'll cover them later. +There are more window-specific methods and properties, which we'll cover later. ## DOM (Document Object Model) -Document Object Model, or DOM for short, represents all page content as objects that can be modified. +The Document Object Model, or DOM for short, represents all page content as objects that can be modified. The `document` object is the main "entry point" to the page. We can change or create anything on the page using it. @@ -49,18 +49,18 @@ document.body.style.background = "red"; setTimeout(() => document.body.style.background = "", 1000); ``` -Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). +Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). ```smart header="DOM is not only for browsers" The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too. -For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though. +For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though. ``` ```smart header="CSSOM for styling" There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them. -CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. +The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. ``` ## BOM (Browser Object Model) @@ -69,7 +69,7 @@ The Browser Object Model (BOM) represents additional objects provided by the bro For instance: -- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). +- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc). - The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one. Here's how we can use the `location` object: @@ -81,12 +81,12 @@ if (confirm("Go to Wikipedia?")) { } ``` -Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. +The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user. ```smart header="Specifications" -BOM is the part of the general [HTML specification](https://html.spec.whatwg.org). +The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org). -Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . +Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . ``` ## Summary @@ -94,20 +94,20 @@ Yes, you heard that right. The HTML spec at is no Talking about standards, we have: DOM specification -: Describes the document structure, manipulations and events, see . +: Describes the document structure, manipulations, and events, see . CSSOM specification -: Describes stylesheets and style rules, manipulations with them and their binding to documents, see . +: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see . HTML specification : Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods. Additionally, some classes are described separately at . -Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything. +Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all. -When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g , . -Now we'll get down to learning DOM, because the document plays the central role in the UI. +Now, we'll get down to learning the DOM, because the document plays the central role in the UI. diff --git a/2-ui/1-document/01-browser-environment/windowObjects.svg b/2-ui/1-document/01-browser-environment/windowObjects.svg index d1b280ee8..b7e18bb34 100644 --- a/2-ui/1-document/01-browser-environment/windowObjects.svg +++ b/2-ui/1-document/01-browser-environment/windowObjects.svg @@ -1 +1 @@ -windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM \ No newline at end of file +windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 019398be9..f7f2be91d 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -51,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -143,7 +143,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); ````warn header="Tables always have ``" -An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in the DOM automatically. +An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically. For the HTML: @@ -160,7 +160,7 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); -You see? The `` appeared out of nowhere. You should keep this in mind while working with tables to avoid surprises. +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. ```` ## Other node types @@ -188,7 +188,7 @@ For example, comments:
@@ -199,7 +199,7 @@ We may think -- why is a comment added to the DOM? It doesn't affect the visual **Everything in HTML, even comments, becomes a part of the DOM.** -Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. We are not going to touch that node, we even don't draw it on diagrams for that reason, but it's there. +Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there. The `document` object that represents the whole document is, formally, a DOM node as well. @@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg index c0096060a..eb99f193f 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole0.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg index db92359d5..02ef5f0a6 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole1.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg index 19ea221d2..448eea9d1 100644 --- a/2-ui/1-document/02-dom-nodes/elk.svg +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg index 658ee5ea2..60696ec0d 100644 --- a/2-ui/1-document/02-dom-nodes/inspect.svg +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index f7123d70d..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Element-only navigation -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg index a9ce1fd8d..fd0b2826a 100644 --- a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg +++ b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg @@ -1 +1 @@ -document.documentElement <HTML>document.body (if inside body)parent Element<DIV>next Element Siblingprevious Element Siblingchildrenfirst Element Child last Element Child \ No newline at end of file +document.documentElement <HTML>document.body (if inside body)parent Element<DIV>next Element Siblingprevious Element Siblingchildrenfirst Element Child last Element Child \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/dom-links.svg b/2-ui/1-document/03-dom-navigation/dom-links.svg index 126530e93..6c34bca4a 100644 --- a/2-ui/1-document/03-dom-navigation/dom-links.svg +++ b/2-ui/1-document/03-dom-navigation/dom-links.svg @@ -1 +1 @@ -documentdocument.documentElement <HTML>document.body (if inside body)parentNode<DIV>nextSiblingpreviousSiblingchildNodesfirstChild lastChild \ No newline at end of file +documentdocument.documentElement <HTML>document.body (if inside body)parentNode<DIV>nextSiblingpreviousSiblingchildNodesfirstChild lastChild \ No newline at end of file diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index f5ab0b785..405129694 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element: ``` ```warn header="Please don't use id-named global variables to access elements" -This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's kind of standard. But it is supported mainly for compatibility. +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from. @@ -71,7 +71,7 @@ If there are multiple elements with the same `id`, then the behavior of methods ``` ```warn header="Only `document.getElementById`, not `anyElem.getElementById`" -The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. +The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document. ``` ## querySelectorAll [#querySelectorAll] @@ -116,7 +116,7 @@ In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but t Previous methods were searching the DOM. -The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. +The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us. @@ -142,7 +142,7 @@ For instance: *Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. -The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned. @@ -154,7 +154,7 @@ For instance:
  • Chapter 1
  • -
  • Chapter 1
  • +
  • Chapter 2
@@ -363,7 +363,7 @@ There are 6 main methods to search for nodes in DOM: -By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts. +By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts. Besides that: diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index cea166e8d..99dde5bcd 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -10,7 +10,7 @@ Different DOM nodes may have different properties. For instance, an element node Each DOM node belongs to the corresponding built-in class. -The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. Here's the picture, explanations to follow: @@ -18,16 +18,39 @@ Here's the picture, explanations to follow: The classes are: -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. -- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. -- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. -- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by concrete HTML elements: +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything. + + Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. + +- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. + + It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality). + +- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. + + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

Hello

`. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements. + + It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. + + A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`. + +- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time. + + It is inherited by concrete HTML elements: - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `` elements, - - ...and so on, each tag has its own class that may provide specific properties and methods. + - ...and so on. + +There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, `
`, `
` do not have any specific properties, so they are instances of `HTMLElement` class. -So, the full set of properties and methods of a given node comes as the result of the inheritance. +So, the full set of properties and methods of a given node comes as the result of the chain of inheritance. For example, let's consider the DOM object for an `` element. It belongs to [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class. @@ -128,13 +151,13 @@ For instance: ```html run - ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run @@ -298,7 +298,7 @@ For instance, here for the order state the attribute `order-state` is used:
``` -Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`? +Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`? Because an attribute is more convenient to manage. The state can be changed as easy as: diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index e127bc0ef..40c75dff3 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -6,7 +6,7 @@ importance: 5 We have an empty DOM element `elem` and a string `text`. -Which of these 3 commands do exactly the same? +Which of these 3 commands will do exactly the same? 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md index 15238fcf4..1414e90c1 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md @@ -39,15 +39,19 @@ The clock-managing functions: ```js let timerId; -function clockStart() { // run the clock - timerId = setInterval(update, 1000); +function clockStart() { // run the clock + if (!timerId) { // only set a new interval if the clock is not running + timerId = setInterval(update, 1000); + } update(); // (*) } function clockStop() { clearInterval(timerId); - timerId = null; + timerId = null; // (**) } ``` Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then. + +Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`. diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html index 1bf642b10..84ee26f19 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html @@ -43,15 +43,19 @@ } function clockStart() { - timerId = setInterval(update, 1000); + // set a new interval only if the clock is stopped + // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again + if (!timerId) { + timerId = setInterval(update, 1000); + } update(); // <-- start right now, don't wait 1 second till the first setInterval works } function clockStop() { clearInterval(timerId); + timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart() } - clockStart(); diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b9..3d1f6698f 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ The HTML in the task is incorrect. That's the reason of the odd thing. -The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
`. +The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`. Now it's obvious that when we remove the table, it remains. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
`. +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`. The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index f87074dba..861f70503 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -22,6 +22,6 @@ Why does that happen? alert(table); // the table, as it should be table.remove(); - // why there's still aaa in the document? + // why there's still "aaa" in the document? ``` diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index 43b0a34a7..a57e7e2d9 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -10,7 +10,7 @@ For every list item: 1. Ask a user about its content using `prompt`. 2. Create the `
  • ` with it and add it to `
      `. -3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt). +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). All elements should be created dynamically. diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg index 6e1fb4874..0843713ce 100644 --- a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg +++ b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg @@ -1 +1 @@ -ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file +ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.svg b/2-ui/1-document/07-modifying-document/insert-adjacent.svg index 64beee037..e26fd023a 100644 --- a/2-ui/1-document/07-modifying-document/insert-adjacent.svg +++ b/2-ui/1-document/07-modifying-document/insert-adjacent.svg @@ -1 +1 @@ -ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file +ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 9154d43d6..46aaa3b00 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // back to normal If we set `style.display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style.display` property at all. +Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this: + +```js run +document.body.style.background = 'red'; //set background to red + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second +``` + ````smart header="Full rewrite with `style.cssText`" Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. @@ -261,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: - -```html run - - -``` ```` ```smart header="Styles applied to `:visited` links are hidden!" diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg index ca8bbc3bd..f5bd9f4f9 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index 13e245ebb..66f28115f 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -17,8 +17,8 @@ As a sample element to demonstrate properties we'll use the one given below: width: 300px; height: 200px; border: 25px solid #E8C48F; - padding: 20px; - overflow: auto; + padding: 20px; + overflow: auto; } ``` @@ -106,7 +106,7 @@ Geometry properties are calculated only for displayed elements. If an element (or any of its ancestors) has `display:none` or is not in the document, then all geometry properties are zero (or `null` for `offsetParent`). -For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or it's ancestor) has `display:none`. +For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or its ancestor) has `display:none`. We can use this to check if an element is hidden, like this: @@ -116,7 +116,7 @@ function isHidden(elem) { } ``` -Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty `
      `). +Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes. ```` ## clientTop/Left diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg index 3c77f09ac..20a59e18d 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-all.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg index 12bc456a6..e8dd3d60a 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg @@ -1 +1 @@ -clientTop: 25px = borderclientLeft: 41px \ No newline at end of file +clientTop: 25px = borderclientLeft: 41px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg index 5708047d4..8097afa78 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg index fe4b3c69d..2603b05fb 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg index 62de5f572..330d2a7c0 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg @@ -1 +1 @@ -clientWidth: 284px = content widthCSS width: 300pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with padding: 0; width: 300px; \ No newline at end of file +clientWidth: 284px = content widthCSS width: 300pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with padding: 0; width: 300px; \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.svg b/2-ui/1-document/09-size-and-scroll/metric-css.svg index e910a9c87..1f2e5f780 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-css.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-css.svg @@ -1 +1 @@ -padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg index d72a20138..2d108473e 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg @@ -1 +1 @@ -offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file +offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg index 76e20b1bc..4d30d90cc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg index 3c00bed00..7f72de422 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg index 29e25eb6f..75a24e3bc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 95a5cd48b..08a2f6576 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -73,6 +73,12 @@ alert('Current scroll from the left: ' + window.pageXOffset); These properties are read-only. +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + ## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] ```warn diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg index b0dff4d4e..18cd37a74 100644 --- a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg +++ b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg @@ -1 +1 @@ -documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file +documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 4775ff0eb..fc605c414 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,7 @@ Additionally, there are derived properties: ```online For instance click this button to see its window coordinates: -

      +

      ``` -Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `mouse.onclick`, so event handlers are totally separate. +Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `menu.onclick`, so event handlers are totally separate. The output order becomes: 1 -> 2 -> nested. diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md index 651c3273f..9574b0c83 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/article.md +++ b/2-ui/3-event-details/1-mouse-events-basics/article.md @@ -1,3 +1,4 @@ + # Mouse events In this chapter we'll get into more details about mouse events and their properties. @@ -39,9 +40,9 @@ In cases when a single action initiates multiple events, their order is fixed. T ```online Click the button below and you'll see the events. Try double-click too. -On the teststand below all mouse events are logged, and if there is more than a 1 second delay between them they are separated by a horizontal ruler. +On the teststand below, all mouse events are logged, and if there is more than a 1 second delay between them, they are separated by a horizontal rule. -Also we can see the `button` property that allows to detect the mouse button, it's explained below. +Also, we can see the `button` property that allows us to detect the mouse button; it's explained below.
      ``` @@ -52,7 +53,7 @@ Click-related events always have the `button` property, which allows to get the We usually don't use it for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click. -From the other hand, `mousedown` and `mouseup` handlers we may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". +On the other hand, `mousedown` and `mouseup` handlers may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". The possible values of `event.button` are: @@ -154,7 +155,7 @@ Move the mouse over the input field to see `clientX/clientY` (the example is in ## Preventing selection on mousedown -Double mouse click has a side-effect that may be disturbing in some interfaces: it selects text. +Double mouse click has a side effect that may be disturbing in some interfaces: it selects text. For instance, double-clicking on the text below selects it in addition to our handler: diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html index e998165fd..84d52b18c 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html @@ -54,7 +54,7 @@

      Once upon a time there was a mother pig who had three little pigs.

      -

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

      The three little pigs set off. "We will take care that the wolf does not catch us," they said.

      diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html index 2dc4394e7..774e24a21 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html @@ -54,7 +54,7 @@

      Once upon a time there was a mother pig who had three little pigs.

      -

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

      The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

      The three little pigs set off. "We will take care that the wolf does not catch us," they said.

      diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js index 4e6e2a3e9..7503ca9c2 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js @@ -88,7 +88,7 @@ class HoverIntent { if (speed < this.sensitivity) { clearInterval(this.checkSpeedInterval); this.isHover = true; - this.over.call(this.elem, event); + this.over.call(this.elem); } else { // speed fast, remember new coordinates as the previous ones this.prevX = this.lastX; diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md index c7ac0d4db..d409c3f12 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md @@ -80,7 +80,7 @@ An important feature of `mouseout` -- it triggers, when the pointer moves from a
      ``` -If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`! +If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`! ![](mouseover-to-child.svg) diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg index 07830295c..6044eff17 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg @@ -1 +1 @@ -mouseoutmouseover#parent#child \ No newline at end of file +mouseoutmouseover#parent#child \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg index 07176ba2d..22335b52e 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg @@ -1 +1 @@ -#TOtargetrelatedTarget = null \ No newline at end of file +#TOtargetrelatedTarget = null \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg index 262ddf596..437f03b10 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg @@ -1 +1 @@ -#TO#FROM<DIV><DIV><DIV>mouseovermouseout \ No newline at end of file +#TO#FROM<DIV><DIV><DIV>mouseovermouseout \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg index 784f435de..1277ddff5 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg @@ -1 +1 @@ -<DIV>mouseovermouseout \ No newline at end of file +<DIV>mouseovermouseout \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg index b38d76fbe..78210845b 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg @@ -1 +1 @@ -mouseoutmouseover#parent#child \ No newline at end of file +mouseoutmouseover#parent#child \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js index 6d87199c2..5752e83ae 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js @@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler; function handler(event) { let type = event.type; - while (type < 11) type += ' '; + while (type.length < 11) type += ' '; log(type + " target=" + event.target.id) return false; diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg index ca8bbc3bd..f5bd9f4f9 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 6cb1152c1..4c928eef1 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -18,19 +18,19 @@ The basic Drag'n'Drop algorithm looks like this: 2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. 3. On `mouseup` - perform all actions related to finishing the drag'n'drop. -These are the basics. Later we'll see how to other features, such as highlighting current underlying elements while we drag over them. +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. Here's the implementation of dragging a ball: ```js -ball.onmousedown = function(event) { +ball.onmousedown = function(event) { // (1) prepare to moving: make absolute and on top by z-index ball.style.position = 'absolute'; ball.style.zIndex = 1000; // move it out of any current parents directly into body // to make it positioned relative to the body - document.body.append(ball); + document.body.append(ball); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { @@ -93,14 +93,14 @@ So we should listen on `document` to catch it. ## Correct positioning -In the examples above the ball is always moved so, that it's center is under the pointer: +In the examples above the ball is always moved so that its center is under the pointer: ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` -Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. +Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. It would be better if we keep the initial shift of the element relative to the pointer. @@ -124,7 +124,7 @@ Let's update our algorithm: ```js // onmousemove - // ball has position:absoute + // ball has position:absolute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -219,7 +219,7 @@ That's why the initial idea to put handlers on potential droppables doesn't work So, what to do? -There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). +There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned. We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this: diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/ball_shift.svg b/2-ui/3-event-details/4-mouse-drag-and-drop/ball_shift.svg index 564730c56..29fdb31ef 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/ball_shift.svg +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/ball_shift.svg @@ -1,41 +1 @@ - - - - ball_shift.svg - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - shiftX - - - shiftY - - - - - - - - - - - \ No newline at end of file +shiftXshiftY \ No newline at end of file diff --git a/2-ui/3-event-details/6-pointer-events/article.md b/2-ui/3-event-details/6-pointer-events/article.md index 3e751a4af..ecc144712 100644 --- a/2-ui/3-event-details/6-pointer-events/article.md +++ b/2-ui/3-event-details/6-pointer-events/article.md @@ -9,16 +9,16 @@ Let's make a small overview, so that you understand the general picture and the - Long ago, in the past, there were only mouse events. Then touch devices became widespread, phones and tablets in particular. For the existing scripts to work, they generated (and still generate) mouse events. For instance, tapping a touchscreen generates `mousedown`. So touch devices worked well with web pages. - + But touch devices have more capabilities than a mouse. For example, it's possible to touch multiple points at once ("multi-touch"). Although, mouse events don't have necessary properties to handle such multi-touches. - So touch events were introduced, such as `touchstart`, `touchend`, `touchmove`, that have touch-specific properties (we don't cover them in detail here, because pointer events are even better). - Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome. + Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome. - To solve these issues, the new standard Pointer Events was introduced. It provides a single set of events for all kinds of pointing devices. -As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compartible with Pointer Events level 2. +As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compatible with Pointer Events level 2. Unless you develop for old browsers, such as Internet Explorer 10, or for Safari 12 or below, there's no point in using mouse or touch events any more -- we can switch to pointer events. @@ -43,12 +43,12 @@ Pointer events are named similarly to mouse events: | `gotpointercapture` | - | | `lostpointercapture` | - | -As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon. +As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon. ```smart header="Replacing `mouse` with `pointer` in our code" We can replace `mouse` events with `pointer` in our code and expect things to continue working fine with mouse. -The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`. +The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`. ``` ## Pointer event properties @@ -56,20 +56,20 @@ The support for touch devices will also "magically" improve. Although, we may ne Pointer events have the same properties as mouse events, such as `clientX/Y`, `target`, etc., plus some others: - `pointerId` - the unique identifier of the pointer causing the event. - + Browser-generated. Allows us to handle multiple pointers, such as a touchscreen with stylus and multi-touch (examples will follow). -- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch". +- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch". We can use this property to react differently on various pointer types. - `isPrimary` - is `true` for the primary pointer (the first finger in multi-touch). Some pointer devices measure contact area and pressure, e.g. for a finger on the touchscreen, there are additional properties for that: -- `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`. +- `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`. - `height` - the height of the area where the pointer touches the device. Where unsupported, it's always `1`. - `pressure` - the pressure of the pointer tip, in range from 0 to 1. For devices that don't support pressure must be either `0.5` (pressed) or `0`. - `tangentialPressure` - the normalized tangential pressure. -- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative the surface. +- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative to the surface. These properties aren't supported by most devices, so they are rarely used. You can find the details about them in the [specification](https://w3c.github.io/pointerevents/#pointerevent-interface) if needed. @@ -102,16 +102,16 @@ Please note: you must be using a touchscreen device, such as a phone or a tablet ## Event: pointercancel -The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. +The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated. -Such causes are: +Such causes are: - The pointer device hardware was physically disabled. -- The device orientation changed (tablet rotated). +- The device orientation changed (tablet rotated). - The browser decided to handle the interaction on its own, considering it a mouse gesture or zoom-and-pan action or something else. We'll demonstrate `pointercancel` on a practical example to see how it affects us. -Let's say we're impelementing drag'n'drop for a ball, just as in the beginning of the article . +Let's say we're implementing drag'n'drop for a ball, just as in the beginning of the article . Here is the flow of user actions and the corresponding events: @@ -126,7 +126,7 @@ Here is the flow of user actions and the corresponding events: So the issue is that the browser "hijacks" the interaction: `pointercancel` fires in the beginning of the "drag-and-drop" process, and no more `pointermove` events are generated. ```online -Here's the drag'n'drop demo with loggin of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: +Here's the drag'n'drop demo with logging of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`: [iframe src="ball" height=240 edit] ``` @@ -141,7 +141,7 @@ We need to do two things: - We can do this by setting `ball.ondragstart = () => false`, just as described in the article . - That works well for mouse events. 2. For touch devices, there are other touch-related browser actions (besides drag'n'drop). To avoid problems with them too: - - Prevent them by setting `#ball { touch-action: none }` in CSS. + - Prevent them by setting `#ball { touch-action: none }` in CSS. - Then our code will start working on touch devices. After we do that, the events will work as intended, the browser won't hijack the process and doesn't emit `pointercancel`. @@ -163,7 +163,7 @@ Pointer capturing is a special feature of pointer events. The idea is very simple, but may seem quite odd at first, as nothing like that exists for any other event type. The main method is: -- `elem.setPointerCapture(pointerId)` - binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened. +- `elem.setPointerCapture(pointerId)` -- binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened. In other words, `elem.setPointerCapture(pointerId)` retargets all subsequent events with the given `pointerId` to `elem`. @@ -172,29 +172,43 @@ The binding is removed: - automatically when `elem` is removed from the document, - when `elem.releasePointerCapture(pointerId)` is called. +Now what is it good for? It's time to see a real-life example. + **Pointer capturing can be used to simplify drag'n'drop kind of interactions.** -As an example, let's recall how one can implement a custom slider, described in the . +Let's recall how one can implement a custom slider, described in the . + +We can make a `slider` element to represent the strip and the "runner" (`thumb`) inside it: + +```html +
      +
      +
      +``` + +With styles, it looks like this: + +[iframe src="slider-html" height=40 edit] -We make a slider element with the strip and the "runner" (`thumb`) inside it. +

      -Then it works like this: +And here's the working logic, as it was described, after replacing mouse events with similar pointer events: -1. The user presses on the slider `thumb` - `pointerdown` triggers. -2. Then they move the pointer - `pointermove` triggers, and we move the `thumb` along. - - ...As the pointer moves, it may leave the slider `thumb`: go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer. +1. The user presses on the slider `thumb` -- `pointerdown` triggers. +2. Then they move the pointer -- `pointermove` triggers, and our code moves the `thumb` element along. + - ...As the pointer moves, it may leave the slider `thumb` element, go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer. -So, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `pointermove` event handler on the whole `document`. +In the mouse event based solution, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `mousemove` event handler on the whole `document`. -That solution looks a bit "dirty". One of the problems is that pointer movements around the document may cause side effects, trigger other event handlers, totally not related to the slider. +That's not a cleanest solution, though. One of the problems is that when a user moves the pointer around the document, it may trigger event handlers (such as `mouseover`) on some other elements, invoke totally unrelated UI functionality, and we don't want that. -Pointer capturing provides a means to bind `pointermove` to `thumb` and avoid any such problems: +This is the place where `setPointerCapture` comes into play. - We can call `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler, -- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`. +- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`. - When `pointerup` happens (dragging complete), the binding is removed automatically, we don't need to care about it. -So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Besides, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`. +So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Nevertheless, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`. Here's the essential code: @@ -202,15 +216,23 @@ Here's the essential code: thumb.onpointerdown = function(event) { // retarget all pointer events (until pointerup) to thumb thumb.setPointerCapture(event.pointerId); -}; -thumb.onpointermove = function(event) { - // moving the slider: listen on the thumb, as all pointer events are retargeted to it - let newLeft = event.clientX - slider.getBoundingClientRect().left; - thumb.style.left = newLeft + 'px'; + // start tracking pointer moves + thumb.onpointermove = function(event) { + // moving the slider: listen on the thumb, as all pointer events are retargeted to it + let newLeft = event.clientX - slider.getBoundingClientRect().left; + thumb.style.left = newLeft + 'px'; + }; + + // on pointer up finish tracking pointer moves + thumb.onpointerup = function(event) { + thumb.onpointermove = null; + thumb.onpointerup = null; + // ...also process the "drag end" if needed + }; }; -// note: no need to call thumb.releasePointerCapture, +// note: no need to call thumb.releasePointerCapture, // it happens on pointerup automatically ``` @@ -218,15 +240,27 @@ thumb.onpointermove = function(event) { The full demo: [iframe src="slider" height=100 edit] + +

      + +In the demo, there's also an additional element with `onmouseover` handler showing the current date. + +Please note: while you're dragging the thumb, you may hover over this element, and its handler *does not* trigger. + +So the dragging is now free of side effects, thanks to `setPointerCapture`. ``` + + At the end, pointer capturing gives us two benefits: 1. The code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. The binding is released automatically. -2. If there are any `pointermove` handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider. +2. If there are other pointer event handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider. ### Pointer capturing events -There are two associated pointer events: +There's one more thing to mention here, for the sake of completeness. + +There are two events associated with pointer capturing: - `gotpointercapture` fires when an element uses `setPointerCapture` to enable capturing. - `lostpointercapture` fires when the capture is released: either explicitly with `releasePointerCapture` call, or automatically on `pointerup`/`pointercancel`. @@ -237,7 +271,7 @@ Pointer events allow handling mouse, touch and pen events simultaneously, with a Pointer events extend mouse events. We can replace `mouse` with `pointer` in event names and expect our code to continue working for mouse, with better support for other device types. -For drag'n'drops and complex touch interactions that the browser may decide to hijack and handle on its own - remember to cancel the default action on events and set `touch-events: none` in CSS for elements that we engage. +For drag'n'drops and complex touch interactions that the browser may decide to hijack and handle on its own - remember to cancel the default action on events and set `touch-action: none` in CSS for elements that we engage. Additional abilities of pointer events are: diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html new file mode 100644 index 000000000..781016f52 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html @@ -0,0 +1,6 @@ + + + +
      +
      +
      diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css new file mode 100644 index 000000000..9b3d3b82d --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css @@ -0,0 +1,19 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/index.html b/2-ui/3-event-details/6-pointer-events/slider.view/index.html index 2c2a69ec7..b29e646a1 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/index.html +++ b/2-ui/3-event-details/6-pointer-events/slider.view/index.html @@ -5,22 +5,33 @@
      +

      Mouse over here to see the date

      + diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css index 9b3d3b82d..a84cd5e7e 100644 --- a/2-ui/3-event-details/6-pointer-events/slider.view/style.css +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -8,6 +8,7 @@ } .thumb { + touch-action: none; width: 10px; height: 25px; border-radius: 3px; diff --git a/2-ui/3-event-details/7-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md index 617852ccf..12fe63201 100644 --- a/2-ui/3-event-details/7-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters To reliably track layout-dependent characters, `event.key` may be a better way. -On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch. +On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch. Do we want to handle layout-dependant keys? Then `event.key` is the way to go. @@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept ```html autorun height=60 run ``` -Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. -Let's relax it a little bit: +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +165,9 @@ function checkPhoneKey(key) { Now arrows and deletion works well. -...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid. +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. ## Legacy @@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more. +## Mobile Keyboards + +When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values). + +While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices. + ## Summary Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS. diff --git a/2-ui/3-event-details/7-keyboard-events/german-layout.svg b/2-ui/3-event-details/7-keyboard-events/german-layout.svg index eb827607a..7ac9a4008 100644 --- a/2-ui/3-event-details/7-keyboard-events/german-layout.svg +++ b/2-ui/3-event-details/7-keyboard-events/german-layout.svg @@ -1,196 +1 @@ - - - - german-layout.svg - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Strg - - - Strg - - - - Alt - - - - Alt Gr - - - Win - - - Win - - - Menu - - - - - - - \ No newline at end of file +StrgStrgAl tAlt GrWinWinMenu \ No newline at end of file diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html index 401062830..a0d5a4f40 100644 --- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html @@ -28,7 +28,7 @@ - + diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js index 5eba24c7a..d97f7a7b5 100644 --- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js @@ -5,6 +5,8 @@ let lastTime = Date.now(); function handle(e) { if (form.elements[e.type + 'Ignore'].checked) return; + area.scrollTop = 1e6; + let text = e.type + ' key=' + e.key + ' code=' + e.code + diff --git a/2-ui/3-event-details/7-keyboard-events/us-layout.svg b/2-ui/3-event-details/7-keyboard-events/us-layout.svg index 6f7692f20..353f225f1 100644 --- a/2-ui/3-event-details/7-keyboard-events/us-layout.svg +++ b/2-ui/3-event-details/7-keyboard-events/us-layout.svg @@ -1,166 +1 @@ - - - - us-layout.svg - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Caps Lock - - - - - - - - - - - - - - - - - - - - - - - - - - - Shift - - - - - - - Shift - - - - - - - - - - - - - - - - - - - \ No newline at end of file +Caps LockShiftShift \ No newline at end of file diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index 1c6188b5b..7bc87a0f0 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them. Document forms are members of the special collection `document.forms`. -That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form. +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. ```js no-beautify -document.forms.my - the form with name="my" -document.forms[0] - the first form in the document +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document ``` When we have a form, then any element is available in the named collection `form.elements`. @@ -36,9 +36,9 @@ For instance: ``` -There may be multiple elements with the same name, that's often the case with radio buttons. +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. -In that case `form.elements[name]` is a collection, for instance: +In that case, `form.elements[name]` is a *collection*. For instance: ```html run height=40
      @@ -119,7 +119,7 @@ That's easy to see in an example: ``` -That's usually not a problem, because we rarely change names of form elements. +That's usually not a problem, however, because we rarely change names of form elements. ```` @@ -155,7 +155,7 @@ Let's talk about form controls. ### input and textarea -We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes. +We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons. Like this: @@ -177,18 +177,16 @@ It stores only the HTML that was initially on the page, not the current value. A ``: -1. Find the corresponding `
  • ` is in "edit mode", clicks on other cells are ignored. - The table may have many cells. Use event delegation. diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index fc48c21ff..644d814d9 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -9,4 +9,5 @@ Focus on the mouse. Then use arrow keys to move it: [demo src="solution"] P.S. Don't put event handlers anywhere except the `#mouse` element. + P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index d4348d25b..c253dc11d 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -90,6 +90,8 @@ If we enter something into the input and then try to use `key:Tab` or click away Please note that we can't "prevent losing focus" by calling `event.preventDefault()` in `onblur`, because `onblur` works *after* the element lost the focus. +In practice though, one should think well, before implementing something like this, because we generally *should show errors* to the user, but *should not prevent their progress* in filling our form. They may want to fill other fields first. + ```warn header="JavaScript-initiated focus loss" A focus loss can occur for many reasons. @@ -104,7 +106,7 @@ The best recipe is to be careful when using these events. If we want to track us ``` ## Allow focusing on any element: tabindex -By default many elements do not support focusing. +By default, many elements do not support focusing. The list varies a bit between browsers, but one thing is always correct: `focus/blur` support is guaranteed for elements that a visitor can interact with: ` + ## Selection properties -Similar to a range, a selection has a start, called "anchor", and the end, called "focus". +As said, a selection may in theory contain multiple ranges. We can get these range objects using the method: + +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. + +Also, there exist properties that often provide better convenience. + +Similar to a range, a selection object has a start, called "anchor", and the end, called "focus". The main selection properties are: @@ -289,36 +352,39 @@ The main selection properties are: - `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist. - `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox. -````smart header="Selection end may be in the document before start" -There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc. +```smart header="Selection end/start vs Range" + +There's an important difference between a selection anchor/focus compared with a `Range` start/end. + +As we know, `Range` objects always have their start before the end. + +For selections, that's not always the case. -Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left". +Selecting something with a mouse can be done in both directions: either "left-to-right" or "right-to-left". -If the start (anchor) of the selection goes in the document before the end (focus), this selection is said to have "forward" direction. +In other words, when the mouse button is pressed, and then it moves forward in the document, then its end (focus) will be after its start (anchor). E.g. if the user starts selecting with mouse and goes from "Example" to "italic": ![](selection-direction-forward.svg) -Otherwise, if they go from the end of "italic" to "Example", the selection is directed "backward", its focus will be before the anchor: +...But the same selection could be done backwards: starting from "italic" to "Example" (backward direction), then its end (focus) will be before the start (anchor): ![](selection-direction-backward.svg) - -That's different from `Range` objects that are always directed forward: the range start can't be after its end. -```` +``` ## Selection events There are events on to keep track of selection: -- `elem.onselectstart` -- when a selection starts on `elem`, e.g. the user starts moving mouse with pressed button. - - Preventing the default action makes the selection not start. -- `document.onselectionchange` -- whenever a selection changes. - - Please note: this handler can be set only on `document`. +- `elem.onselectstart` -- when a selection *starts* specifically on element `elem` (or inside it). For instance, when the user presses the mouse button on it and starts to move the pointer. + - Preventing the default action cancels the selection start. So starting a selection from this element becomes impossible, but the element is still selectable. The visitor just needs to start the selection from elsewhere. +- `document.onselectionchange` -- whenever a selection changes or starts. + - Please note: this handler can be set only on `document`, it tracks all selections in it. ### Selection tracking demo -Here's a small demo that shows selection boundaries dynamically as it changes: +Here's a small demo. It tracks the current selection on the `document` and shows its boundaries: ```html run height=80

    Select me: italic and bold

    @@ -326,21 +392,25 @@ Here's a small demo that shows selection boundaries dynamically as it changes: From – To ``` -### Selection getting demo +### Selection copying demo + +There are two approaches to copying the selected content: -To get the whole selection: -- As text: just call `document.getSelection().toString()`. -- As DOM nodes: get the underlying ranges and call their `cloneContents()` method (only first range if we don't support Firefox multiselection). +1. We can use `document.getSelection().toString()` to get it as text. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. -And here's the demo of getting the selection both as text and as DOM nodes: +Here's the demo of copying the selected content both as text and as DOM nodes: ```html run height=100

    Select me: italic and bold

    @@ -368,15 +438,15 @@ As text: ## Selection methods -Selection methods to add/remove ranges: +We can work with the selection by adding/removing ranges: -- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except firefox, only `0` is used. +- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used. - `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range. - `removeRange(range)` -- remove `range` from the selection. - `removeAllRanges()` -- remove all ranges. - `empty()` -- alias to `removeAllRanges`. -Also, there are convenience methods to manipulate the selection range directly, without `Range`: +There are also convenience methods to manipulate the selection range directly, without intermediate `Range` calls: - `collapse(node, offset)` -- replace selected range with a new one that starts and ends at the given `node`, at position `offset`. - `setPosition(node, offset)` -- alias to `collapse`. @@ -388,7 +458,7 @@ Also, there are convenience methods to manipulate the selection range directly, - `deleteFromDocument()` -- remove selected content from the document. - `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partially if the second argument is `true`) -So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object. +For most tasks these methods are just fine, there's no need to access the underlying `Range` object. For example, selecting the whole contents of the paragraph `

    `: @@ -415,10 +485,10 @@ The same thing using ranges: ``` -```smart header="To select, remove the existing selection first" -If the selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. +```smart header="To select something, remove the existing selection first" +If a document selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges. -The exception is some selection methods, that replace the existing selection, like `setBaseAndExtent`. +The exception is some selection methods, that replace the existing selection, such as `setBaseAndExtent`. ``` ## Selection in form controls @@ -494,7 +564,7 @@ Focus on me, the cursor will be at position 10. // zero delay setTimeout to run after browser "focus" action finishes setTimeout(() => { // we can set any selection - // if start=end, the cursor it exactly at that place + // if start=end, the cursor is exactly at that place area.selectionStart = area.selectionEnd = 10; }); }; diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg index 4550c3733..a97d1b47a 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg index dd19679cc..2a8f9aca3 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg index 66397f20f..32843436d 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg @@ -1 +1 @@ -startContainer (<p>.firstChild)startOffset (=2)commonAncestorContainer (<p>)endContainer (<b>.firstChild)endOffset (=3) \ No newline at end of file +startContainer (<p>.firstChild)startOffset (=2)commonAncestorContainer (<p>)endContainer (<b>.firstChild)endOffset (=3) \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg index fd058bebb..859f755ce 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg new file mode 100644 index 000000000..2951607a2 --- /dev/null +++ b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg @@ -0,0 +1 @@ +<p>Hello</p>p.firstChild \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg b/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg index a84bdeb55..85615d38f 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg @@ -1 +1 @@ -focusanchor \ No newline at end of file +focusanchormouse move direction \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg b/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg index fd7716c5a..511b00a26 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg @@ -1 +1 @@ -anchorfocus \ No newline at end of file +anchorfocusmouse move direction \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg b/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg index 879e1cecb..aa7ff1eb7 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg @@ -1 +1 @@ -selection \ No newline at end of file +selection \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md new file mode 100644 index 000000000..2911b76cf --- /dev/null +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md @@ -0,0 +1,50 @@ +The console output is: 1 7 3 5 2 6 4. + +The task is quite simple, we just need to know how microtask and macrotask queues work. + +Let's see what's going on, step by step. + +```js +console.log(1); +// The first line executes immediately, it outputs `1`. +// Macrotask and microtask queues are empty, as of now. + +setTimeout(() => console.log(2)); +// `setTimeout` appends the callback to the macrotask queue. +// - macrotask queue content: +// `console.log(2)` + +Promise.resolve().then(() => console.log(3)); +// The callback is appended to the microtask queue. +// - microtask queue content: +// `console.log(3)` + +Promise.resolve().then(() => setTimeout(() => console.log(4))); +// The callback with `setTimeout(...4)` is appended to microtasks +// - microtask queue content: +// `console.log(3); setTimeout(...4)` + +Promise.resolve().then(() => console.log(5)); +// The callback is appended to the microtask queue +// - microtask queue content: +// `console.log(3); setTimeout(...4); console.log(5)` + +setTimeout(() => console.log(6)); +// `setTimeout` appends the callback to macrotasks +// - macrotask queue content: +// `console.log(2); console.log(6)` + +console.log(7); +// Outputs 7 immediately. +``` + +To summarize, + +1. Numbers `1` and `7` show up immediately, because simple `console.log` calls don't use any queues. +2. Then, after the main code flow is finished, the microtask queue runs. + - It has commands: `console.log(3); setTimeout(...4); console.log(5)`. + - Numbers `3` and `5` show up, while `setTimeout(() => console.log(4))` adds the `console.log(4)` call to the end of the macrotask queue. + - The macrotask queue is now: `console.log(2); console.log(6); console.log(4)`. +3. After the microtask queue becomes empty, the macrotask queue executes. It outputs `2`, `6`, `4`. + +Finally, we have the output: `1 7 3 5 2 6 4`. \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md new file mode 100644 index 000000000..ad406b3be --- /dev/null +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md @@ -0,0 +1,21 @@ +importance: 5 + +--- + +# What will be the output of this code? + +```js +console.log(1); + +setTimeout(() => console.log(2)); + +Promise.resolve().then(() => console.log(3)); + +Promise.resolve().then(() => setTimeout(() => console.log(4))); + +Promise.resolve().then(() => console.log(5)); + +setTimeout(() => console.log(6)); + +console.log(7); +``` diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index 3ea0c2c57..f33188491 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -17,7 +17,7 @@ The general algorithm of the engine: - execute them, starting with the oldest task. 2. Sleep until a task appears, then go to 1. -That's a formalization for what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. +That's a formalization of what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. Examples of tasks: @@ -30,19 +30,19 @@ Tasks are set -- the engine handles them -- then waits for more tasks (while sle It may happen that a task comes while the engine is busy, then it's enqueued. -The tasks form a queue, so-called "macrotask queue" (v8 term): +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): ![](eventLoop.svg) -For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated on the picture above. +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. -Tasks from the queue are processed on "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. +Tasks from the queue are processed on a "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. So far, quite simple, right? Two more details: 1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. -2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after a time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after some time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. That was the theory. Now let's see how we can apply that knowledge. @@ -54,7 +54,7 @@ For example, syntax-highlighting (used to colorize code examples on this page) i While the engine is busy with syntax highlighting, it can't do other DOM-related stuff, process user events, etc. It may even cause the browser to "hiccup" or even "hang" for a bit, which is unacceptable. -We can avoid problems by splitting the big task into pieces. Highlight first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. +We can avoid problems by splitting the big task into pieces. Highlight the first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. To demonstrate this approach, for the sake of simplicity, instead of text-highlighting, let's take a function that counts from `1` to `1000000000`. diff --git a/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg b/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg index 201d9a14a..593cbab9b 100644 --- a/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg +++ b/2-ui/99-ui-misc/03-event-loop/eventLoop-full.svg @@ -1 +1 @@ -...mousemoveevent looprendermicrotasksrendermicrotasksscriptsetTimeout \ No newline at end of file +...mousemoveevent looprendermicrotasksrendermicrotasksscriptsetTimeout \ No newline at end of file diff --git a/2-ui/99-ui-misc/03-event-loop/eventLoop.svg b/2-ui/99-ui-misc/03-event-loop/eventLoop.svg index f7de0b6ad..6dc459ef8 100644 --- a/2-ui/99-ui-misc/03-event-loop/eventLoop.svg +++ b/2-ui/99-ui-misc/03-event-loop/eventLoop.svg @@ -1 +1 @@ -...mousemovescriptevent loopmacrotask queuesetTimeout \ No newline at end of file +...mousemovescriptevent loopmacrotask queuesetTimeout \ No newline at end of file diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index 7540b0304..f2c87d1e0 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -38,26 +38,6 @@ button.onclick = () => { This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. -What if the popup opens from `onclick`, but after `setTimeout`? That's a bit tricky. - -Try this code: - -```js run -// open after 3 seconds -setTimeout(() => window.open('http://google.com'), 3000); -``` - -The popup opens in Chrome, but gets blocked in Firefox. - -...If we decrease the delay, the popup works in Firefox too: - -```js run -// open after 1 seconds -setTimeout(() => window.open('http://google.com'), 1000); -``` - -The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: @@ -87,7 +67,7 @@ Settings for `params`: There is also a number of less supported browser-specific features, which are usually not used. Check window.open in MDN for examples. -## Example: a minimalistic window +## Example: a minimalistic window Let's open a window with minimal set of features, just to see which of them browser allows to disable: @@ -120,7 +100,7 @@ Rules for omitted settings: ## Accessing popup from window -The `open` call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more. +The `open` call returns a reference to the new window. It can be used to manipulate its properties, change location and even more. In this example, we generate popup content from JavaScript: @@ -192,7 +172,7 @@ newWindow.onload = function() { ``` -## Scrolling and resizing +## Moving and resizing There are methods to move/resize a window: @@ -239,7 +219,7 @@ There's also `window.onscroll` event. Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere. -Although, in practice they are severely limited, because in the past evil pages abused them. +Although, in practice they are severely limited, because in the past evil pages abused them. For instance, look at this code: @@ -257,10 +237,10 @@ Still, there are some use cases when such calls do work and can be useful. For instance: -- When we open a popup, it's might be a good idea to run a `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. +- When we open a popup, it might be a good idea to run `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. - If we want to track when a visitor actually uses our web-app, we can track `window.onfocus/onblur`. That allows us to suspend/resume in-page activities, animations etc. But please note that the `blur` event means that the visitor switched out from the window, but they still may observe it. The window is in the background, but still may be visible. -## Summary +## Summary Popup windows are used rarely, as there are alternatives: loading and displaying information in-page, or in iframe. diff --git a/3-frames-and-windows/03-cross-window-communication/article.md b/3-frames-and-windows/03-cross-window-communication/article.md index 091a0cecb..4d4e320e4 100644 --- a/3-frames-and-windows/03-cross-window-communication/article.md +++ b/3-frames-and-windows/03-cross-window-communication/article.md @@ -116,6 +116,13 @@ document.domain = 'site.com'; That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain. +```warn header="Deprecated, but still working" +The `document.domain` property is in the process of being removed from the [specification](https://html.spec.whatwg.org/multipage/origin.html#relaxing-the-same-origin-restriction). The cross-window messaging (explained soon below) is the suggested replacement. + +That said, as of now all browsers support it. And the support will be kept for the future, not to break old code that relies on `document.domain`. +``` + + ## Iframe: wrong document pitfall When an iframe comes from the same origin, and we may access its `document`, there's a pitfall. It's not related to cross-origin things, but important to know. @@ -268,7 +275,7 @@ Arguments: `targetOrigin` : Specifies the origin for the target window, so that only a window from the given origin will get the message. -The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read it's `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. +The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read its `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. Specifying `targetOrigin` ensures that the window only receives the data if it's still at the right site. Important when the data is sensitive. diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 1daa87dd0..34d0a91ae 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -154,7 +154,7 @@ Depending on your browser, the `iframe` above is either empty or alerting you th ## Showing with disabled functionality -The `X-Frame-Options` header has a side-effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. +The `X-Frame-Options` header has a side effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. So there are other solutions... For instance, we can "cover" the page with a `

    ` with styles `height: 100%; width: 100%;`, so that it will intercept all clicks. That `
    ` is to be removed if `window == top` or if we figure out that we don't need the protection. diff --git a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js index 2f51384ef..00c37bb94 100644 --- a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js +++ b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js @@ -2,9 +2,9 @@ function concat(arrays) { // sum of individual array lengths let totalLength = arrays.reduce((acc, value) => acc + value.length, 0); - if (!arrays.length) return null; - let result = new Uint8Array(totalLength); + + if (!arrays.length) return result; // for each array - copy it over result // next array is copied right after the previous one diff --git a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg index 89ae96407..b697d6304 100644 --- a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg +++ b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg @@ -1 +1 @@ -8-bit integer256 \ No newline at end of file +8-bit integer256 \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg index c7b74cd63..8e3074fdf 100644 --- a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg +++ b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg @@ -1 +1 @@ -8-bit integer257 \ No newline at end of file +8-bit integer257 \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg index 12daeeaec..b9de47de1 100644 --- a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg +++ b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg @@ -1 +1 @@ -02134567012301new ArrayBuffer(16)ArrayBufferViewUint16Array Int16ArrayUint8Array Int8Array Uint8ClampedArrayUint32Array Int32Array Float32ArrayFloat64ArrayDataViewget/setUint8(offset) get/setFloat32(offset)...BufferSource1023456789101112131415 \ No newline at end of file +02134567012301new ArrayBuffer(16)ArrayBufferViewUint16Array Int16ArrayUint8Array Int8Array Uint8ClampedArrayUint32Array Int32Array Float32ArrayFloat64ArrayDataViewget/setUint8(offset) get/setFloat32(offset)...BufferSource1023456789101112131415 \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg index 02160e31e..b022796ad 100644 --- a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg +++ b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg @@ -1 +1 @@ -100213234567891011121314154567012301new ArrayBuffer(16)Uint16ArrayUint8ArrayUint32ArrayFloat64Array \ No newline at end of file +100213234567891011121314154567012301new ArrayBuffer(16)Uint16ArrayUint8ArrayUint32ArrayFloat64Array \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/article.md b/4-binary/01-arraybuffer-binary-arrays/article.md index 4beb6f5cd..2827e277e 100644 --- a/4-binary/01-arraybuffer-binary-arrays/article.md +++ b/4-binary/01-arraybuffer-binary-arrays/article.md @@ -30,11 +30,11 @@ Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in com **To manipulate an `ArrayBuffer`, we need to use a "view" object.** -A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. +A view object does not store anything on its own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. For instance: -- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". +- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". - **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer". - **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer". - **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from 5.0x10-324 to 1.8x10308. @@ -71,13 +71,13 @@ for(let num of view) { ## TypedArray -The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities. +The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properties. Please note, there's no constructor called `TypedArray`, it's just a common "umbrella" term to represent one of views over `ArrayBuffer`: `Int8Array`, `Uint8Array` and so on, the full list will soon follow. When you see something like `new TypedArray`, it means any of `new Int8Array`, `new Uint8Array`, etc. -Typed array behave like regular arrays: have indexes and iterable. +Typed arrays behave like regular arrays: have indexes and are iterable. A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types. @@ -126,9 +126,9 @@ new TypedArray(); We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided). -To access the `ArrayBuffer`, there are properties: -- `arr.buffer` -- references the `ArrayBuffer`. -- `arr.byteLength` -- the length of the `ArrayBuffer`. +To access the underlying `ArrayBuffer`, there are following properties in `TypedArray`: +- `buffer` -- references the `ArrayBuffer`. +- `byteLength` -- the length of the `ArrayBuffer`. So, we can always move from one view to another: ```js @@ -259,7 +259,7 @@ To do almost any operation on `ArrayBuffer`, we need a view. - `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits. - Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`. -In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed. +In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common denominator". We can access it as `.buffer` and make another view if needed. There are also two additional terms, that are used in descriptions of methods that operate on binary data: - `ArrayBufferView` is an umbrella term for all these kinds of views. diff --git a/4-binary/02-text-decoder/article.md b/4-binary/02-text-decoder/article.md index fe9b8c042..a0c80145c 100644 --- a/4-binary/02-text-decoder/article.md +++ b/4-binary/02-text-decoder/article.md @@ -2,7 +2,7 @@ What if the binary data is actually a string? For instance, we received a file with textual data. -The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an actual JavaScript string, given the buffer and the encoding. +The built-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows one to read the value into an actual JavaScript string, given the buffer and the encoding. We first need to create it: ```js @@ -12,7 +12,7 @@ let decoder = new TextDecoder([label], [options]); - **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported. - **`options`** -- optional object: - **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`. - - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), rarely needed. + - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order Unicode mark), rarely needed. ...And then decode: diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index bb475bc7f..fc0150577 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -55,7 +55,7 @@ This behavior is similar to JavaScript strings: we can't change a character in a ## Blob as URL -A Blob can be easily used as an URL for ``, `` or other tags, to show its contents. +A Blob can be easily used as a URL for ``, `` or other tags, to show its contents. Thanks to `type`, we can also download/upload `Blob` objects, and the `type` naturally becomes `Content-Type` in network requests. @@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob); We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically. -Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML: +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: ```js run let link = document.createElement('a'); @@ -99,9 +99,9 @@ blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. -A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects an url. +A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects a URL. -There's a side-effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. +There's a side effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. The mapping is automatically cleared on document unload, so `Blob` objects are freed then. But if an app is long-living, then that doesn't happen soon. @@ -151,7 +151,7 @@ reader.onload = function() { }; ``` -Both ways of making an URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. +Both ways of making a URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. ```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url" + We need to revoke them if care about memory. @@ -186,7 +186,7 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas -// toBlob is async opereation, callback is called when done +// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement('a'); @@ -211,21 +211,44 @@ For screenshotting a page, we can use a library such as /* process the ArrayBuffer */); ``` +## From Blob to stream + +When we read and write to a blob of more than `2 GB`, the use of `arrayBuffer` becomes more memory intensive for us. At this point, we can directly convert the blob to a stream. + +A stream is a special object that allows to read from it (or write into it) portion by portion. It's outside of our scope here, but here's an example, and you can read more at . Streams are convenient for data that is suitable for processing piece-by-piece. + +The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the `Blob`. + +Then we can read from it, like this: + +```js +// get readableStream from blob +const readableStream = blob.stream(); +const stream = readableStream.getReader(); + +while (true) { + // for each iteration: value is the next blob fragment + let { done, value } = await stream.read(); + if (done) { + // no more data in the stream + console.log('all blob processed.'); + break; + } + + // do something with the data portion we've just read from the blob + console.log(value); +} +``` ## Summary @@ -235,7 +258,9 @@ That makes Blobs convenient for upload/download operations, that are so common i Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types. -We can easily convert betweeen `Blob` and low-level binary data types: +We can easily convert between `Blob` and low-level binary data types: + +- We can make a `Blob` from a typed array using `new Blob(...)` constructor. +- We can get back `ArrayBuffer` from a Blob using `blob.arrayBuffer()`, and then create a view over it for low-level binary processing. -- We can make a Blob from a typed array using `new Blob(...)` constructor. -- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing. +Conversion streams are very useful when we need to handle large blob. You can easily create a `ReadableStream` from a blob. The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the blob. diff --git a/4-binary/03-blob/blob.svg b/4-binary/03-blob/blob.svg index b1cf6fcc8..8f4245451 100644 --- a/4-binary/03-blob/blob.svg +++ b/4-binary/03-blob/blob.svg @@ -1 +1 @@ -image/pngblob1blob2strbuffer...typeBlobblobParts+= \ No newline at end of file +image/pngblob1blob2strbuffer...typeBlobblobParts+= \ No newline at end of file diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 8db2243b3..4669fc451 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -27,7 +27,7 @@ let promise = fetch(url, [options]) - **`url`** -- the URL to access. - **`options`** -- optional parameters: method, headers etc. -Without `options`, that is a simple GET request, downloading the contents of the `url`. +Without `options`, this is a simple GET request, downloading the contents of the `url`. The browser starts the request right away and returns a promise that the calling code should use to get the result. @@ -195,7 +195,7 @@ To make a `POST` request, or a request with another method, we need to use `fetc - **`method`** -- HTTP-method, e.g. `POST`, - **`body`** -- the request body, one of: - a string (e.g. JSON-encoded), - - `FormData` object, to submit the data as `form/multipart`, + - `FormData` object, to submit the data as `multipart/form-data`, - `Blob`/`BufferSource` to send binary data, - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. @@ -298,13 +298,13 @@ fetch(url, options) Response properties: - `response.status` -- HTTP code of the response, -- `response.ok` -- `true` is the status is 200-299. +- `response.ok` -- `true` if the status is 200-299. - `response.headers` -- Map-like object with HTTP headers. Methods to get response body: - **`response.text()`** -- return the response as text, - **`response.json()`** -- parse the response as JSON object, -- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter), +- **`response.formData()`** -- return the response as `FormData` object (`multipart/form-data` encoding, see the next chapter), - **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), - **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level binary data), diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index d281d0756..a73d554b1 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -47,7 +47,7 @@ As you can see, that's almost one-liner: ``` -In this example, the server code is not presented, as it's beyound our scope. The server accepts the POST request and replies "User saved". +In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved". ## FormData Methods @@ -75,7 +75,7 @@ formData.append('key2', 'value2'); // List key/value pairs for(let [name, value] of formData) { - alert(`${name} = ${value}`); // key1=value1, then key2=value2 + alert(`${name} = ${value}`); // key1 = value1, then key2 = value2 } ``` @@ -168,7 +168,7 @@ The server reads form data and the file, as if it were a regular form submission [FormData](https://xhr.spec.whatwg.org/#interface-formdata) objects are used to capture HTML form and submit it using `fetch` or another network method. -We can either create `new FormData(form)` from an HTML form, or create a object without a form at all, and then append fields with methods: +We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods: - `formData.append(name, value)` - `formData.append(name, blob, fileName)` diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index 2d003157d..76b05d514 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -5,11 +5,11 @@ The `fetch` method allows to track *download* progress. Please note: there's currently no way for `fetch` to track *upload* progress. For that purpose, please use [XMLHttpRequest](info:xmlhttprequest), we'll cover it later. -To track download progress, we can use `response.body` property. It's `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. +To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment. -Here's the sketch of code that reads the reponse from `response.body`: +Here's the sketch of code that reads the response from `response.body`: ```js // instead of response.json() and other methods @@ -110,3 +110,5 @@ Let's explain that step-by-step: At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress. + +Also, if the size is unknown, we should check `receivedLength` in the loop and break it once it reaches a certain limit. So that the `chunks` won't overflow the memory. diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index 6548f81d2..eadc5aac2 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -18,15 +18,15 @@ let controller = new AbortController(); A controller is an extremely simple object. - It has a single method `abort()`, -- And a single property `signal` that allows to set event liseners on it. +- And a single property `signal` that allows to set event listeners on it. When `abort()` is called: - `controller.signal` emits the `"abort"` event. - `controller.signal.aborted` property becomes `true`. -Generally, we have two parties in the process: -1. The one that performs an cancelable operation, it sets a listener on `controller.signal`. -2. The one one that cancels: it calls `controller.abort()` when needed. +Generally, we have two parties in the process: +1. The one that performs a cancelable operation, it sets a listener on `controller.signal`. +2. The one that cancels: it calls `controller.abort()` when needed. Here's the full example (without `fetch` yet): @@ -34,8 +34,8 @@ Here's the full example (without `fetch` yet): let controller = new AbortController(); let signal = controller.signal; -// The party that performs a cancelable operation -// gets "signal" object +// The party that performs a cancelable operation +// gets the "signal" object // and sets the listener to trigger when controller.abort() is called signal.addEventListener('abort', () => alert("abort!")); @@ -46,15 +46,15 @@ controller.abort(); // abort! alert(signal.aborted); // true ``` -As we can see, `AbortController` is just a means to pass `abort` events when `abort()` is called on it. +As we can see, `AbortController` is just a mean to pass `abort` events when `abort()` is called on it. -We could implement same kind of event listening in our code on our own, without `AbortController` object at all. +We could implement the same kind of event listening in our code on our own, without the `AbortController` object. -But what's valuable is that `fetch` knows how to work with `AbortController` object, it's integrated with it. +But what's valuable is that `fetch` knows how to work with the `AbortController` object. It's integrated in it. ## Using with fetch -To become able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: +To be able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: ```js let controller = new AbortController(); @@ -65,7 +65,7 @@ fetch(url, { The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. -Now, to to abort, call `controller.abort()`: +Now, to abort, call `controller.abort()`: ```js controller.abort(); @@ -97,7 +97,7 @@ try { ## AbortController is scalable -`AbortController` is scalable, it allows to cancel multiple fetches at once. +`AbortController` is scalable. It allows to cancel multiple fetches at once. Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: @@ -113,7 +113,7 @@ let fetchJobs = urls.map(url => fetch(url, { let results = await Promise.all(fetchJobs); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches ``` @@ -137,12 +137,12 @@ let fetchJobs = urls.map(url => fetch(url, { // fetches // Wait for fetches and our task in parallel let results = await Promise.all([...fetchJobs, ourJob]); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches and ourJob ``` ## Summary -- `AbortController` is a simple object that generates `abort` event on it's `signal` property when `abort()` method is called (and also sets `signal.aborted` to `true`). -- `fetch` integrates with it: we pass `signal` property as the option, and then `fetch` listens to it, so it becomes possible to abort the `fetch`. +- `AbortController` is a simple object that generates an `abort` event on its `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`). +- `fetch` integrates with it: we pass the `signal` property as the option, and then `fetch` listens to it, so it's possible to abort the `fetch`. - We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`. diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 0c1429697..4420f43c7 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -28,7 +28,7 @@ Seriously. Let's make a very brief historical digression. **For many years a script from one site could not access the content of another site.** -That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access user's mailbox at website `gmail.com`. People felt safe. +That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access the user's mailbox at website `gmail.com`. People felt safe. JavaScript also did not have any special methods to perform network requests at that time. It was a toy language to decorate a web page. @@ -44,7 +44,7 @@ One way to communicate with another server was to submit a `` there. Peopl */!* - + *!* */!* @@ -97,43 +97,43 @@ After a while, networking methods appeared in browser JavaScript. At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but with any new capabilities requiring an explicit allowance by the server, expressed in special headers. -## Simple requests +## Safe requests There are two types of cross-origin requests: -1. Simple requests. +1. Safe requests. 2. All the others. -Simple Requests are, well, simpler to make, so let's start with them. +Safe Requests are simpler to make, so let's start with them. -A [simple request](http://www.w3.org/TR/cors/#terminology) is a request that satisfies two conditions: +A request is safe if it satisfies two conditions: -1. [Simple method](http://www.w3.org/TR/cors/#simple-method): GET, POST or HEAD -2. [Simple headers](http://www.w3.org/TR/cors/#simple-header) -- the only allowed custom headers are: +1. [Safe method](https://fetch.spec.whatwg.org/#cors-safelisted-method): GET, POST or HEAD +2. [Safe headers](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) -- the only allowed custom headers are: - `Accept`, - `Accept-Language`, - `Content-Language`, - `Content-Type` with the value `application/x-www-form-urlencoded`, `multipart/form-data` or `text/plain`. -Any other request is considered "non-simple". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations. +Any other request is considered "unsafe". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations. -**The essential difference is that a "simple request" can be made with a `` or a ` ``` -Now let's cover animation properties one by one. +Now, let's cover animation properties one by one. ## transition-property -In `transition-property` we write a list of property to animate, for instance: `left`, `margin-left`, `height`, `color`. +In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties". -Not all properties can be animated, but [many of them](http://www.w3.org/TR/css3-transitions/#animatable-properties-). The value `all` means "animate all properties". +Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties). ## transition-duration -In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](http://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`. +In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](https://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`. ## transition-delay -In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay: 1s`, then animation starts after 1 second after the change. +In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds. -Negative values are also possible. Then the animation starts from the middle. For instance, if `transition-duration` is `2s`, and the delay is `-1s`, then the animation takes 1 second and starts from the half. +Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. -Here's the animation shifts numbers from `0` to `9` using CSS `translate` property: +Here the animation shifts numbers from `0` to `9` using CSS `translate` property: [codetabs src="digits"] @@ -108,13 +108,13 @@ In the example above JavaScript adds the class `.animate` to the element -- and stripe.classList.add('animate'); ``` -We can also start it "from the middle", from the exact number, e.g. corresponding to the current second, using the negative `transition-delay`. +We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`. Here if you click the digit -- it starts the animation from the current second: [codetabs src="digits-negative-delay"] -JavaScript does it by an extra line: +JavaScript does it with an extra line: ```js stripe.onclick = function() { @@ -129,25 +129,25 @@ stripe.onclick = function() { ## transition-timing-function -Timing function describes how the animation process is distributed along the time. Will it start slowly and then go fast or vise versa. +The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa. -That's the most complicated property from the first sight. But it becomes very simple if we devote a bit time to it. +It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it. -That property accepts two kinds of values: a Bezier curve or steps. Let's start from the curve, as it's used more often. +That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often. ### Bezier curve -The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfies the conditions: +The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions: 1. First control point: `(0,0)`. 2. Last control point: `(1,1)`. -3. For intermediate points values of `x` must be in the interval `0..1`, `y` can be anything. +3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything. The syntax for a Bezier curve in CSS: `cubic-bezier(x2, y2, x3, y3)`. Here we need to specify only 2nd and 3rd control points, because the 1st one is fixed to `(0,0)` and the 4th one is `(1,1)`. -The timing function describes how fast the animation process goes in time. +The timing function describes how fast the animation process goes. -- The `x` axis is the time: `0` -- the starting moment, `1` -- the last moment of `transition-duration`. +- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`. - The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value. The simplest variant is when the animation goes uniformly, with the same linear speed. That can be specified by the curve `cubic-bezier(0, 0, 1, 1)`. @@ -168,7 +168,7 @@ The CSS `transition` is based on that curve: .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` @@ -191,13 +191,13 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` There are several built-in curves: `linear`, `ease`, `ease-in`, `ease-out` and `ease-in-out`. -The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, we saw it already. +The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above. Other names are shorthands for the following `cubic-bezier`: @@ -210,27 +210,27 @@ Other names are shorthands for the following `cubic-bezier`: So we could use `ease-out` for our slowing down train: - ```css .train { left: 0; transition: left 5s ease-out; - /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ + /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */ } ``` But it looks a bit differently. -**A Bezier curve can make the animation "jump out" of its range.** +**A Bezier curve can make the animation exceed its range.** -The control points on the curve can have any `y` coordinates: even negative or huge. Then the Bezier curve would also jump very low or high, making the animation go beyond its normal range. +The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range. In the example below the animation code is: + ```css .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); - /* JavaScript sets left to 400px */ + /* click on a train sets left to 450px */ } ``` @@ -244,21 +244,29 @@ But if you click the train, you'll see that: [codetabs src="train-over"] -Why it happens -- pretty obvious if we look at the graph of the given Bezier curve: +Why it happens is pretty obvious if we look at the graph of the given Bezier curve: ![](bezier-train-over.svg) -We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made put it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. +We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. -As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property lower than the starting `left` and `y>1` -- over the final `left`. +As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`. That's a "soft" variant for sure. If we put `y` values like `-99` and `99` then the train would jump out of the range much more. -But how to make the Bezier curve for a specific task? There are many tools. For instance, we can do it on the site . +But how do we make a Bezier curve for a specific task? There are many tools. + +- For instance, we can do it on the site . +- Browser developer tools also have special support for Bezier curves in CSS: + 1. Open the developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). + 2. Select the `Elements` tab, then pay attention to the `Styles` sub-panel at the right side. + 3. CSS properties with a word `cubic-bezier` will have an icon before this word. + 4. Click this icon to edit the curve. + ### Steps -Timing function `steps(number of steps[, start/end])` allows to split animation into steps. +The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps. Let's see that in an example with digits. @@ -266,7 +274,19 @@ Here's a list of digits, without any animations, just as a source: [codetabs src="step-list"] -We'll make the digits appear in a discrete way by making the part of the list outside of the red "window" invisible and shifting the list to the left with each step. +In the HTML, a stripe of digits is enclosed into a fixed-length `
    `: + +```html +
    +
    0123456789
    +
    +``` + +The `#digit` div has a fixed width and a border, so it looks like a red window. + +We'll make a timer: the digits will appear one by one, in a discrete way. + +To achieve that, we'll hide the `#stripe` outside of `#digit` using `overflow: hidden`, and then shift the `#stripe` to the left step-by-step. There will be 9 steps, a step-move for each digit: @@ -277,58 +297,60 @@ There will be 9 steps, a step-move for each digit: } ``` -In action: - -[codetabs src="step"] - The first argument of `steps(9, start)` is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so `transition: 9s` gives us 9 seconds for the whole animation – 1 second per digit. The second argument is one of two words: `start` or `end`. -The `start` means that in the beginning of animation we need to do make the first step immediately. +The `start` means that in the beginning of animation we need to make the first step immediately. + +In action: + +[codetabs src="step"] -We can observe that during the animation: when we click on the digit it changes to `1` (the first step) immediately, and then changes in the beginning of the next second. +A click on the digit changes it to `1` (the first step) immediately, and then changes in the beginning of the next second. The process is progressing like this: - `0s` -- `-10%` (first change in the beginning of the 1st second, immediately) - `1s` -- `-20%` - ... -- `8s` -- `-80%` +- `8s` -- `-90%` - (the last second shows the final value). +Here, the first change was immediate because of `start` in the `steps`. + The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second. -So the process would go like this: +So the process for `steps(9, end)` would go like this: -- `0s` -- `0` +- `0s` -- `0` (during the first second nothing changes) - `1s` -- `-10%` (first change at the end of the 1st second) - `2s` -- `-20%` - ... - `9s` -- `-90%` -Here's `steps(9, end)` in action (note the pause between the first digit change): +Here's `steps(9, end)` in action (note the pause before the first digit change): [codetabs src="step-end"] -There are also shorthand values: +There are also some pre-defined shorthands for `steps(...)`: - `step-start` -- is the same as `steps(1, start)`. That is, the animation starts immediately and takes 1 step. So it starts and finishes immediately, as if there were no animation. - `step-end` -- the same as `steps(1, end)`: make the animation in a single step at the end of `transition-duration`. -These values are rarely used, because that's not really animation, but rather a single-step change. +These values are rarely used, as they represent not a real animation, but rather a single-step change. We mention them here for completeness. -## Event transitionend +## Event: "transitionend" -When the CSS animation finishes the `transitionend` event triggers. +When the CSS animation finishes, the `transitionend` event triggers. It is widely used to do an action after the animation is done. Also we can join animations. -For instance, the ship in the example below starts to swim there and back on click, each time farther and farther to the right: +For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right: [iframe src="boat" height=300 edit link] -The animation is initiated by the function `go` that re-runs each time when the transition finishes and flips the direction: +The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction: ```js boat.onclick = function() { @@ -337,11 +359,11 @@ boat.onclick = function() { function go() { if (times % 2) { - // swim to the right + // sail to the right boat.classList.remove('back'); boat.style.marginLeft = 100 * times + 200 + 'px'; } else { - // swim to the left + // sail to the left boat.classList.add('back'); boat.style.marginLeft = 100 * times - 200 + 'px'; } @@ -357,7 +379,7 @@ boat.onclick = function() { }; ``` -The event object for `transitionend` has few specific properties: +The event object for `transitionend` has a few specific properties: `event.propertyName` : The property that has finished animating. Can be good if we animate multiple properties simultaneously. @@ -369,7 +391,7 @@ The event object for `transitionend` has few specific properties: We can join multiple simple animations together using the `@keyframes` CSS rule. -It specifies the "name" of the animation and rules: what, when and where to animate. Then using the `animation` property we attach the animation to the element and specify additional parameters for it. +It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it. Here's an example with explanations: @@ -405,11 +427,92 @@ Here's an example with explanations: There are many articles about `@keyframes` and a [detailed specification](https://drafts.csswg.org/css-animations/). -Probably you won't need `@keyframes` often, unless everything is in the constant move on your sites. +You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. + +## Performance + +Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. + +However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. + +In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: + +1. **Layout**: re-compute the geometry and position of each element, then +2. **Paint**: re-compute how everything should look like at their places, including background, colors, +3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. + +During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . + +The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. + +Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. + +The `transform` property is a great choice, because: +- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). +- CSS transforms never affect neighbour elements. + +...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. + +In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. + +Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. + +Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. + +The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. + +Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. + +For example, here clicking on the `#boat` element adds the class with `transform: translateX(300px)` and `opacity: 0`, thus making it move `300px` to the right and disappear: + +```html run height=260 autorun no-beautify + + + + +``` + +Here's a more complex example, with `@keyframes`: + +```html run height=80 autorun no-beautify +

    click me to start / stop

    + +``` ## Summary -CSS animations allow to smoothly (or not) animate changes of one or multiple CSS properties. +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that. @@ -419,9 +522,11 @@ Limitations of CSS animations compared to JavaScript animations: + Simple things done simply. + Fast and lightweight for CPU. - JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. -- Not just property changes. We can create new elements in JavaScript for purposes of animation. +- Not just property changes. We can create new elements in JavaScript as part of the animation. ``` -The majority of animations can be implemented using CSS as described in this chapter. And `transitionend` event allows to run JavaScript after the animation, so it integrates fine with the code. +In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. + +The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. But in the next chapter we'll do some JavaScript animations to cover more complex cases. diff --git a/7-animation/2-css-animations/bezier-linear.svg b/7-animation/2-css-animations/bezier-linear.svg index 34949d61e..0c2e970f2 100644 --- a/7-animation/2-css-animations/bezier-linear.svg +++ b/7-animation/2-css-animations/bezier-linear.svg @@ -1 +1 @@ -12 \ No newline at end of file +12 \ No newline at end of file diff --git a/7-animation/2-css-animations/bezier-train-over.svg b/7-animation/2-css-animations/bezier-train-over.svg index ff5501c43..d12d09225 100644 --- a/7-animation/2-css-animations/bezier-train-over.svg +++ b/7-animation/2-css-animations/bezier-train-over.svg @@ -1 +1 @@ -(1,1)(0,0)(0,1)(1,0)1243 \ No newline at end of file +(1,1)(0,0)(0,1)(1,0)1243 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease-in-out.svg b/7-animation/2-css-animations/ease-in-out.svg index 29ef69a11..d5c8809d8 100644 --- a/7-animation/2-css-animations/ease-in-out.svg +++ b/7-animation/2-css-animations/ease-in-out.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease-in.svg b/7-animation/2-css-animations/ease-in.svg index 0e8b797ab..38c98ecbc 100644 --- a/7-animation/2-css-animations/ease-in.svg +++ b/7-animation/2-css-animations/ease-in.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease-out.svg b/7-animation/2-css-animations/ease-out.svg index c28372870..9d22eeafd 100644 --- a/7-animation/2-css-animations/ease-out.svg +++ b/7-animation/2-css-animations/ease-out.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease.svg b/7-animation/2-css-animations/ease.svg index a3a1feecd..8f9d41fe8 100644 --- a/7-animation/2-css-animations/ease.svg +++ b/7-animation/2-css-animations/ease.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/train-curve.svg b/7-animation/2-css-animations/train-curve.svg index f15235990..298dacd4c 100644 --- a/7-animation/2-css-animations/train-curve.svg +++ b/7-animation/2-css-animations/train-curve.svg @@ -1 +1 @@ -1243 \ No newline at end of file +1243 \ No newline at end of file diff --git a/7-animation/3-js-animation/1-animate-ball/solution.md b/7-animation/3-js-animation/1-animate-ball/solution.md index 5d3f08eef..0dc67b8bd 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.md +++ b/7-animation/3-js-animation/1-animate-ball/solution.md @@ -2,7 +2,7 @@ To bounce we can use CSS property `top` and `position:absolute` for the ball ins The bottom coordinate of the field is `field.clientHeight`. The CSS `top` property refers to the upper edge of the ball. So it should go from `0` till `field.clientHeight - ball.clientHeight`, that's the final lowest position of the upper edge of the ball. -To to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. +To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. Here's the final code for the animation: diff --git a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html index 7e031e8d1..146033cf7 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html +++ b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html index b246f422f..f587ff607 100644 --- a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html +++ b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/article.md b/7-animation/3-js-animation/article.md index 004954340..b85e91e21 100644 --- a/7-animation/3-js-animation/article.md +++ b/7-animation/3-js-animation/article.md @@ -77,9 +77,9 @@ setInterval(animate3, 20); These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother. -There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. +There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. -But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. +But how do we know about that in JavaScript? There's a specification [Animation timing](https://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. The syntax: ```js @@ -96,7 +96,7 @@ The returned value `requestId` can be used to cancel the call: cancelAnimationFrame(requestId); ``` -The `callback` gets one argument -- the time passed from the beginning of the page load in microseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). +The `callback` gets one argument -- the time passed from the beginning of the page load in milliseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). Usually `callback` runs very soon, unless the CPU is overloaded or the laptop battery is almost discharged, or there's another reason. @@ -159,7 +159,7 @@ Function `animate` accepts 3 parameters that essentially describes the animation } ``` - It's graph: + Its graph: ![](linear.svg) That's just like `transition-timing-function: linear`. There are more interesting variants shown below. @@ -227,7 +227,7 @@ See in action (click to activate): [iframe height=40 src="quad" link] -...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster. +...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster. Here's the graph for `progress` in the power `5`: @@ -283,7 +283,7 @@ The `bounce` function does the same, but in the reverse order: "bouncing" starts ```js function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } @@ -397,7 +397,7 @@ The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and ` ![](circ-ease.svg) -- Red is the regular variantof `circ` (`easeIn`). +- Red is the regular variant of `circ` (`easeIn`). - Green -- `easeOut`. - Blue -- `easeInOut`. @@ -405,7 +405,7 @@ As we can see, the graph of the first half of the animation is the scaled down ` ## More interesting "draw" -Instead of moving the element we can do something else. All we need is to write the write the proper `draw`. +Instead of moving the element we can do something else. All we need is to write the proper `draw`. Here's the animated "bouncing" text typing: @@ -452,4 +452,4 @@ Surely we could improve it, add more bells and whistles, but JavaScript animatio JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here. -The same is about `draw`: we can animate anything, not just CSS properties. +The same is true about `draw`: we can animate anything, not just CSS properties. diff --git a/7-animation/3-js-animation/back.svg b/7-animation/3-js-animation/back.svg index 836a72cc5..fcef09ad7 100644 --- a/7-animation/3-js-animation/back.svg +++ b/7-animation/3-js-animation/back.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/bezier-linear.svg b/7-animation/3-js-animation/bezier-linear.svg index 34949d61e..0c2e970f2 100644 --- a/7-animation/3-js-animation/bezier-linear.svg +++ b/7-animation/3-js-animation/bezier-linear.svg @@ -1 +1 @@ -12 \ No newline at end of file +12 \ No newline at end of file diff --git a/7-animation/3-js-animation/bounce-easeinout.view/index.html b/7-animation/3-js-animation/bounce-easeinout.view/index.html index 837c50db1..aed3d9d08 100644 --- a/7-animation/3-js-animation/bounce-easeinout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeinout.view/index.html @@ -26,7 +26,7 @@ function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce-easeout.view/index.html b/7-animation/3-js-animation/bounce-easeout.view/index.html index e52eae8de..69dbb7ce0 100644 --- a/7-animation/3-js-animation/bounce-easeout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeout.view/index.html @@ -22,7 +22,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce-inout.svg b/7-animation/3-js-animation/bounce-inout.svg index 7274d715d..363633abd 100644 --- a/7-animation/3-js-animation/bounce-inout.svg +++ b/7-animation/3-js-animation/bounce-inout.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/bounce.view/index.html b/7-animation/3-js-animation/bounce.view/index.html index 1be2580d9..3575ed820 100644 --- a/7-animation/3-js-animation/bounce.view/index.html +++ b/7-animation/3-js-animation/bounce.view/index.html @@ -19,7 +19,7 @@ animate({ duration: 3000, timing: function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/circ-ease.svg b/7-animation/3-js-animation/circ-ease.svg index cf2ed8d9e..a7db9abcf 100644 --- a/7-animation/3-js-animation/circ-ease.svg +++ b/7-animation/3-js-animation/circ-ease.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/circ.svg b/7-animation/3-js-animation/circ.svg index 1c2beade4..3595dd624 100644 --- a/7-animation/3-js-animation/circ.svg +++ b/7-animation/3-js-animation/circ.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/elastic.svg b/7-animation/3-js-animation/elastic.svg index 851da406b..17f04ccde 100644 --- a/7-animation/3-js-animation/elastic.svg +++ b/7-animation/3-js-animation/elastic.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/linear.svg b/7-animation/3-js-animation/linear.svg index 7a5bd71a3..daa753f0c 100644 --- a/7-animation/3-js-animation/linear.svg +++ b/7-animation/3-js-animation/linear.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/quad.svg b/7-animation/3-js-animation/quad.svg index e9bc6ac99..25a4d0005 100644 --- a/7-animation/3-js-animation/quad.svg +++ b/7-animation/3-js-animation/quad.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/quint.svg b/7-animation/3-js-animation/quint.svg index ad8ece285..c879ef931 100644 --- a/7-animation/3-js-animation/quint.svg +++ b/7-animation/3-js-animation/quint.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/text.view/index.html b/7-animation/3-js-animation/text.view/index.html index e404fe5c4..4947e4cd4 100644 --- a/7-animation/3-js-animation/text.view/index.html +++ b/7-animation/3-js-animation/text.view/index.html @@ -29,14 +29,14 @@ timing: bounce, draw: function(progress) { let result = (to - from) * progress + from; - textArea.value = text.substr(0, Math.ceil(result)) + textArea.value = text.slice(0, Math.ceil(result)) } }); } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md index 3279cb133..c3522dea9 100644 --- a/8-web-components/1-webcomponents-intro/article.md +++ b/8-web-components/1-webcomponents-intro/article.md @@ -26,9 +26,9 @@ The International Space Station: ...And this thing flies, keeps humans alive in space! -How such complex devices are created? +How are such complex devices created? -Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it. +Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it? ## Component architecture diff --git a/8-web-components/1-webcomponents-intro/web-components-twitter.svg b/8-web-components/1-webcomponents-intro/web-components-twitter.svg index 534e629b9..8f59f789f 100644 --- a/8-web-components/1-webcomponents-intro/web-components-twitter.svg +++ b/8-web-components/1-webcomponents-intro/web-components-twitter.svg @@ -1 +1 @@ -1243567 \ No newline at end of file +1243567 \ No newline at end of file diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index 5a07cc679..a84ed1192 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -149,7 +149,7 @@ The `connectedCallback` triggers when the element is added to the document. Not In the current implementation of ``, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, we expect the change to be immediately visible. So let's fix this. -We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for an attribute for performance reasons. +We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons). Here's a new ``, that auto-updates when attributes change: @@ -320,7 +320,7 @@ For example, buttons are instances of `HTMLButtonElement`, let's build upon it. class HelloButton extends HTMLButtonElement { /* custom element methods */ } ``` -2. Provide an third argument to `customElements.define`, that specifies the tag: +2. Provide the third argument to `customElements.define`, that specifies the tag: ```js customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*); ``` @@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar ## References - HTML Living Standard: . -- Compatiblity: . +- Compatiblity: . ## Summary @@ -397,4 +397,4 @@ Custom elements can be of two types: /*