ในบทนี้จะแนะนำวิธีการวนซ้ำด้วย for๛of พร้อมกับออบเจ็กต์ชนิดใหม่ ๒ ชนิดที่มีลักษณะคล้ายกับ
แถวลำดับ
(array) ที่มีอยู่แต่แรก
แต่ว่ามีการใช้งานต่างกันออกไป คือ
แม็ป (map) และ
เซ็ต (set) ทั้ง ๒ นี้ถูกเพิ่มเข้ามาใน
ES6
การวนซ้ำด้วย for๛of ในแถวลำดับและสายอักขระ
for๛of เป็นคำสั่งที่ใช้กับข้อมูลชนิดแถวลำดับ รวมถึงข้อมูลชนิดอื่นๆที่สามารถไล่เรียงได้ เช่นแม็ปและเซ็ต
ซึ่งจะกล่าวถึงต่อไป
หน้าที่ของ for๛of คือเอาไว้ไล่สมาชิกข้างในออกมาทีละตัว
ตัวอย่างการใช้งาน
let s = "";
let rikekoi = ["ยุกิมุระ", "ฮิมุโระ", "คานาเดะ", "อิบาราดะ"];
for (let k of rikekoi){
s += "[" + k + "]";
}
alert(s); // ได้ [ยุกิมุระ][ฮิมุโระ][คานาเดะ][อิบาราดะ]
ในจาวาสคริปต์ตั้งแต่เดิมก็มีคำสั่งวนซ้ำด้วย for๛in (ดูใน
บทที่ ๒๓) ซึ่งดูเผินๆแล้วคล้ายกับ for๛of แต่จริงๆต่างกันพอสมควร
และอาจทำให้สับสนได้ง่าย
for๛in นั้นเมื่อใช้กับแถวลำดับจะไล่เรียงเลขลำดับของค่าข้างใน ไม่ใช่ตัวค่าข้างใน
ถ้าต้องการค่าข้างในก็ต้องใช้ตัวเลขนั้นมาใส่ใน [] อีกที
ตัวอย่างการใช้ for๛in เทียบกับ for๛of ด้านบนเพื่อให้เห็นความแตกต่าง
let s = "";
let rikekoi = ["ชินยะ", "อายาเมะ", "โคโตโนฮะ", "เอนะ"];
for (let i in rikekoi){
let k = rikekoi[i];
s += "[" + i + ": " + k + "]";
}
alert(s); // ได้ [0: ชินยะ][1: อายาเมะ][2: โคโตโนฮะ][3: เอนะ]
จะเห็นว่าเมื่อใช้ for๛in จะเป็นการไล่เลขลำดับ 0, 1, 2, ... ถ้าจะเอาข้อมูลข้างในก็ต้องเอาเลขลำดับที่ได้นั้นไปใช้เป็นคีย์อีกที ซึ่งจะดูแล้วยุ่งยาก
ในขณะที่ for๛of นั้นจะไล่เรียงเอาค่าข้างในเลย
for๛of ในจาวาสคริปต์จะเหมือนกับ for๛in ในภาษาไพธอนหรือรูบี ในขณะที่ for๛in ในจาวาสคริปต์จะต่างออกไป
ดังนั้นต้องระวังอย่าสับสน
นอกจากนี้ for๛of ยังสามารถใช้กับสายอักขระได้ด้วย เช่น
let sss = "นางาโนะ";
let arr = []
for (let s of sss){
arr.unshift(s);
}
alert(arr); // ได้ ะ,น,โ,า,ง,า,น
ข้อแตกต่างระหว่างแม็ปกับออบเจ็กต์ธรรมดา
แม็ป (map) เป็นสิ่งที่อาจเปรียบเหมือน
แถวลำดับแบบจับคู่ (associative array) ในภาษาอื่นทั่วไป
หรือถ้าเทียบเป็นภาษาไพธอนก็คือ
ดิกชันนารี (dictionary) ในภาษาซีคือ
โครงสร้างข้อมูล
(structure)
ใน
บทที่ ๙
ได้พูดถึงเรื่องการสร้างออบเจ็กต์ในจาวาสคริปต์นั้นก็เปรียบเสมือนแถวลำดับแบบจับคู่
สามารถใช้งานในลักษณะเดียวกันได้เหมือนกัน คือมี
คีย์ (key) จับคู๋กับ
ค่า (value)
อย่างไรก็ตาม แม็ป ซึ่งถูกนำเข้ามาใช้ใหม่ใน ES6 นี้ก็ยังมีข้อแตกต่างจากออบเจ็กต์แบบดั้งเดิม ดังนี้
- ค่าของข้อมูลในออบเจ็กต์ธรรมดานั้นจริงๆก็คือค่าของพรอเพอร์ตี แต่ในแม็ปจะเก็บส่วนของค่าเอาไว้แยกจากพรอเพอร์ตี
ช่วยให้ไม่ปะปนกัน ซึ่งบางครั้งก็ทำให้เกิดบั๊กที่ไม่คาดคิดได้เหมือนกัน
- ในออบเจ็กต์ธรรมดานั้น คีย์ก็คือชื่อพรอเพอร์ตี ซึ่งอาจเป็นได้แค่สายอักขระหรือซิมโบลเท่านั้น (การใช้ซิมโบลดูใน บทที่ ๓๕) หรือถึงแม้จะใช้เป็นตัวเลข
แต่เนื้อในก็คือสายอักขระอยู่ดี ในขณะที่แม็ปจะใช้คีย์เป็นข้อมูลขนิดไหนก็ได้
- แม็ปสามารถหาจำนวนข้อมูลข้างในได้ง่ายกว่า แค่ดูที่พรอเพอร์ตี .size ในขณะที่ออบเจ็กต์ธรรมดาไม่มีวิธีดูโดยตรง
อาจต้องใช้ฟังก์ชัน Object.keys() เพื่อไล่เอาคีย์ออกมา แล้วค่อยหา .length
- ออบเจ็กต์ธรรมดาใช้การวนซ้ำด้วย for๛of ไม่ได้ แต่แม็ปใช้ได้
- ออบเจ็กต์ธรรมดาใช้การวนซ้ำด้วย for๛in ได้ แต่แม็ปใช้ไม่ได้
การสร้างและใช้แม็ป
แม็ปเป็นออบเจ็กต์ชนิดหนึ่ง วิธีการสร้างต้องใช้ new กับคอนสตรักเตอร์ Map()
โดยใส่แถวลำดับที่ใส่แถวลำดับของคีย์และค่า
let mappu = new Map([
["เอฮิเมะ", "มัตสึยามะ"],
["คางาวะ", "ทากามัตสึ"],
["คานางาวะ", "โยโกฮามะ"],
]);
การดูค่าข้างในใช้ .get("คีย์") ไม่ใช่ใช้วงเล็บเหลี่ยม [ ] เหมือนอย่างในออบเจ็กต์ธรรมดา ถ้าใส่คีย์ที่ไม่มีจะได้
undefined
alert(mappu.get("คางาวะ")); // ได้ ทากามัตสึ
alert(mappu.get("ไอจิ")); // ได้ undefined
alert(mappu["คางาวะ"]); // ได้ undifined
สามารถใช้ .set() เพื่อเพิ่มสมาชิกใหม่เข้าไปทีหลังก็ได้
mappu.set("ไอจิ", "นาโงยะ")
alert(mappu.get("ไอจิ")); // ได้ นาโงยะ
สามารถใช้ .forEach(ฟังก์ชัน) เพื่อไล่เรียงค่าออกมาดูทีละตัวได้
let s = "";
mappu.forEach((x) => {
s += "~" + x + "≈";
})
alert(s); // ได้ ~มัตสึยามะ≈~ทากามัตสึ≈~โยโกฮามะ≈~นาโงยะ≈
ดูว่ามีคีย์ที่ต้องการหรือเปล่าโดยใช้ .has()
alert(mappu.has("ไอจิ")); // ได้ true
alert(mappu.has("กิฟุ")); // ได้ false
ดูจำนวนสมาชิกที่มีอยู่ได้ที่พรอเพอร์ตี .size (ระวัง ไม่ต้องใส่วงเล็บข้างหลังเพราะเป็นพรอเพอร์ตี
ไม่ใช่เมธอดเหมือนอย่างตัวอื่น)
alert(mappu.size) // ได้ 4
ลบสมาชิกได้โดยใช้ .delete("คีย์") หรือถ้าต้องการลบทั้งหมดก็ใช้ .clear()
mappu.delete("คางาวะ")
alert(mappu.size) // ได้ 3
mappu.clear()
alert(mappu.size) // ได้ 0
ข้อควรระวังเกี่ยวกับคีย์ในการใช้แม็ป
แม็ปจะต่างจากออบเจ็กต์ตรงที่ตัวเลขและสายอักขระใช้แทนกันไม่ได้ เช่นลองเทียบดูตามนี้
let obji = { 1.1: "หนึ่งจุดหนึ่ง" };
alert(obji[1.1]); // ได้ หนึ่งจุดหนึ่ง
alert(obji["1.1"]); // ได้ หนึ่งจุดหนึ่ง
let mapi = new Map([[1.1, "หนึ่งจุดหนึ่ง"]]);
alert(mapi.get(1.1)); // ได้ หนึ่งจุดหนึ่ง
alert(mapi.get("1.1")); // ได้ undefined
จะเห็นว่าออบเจ็กต์ธรรมดาใส่ตัวเลขหรือสายอักขระก็ได้ แต่แม็ปต้องใส่เป็นตัวเลขจริงๆเท่านั้น
เพราะคีย์ที่ใส่ไปนั้นเป็นตัวเลข แต่ถ้าคีย์ที่ใส่ไปเป็นสายอักขระก็ต้องใส่คีย์เป็นสายอักขระเท่านั้นจึงจะเข้าถึง
และ NaN ก็ใช้เป็นคีย์ได้เช่นกัน และแม้ว่าปกติแล้ว NaN==NaN จะได้ false ก็ตาม แต่ในโลกของแม็ปจะมองว่า NaN ก็คือ NaN
เหมือนกัน
let mapo = new Map([[NaN,"หน่านี้ !"]]);
alert(mapo.get(NaN)) // ได้ หน่านี้ !
ในขณะที่ถ้าคีย์เป็นออบเจ็กต์ จะดูว่าเป็นออบเจ็กต์ตัวเดียวกันหรือเปล่า ถ้าเป็นออบเจ็กต์คนละตัวกัน
แม้จะหน้าตาเหมือนกันก็ไม่ถือว่าใช้แทนกันได้
let mapa = new Map([[{},"ไรเนี่ย !"]]);
alert(mapa.get({})) // ได้ undefined
let oba = {};
let mape = new Map([[oba,"ไรเนี่ย !"]]);
alert(mape.get(oba)) // ได้ ไรเนี่ย !
การวนซ้ำด้วย for๛of ในแม็ป
แม็ปสามารถใช้ for๛of ได้เช่นเดียวกับแถวลำดับ แต่มีข้อแตกต่างคือแม็ปจะมีทั้งคีย์และค่าออกมาพร้อมกัน จึงต้องการตัวแปร ๒
ตัวมารับ
ตัวอย่างการใช้งาน
let mappa = new Map([
["ฮกไกโด", "ซัปโปโระ"],
["อิวาเตะ", "โมริโอกะ"],
["มิยางิ", "เซนได"],
]);
let s = "";
for (let [k, v] of mappa){
s += "[" + k + ": " + v + "]";
}
alert(s); // ได้ [ฮกไกโด: ซัปโปโระ][อิวาเตะ: โมริโอกะ][มิยางิ: เซนได]
หากต้องการไล่แค่คีย์ให้ใช้เมธอด .keys() หรือถ้าต้องการแค่ค่าก็ใช้เมธอด .values() นอกจากนี้ยังมีเมธอด entries()
ซึ่งจะไล่เรียงค่ากับคีย์ออกมาพร้อมกัน แต่ก็ไม่ต่างอะไรกับการใช้ for๛of โดยตรง
let mappia = new Map([
["โอกินาวะ", "นาฮะ"],
["ชิมาเนะ", "มัตสึเอะ"],
["เฮียวโงะ", "โควเบะ"],
]);
let kk = "";
for (let k of mappia.keys()) {
kk += "[" + k + "]";
}
alert(kk); // ได้ [โอกินาวะ][ชิมาเนะ][เฮียวโงะ]
let vv = "";
for (let v of mappia.values()) {
vv += "[" + v + "]";
}
alert(vv); // ได้ [นาฮะ][มัตสึเอะ][โควเบะ]
let kkvv = "";
for (let [k, v] of mappia.entries()) {
kkvv += "[" + k + ": " + v + "]";
}
alert(kkvv); // ได้ [โอกินาวะ: นาฮะ][ชิมาเนะ: มัตสึเอะ][เฮียวโงะ: โควเบะ]
ที่จริงแล้วเมธอด .keys() .values() .entry() นี้เป็นเมธอดที่จะให้ผลเป็นอิเทอเรเตอร์ที่มาดึงข้อมูลในออบเจ็กต์นั้นไปวนซ้ำ เกี่ยวกับเรื่องอิเทอเรเตอร์เขียนถึงใน
บทที่ ๓๘
สรุปเมธอดและพรอเพอร์ตีของแม็ป
size |
จำนวนสมาชิกทั้งหมด |
set(คีย์, ค่า) |
ตั้งค่าให้คีย์นั้น |
get(คีย์) |
เอาค่าที่เก็บอยู่ในคีย์นั้น |
has(คีย์) |
ดูว่ามีคีย์นั้นหรือไม่ (ได้ true, false) |
forEach(ฟังก์ชัน) |
ไล่เอาคีย์มาทำกับฟังก์ชันทีละตัว |
keys() |
ไล่เรียงคีย์ (ใช้กับ for๛of) |
values() |
ไล่เรียงค่า (ใช้กับ for๛of) |
entries() |
ไล่เรียงค่าและคีย์ (ใช้กับ for๛of) |
delete(คีย์) |
ลบคีย์ตัวนั้นออก |
clear() |
ลบสมาชิกทั้งหมด |
การสร้างและใช้เซ็ต
เซ็ต (set) คือออบเจ็กต์ที่เก็บค่าหลายๆตัวเรียงต่อกันคล้ายกับแถวลำดับ
แต่จะต่างกันตรงที่จะไม่มีสมาชิกซ้ำกันในเซ็ต
การสร้างเซ็ตทำได้โดยใช้คอนสตรักเตอร์ Set() โดยใส่แถวลำดับของสมาชิกที่ต้องการใส่ในเซ็ตลงไป
let setto = new Set(["มาโดกะ", "โฮมุระ", "ซายากะ"]);
alert(setto); // ได้ [object Set]
ดูจำนวนสมาชิกได้ที่พรอเพอร์ตี .size
alert(setto.size); // ได้ 3
เพิ่มตัวใหม่เข้าไปโดยใช้ .add() แต่ถ้าตัวที่เพิ่มเข้าไปมีอยู่แล้วจะไม่เกิดอะไรขึ้น
setto.add("มามิ");
alert(setto.size); // ได้ 4
setto.add("โฮมุระ");
alert(setto.size); // ได้ 4
ดูว่ามีค่าไหนอยู่หรือเปล่าด้วย .has()
alert(setto.has("เคียวโกะ")); // ได้ false
alert(setto.has("ซายากะ")); // ได้ true
ใช้ .forEach() เพื่อวนซ้ำได้
let ss = "";
setto.forEach((x)=>{
ss += "{" + x + "}";
});
alert(ss); // ได้ {มาโดกะ}{โฮมุระ}{ซายากะ}{มามิ}
ใช้ for๛of ในการวนซ้ำได้แบบเดียวกับแถวลำดับ โดยอาจใช้ .values() ก็จะได้ผลเหมือนเดิม
let s = "";
for (let x of setto) {
s += "♫" + x + "♬";
}
alert(s) // ได้ ♫มาโดกะ♬♫โฮมุระ♬♫ซายากะ♬♫มามิ♬
let s = "";
for (let x of setto.values()) {
s += "♩" + x + "♪";
}
alert(s); // ได้ ♩มาโดกะ♪♩โฮมุระ♪♩ซายากะ♪♩มามิ♪
ลบค่าออกได้โดยใช้ .delete() ถ้าจะลบทั้งหมดใช้ .clear()
setto.delete("มามิ");
alert(setto.size); // ได้ 3
setto.clear();
alert(setto.size); // ได้ 0
สรุปเมธอดและพรอเพอร์ตีของเซ็ต
size |
จำนวนสมาชิกทั้งหมด |
add(ค่า) |
เพิ่มค่านั้นเข้าไป |
has(ค่า) |
ดูว่ามีค่านั้นหรือไม่ (ได้ true, false) |
forEach(ฟังก์ชัน) |
ไล่เอาค่ามาทำกับฟังก์ชันทีละตัว |
values() |
ไล่เรียงค่า (ใช้กับ for๛of) |
delete(ค่า) |
ลบคีย์ตัวนั้นออก |
clear() |
ลบสมาชิกทั้งหมด |