φυβλαςのβλογ
phyblas的博客



บทที่ ๓๗: การวนซ้ำด้วย for๛of และการใช้แม็ปและเซ็ต
เขียนเมื่อ 2020/02/05 09:22
แก้ไขล่าสุด 2021/09/28 16:42


ในบทนี้จะแนะนำวิธีการวนซ้ำด้วย 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() ลบสมาชิกทั้งหมด






-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ