From acab6f6a2f40bc35f8a9b7542a823c0cfc27538f Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Thu, 28 Mar 2024 23:07:54 +0700 Subject: [PATCH 1/6] Refactor code to improve performance and readability --- 1-js/02-first-steps/13-while-for/article.md | 388 ++------------------ 1 file changed, 25 insertions(+), 363 deletions(-) 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 437f67e9c..7b8c6e4f9 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -1,65 +1,66 @@ -# การวนซ้ำ (Loops): while และ for +# วงวน (Loops): while และ for -เราจำเป็นต้องทำซ้ำคำสั่งบ่อยๆ +เราจำเป็นต้องทำคำสั่งซ้ำๆ บ่อยครั้ง -ยกตัวอย่างเช่น การแสดงสินค้าจากรายการออกมาทีละชิ้น หรือแค่รันโค้ดเดิมซ้ำๆ กับตัวเลขแต่ละตัวจาก 1 ถึง 10 +ยกตัวอย่างเช่น การแสดงสินค้าในรายการทีละชิ้น หรือการรันโค้ดเดิมซ้ำๆ กับตัวเลขตั้งแต่ 1 ถึง 10 -*Loops* คือวิธีการที่ใช้ในการทำซ้ำโค้ดเดิมหลายๆ ครั้ง +*วงวน (Loops)* คือวิธีการทำซ้ำโค้ดเดิมหลายๆ รอบ -```smart header="การวนซ้ำ for..of และ for..in" +```smart header="วงวน for..of และ for..in" ประกาศสำหรับผู้อ่านขั้นสูง -บทความนี้ครอบคลุมเฉพาะ loops พื้นฐาน: `while`, `do..while` และ `for(..;..;..)` +บทความนี้กล่าวถึงวงวนพื้นฐานเท่านั้น ได้แก่ `while`, `do..while` และ `for(..;..;..)` -ถ้าคุณมาถึงบทความนี้เพื่อค้นหาประเภทอื่นๆ ของ loops ลองดูที่นี่: -- ดู [for..in](info:object#forin) สำหรับการวนซ้ำคุณสมบัติของออบเจ็กต์ -- ดู [for..of](info:array#loops) และ [iterables](info:iterable) สำหรับการวนซ้ำอาร์เรย์และออบเจ็กต์ที่สามารถวนซ้ำได้ +ถ้าคุณมาที่บทความนี้เพื่อหาวงวนประเภทอื่นๆ ลองดูตรงนี้: -ถ้าไม่ใช่ ขอให้อ่านต่อไป +- ดู [for..in](info:object#forin) สำหรับการวนซ้ำ property ของ object +- ดู [for..of](info:array#loops) และ [iterables](info:iterable) สำหรับการวนซ้ำ array และ iterable objects + +มิฉะนั้นก็อ่านต่อได้เลย ``` -## การวนซ้ำแบบ "while" +## วงวน "while" -การวนซ้ำแบบ `while` มีรูปแบบดังนี้: +วงวน `while` มีรูปแบบไวยากรณ์ดังนี้: ```js -while (เงื่อนไข) { - // โค้ด +while (condition) { + // code // เรียกว่า "loop body" } ``` -ในขณะที่ `เงื่อนไข` ยังเป็นจริง `โค้ด` ใน loop body จะถูกรัน +ตราบใดที่ `condition` ยังเป็นจริง `code` ใน loop body ก็จะถูกรัน -ยกตัวอย่างเช่น loop ด้านล่างจะแสดง `i` ตราบใดที่ `i < 3`: +ยกตัวอย่างเช่น วงวนด้านล่างจะแสดง `i` ตราบใดที่ `i < 3`: ```js run let i = 0; -while (i < 3) { // แสดง 0, จากนั้น 1, จากนั้น 2 +while (i < 3) { // แสดง 0, แล้ว 1, แล้ว 2 alert( i ); i++; } ``` -การรันโค้ดใน loop body หนึ่งรอบ เรียกว่า *an iteration* loop ในตัวอย่างด้านบน จะทำ 3 iterations +การรันโค้ดใน loop body หนึ่งรอบเรียกว่า *อิเทอเรชัน (iteration)* วงวนในตัวอย่างข้างต้นจะทำ 3 อิเทอเรชัน -ถ้าไม่มีการเพิ่ม `i++` ใน loop จะวนซ้ำไปเรื่อยๆ (ในทางทฤษฎี) ในทางปฏิบัติ เบราว์เซอร์จะมีวิธีการในการหยุด loop แบบนี้ และใน JavaScript ฝั่งเซิร์ฟเวอร์ เราสามารถหยุดกระบวนการได้ +ถ้าขาด `i++` ในตัวอย่าง วงวนจะทำซ้ำไปตลอด (ในทางทฤษฎี) แต่ในทางปฏิบัติ เบราว์เซอร์จะมีวิธีหยุดวงวนแบบนี้ และใน JavaScript ฝั่งเซิร์ฟเวอร์ เราก็หยุดโพรเซสได้ -นิพจน์หรือตัวแปรใดก็ได้สามารถใช้เป็นเงื่อนไขในการวนซ้ำได้ ไม่จำกัดแค่เป็นการเปรียบเทียบเท่านั้น: เงื่อนไขจะถูกประเมินและแปลงเป็น boolean โดย `while` +นิพจน์หรือตัวแปรใดๆ ก็ตามสามารถใช้เป็นเงื่อนไขวงวนได้ ไม่จำกัดแค่การเปรียบเทียบ: `while` จะประเมินเงื่อนไขและแปลงเป็น boolean -ตัวอย่างเช่น วิธีที่สั้นกว่า `while (i != 0)` คือ `while (i)`: +เช่น วิธีเขียน `while (i != 0)` ให้สั้นลงคือ `while (i)`: ```js run let i = 3; *!* -while (i) { // เมื่อ i เป็น 0, เงื่อนไขจะเป็น falsy แล้ว loop จะหยุด +while (i) { // เมื่อ i เป็น 0, เงื่อนไขจะเป็น falsy และวงวนจะหยุด */!* alert( i ); i--; } ``` -````smart header="ไม่ต้องใส่ปีกกา { } ถ้า loop body มีแค่คำสั่งเดียว" +````smart header="ไม่ต้องใส่ปีกกา { } ถ้า loop body มีคำสั่งเดียว" ถ้า loop body มีแค่คำสั่งเดียว เราสามารถละปีกกา `{…}` ได้: ```js run @@ -68,343 +69,4 @@ let i = 3; while (i) alert(i--); */!* ``` -```` - -## การวนซ้ำแบบ "do..while" - -การตรวจสอบเงื่อนไขสามารถย้ายไปอยู่ *ข้างล่าง* loop body ได้ ด้วยการใช้ `do..while`: - -```js -do { - // loop body -} while (เงื่อนไข); -``` - -Loop จะรันโค้ดใน body ก่อน แล้วค่อยมาตรวจสอบเงื่อนไข และถ้าเงื่อนไขเป็นจริง ก็จะรันซ้ำไปเรื่อยๆ - -ตัวอย่าง: - -```js run -let i = 0; -do { - alert( i ); - i++; -} while (i < 3); -``` - -รูปแบบนี้ควรใช้เฉพาะตอนที่คุณต้องการให้ loop body รันอย่างน้อยหนึ่งครั้ง ไม่ว่าเงื่อนไขจะเป็นจริงหรือไม่ก็ตาม ในกรณีทั่วไป ควรเลือกใช้รูปแบบ `while(…) {…}` มากกว่า - -## การวนซ้ำแบบ "for" - -การวนซ้ำแบบ `for` มีความซับซ้อนมากกว่า แต่ก็เป็นรูปแบบที่นิยมใช้มากที่สุดเช่นกัน - -มีรูปแบบดังนี้: - -```js -for (เริ่มต้น; เงื่อนไข; ขั้นตอน) { - // ... loop body ... -} -``` - -ลองมาศึกษาความหมายของแต่ละส่วนจากตัวอย่างกัน loop ด้านล่างจะรัน `alert(i)` เมื่อ `i` มีค่าตั้งแต่ `0` ถึง `3` (ไม่รวม `3`): - -```js run -for (let i = 0; i < 3; i++) { // แสดง 0, จากนั้น 1, จากนั้น 2 - alert(i); -} -``` - -มาดูแต่ละส่วนของคำสั่ง `for` กัน: - -| ส่วน | | | -|-------|----------|----------------------------------------------------------------------------| -| เริ่มต้น | `let i = 0` | รันครั้งเดียวตอนเข้า loop | -| เงื่อนไข | `i < 3`| ตรวจสอบก่อนทุกรอบของ loop ถ้าเป็น false loop จะหยุด | -| body | `alert(i)`| รันซ้ำๆ ตราบใดที่เงื่อนไขยังเป็นจริง | -| ขั้นตอน| `i++` | รันหลังจาก body ในแต่ละรอบ | - -อัลกอริทึมทั่วไปของ loop จะทำงานดังนี้: - -``` -รันส่วนเริ่มต้น -→ (ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน) -→ (ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน) -→ (ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน) -→ ... -``` - -ถ้าพูดให้ชัดเจน `เริ่มต้น` จะรันครั้งเดียว แล้วก็จะเข้าสู่ iteration: หลังจากการตรวจสอบ `เงื่อนไข` `body` และ `ขั้นตอน` จะถูกรัน - -ถ้าคุณใหม่กับ loops การกลับไปอ่านตัวอย่างพร้อมๆ กับเขียนว่าแต่ละขั้นตอนทำงานอย่างไรก็อาจจะช่วยได้ - -นี่คือสิ่งที่เกิดขึ้นในตัวอย่างของเรา: - -```js -// for (let i = 0; i < 3; i++) alert(i) - -// รันส่วนเริ่มต้น -let i = 0 -// ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน -if (i < 3) { alert(i); i++ } -// ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน -if (i < 3) { alert(i); i++ } -// ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน -if (i < 3) { alert(i); i++ } -// ...เสร็จสิ้น เพราะตอนนี้ i == 3 -``` - -````smart header="ประกาศตัวแปรใน loop" -ในตัวอย่าง ตัวแปร "counter" `i` ถูกประกาศภายใน loop โดยตรง นี่เรียกว่า "inline" variable declaration ตัวแปรเหล่านี้จะมองเห็นเฉพาะใน loop เท่านั้น - -```js run -for (*!*let*/!* i = 0; i < 3; i++) { - alert(i); // 0, 1, 2 -} -alert(i); // error, ไม่มีตัวแปรนี้ -``` - -แทนที่จะประกาศตัวแปรใหม่ เราสามารถใช้ตัวแปรที่มีอยู่แล้วได้: - -```js run -let i = 0; - -for (i = 0; i < 3; i++) { // ใช้ตัวแปรที่มีอยู่แล้ว - alert(i); // 0, 1, 2 -} - -alert(i); // 3, มองเห็น เพราะประกาศนอก loop -``` -```` - -### การข้ามส่วน - -เราสามารถข้ามส่วนใดส่วนหนึ่งของ `for` ได้ - -ยกตัวอย่างเช่น เราสามารถละ `เริ่มต้น` ได้ถ้าเราไม่ต้องทำอะไรตอนเริ่ม loop - -อย่างเช่นที่นี่: - -```js run -let i = 0; // เราประกาศ i ไว้แล้วก่อนหน้านี้ - -for (; i < 3; i++) { // ไม่จำเป็นต้องมี "เริ่มต้น" - alert( i ); // 0, 1, 2 -} -``` - -เราสามารถละส่วน `ขั้นตอน` ได้เช่นกัน: - -```js run -let i = 0; - -for (; i < 3;) { - alert( i++ ); -} -``` - -นี่จะทำให้ loop เหมือนกับ `while (i < 3)` - -เราสามารถลบได้ทุกอย่างเลย เพื่อสร้าง infinite loop: - -```js -for (;;) { - // วนซ้ำไม่มีที่สิ้นสุด -} -``` - -โปรดทราบว่า semicolon `;` ทั้งสองใน `for` ต้องมีนะ ไม่อย่างนั้นจะเป็น syntax error - -## การหยุด loop - -โดยปกติ loop จะหยุดเมื่อเงื่อนไขเป็น falsy - -แต่เราสามารถบังคับให้หยุดเมื่อไรก็ได้ด้วยคำสั่ง `break` - -ยกตัวอย่างเช่น loop ด้านล่างจะถามผู้ใช้ให้กรอกตัวเลข และจะ "break" เมื่อไม่มีการป้อนตัวเลข: - -```js run -let sum = 0; - -while (true) { - - let value = +prompt("ใส่ตัวเลข", ''); - -*!* - if (!value) break; // (*) -*/!* - - sum += value; - -} -alert( 'ผลรวม: ' + sum ); -``` - -คำสั่ง `break` จะถูกเรียกที่บรรทัด `(*)` ถ้าผู้ใช้ป้อนบรรทัดว่าง หรือกด cancel มันจะหยุด loop ในทันที และส่งการทำงานไปยังบรรทัดแรกหลัง loop นั่นคือ `alert` - -การผสมผสานของ "infinite loop + `break` เมื่อต้องการ" เหมาะสำหรับสถานการณ์ที่ต้องการตรวจสอบเงื่อนไขในกลางหรือหลายตำแหน่งในส่วน body ของลูป - -## การข้ามไปยัง Iteration ถัดไป [#continue] - -คำสั่ง `continue` เป็น "เวอร์ชันเบาๆ" ของ `break` มันไม่ได้หยุด loop ทั้งหมด แต่มันจะหยุด iteration ปัจจุบัน และบังคับให้ loop เริ่มใหม่ (ถ้าเงื่อนไขอนุญาต) - -เราสามารถใช้มันได้ถ้าเราทำส่วนปัจจุบันเสร็จแล้ว และต้องการไปยังรอบถัดไป - -Loop ด้านล่างใช้ `continue` เพื่อแสดงผลเฉพาะค่าที่เป็นเลขคี่: - -```js run no-beautify -for (let i = 0; i < 10; i++) { - - // ถ้า true ข้ามส่วนที่เหลือของ body ไป - *!*if (i % 2 == 0) continue;*/!* - - alert(i); // 1, แล้วก็ 3, 5, 7, 9 -} -``` - -สำหรับค่า `i` ที่เป็นเลขคู่ คำสั่ง `continue` จะหยุดการทำงานของ body และส่งต่อการควบคุมไปยัง iteration ถัดไปของ `for` (ด้วยเลขตัวถัดไป) ดังนั้น `alert` จะถูกเรียกใช้เฉพาะสำหรับเลขคี่ - -````smart header="`continue` ช่วยลดการซ้อนกันของโค้ด" -Loop ที่แสดงแต่เลขคี่สามารถเขียนได้แบบนี้: - -```js run -for (let i = 0; i < 10; i++) { - - if (i % 2) { - alert( i ); - } - -} -``` - -ในแง่ของเทคนิค สิ่งนี้เหมือนกับตัวอย่างด้านบน แน่นอน เราสามารถห่อโค้ดใส่บล็อก `if` แทนการใช้ `continue` ได้ - -แต่ผลข้างเคียงคือ มันจะสร้างการซ้อนกันของโค้ดขึ้นมาอีกชั้นหนึ่ง (การเรียก `alert` อยู่ในปีกกาหยัก) ถ้าโค้ดใน `if` ยาวกว่าสองสามบรรทัด มันอาจทำให้โค้ดอ่านยากขึ้น -```` - -````warn header="ไม่มี `break/continue` อยู่ทางขวาของ '?'" -โปรดทราบว่า syntax constructs ที่ไม่ใช่ expressions ไม่สามารถใช้กับตัวดำเนินการ ternary `?` ได้ โดยเฉพาะคำสั่งอย่าง `break/continue` จะไม่อนุญาตให้ใช้ได้ - -ตัวอย่างเช่น ถ้าเราเอาโค้ดนี้: - -```js -if (i > 5) { - alert(i); -} else { - continue; -} -``` - -...แล้วเขียนใหม่โดยใช้เครื่องหมายคำถาม: - -```js no-beautify -(i > 5) ? alert(i) : *!*continue*/!*; // ไม่อนุญาต continue ให้ใช้ที่นี่ -``` - -...มันจะไม่ทำงาน: จะเป็น syntax error - -นี่เป็นอีกเหตุผลหนึ่งที่ไม่ควรใช้ตัวดำเนินการ `?` แทน `if` -```` - -## Labels สำหรับ break/continue - -บางครั้งเราต้องการ break ออกจาก nested loops หลายชั้นในครั้งเดียว - -ตัวอย่างเช่น ในโค้ดด้านล่าง เราจะวน loop บน `i` และ `j` เพื่อรับค่าพิกัด `(i, j)` จาก `(0,0)` ถึง `(2,2)`: - -```js run no-beautify -for (let i = 0; i < 3; i++) { - - for (let j = 0; j < 3; j++) { - - let input = prompt(`Value at coords (${i},${j})`, ''); - - // ถ้าเราต้องการออกจากนี่ไปที่ Done (ด้านล่าง) ล่ะ? - } -} - -alert('Done!'); -``` - -เราต้องการหยุดกระบวนการนี้ได้ถ้าผู้ใช้ยกเลิกการป้อนค่า - -`break` ปกติหลัง `input` จะออกจาก inner loop เท่านั้น มันไม่พอ - เราต้องใช้ labels! - -*Label* คือ identifier ที่ตามด้วยเครื่องหมายทวิภาคก่อน loop: - -```js -labelName: for (...) { - ... -} -``` - -คำสั่ง `break ` ในตัวอย่างด้านล่างจะหยุด loop แล้วออกไปที่ label: - -```js run no-beautify -*!*outer:*/!* for (let i = 0; i < 3; i++) { - - for (let j = 0; j < 3; j++) { - - let input = prompt(`Value at coords (${i},${j})`, ''); - - // ถ้าใส่สตริงว่าง หรือกด cancel, ให้ออกจากทั้งสอง loops - if (!input) *!*break outer*/!*; // (*) - - // ทำบางอย่างกับค่าที่ได้... - } -} - -alert('Done!'); -``` - -ในโค้ดด้านบน `break outer` จะมองหา label ชื่อ `outer` ข้างบนและหยุดการทำงานของ loop นั้น - -เพราะฉะนั้นการทำงานจะกระโดดจาก `(*)` ไปที่ `alert('Done!')` เลย - -เราสามารถย้าย label ไปอยู่บนบรรทัดแยกต่างหากได้: - -```js no-beautify -outer: -for (let i = 0; i < 3; i++) { ... } -``` - -คำสั่ง `continue` ก็สามารถใช้กับ label ได้ในแบบเดียวกัน ในกรณีนี้ การทำงานของโค้ดจะกระโดดไปที่การเริ่ม iteration ถัดไปของ loop ที่มี label - -````warn header="Labels ไม่อนุญาตให้ \"กระโดด\" ไปที่ไหนก็ได้" -Labels ไม่อนุญาตให้เรากระโดดไปยังจุดใด ๆ ในโค้ดได้อย่างอิสระ - -ตัวอย่างเช่น มันไม่สามารถทำแบบนี้ได้: - -```js -break label; // กระโดดไปยัง label ด้านล่าง (ไม่ทำงาน) - -label: for (...) -``` - -คำสั่ง `break` ต้องอยู่ภายใน code block เท่านั้น ในทางเทคนิค label สามารถใส่หน้า code block ใด ๆ ก็ได้ เช่น: - -```js -label: { - // ... - break label; // ทำงาน - // ... -} -``` - -...แม้ว่า 99.9% ของครั้ง `break` จะถูกใช้ข้างใน loops เหมือนในตัวอย่างที่เราเห็นด้านบน - -`continue` ก็ใช้ได้เฉพาะข้างใน loop เท่านั้น -```` - -## สรุป - -เราได้พูดถึง loop 3 แบบ: - -- `while` -- เงื่อนไขจะถูกตรวจสอบก่อนเริ่มแต่ละ iteration -- `do..while` -- เงื่อนไขจะถูกตรวจสอบหลังจบแต่ละ iteration -- `for (;;)` -- เงื่อนไขจะถูกตรวจสอบก่อนเริ่มแต่ละ iteration และสามารถกำหนดการตั้งค่าเพิ่มเติมได้ - -ในการสร้าง "infinite" loop โดยทั่วไปจะใช้ `while(true)` การสร้างแบบนี้ เหมือนกับ loop แบบอื่น ๆ สามารถหยุดได้ด้วยคำสั่ง `break` - -ถ้าเราไม่ต้องการทำอะไรใน iteration ปัจจุบัน และต้องการข้ามไปที่รอบถัดไป เราสามารถใช้คำสั่ง `continue` - -`break/continue` รองรับการใส่ label ไว้ข้างหน้า loop label เป็นวิธีเดียวที่ `break/continue` จะสามารถออกจาก nested loop ได้และไปยัง outer loop +```` \ No newline at end of file From a8ad9e095bcf2e2e0e324a84bf1b26ca0307970f Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Thu, 28 Mar 2024 23:18:12 +0700 Subject: [PATCH 2/6] Add do..while loop example --- 1-js/02-first-steps/13-while-for/article.md | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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 7b8c6e4f9..b4a57092c 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -69,4 +69,28 @@ let i = 3; while (i) alert(i--); */!* ``` -```` \ No newline at end of file +```` + +## วงวน "do..while" + +การตรวจสอบเงื่อนไขสามารถย้ายไปอยู่*ใต้* loop body ได้โดยใช้ `do..while`: + +```js +do { + // loop body +} while (condition); +``` + +วงวนจะรัน loop body ก่อน จากนั้นจึงตรวจสอบเงื่อนไข และหากเงื่อนไขเป็นจริง ก็จะรันซ้ำแล้วซ้ำอีก + +ตัวอย่างเช่น: + +```js run +let i = 0; +do { + alert( i ); + i++; +} while (i < 3); +``` + +ควรใช้รูปแบบไวยากรณ์นี้เฉพาะเมื่อต้องการให้ loop body รันอย่างน้อย**หนึ่งครั้ง** โดยไม่คำนึงว่าเงื่อนไขจะเป็นจริงหรือไม่ โดยปกติแล้วมักนิยมใช้รูปแบบ `while(…) {…}` มากกว่า \ No newline at end of file From 4ddaa9fbb0014dfe82c497937b01267e61f0a8b8 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Thu, 28 Mar 2024 23:21:56 +0700 Subject: [PATCH 3/6] Update while and for loop examples in article.md --- 1-js/02-first-steps/13-while-for/article.md | 84 ++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) 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 b4a57092c..805482d66 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -93,4 +93,86 @@ do { } while (i < 3); ``` -ควรใช้รูปแบบไวยากรณ์นี้เฉพาะเมื่อต้องการให้ loop body รันอย่างน้อย**หนึ่งครั้ง** โดยไม่คำนึงว่าเงื่อนไขจะเป็นจริงหรือไม่ โดยปกติแล้วมักนิยมใช้รูปแบบ `while(…) {…}` มากกว่า \ No newline at end of file +ควรใช้รูปแบบไวยากรณ์นี้เฉพาะเมื่อต้องการให้ loop body รันอย่างน้อย**หนึ่งครั้ง** โดยไม่คำนึงว่าเงื่อนไขจะเป็นจริงหรือไม่ โดยปกติแล้วมักนิยมใช้รูปแบบ `while(…) {…}` มากกว่า + +## วงวน "for" + +วงวน `for` มีความซับซ้อนมากกว่า แต่ก็เป็นวงวนที่ใช้กันบ่อยที่สุดเช่นกัน + +รูปแบบคือ: + +```js +for (begin; condition; step) { + // ... loop body ... +} +``` + +มาดูความหมายของแต่ละส่วนจากตัวอย่างกัน วงวนด้านล่างจะรัน `alert(i)` สำหรับ `i` ตั้งแต่ `0` ถึง `3` (ไม่รวม `3`): + +```js run +for (let i = 0; i < 3; i++) { // แสดง 0, แล้ว 1, แล้ว 2 + alert(i); +} +``` + +มาดูคำสั่ง `for` ทีละส่วน: + +| ส่วน | | | +|-----------|---------------|-------------------------------------------------------------------------------| +| begin | `let i = 0` | รันครั้งเดียวตอนเข้าวงวน | +| condition | `i < 3` | ถูกตรวจสอบก่อนการรันวงวนแต่ละรอบ ถ้าเป็น false วงวนจะหยุด | +| body | `alert(i)` | รันซ้ำไปเรื่อยๆ ตราบใดที่ condition ยังเป็นจริง | +| step | `i++` | รันหลัง body ในแต่ละรอบ | + +อัลกอริทึมทั่วไปของวงวนทำงานแบบนี้: + +``` +รันส่วนเริ่มต้น +→ (ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน) +→ (ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน) +→ (ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน) +→ ... +``` + +นั่นคือ `begin` รันครั้งเดียว แล้วก็จะเข้าสู่ iteration: หลังจากทดสอบ `condition` แล้ว `body` และ `step` จะถูกรัน + +ถ้าคุณใหม่กับวงวน การย้อนกลับไปดูตัวอย่างพร้อมๆ กับเขียนว่าแต่ละขั้นตอนทำงานอย่างไร อาจจะช่วยให้เข้าใจได้ดีขึ้น + +ต่อไปนี้คือสิ่งที่เกิดขึ้นจริงๆ ในตัวอย่างของเรา: + +```js +// for (let i = 0; i < 3; i++) alert(i) + +// รันส่วนเริ่มต้น +let i = 0 +// ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน +if (i < 3) { alert(i); i++ } +// ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน +if (i < 3) { alert(i); i++ } +// ถ้าเงื่อนไขเป็นจริง → รัน body และรันขั้นตอน +if (i < 3) { alert(i); i++ } +// ...เสร็จสิ้น เพราะตอนนี้ i == 3 +``` + +````smart header="การประกาศตัวแปรใน loop" +ในที่นี้ ตัวแปร "counter" `i` ถูกประกาศในวงวนโดยตรง เรียกว่าการประกาศตัวแปร "inline" ตัวแปรเหล่านี้จะมองเห็นได้เฉพาะในวงวนเท่านั้น + +```js run +for (*!*let*/!* i = 0; i < 3; i++) { + alert(i); // 0, 1, 2 +} +alert(i); // error, ไม่มีตัวแปรนี้ +``` + +แทนที่จะประกาศตัวแปรใหม่ เราสามารถใช้ตัวแปรที่มีอยู่แล้ว: + +```js run +let i = 0; + +for (i = 0; i < 3; i++) { // ใช้ตัวแปรที่มีอยู่ + alert(i); // 0, 1, 2 +} + +alert(i); // 3, มองเห็นได้ เพราะถูกประกาศนอกวงวน +``` +```` \ No newline at end of file From 71f6b570eea4718272e366677a71d14722be688f Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Thu, 28 Mar 2024 23:26:21 +0700 Subject: [PATCH 4/6] =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=82?= =?UTF-8?q?=E0=B9=89=E0=B8=B2=E0=B8=A1=E0=B8=AA=E0=B9=88=E0=B8=A7=E0=B8=99?= =?UTF-8?q?=E0=B8=95=E0=B9=88=E0=B8=B2=E0=B8=87=E0=B9=86=20=E0=B8=82?= =?UTF-8?q?=E0=B8=AD=E0=B8=87=E0=B8=A7=E0=B8=87=E0=B8=A7=E0=B8=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1-js/02-first-steps/13-while-for/article.md | 69 ++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) 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 805482d66..8b4ec51a9 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -175,4 +175,71 @@ for (i = 0; i < 3; i++) { // ใช้ตัวแปรที่มีอยู alert(i); // 3, มองเห็นได้ เพราะถูกประกาศนอกวงวน ``` -```` \ No newline at end of file +```` + +### การข้ามส่วนต่างๆ ของวงวน + +เราสามารถข้ามส่วนใดๆ ของ `for` ก็ได้ + +ยกตัวอย่างเช่น เราสามารถละส่วน `begin` ได้ ถ้าเราไม่จำเป็นต้องทำอะไรที่จุดเริ่มต้นของวงวน + +เช่นในตัวอย่างนี้: + +```js run +let i = 0; // เรามีการประกาศและกำหนดค่า i ไว้ก่อนแล้ว + +for (; i < 3; i++) { // ไม่จำเป็นต้องมีส่วน "begin" + alert( i ); // 0, 1, 2 +} +``` + +เราสามารถตัดส่วน `step` ออกได้ด้วย: + +```js run +let i = 0; + +for (; i < 3;) { + alert( i++ ); +} +``` + +การทำแบบนี้จะทำให้วงวนเหมือนกับ `while (i < 3)` + +จริงๆ แล้วเราสามารถตัดทุกอย่างออกได้เลย เพื่อสร้างวงวนอนันต์: + +```js +for (;;) { + // ทำซ้ำไม่รู้จบ +} +``` + +โปรดสังเกตว่าเครื่องหมายอัฒภาค `;` สองตัวของ `for` ต้องมี ไม่อย่างนั้นจะเกิด syntax error + +## การออกจากวงวน + +โดยปกติ วงวนจะจบลงเมื่อเงื่อนไขเป็น falsy + +แต่เราสามารถบังคับให้ออกจากวงวนเมื่อไรก็ได้ โดยใช้คำสั่ง `break` + +ตัวอย่างเช่น วงวนด้านล่างจะขอให้ผู้ใช้ป้อนชุดของตัวเลข และจะ "ออกจากวงวน" เมื่อไม่มีการป้อนตัวเลข: + +```js run +let sum = 0; + +while (true) { + + let value = +prompt("ป้อนตัวเลข", ''); + +*!* + if (!value) break; // (*) +*/!* + + sum += value; + +} +alert( 'ผลรวม: ' + sum ); +``` + +คำสั่ง `break` จะทำงานที่บรรทัด `(*)` หากผู้ใช้ป้อนค่าว่างหรือกดยกเลิก มันจะหยุดวงวนทันทีแล้วส่งการควบคุมไปที่บรรทัดแรกหลังวงวน นั่นคือ `alert` + +การใช้ "วงวนอนันต์ + `break` เมื่อจำเป็น" เป็นวิธีที่ดีสำหรับสถานการณ์ที่ต้องตรวจเช็คเงื่อนไขวงวน ไม่ใช่ที่จุดเริ่มต้นหรือจุดสิ้นสุด แต่อยู่ตรงกลางหรือหลายๆ ที่ในวงวน \ No newline at end of file From e7743bff37c2d9b9774a1ab0497d26a889667b1b Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Thu, 28 Mar 2024 23:30:27 +0700 Subject: [PATCH 5/6] =?UTF-8?q?=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=82?= =?UTF-8?q?=E0=B9=89=E0=B8=B2=E0=B8=A1=E0=B9=84=E0=B8=9B=E0=B8=97=E0=B8=B3?= =?UTF-8?q?=E0=B8=A3=E0=B8=AD=E0=B8=9A=E0=B8=96=E0=B8=B1=E0=B8=94=E0=B9=84?= =?UTF-8?q?=E0=B8=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1-js/02-first-steps/13-while-for/article.md | 64 ++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) 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 8b4ec51a9..d66c675ef 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -242,4 +242,66 @@ alert( 'ผลรวม: ' + sum ); คำสั่ง `break` จะทำงานที่บรรทัด `(*)` หากผู้ใช้ป้อนค่าว่างหรือกดยกเลิก มันจะหยุดวงวนทันทีแล้วส่งการควบคุมไปที่บรรทัดแรกหลังวงวน นั่นคือ `alert` -การใช้ "วงวนอนันต์ + `break` เมื่อจำเป็น" เป็นวิธีที่ดีสำหรับสถานการณ์ที่ต้องตรวจเช็คเงื่อนไขวงวน ไม่ใช่ที่จุดเริ่มต้นหรือจุดสิ้นสุด แต่อยู่ตรงกลางหรือหลายๆ ที่ในวงวน \ No newline at end of file +การใช้ "วงวนอนันต์ + `break` เมื่อจำเป็น" เป็นวิธีที่ดีสำหรับสถานการณ์ที่ต้องตรวจเช็คเงื่อนไขวงวน ไม่ใช่ที่จุดเริ่มต้นหรือจุดสิ้นสุด แต่อยู่ตรงกลางหรือหลายๆ ที่ในวงวน + +## การข้ามไปทำรอบถัดไป [#continue] + +คำสั่ง `continue` เป็น "เวอร์ชันที่เบากว่า" ของ `break` มันไม่ได้หยุดวงวนทั้งหมด แต่จะหยุดการทำงานในรอบปัจจุบันแล้วบังคับให้วงวนเริ่มรอบใหม่ (ถ้าเงื่อนไขอนุญาต) + +เราสามารถใช้มันได้ถ้าเราเสร็จงานในรอบปัจจุบันแล้ว และต้องการข้ามไปทำรอบถัดไปเลย + +วงวนด้านล่างใช้ `continue` เพื่อแสดงผลแค่ค่าคี่เท่านั้น: + +```js run no-beautify +for (let i = 0; i < 10; i++) { + + // ถ้าเป็นจริง ให้ข้ามส่วนที่เหลือในรอบนี้ + *!*if (i % 2 == 0) continue;*/!* + + alert(i); // 1, แล้วก็ 3, 5, 7, 9 +} +``` + +สำหรับค่า `i` ที่เป็นเลขคู่ คำสั่ง `continue` จะหยุดการทำงานในรอบปัจจุบันแล้วส่งการควบคุมไปเริ่มรอบถัดไปของ `for` (ด้วยตัวเลขถัดไป) ดังนั้น `alert` จะถูกเรียกเฉพาะสำหรับค่าคี่เท่านั้น + +````smart header="คำสั่ง `continue` ช่วยลดการซ้อนโค้ด" +วงวนที่แสดงค่าคี่อาจเขียนได้แบบนี้: + +```js run +for (let i = 0; i < 10; i++) { + + if (i % 2) { + alert( i ); + } + +} +``` + +ในแง่เทคนิค มันจะเหมือนกับตัวอย่างข้างบน แน่นอนว่าเราเพียงแค่ใส่โค้ดไว้ในบล็อก `if` แทนการใช้ `continue` ก็ได้ + +แต่มันก่อให้เกิดผลข้างเคียงคือเพิ่มระดับการซ้อนโค้ดอีกชั้น (เรียก `alert` ภายในปีกกาหลังเงื่อนไข) ถ้าโค้ดใน `if` ยาวกว่าสองสามบรรทัด มันอาจลดความสามารถในการอ่านโค้ดโดยรวม +```` + +````warn header="ไม่มี `break/continue` ทางขวาของเครื่องหมาย '?'" +โปรดทราบว่าโครงสร้างทางไวยากรณ์ที่ไม่ใช่นิพจน์ ไม่สามารถใช้กับตัวดำเนินการ ternary `?` ได้ โดยเฉพาะอย่างยิ่ง คำสั่งอย่างเช่น `break/continue` จะใช้ไม่ได้ + +ตัวอย่างเช่น ถ้าเรามีโค้ดแบบนี้: + +```js +if (i > 5) { + alert(i); +} else { + continue; +} +``` + +...แล้วเขียนใหม่โดยใช้เครื่องหมายคำถาม: + +```js no-beautify +(i > 5) ? alert(i) : *!*continue*/!*; // continue ใช้ที่นี่ไม่ได้ +``` + +...มันจะไม่ทำงาน: จะเกิด syntax error + +นี่เป็นอีกเหตุผลที่ไม่ควรใช้ตัวดำเนินการ `?` แทน `if` +```` \ No newline at end of file From b89eea88db981f0d25438b156a5956db498ebdee Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Thu, 28 Mar 2024 23:35:41 +0700 Subject: [PATCH 6/6] =?UTF-8?q?=E0=B8=9B=E0=B9=89=E0=B8=B2=E0=B8=A2?= =?UTF-8?q?=E0=B8=81=E0=B8=B3=E0=B8=81=E0=B8=B1=E0=B8=9A=E0=B8=AA=E0=B8=B3?= =?UTF-8?q?=E0=B8=AB=E0=B8=A3=E0=B8=B1=E0=B8=9A=20break/continue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1-js/02-first-steps/13-while-for/article.md | 106 +++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) 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 d66c675ef..47ef53112 100644 --- a/1-js/02-first-steps/13-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -304,4 +304,108 @@ if (i > 5) { ...มันจะไม่ทำงาน: จะเกิด syntax error นี่เป็นอีกเหตุผลที่ไม่ควรใช้ตัวดำเนินการ `?` แทน `if` -```` \ No newline at end of file +```` + +## ป้ายกำกับสำหรับ break/continue + +บางครั้งเราต้องการออกจากลูปที่ซ้อนกันหลายชั้นในครั้งเดียว + +ตัวอย่างเช่น ในโค้ดด้านล่าง เราวนลูปด้วย `i` และ `j` พร้อมถามพิกัด `(i, j)` ตั้งแต่ `(0,0)` ถึง `(2,2)`: + +```js run no-beautify +for (let i = 0; i < 3; i++) { + + for (let j = 0; j < 3; j++) { + + let input = prompt(`ค่าที่พิกัด (${i},${j})`, ''); + + // จะทำอย่างไรถ้าเราต้องการออกจากที่นี่ไปยัง Done (ด้านล่าง)? + } +} + +alert('Done!'); +``` + +เราต้องการวิธีหยุดกระบวนการ หากผู้ใช้ยกเลิกการป้อนข้อมูล + +คำสั่ง `break` ปกติหลัง `input` จะออกจากลูป inner เท่านั้น ซึ่งยังไม่เพียงพอ -- ตอนนี้ป้ายกำกับมาช่วยเราได้! + +*ป้ายกำกับ* คือตัวระบุที่มีเครื่องหมายทวิภาคนำหน้าลูป: + +```js +labelName: for (...) { + ... +} +``` + +คำสั่ง `break ` ในลูปด้านล่างจะออกไปยังป้ายกำกับ: + +```js run no-beautify +*!*outer:*/!* for (let i = 0; i < 3; i++) { + + for (let j = 0; j < 3; j++) { + + let input = prompt(`ค่าที่พิกัด (${i},${j})`, ''); + + // ถ้า string ว่างหรือยกเลิก จะออกจากลูปทั้งสอง + if (!input) *!*break outer*/!*; // (*) + + // ทำอะไรบางอย่างกับค่า... + } +} + +alert('Done!'); +``` + +ในโค้ดข้างบน `break outer` จะค้นหาป้ายกำกับชื่อ `outer` ขึ้นไปและออกจากลูปนั้น + +ดังนั้นการควบคุมจะข้ามจาก `(*)` ไปยัง `alert('Done!')` ทันที + +เราสามารถย้ายป้ายกำกับไปอยู่บนบรรทัดแยกต่างหากก็ได้: + +```js no-beautify +outer: +for (let i = 0; i < 3; i++) { ... } +``` + +คำสั่ง `continue` ก็ใช้กับป้ายกำกับได้เช่นกัน ในกรณีนี้ การทำงานของโค้ดจะข้ามไปยังรอบถัดไปของลูปที่ติดป้ายกำกับ + +````warn header="ป้ายกำกับไม่อนุญาตให้ \"กระโดด\" ไปที่ไหนก็ได้" +ป้ายกำกับไม่อนุญาตให้เรากระโดดไปยังตำแหน่งใดๆ ในโค้ดตามใจชอบ + +เช่น ไม่สามารถทำแบบนี้: + +```js +break label; // กระโดดไปที่ป้ายกำกับด้านล่าง (ทำไม่ได้) + +label: for (...) +``` + +คำสั่ง `break` ต้องอยู่ในบล็อกโค้ด โดยทั่วไปแล้ว บล็อกโค้ดใดๆ ที่มีป้ายกำกับก็ใช้ได้ เช่น: + +```js +label: { + // ... + break label; // ใช้ได้ + // ... +} +``` + +...ถึงแม้ว่า 99.9% ของเวลา `break` จะใช้ในลูป ตามที่เราเห็นในตัวอย่างข้างบน + +`continue` ใช้ได้เฉพาะข้างในลูปเท่านั้น +```` + +## สรุป + +เราได้เรียนรู้ลูป 3 ประเภท: + +- `while` -- ตรวจสอบเงื่อนไขก่อนแต่ละรอบ +- `do..while` -- ตรวจสอบเงื่อนไขหลังแต่ละรอบ +- `for (;;)` -- ตรวจสอบเงื่อนไขก่อนแต่ละรอบ พร้อมตั้งค่าเพิ่มเติมได้ + +โดยทั่วไป เพื่อสร้างลูป "ไม่มีที่สิ้นสุด" จะใช้ `while(true)` ลูปแบบนี้เหมือนลูปอื่นๆ คือสามารถหยุดได้ด้วยคำสั่ง `break` + +ถ้าเราไม่ต้องการทำอะไรในรอบปัจจุบัน และต้องการข้ามไปทำรอบถัดไป เราสามารถใช้คำสั่ง `continue` + +`break/continue` รองรับป้ายกำกับไว้ข้างหน้าลูป ป้ายกำกับเป็นวิธีเดียวที่ `break/continue` จะออกจากลูปที่ซ้อนเพื่อไปยังลูปด้านนอกได้ \ No newline at end of file