วัตถุ ที่ทำซ้ำได้ นั้นเป็นลักษณะทั่วไปของอาร์เรย์ นั่นเป็นแนวคิดที่ช่วยให้เราสร้างอ็อบเจ็กต์ใดๆ ก็ตามที่สามารถใช้งานได้ใน for..of
loop
แน่นอนว่า Array สามารถทำซ้ำได้ แต่มีออบเจ็กต์ในตัวอื่นๆ อีกมากมายที่สามารถทำซ้ำได้เช่นกัน ตัวอย่างเช่น สตริงก็สามารถทำซ้ำได้เช่นกัน
หากวัตถุไม่ใช่อาร์เรย์ในทางเทคนิค แต่เป็นตัวแทนคอลเลกชัน (รายการ ชุด) ของบางสิ่งบางอย่าง ดังนั้น for..of
จึงเป็นไวยากรณ์ที่ดีในการวนซ้ำ ดังนั้นเรามาดูวิธีทำให้มันทำงานกัน
เราสามารถเข้าใจแนวคิดของ iterable ได้อย่างง่ายดายโดยการสร้างแนวคิดขึ้นมาเอง
ตัวอย่างเช่น เรามีอ็อบเจ็กต์ที่ไม่ใช่อาร์เรย์ แต่ดูเหมาะสำหรับ for..of
เช่นเดียวกับวัตถุ range
ที่แสดงถึงช่วงของตัวเลข:
ให้ช่วง = { จาก: 1, ถึง: 5 - // เราต้องการให้ for..of ทำงาน: // สำหรับ(ให้จำนวนช่วง) ... num=1,2,3,4,5
ในการทำให้อ็อบเจ็กต์ range
สามารถวนซ้ำได้ (และปล่อย for..of
ทำงาน) เราจำเป็นต้องเพิ่มวิธีการให้กับอ็อบเจ็กต์ชื่อ Symbol.iterator
(สัญลักษณ์พิเศษในตัวสำหรับสิ่งนั้น)
เมื่อ for..of
เริ่มต้น มันจะเรียกเมธอดนั้นหนึ่งครั้ง (หรือเกิดข้อผิดพลาดหากไม่พบ) วิธีการจะต้องส่งคืน ตัววนซ้ำ - วัตถุที่มีวิธีการ next
เป็นต้นไป for..of
ใช้งานได้ กับวัตถุที่ส่งคืนเท่านั้น
เมื่อ for..of
ต้องการค่าถัดไป มันจะเรียก next()
บนวัตถุนั้น
ผลลัพธ์ของ next()
จะต้องมีรูปแบบ {done: Boolean, value: any}
โดยที่ done=true
หมายความว่าการวนซ้ำเสร็จสิ้นแล้ว มิฉะนั้น value
จะเป็นค่าถัดไป
นี่คือการใช้งานเต็มรูปแบบสำหรับ range
ที่มีหมายเหตุ:
ให้ช่วง = { จาก: 1, ถึง: 5 - // 1. โทรไปที่ for..of เริ่มแรกเรียกสิ่งนี้ ช่วง [Symbol.iterator] = ฟังก์ชั่น () { // ...มันจะส่งคืนวัตถุตัววนซ้ำ: // 2. เป็นต้นไป for..of ใช้ได้กับวัตถุตัววนซ้ำด้านล่างเท่านั้น โดยถามถึงค่าถัดไป กลับ { ปัจจุบัน: this.from, สุดท้าย: this.to, // 3. next() ถูกเรียกในการวนซ้ำแต่ละครั้งโดย for..of loop ต่อไป() { // 4. ควรคืนค่าเป็นวัตถุ {done:.., value :...} ถ้า (this.current <= this.last) { กลับ { เสร็จสิ้น: เท็จ ค่า: this.current++ }; } อื่น { กลับ { เสร็จสิ้น: จริง }; - - - - // ตอนนี้มันใช้งานได้แล้ว! สำหรับ (ให้จำนวนช่วง) { การแจ้งเตือน (หมายเลข); // 1 แล้วก็ 2, 3, 4, 5 -
โปรดทราบคุณลักษณะหลักของ iterable: การแยกข้อกังวล
range
นั้นไม่มีเมธอด next()
แต่วัตถุอื่นที่เรียกว่า "ตัววนซ้ำ" จะถูกสร้างขึ้นโดยการเรียกไปยัง range[Symbol.iterator]()
และ next()
จะสร้างค่าสำหรับการวนซ้ำ
ดังนั้นวัตถุตัววนซ้ำจึงแยกออกจากวัตถุที่วนซ้ำ
ในทางเทคนิคแล้ว เราอาจรวมพวกมันเข้าด้วยกันและใช้ range
เป็นตัววนซ้ำเพื่อทำให้โค้ดง่ายขึ้น
แบบนี้:
ให้ช่วง = { จาก: 1, ถึง: 5, [สัญลักษณ์.ตัววนซ้ำ]() { this.current = this.from; คืนสิ่งนี้; - ต่อไป() { ถ้า (this.current <= this.to) { กลับ { เสร็จสิ้น: เท็จ ค่า: this.current++ }; } อื่น { กลับ { เสร็จสิ้น: จริง }; - - - สำหรับ (ให้จำนวนช่วง) { การแจ้งเตือน (หมายเลข); // 1 แล้วก็ 2, 3, 4, 5 -
ตอนนี้ range[Symbol.iterator]()
ส่งคืนอ็อบเจ็กต์ range
เอง: มันมีเมธอด next()
ที่จำเป็นและจดจำความคืบหน้าของการวนซ้ำปัจจุบันใน this.current
สั้นลง? ใช่. และบางครั้งก็เป็นเรื่องปกติเช่นกัน
ข้อเสียคือตอนนี้เป็นไปไม่ได้ที่จะมี for..of
ลูปสองตัวที่ทำงานบนวัตถุพร้อมกัน: พวกมันจะแชร์สถานะการวนซ้ำ เนื่องจากมีตัววนซ้ำเพียงตัวเดียวเท่านั้น นั่นก็คือตัววัตถุนั่นเอง แต่การ for-of แบบขนานสองครั้งนั้นเป็นสิ่งที่หาได้ยาก แม้ในสถานการณ์ที่ไม่พร้อมกันก็ตาม
ตัววนซ้ำที่ไม่มีที่สิ้นสุด
ตัววนซ้ำแบบไม่มีที่สิ้นสุดก็เป็นไปได้เช่นกัน ตัวอย่างเช่น range
จะไม่มีที่สิ้นสุดสำหรับ range.to = Infinity
หรือเราสามารถสร้างวัตถุที่ทำซ้ำได้ซึ่งสร้างลำดับอนันต์ของตัวเลขสุ่มเทียม ยังสามารถนำมาใช้ประโยชน์ได้
ไม่มีข้อจำกัดใน next
มันสามารถส่งกลับค่าได้มากขึ้นเรื่อยๆ ซึ่งเป็นเรื่องปกติ
แน่นอนว่า for..of
วนซ้ำซ้ำได้จะไม่มีที่สิ้นสุด แต่เราสามารถหยุดมันได้เสมอโดยใช้ break
อาร์เรย์และสตริงเป็นตัวทำซ้ำในตัวที่ใช้กันอย่างแพร่หลายที่สุด
สำหรับสตริง for..of
วนซ้ำอักขระ:
สำหรับ (ให้ถ่านของ "ทดสอบ") { // ทริกเกอร์ 4 ครั้ง: หนึ่งครั้งสำหรับอักขระแต่ละตัว การแจ้งเตือน (ถ่าน); // t แล้วก็ e แล้วก็ s แล้วก็ t -
และมันทำงานได้อย่างถูกต้องกับคู่ตัวแทน!
ให้ str = '??'; สำหรับ (ให้ถ่านของ str) { การแจ้งเตือน (ถ่าน); // ? และแล้ว ? -
เพื่อความเข้าใจที่ลึกซึ้งยิ่งขึ้น เรามาดูวิธีใช้ตัววนซ้ำอย่างชัดเจน
เราจะวนซ้ำสตริงในลักษณะเดียวกับ for..of
แต่ด้วยการโทรโดยตรง รหัสนี้สร้างตัววนซ้ำสตริงและรับค่าจากมัน "ด้วยตนเอง":
ให้ str = "สวัสดี"; //ทำแบบเดียวกับ. // สำหรับ (ให้ถ่านของ str) การแจ้งเตือน (ถ่าน); ให้ iterator = str[Symbol.iterator](); ในขณะที่ (จริง) { ให้ผลลัพธ์ = iterator.next(); ถ้า (result.done) แตก; การแจ้งเตือน(result.value); // ส่งออกอักขระทีละตัว -
นั่นไม่จำเป็นเลย แต่ช่วยให้เราควบคุมกระบวนการได้มากกว่า for..of
ตัวอย่างเช่น เราสามารถแบ่งกระบวนการวนซ้ำได้: วนซ้ำเล็กน้อย จากนั้นหยุด ทำอย่างอื่น แล้วดำเนินการต่อในภายหลัง
คำศัพท์อย่างเป็นทางการสองคำมีลักษณะคล้ายกัน แต่แตกต่างกันมาก โปรดตรวจสอบให้แน่ใจว่าคุณเข้าใจดีเพื่อหลีกเลี่ยงความสับสน
Iterables คืออ็อบเจ็กต์ที่ใช้วิธี Symbol.iterator
ตามที่อธิบายไว้ข้างต้น
Array-like คืออ็อบเจ็กต์ที่มีดัชนีและ length
ดังนั้นจึงดูเหมือนอาร์เรย์
เมื่อเราใช้ JavaScript สำหรับงานภาคปฏิบัติในเบราว์เซอร์หรือสภาพแวดล้อมอื่น ๆ เราอาจพบวัตถุที่สามารถทำซ้ำได้หรือมีลักษณะคล้ายอาร์เรย์หรือทั้งสองอย่าง
ตัวอย่างเช่น สตริงมีทั้งแบบวนซ้ำได้ ( for..of
ใช้งานได้) และแบบอาร์เรย์ (มีดัชนีตัวเลขและ length
)
แต่การทำซ้ำได้อาจไม่เหมือนกับอาเรย์ และในทางกลับกัน อาร์เรย์ที่มีลักษณะคล้ายอาเรย์อาจไม่สามารถวนซ้ำได้
ตัวอย่างเช่น range
ในตัวอย่างข้างต้นสามารถทำซ้ำได้ แต่ไม่เหมือนอาร์เรย์ เนื่องจากไม่มีคุณสมบัติและ length
ที่จัดทำดัชนีไว้
และนี่คือออบเจ็กต์ที่มีลักษณะคล้ายอาเรย์ แต่ไม่สามารถทำซ้ำได้:
ให้ arrayLike = { // มีดัชนีและความยาว => เหมือนกับอาร์เรย์ 0: "สวัสดี" 1: "โลก" ความยาว: 2 - // ข้อผิดพลาด (ไม่มี Symbol.iterator) สำหรับ (ให้รายการของ arrayLike) {}
ทั้ง iterables และ array-likes มักจะ ไม่ใช่ arrays พวกมันไม่มี push
, pop
เป็นต้น ซึ่งค่อนข้างไม่สะดวกหากเรามี object ดังกล่าวและต้องการทำงานกับมันเหมือนกับ array เช่นเราต้องการทำงานกับ range
โดยใช้วิธีอาเรย์ จะบรรลุเป้าหมายนั้นได้อย่างไร?
มีวิธีการสากล Array.from ที่ใช้ค่าที่ทำซ้ำได้หรือคล้ายอาร์เรย์และสร้าง Array
"ของจริง" จากนั้น จากนั้นเราก็สามารถเรียกเมธอดอาร์เรย์กับมันได้
ตัวอย่างเช่น:
ให้ arrayLike = { 0: "สวัสดี" 1: "โลก" ความยาว: 2 - ให้ arr = Array.from(arrayLike); - การแจ้งเตือน(arr.pop()); // โลก (วิธีการทำงาน)
Array.from
ที่บรรทัด (*)
รับวัตถุ ตรวจสอบว่าวัตถุนั้นสามารถทำซ้ำได้หรือคล้ายอาร์เรย์ จากนั้นจึงสร้างอาร์เรย์ใหม่และคัดลอกรายการทั้งหมดไปยังวัตถุนั้น
สิ่งเดียวกันนี้เกิดขึ้นสำหรับการทำซ้ำได้:
// สมมติว่าช่วงนั้นนำมาจากตัวอย่างด้านบน ให้ arr = Array.from (ช่วง); การแจ้งเตือน(arr); // 1,2,3,4,5 (การแปลงอาร์เรย์ toString ใช้งานได้)
ไวยากรณ์แบบเต็มสำหรับ Array.from
ยังช่วยให้เรามีฟังก์ชัน “mapping” ที่เป็นทางเลือกได้:
Array.from(obj[, mapFn, thisArg])
อาร์กิวเมนต์ตัวที่สองที่เป็นตัวเลือก mapFn
อาจเป็นฟังก์ชันที่จะใช้กับแต่ละองค์ประกอบก่อนที่จะเพิ่มลงในอาร์เรย์ และ thisArg
ช่วยให้เราสามารถตั้ง this
ได้
ตัวอย่างเช่น:
// สมมติว่าช่วงนั้นนำมาจากตัวอย่างด้านบน // ยกกำลังสองแต่ละตัวเลข ให้ arr = Array.from(range, num => num * num); การแจ้งเตือน(arr); // 1,4,9,16,25
ที่นี่เราใช้ Array.from
เพื่อเปลี่ยนสตริงให้เป็นอาร์เรย์ของอักขระ:
ให้ str = '??'; // แยก str ออกเป็นอาร์เรย์ของอักขระ ให้ตัวอักษร = Array.from(str); การแจ้งเตือน (ตัวอักษร [0]); - การแจ้งเตือน (ตัวอักษร [1]); - การแจ้งเตือน (ตัวอักษรความยาว); // 2
ต่างจาก str.split
ตรงที่มันอาศัยลักษณะที่ทำซ้ำได้ของสตริง ดังนั้น เช่นเดียวกับ for..of
ที่จะทำงานอย่างถูกต้องกับคู่ตัวแทน
ในทางเทคนิคแล้วมันทำเช่นเดียวกับ:
ให้ str = '??'; ให้ตัวอักษร = []; // Array.from ภายในทำการวนซ้ำเดียวกัน สำหรับ (ให้ถ่านของ str) { ตัวอักษร.push(ถ่าน); - การแจ้งเตือน (ตัวอักษร);
…แต่มันสั้นกว่า
เรายังสามารถสร้าง slice
ที่รับรู้ตัวแทนได้ด้วย:
ส่วนฟังก์ชั่น (str, เริ่มต้น, สิ้นสุด) { กลับ Array.from(str).slice(start, end).join(''); - ให้ str = '???'; การแจ้งเตือน ( ชิ้น (str, 1, 3) ); - // วิธีดั้งเดิมไม่รองรับคู่ตัวแทน การแจ้งเตือน ( str.slice (1, 3) ); // ขยะ (สองชิ้นจากคู่ตัวแทนที่แตกต่างกัน)
วัตถุที่สามารถใช้ใน for..of
เรียกว่า iterable
ในทางเทคนิคแล้ว iterables ต้องใช้เมธอดชื่อ Symbol.iterator
ผลลัพธ์ของ obj[Symbol.iterator]()
เรียกว่า iterator มันจัดการกระบวนการวนซ้ำเพิ่มเติม
ตัววนซ้ำต้องมีเมธอดชื่อ next()
ที่ส่งคืนอ็อบเจ็กต์ {done: Boolean, value: any}
ที่นี่ done:true
หมายถึงจุดสิ้นสุดของกระบวนการวนซ้ำ มิฉะนั้น value
จะเป็นค่าถัดไป
เมธอด Symbol.iterator
จะถูกเรียกโดยอัตโนมัติโดย for..of
แต่เราก็สามารถทำได้โดยตรงเช่นกัน
ตัววนซ้ำในตัว เช่น สตริงหรืออาร์เรย์ ก็ใช้ Symbol.iterator
ได้เช่นกัน
ตัววนซ้ำสตริงรู้เกี่ยวกับคู่ตัวแทน
ออบเจ็กต์ที่มีคุณสมบัติและ length
ที่จัดทำดัชนีเรียกว่า array-like วัตถุดังกล่าวอาจมีคุณสมบัติและวิธีการอื่น แต่ไม่มีวิธีการอาร์เรย์ในตัว
หากเราดูภายในข้อกำหนด เราจะเห็นว่าเมธอดบิวท์อินส่วนใหญ่จะถือว่าพวกมันทำงานกับ iterable หรือ array-like แทนที่จะเป็นอาร์เรย์ "ของจริง" เพราะนั่นเป็นนามธรรมมากกว่า
Array.from(obj[, mapFn, thisArg])
สร้าง Array
จริงจาก obj
ที่สามารถทำซ้ำได้หรือคล้ายอาร์เรย์ จากนั้นเราก็สามารถใช้วิธีอาร์เรย์กับมันได้ อาร์กิวเมนต์เสริม mapFn
และ thisArg
ช่วยให้เราใช้ฟังก์ชันกับแต่ละรายการได้