φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๒๗: เมธอดสำหรับไล่จัดการสมาชิกในแถวลำดับ
เขียนเมื่อ 2019/08/09 09:17
แก้ไขล่าสุด 2021/09/28 16:42


เมธอดของแถวลำดับที่เพิ่มมาใน ES5

จาวาสคริปต์ใน ES5 นั้นได้เพิ่มเมธอดสำหรับทำการวนซ้ำเพื่อไล่จัดการสมาชิกต่างๆในแถวลำดับมาหลายตัว

เดิมทีใน ES3 นั้นปกติจะใช้ while หรือ for เพื่อทำการวนซ้ำ ดังที่เขียนถึงในบทที่ ๑๑ แต่เมื่อใช้เมธอดที่เพิ่มมาใน ES5 จะทำให้สะดวกขึ้นมาก

ได้แก่

.forEach(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว
.map(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วสร้างเป็นแถวลำดับของค่าที่ฟังก์ชัน f คืนมา
.filter(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วคัดเหลือเฉพาะที่คืนค่า true
.every(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วดูค่าที่คืนมา ถ้าทุกตัวเป็น true ก็ได้ true ถ้ามี false แม้แต่ตัวเดียวก็จะได้ false
.some(f(x)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว แล้วดูค่าที่คืนมา ถ้าทุกตัวเป็น false ก็ได้ false ถ้ามี true แม้แต่ตัวเดียวก็จะได้ true
.reduce(f(a,b)) ไล่ทำฟังก์ชัน f ซ้ำกับสมาชิกในแถวลำดับทีละตัว ค่าที่คืนมาจะแทนในพารามิเตอร์ตัวแรกของรอบถัดไป สุดท้ายจะได้ค่าที่คืนจากรอบสุดท้าย
.reduceRight(f(a,b)) เหมือน .redece แต่ไล่จากตัวสุดท้ายมา



เมธอด forEach

วิธีใช้
แถวลำดับ.forEach(ฟังก์ชันที่ต้องการให้จัดการกับสมาชิกทุกตัว)

ตัวอย่าง
var ahan = ["ผัดไทย", "ราดหน้า", "แกงส้ม", "โจ๊กคนอร์"];
var saikhai = function(x) {
  raikan_ahan += x + "ใส่ไข่\n";
};

var raikan_ahan = "";
ahan.forEach(saikhai);
alert(raikan_ahan);

ได้
ผัดไทยใส่ไข่
ราดหน้าใส่ไข่
แกงส้มใส่ไข่
โจ๊กคนอร์ใส่ไข่

แต่โดยทั่วไปแล้ววิธีการที่ใช้บ่อยก็คือ สร้างฟังก์ชันขึ้นมาเดี๋ยวนั้นภายในวงเล็บหลัง forEach เลย คือ
แถวลำดับ.forEach(function(พารามิเตอร์) {
  สิ่งที่จะทำซ้ำกับทุกตัว
});

เช่นตัวอย่างที่แล้วเขียนใหม่ให้ได้ผลเหมือนเดิมเป็นแบบนี้
var ahan = ["ผัดไทย", "ราดหน้า", "แกงส้ม", "โจ๊กคนอร์"];

var raikan_ahan = "";
ahan.forEach(function(x) {
  raikan_ahan += x + "ใส่ไข่\n";
});
alert(raikan_ahan);

แบบนี้ก็ไม่ต้องไปสร้างฟังก์ชันขึ้นมาใหม่ก่อน ถ้ายังไงก็จะใช้แค่ตรงนี้แล้วทิ้งไปเลยอยู่แล้ว

ข้อเสียคือเขียนแบบนี้แล้วทำให้วงเล็บซ้อนกัน ดูแล้วเข้าใจยากสำหรับมือใหม่ สาเหตุที่ทำให้จาวาสคริปต์ดูเป็นภาษาที่ไม่สวยงาม มองแล้วยุ่งเหยิงก็มาจากตรงนี้ด้วย

แต่คนที่ใช้จาวาสคริปต์ทั่วไปจะเขียนแบบนี้กันจนชินเหมือนกับไม่มองว่าเป็นปัญหา ดังนั้นจึงต้องทำความคุ้นเคยกับการเขียนแบบนี้เอาไว้ด้วย

เมื่อใช้ .forEach สิ่งที่ถูกส่งเข้าไปในฟังก์ชันนั้นนอกจากข้อมูลในแถวลำดับแล้ว ยังมีเลขลำดับด้วย โดยจะไปแทนในพารามิเตอร์ตัวที่ ๒ ถ้าใส่ลงไปด้วย สามารถใช้เลขลำดับได้
var khrueangduem = ["โอวัลติน", "น้ำเก๊กฮวย", "โค้ก", "ชาอูหลง"];

var raikan = "";
khrueangduem.forEach(function(x, i) {
  raikan += i + ". " + x + "\n";
});
alert(raikan);

ได้
0. โอวัลติน
1. น้ำเก๊กฮวย
2. โค้ก
3. ชาอูหลง

นอกจากนี้หากใส่พารามิเตอร์ตัวที่ ๓ พารามิเตอร์นั้นจะแทนตัวแถวลำดับทั้งหมดเอง เช่น
var chue = ["ฟลุค", "โช้ค", "ท็อป"];
var s = "";
chue.forEach(function(x, i, ar) {
  s += x + "ได้ที่ " + (1 + i) + " ในกลุ่ม {" + ar + "}\n";
});
alert(s);
ได้
ฟลุคได้ที่ 1 ในกลุ่ม {ฟลุค,โช้ค,ท็อป}
โช้คได้ที่ 2 ในกลุ่ม {ฟลุค,โช้ค,ท็อป}
ท็อปได้ที่ 3 ในกลุ่ม {ฟลุค,โช้ค,ท็อป}



ความเปลี่ยนแปลงของข้อมูลในแถวลำดับที่ใช้วนซ้ำ

ข้อมูลในแถวลำดับที่นำมาเข้า .forEach นั้นแค่ถูกใส่เป็นอาร์กิวเมนต์ให้มาแทนในพารามิเตอร์ของฟังก์ชัน ดังนั้นต่อให้มีการแทนค่าใหม่เข้าไปในตัวแปรที่เป็นพารามิเตอร์นั้นก็ไม่ได้เป็นการแก้ค่าแต่อย่างใด
var ar = [1, 2, 3];

ar.forEach(function(x) {
  x = x + 10;
});
alert(ar); // ได้ 1,2,3

จะเห็นว่าค่าในแถวลำดับ ar ไม่ได้มีการเปลี่ยนแปลงแม้จะใส่ค่าใหม่ใน .forEach

แต่หากสมาชิกข้างในเป็นออบเจ็กต์ก็จะเกิดการเปลี่ยนแปลงพรอเพอร์ตีข้างในได้

เช่น
var pokemon = [
  { chue: "พาราส", lv: 20 },
  { chue: "คงปัง", lv: 21 },
  { chue: "ดิกดา", lv: 23 }
];

pokemon.forEach(function(x) {
  x.lv += 2;
});
alert(pokemon[0].lv); // ได้ 22
alert(pokemon[1].lv); // ได้ 23
alert(pokemon[2].lv); // ได้ 25

คุณสมบัติที่กล่าวมานี้ไม่เพียงแค่ forEach แต่ฟังก์ชันอื่นที่จะแนะนำในบทนี้เองก็เช่นกัน



เมธอด map

เมธอด map นั้นคล้ายกับ forEach คือจะวนซ้ำไล่ตามสมาชิกในแถวลำดับ แต่จะต่างกันตรงที่ฟังก์ชันที่ใช้ใน map นั้นจะต้องมีการคืนค่ากลับมา และค่านั้นจะถูกนำไปสร้างเป็นแถวลำดับใหม่

ตัวอย่างเช่น ใช้
var kanmusu = ["คิริชิมะ", "อากางิ", "คงโงว", "ยูดาจิ"];
var kanmusu_kaini = kanmusu.map(function(x) {
  return x + " ไคนิ ";
});
alert(kanmusu_kaini); // ได้ คิริชิมะ ไคนิ ,อากางิ ไคนิ ,คงโงว ไคนิ ,ยูดาจิ ไคนิ
alert(kanmusu); // ได้ คิริชิมะ,อากางิ,คงโงว,ยูดาจิ


map จะคืนค่าที่คืนกลับมาเป็นแถวลำดับใหม่ ส่วนตัวข้อมูลในแถวลำดับเดิมที่ถูกใช้ map นั้นจะไม่ได้มีการเปลี่ยนแปลง



เมธอด filter

เมธอด filter ใช้สำหรับเป็นตัวกรองข้อมูลเพื่อคัดเหลือเฉพาะส่วนที่ต้องการ

วิธีการใช้คล้ายกับ map แต่ฟังก์ชันที่ใช้กับ filter จะเป็นเงื่อนไขในการเหลือข้อมูลไว้ true หรือ false ผลที่ได้คือจะคืนค่าเป็นแถวลำดับของสมาชิกตัวที่ให้ค่าเป็น true ส่วนตัวที่ false จะไม่ได้มาด้วย

ตัวอย่าง
var lek = [2, 5, 6, 9, 10, 11, 19, 28];

var lekkhu = lek.filter(function(x) {
  return x % 2 == 0;
});
alert(lekkhu); // ได้ 2,6,10,28

var lekkhi = lek.filter(function(x) {
  return x % 2 == 1;
});
alert(lekkhi); // ได้ 5,9,11,19


อีกตัวอย่าง
var pokemon = [
  { chue: "การ์ดี", nak: "19.0" },
  { chue: "เนียวโรโม", nak: "12.4" },
  { chue: "เคซีย์", nak: "19.5" },
  { chue: "วันริกี", nak: "19.5" },
  { chue: "มาดัตสึโบมิ", nak: "4.0" }
];
var pokemon_bao = pokemon.filter(function(x) {
  return x.nak < 15;
});
alert(pokemon_bao.length); // ได้ 2
alert(pokemon_bao[0].chue); // ได้ เนียวโรโม
alert(pokemon_bao[1].chue); // ได้ มาดัตสึโบมิ
alert(pokemon_bao[2].chue); // ได้ TypeError: Cannot read property 'chue' of undefined

จากตัวอย่างนี้เป็นการคัดกรองเฉพาะ pokemon ตัวที่มี nak (น้ำหนัก) ต่ำว่า 15 ผลที่ได้จึงเหลือแค่ ๒ ตัว



เมธอด every และ some

เมธอด every จะนำเอาสมาชิกแต่ละตัวในแถวลำดับมาเข้าฟังก์ชันที่ใส่ลงไป แล้วจะดูว่าทุกตัวล้วนคืนค่า true มาหรือเปล่า ถ้า true ทั้งหมดจึงจะคืนค่า true แต่ถ้ามีไม่ใช่ true แม้แต่ตัวเดียวก็จะคืนค่า false

ส่วน some นั้นจะคล้ายกัน แต่จะคืนค่า true เมื่อมี true แม้แค่เพียงตัวเดียว ต้องไม่มี true สักตัวจึงจะได้ false

ตัวอย่าง
var pokemon = [
  { chue: "อีวุย", sung: 0.3, nak: 6.5 },
  { chue: "เชาเวอร์ส", sung: 1.0, nak: 29.0 },
  { chue: "ธันเดอร์ส", sung: 0.8, nak: 24.5 },
  { chue: "บูสเตอร์", sung: 0.9, nak: 25.0 }
];

var f1 = function(x) {
  return x.sung > 0.5;
};
alert(pokemon.map(f1)); // ได้ false,true,true,true
alert(pokemon.some(f1)); // ได้ true
alert(pokemon.every(f1)); // ได้ false
alert(pokemon.filter(f1).length); // ได้ 3

var f2 = function(x) {
  return x.nak > 30;
};
alert(pokemon.map(f2)); // ได้ false,false,false,false
alert(pokemon.some(f2)); // ได้ false
alert(pokemon.every(f2)); // ได้ false
alert(pokemon.filter(f2).length); // ได้ 0

var f3 = function(x) {
  return x.nak < 30;
};
alert(pokemon.map(f3)); // ได้ true,true,true,true
alert(pokemon.some(f3)); // ได้ true
alert(pokemon.every(f3)); // ได้ true
alert(pokemon.filter(f3).length); // ได้ 4

อนึ่ง หากไม่สนเรื่องค่าที่คืนกลับมาแล้ว เมธอดทั้ง every, some, filter, map ล้วนสามารถทำหน้าที่แทน forEach ได้ เพราะยังไงฟังก์ชันที่ถูกใส่ลงไปก็จะถูกนำไปทำกับสมาชิกทุกตัว เพียงแต่ forEach จะไม่ได้คืนค่าอะไรออกมาเท่านั้น



เมธอด reduce และ reduceRight

เมธอดสุดท้ายในกลุ่มนี้ ซึ่งมีการทำงานที่ค่อนข้างซับซ้อนกว่า ๕ ตัวที่แนะนำมาข้างต้นสักหน่อย นั่นคือ .reduce

เมธอด reduce จะทำการจัดการกับสมาชิกในแถวลำดับ โดยที่รอบแรกจะทำกับ ๒ ตัวแรก (ตำแหน่ง 0 กับ 1) โดยจะมาแทนในพารามิเตอร์ตัวแรกและตัวที่ ๒

จากนั้นค่าที่คืนกลับในรอบแรกจะกลายมาเป็นพารามิเตอร์ในการวนซ้ำรอบที่ ๒ โดยที่รอบนี้พารามิเตอร์ตัวที่ ๒ จะเป็นค่าตัวที่ ๓ (ตำแหน่ง 2)

จากนั้นค่าที่คืนกลับจากรอบนี้ก็จะถูกนำไปใช้ในรอบถัดไป และไล่ไปเรื่อยๆ จนครบจำนวนสมาชิก

สมมุติว่ามีสมาชิก n ตัว ฟังก์ชันจะถูกทำทั้งหมด n-1 ครั้ง โดยครั้งที่ i จะใช้สมาชิกตัวที่ i มาเป็นพารามิเตอร์ตัวที่ ๒

หากต้องการเลขลำดับให้ใส่เป็นพารามิเตอร์ตัวที่ ๓ ต่อได้

รูปแบบการเขียนเป็นดังนี้
แถวลำดับ.reduce(function(a, b, i) {
  return ค่าที่จะแทนใน a ในรอบถัดไป;
});

ในที่นี้ b คือสมาชิกตัวหลังที่ไล่ไปถึง ส่วน i คือลำดับของสมาชิกตัวนั้น ส่วน a ในรอบแรกจะเป็นสมาชิกตัวแรก (ลำดับที่ 0) แต่ตั้งแต่รอบถัดไปจะเป็นค่าที่ได้มาจากรอบที่แล้ว

อธิบายเป็นคำพูดแบบนี้อาจเข้าใจได้ยาก ดูตัวอย่างประกอบน่าจะช่วยให้เข้าใจได้ง่ายขึ้น

ตัวอย่าง
var merazoma = ["เม", "รา", "โซ", "ม่า"];
var s = "";
var phonlap = merazoma.reduce(function(a, b, i) {
  s += "รอบที่ " + i + ": ";
  s += "a = " + a + ", ";
  s += "b = " + b + "\n";
  return a + b;
});
alert(s + "สุดท้ายได้: " + phonlap);
ได้
รอบที่ 1: a = เม, b = รา
รอบที่ 2: a = เมรา, b = โซ
รอบที่ 3: a = เมราโซ, b = ม่า
สุดท้ายได้: เมราโซม่า

ในตัวอย่างนี้รอบแรก a เป็นสมาชิกตัวแรก คือ "เม" ส่วน b เป็นตัวถัดมา คือ "รา" สุดท้ายฟังก์ชันนี้คืนค่า "เมรา" ออกมา และถูกนำไปแทนเป็น a ในรอบถัดไป

ไล่แบบนี้ไปเรื่อยๆ จนจบแถวลำดับ

หากเขียนใหม่ให้ทำแบบเดียวกันโดยใช้ while จะได้แบบนี้
var merazoma = ["เม", "รา", "โซ", "ม่า"];
var s = "";
var i = 1;
var len = merazoma.length;
var a = merazoma[0];
while (i < len) {
  var b = merazoma[i];
  s += "รอบที่ " + i + ": ";
  s += "a = " + a + ", ";
  s += "b = " + b + "\n";
  a = a + b;
  i++;
}
var phonlap = a;
alert(s + "สุดท้ายได้: " + phonlap);

จะเห็นว่าพอใช้ .reduce แล้วจะเขียนสั้นกว่าเขียน while ตามแบบเดิม

ส่วนเมธอด reduceRight จะคล้ายกับเมธอด reduce แต่ต่างกันตรงที่ทำจากขวามาซ้าย ไล่จากตัวสุดท้ายก่อน

ตัวอย่าง
var merazoma = ["ม่า", "รา", "กี", "เบ"];
var s = "";
var phonlap = merazoma.reduceRight(function(a, b, i) {
  s += "รอบที่ " + i + ": ";
  s += "a = " + a + ", ";
  s += "b = " + b + "\n";
  return a + b;
});
alert(s + "สุดท้ายได้: " + phonlap);
ได้
รอบที่ 2: a = เบ, b = กี
รอบที่ 1: a = เบกี, b = รา
รอบที่ 0: a = เบกีรา, b = ม่า
สุดท้ายได้: เบกีราม่า

เมื่อใช้ .reduceRight เลขลำดับซึ่งแทนในพารามิเตอร์ตัวที่ ๓ นั้นจะนับถอยหลังแทน โดยเริ่มจาก ความยาว-2 ไล่ไปจนถึง 0



ประยุกต์ใช้ reduce และ reduceRight

ประโยชน์ที่พอจะเห็นใช้งาน .reduce และ .reduceRight กันนั้น เช่น

การรวมสมาชิกทุกตัวในแถวลำดับ
var lek = [3, 5, 9, 12, 18];
var ruam = lek.reduce(function(a, b) {
  return a + b;
});
alert(ruam); // ได้ 47

การหาค่าสูงสุดหรือต่ำสุดในแถวลำดับ
var lek = [66, 32, 11, 71, 43, 25, 38];

var maksut = lek.reduce(function(a, b) {
  if (a > b) return a;
  else return b;
});
alert(maksut); // ได้ 71

var noisut = lek.reduce(function(a, b) {
  if (a < b) return a;
  else return b;
});
alert(noisut); // ได้ 11

หาว่าตัวไหนยาวที่สุด พร้อมทั้งดูค่าลำดับของตัวนั้น
var chue = ["นางาโตะ", "มุตสึ", "อิเสะ", "ยุกิกาเซะ", "ฮิวงะ", "คางะ"];
var yaosut = chue.reduce(function(a, b, i) {
  if (i == 1) a = { i: 0, chue: a };
  b = { i: i, chue: b };
  if (a.chue.length > b.chue.length) return a;
  else return b;
});
alert("ที่ชื่อยาวสุดคือ " + yaosut.chue + " ลำดับที่ " + yaosut.i); // ได้ ที่ชื่อยาวสุดคือ ยุกิกาเซะ ลำดับที่ 3


จะเห็นว่าฟังก์ชันต่างๆเหล่านี้ช่วยให้การวนซ้ำเพื่อทำอะไรต่างๆในสมาชิกของแถวลำดับนั้นง่ายลง เขียนสั้นลง หากเทียบกับการใช้ while เพื่อวนซ้ำ หากใช้เป็นก็สามารถนำไปใช้ทำอะไรต่างๆได้มากมาย




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

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

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

หมวดหมู่

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



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

  ค้นหาบทความ

  บทความแนะนำ

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

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文