ไทย

ปลดล็อกพลังของการเขียนโปรแกรมเชิงฟังก์ชันด้วย JavaScript arrays เรียนรู้วิธีการแปลง กรอง และลดข้อมูลของคุณอย่างมีประสิทธิภาพโดยใช้เมธอดในตัว

การเรียนรู้การเขียนโปรแกรมเชิงฟังก์ชันด้วย JavaScript Arrays

ในภูมิทัศน์ของการพัฒนาเว็บที่เปลี่ยนแปลงตลอดเวลา JavaScript ยังคงเป็นรากฐานสำคัญ แม้ว่ากระบวนทัศน์การเขียนโปรแกรมเชิงวัตถุและแบบคำสั่งจะยังคงโดดเด่นมานานแล้ว แต่การเขียนโปรแกรมเชิงฟังก์ชัน (FP) กำลังได้รับความสนใจอย่างมาก FP เน้นย้ำถึงความไม่เปลี่ยนรูป ฟังก์ชันบริสุทธิ์ และโค้ดแบบประกาศ ซึ่งนำไปสู่แอปพลิเคชันที่มีความแข็งแกร่ง สามารถบำรุงรักษาได้ และคาดการณ์ได้มากขึ้น วิธีที่มีประสิทธิภาพที่สุดวิธีหนึ่งในการนำการเขียนโปรแกรมเชิงฟังก์ชันมาใช้ใน JavaScript คือการใช้ประโยชน์จากวิธีการอาร์เรย์ดั้งเดิม

คู่มือที่ครอบคลุมนี้จะเจาะลึกถึงวิธีที่คุณสามารถใช้ประโยชน์จากพลังของหลักการเขียนโปรแกรมเชิงฟังก์ชันโดยใช้อาร์เรย์ JavaScript เราจะสำรวจแนวคิดหลักและสาธิตวิธีนำไปใช้โดยใช้วิธีการต่างๆ เช่น map, filter และ reduce ซึ่งจะเปลี่ยนวิธีที่คุณจัดการการจัดการข้อมูล

การเขียนโปรแกรมเชิงฟังก์ชันคืออะไร

ก่อนที่จะเจาะลึกถึงอาร์เรย์ JavaScript ขอให้เรากำหนดการเขียนโปรแกรมเชิงฟังก์ชันโดยสังเขป ที่แก่นแท้ของ FP คือกระบวนทัศน์การเขียนโปรแกรมที่ถือว่าการคำนวณเป็นการประเมินฟังก์ชันทางคณิตศาสตร์และหลีกเลี่ยงการเปลี่ยนแปลงสถานะและข้อมูลที่เปลี่ยนแปลงได้ หลักการสำคัญ ได้แก่:

การนำหลักการเหล่านี้ไปใช้อาจนำไปสู่โค้ดที่เข้าใจ ทดสอบ และแก้ไขข้อบกพร่องได้ง่ายขึ้น โดยเฉพาะอย่างยิ่งในแอปพลิเคชันที่ซับซ้อน วิธีการอาร์เรย์ของ JavaScript เหมาะอย่างยิ่งสำหรับการใช้งานแนวคิดเหล่านี้

พลังของวิธีการ Array ของ JavaScript

JavaScript arrays มาพร้อมกับชุดวิธีการในตัวที่หลากหลาย ซึ่งช่วยให้สามารถจัดการข้อมูลที่ซับซ้อนได้โดยไม่ต้องใช้ลูปแบบดั้งเดิม (เช่น for หรือ while) วิธีการเหล่านี้มักจะส่งคืนอาร์เรย์ใหม่ ส่งเสริมความไม่เปลี่ยนรูป และยอมรับฟังก์ชันเรียกกลับ ทำให้สามารถเข้าถึงได้ในเชิงฟังก์ชัน

มาสำรวจวิธีการอาร์เรย์พื้นฐานที่สุดกัน:

1. Array.prototype.map()

เมธอด map() จะสร้างอาร์เรย์ใหม่ที่เต็มไปด้วยผลลัพธ์ของการเรียกใช้ฟังก์ชันที่ให้ไว้ในทุกองค์ประกอบในอาร์เรย์ที่เรียก เหมาะอย่างยิ่งสำหรับการแปลงแต่ละองค์ประกอบของอาร์เรย์ให้เป็นสิ่งใหม่

ไวยากรณ์:

array.map(callback(currentValue[, index[, array]])[, thisArg])

คุณสมบัติหลัก:

ตัวอย่าง: การเพิ่มจำนวนแต่ละจำนวนเป็นสองเท่า

ลองนึกภาพว่าคุณมีอาร์เรย์ของตัวเลขและคุณต้องการสร้างอาร์เรย์ใหม่ที่แต่ละตัวเลขเพิ่มขึ้นเป็นสองเท่า

const numbers = [1, 2, 3, 4, 5];

// ใช้ map สำหรับการแปลง
const doubledNumbers = numbers.map(number => number * 2);

console.log(numbers); // เอาต์พุต: [1, 2, 3, 4, 5] (อาร์เรย์ต้นฉบับยังคงไม่เปลี่ยนแปลง)
console.log(doubledNumbers); // เอาต์พุต: [2, 4, 6, 8, 10]

ตัวอย่าง: การดึงคุณสมบัติจากวัตถุ

กรณีการใช้งานทั่วไปคือการดึงคุณสมบัติเฉพาะจากอาร์เรย์ของวัตถุ สมมติว่าเรามีรายการผู้ใช้และต้องการรับเพียงชื่อของพวกเขา

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const userNames = users.map(user => user.name);

console.log(userNames); // เอาต์พุต: ['Alice', 'Bob', 'Charlie']

2. Array.prototype.filter()

เมธอด filter() สร้างอาร์เรย์ใหม่ที่มีองค์ประกอบทั้งหมดที่ผ่านการทดสอบที่ดำเนินการโดยฟังก์ชันที่ให้ไว้ ใช้เพื่อเลือกองค์ประกอบตามเงื่อนไข

ไวยากรณ์:

array.filter(callback(element[, index[, array]])[, thisArg])

คุณสมบัติหลัก:

ตัวอย่าง: การกรองตัวเลขคู่

มากรองอาร์เรย์ตัวเลขเพื่อเก็บเฉพาะตัวเลขคู่

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// ใช้ filter เพื่อเลือกตัวเลขคู่
const evenNumbers = numbers.filter(number => number % 2 === 0);

console.log(numbers); // เอาต์พุต: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(evenNumbers); // เอาต์พุต: [2, 4, 6, 8, 10]

ตัวอย่าง: การกรองผู้ใช้ที่ใช้งานอยู่

จากอาร์เรย์ผู้ใช้ของเรา มากรองหาผู้ใช้ที่ถูกระบุว่าใช้งานอยู่

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const activeUsers = users.filter(user => user.isActive);

console.log(activeUsers); 
/* เอาต์พุต:
[
  { id: 1, name: 'Alice', isActive: true },
  { id: 3, name: 'Charlie', isActive: true }
]
*/

3. Array.prototype.reduce()

เมธอด reduce() จะดำเนินการฟังก์ชันเรียกกลับ “ตัวลด” ที่ผู้ใช้จัดหาให้ในแต่ละองค์ประกอบของอาร์เรย์ตามลำดับ โดยส่งผ่านค่าที่ส่งคืนจากการคำนวณบนองค์ประกอบก่อนหน้า ผลลัพธ์สุดท้ายของการเรียกใช้ตัวลดทั่วทั้งองค์ประกอบทั้งหมดของอาร์เรย์คือค่าเดียว

นี่เป็นวิธีการอาร์เรย์ที่ใช้งานได้หลากหลายที่สุดและเป็นรากฐานสำคัญของรูปแบบการเขียนโปรแกรมเชิงฟังก์ชันจำนวนมาก ทำให้คุณสามารถ “ลด” อาร์เรย์เป็นค่าเดียว (เช่น ผลรวม ผลิตภัณฑ์ จำนวน หรือแม้แต่วัตถุหรืออาร์เรย์ใหม่)

ไวยากรณ์:

array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

คุณสมบัติหลัก:

ตัวอย่าง: การรวมตัวเลข

มาบวกตัวเลขทั้งหมดในอาร์เรย์ของเรากัน

const numbers = [1, 2, 3, 4, 5];

// ใช้ reduce เพื่อรวมตัวเลข
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 0 คือ initialValue

console.log(sum); // เอาต์พุต: 15

คำอธิบาย:

ตัวอย่าง: การจัดกลุ่มวัตถุตามคุณสมบัติ

เราสามารถใช้ reduce เพื่อแปลงอาร์เรย์ของวัตถุเป็นวัตถุที่ค่าถูกจัดกลุ่มตามคุณสมบัติเฉพาะ มาจัดกลุ่มผู้ใช้ของเราตามสถานะ `isActive` ของพวกเขา

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const groupedUsers = users.reduce((acc, user) => {
  const status = user.isActive ? 'active' : 'inactive';
  if (!acc[status]) {
    acc[status] = [];
  }
  acc[status].push(user);
  return acc;
}, {}); // วัตถุว่างเปล่า {} คือ initialValue

console.log(groupedUsers);
/* เอาต์พุต:
{
  active: [
    { id: 1, name: 'Alice', isActive: true },
    { id: 3, name: 'Charlie', isActive: true }
  ],
  inactive: [
    { id: 2, name: 'Bob', isActive: false },
    { id: 4, name: 'David', isActive: false }
  ]
}
*/

ตัวอย่าง: การนับจำนวนครั้งที่ปรากฏ

มานับความถี่ของผลไม้แต่ละชนิดในรายการกัน

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const fruitCounts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});

console.log(fruitCounts); // เอาต์พุต: { apple: 3, banana: 2, orange: 1 }

4. Array.prototype.forEach()

ในขณะที่ forEach() ไม่ได้ส่งคืนอาร์เรย์ใหม่ และมักถูกพิจารณาว่าเป็นแบบสั่งมากกว่า เนื่องจากจุดประสงค์หลักคือการดำเนินการฟังก์ชันสำหรับแต่ละองค์ประกอบอาร์เรย์ มันยังคงเป็นวิธีการพื้นฐานที่มีบทบาทในรูปแบบเชิงฟังก์ชัน โดยเฉพาะอย่างยิ่งเมื่อจำเป็นต้องมีผลข้างเคียง หรือเมื่อมีการทำซ้ำโดยไม่จำเป็นต้องมีผลลัพธ์ที่เปลี่ยนแปลง

ไวยากรณ์:

array.forEach(callback(element[, index[, array]])[, thisArg])

คุณสมบัติหลัก:

ตัวอย่าง: การบันทึกแต่ละองค์ประกอบ

const messages = ['Hello', 'Functional', 'World'];

messages.forEach(message => console.log(message));
// เอาต์พุต:
// Hello
// Functional
// World

หมายเหตุ: สำหรับการแปลงและการกรอง map และ filter เป็นที่ต้องการเนื่องจากความไม่เปลี่ยนรูปและลักษณะแบบประกาศ ใช forEach เมื่อคุณต้องการดำเนินการเฉพาะสำหรับแต่ละรายการโดยไม่ต้องรวบรวมผลลัพธ์ลงในโครงสร้างใหม่

5. Array.prototype.find() และ Array.prototype.findIndex()

วิธีการเหล่านี้มีประโยชน์สำหรับการระบุตำแหน่งองค์ประกอบเฉพาะในอาร์เรย์

ตัวอย่าง: การค้นหาผู้ใช้

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const bob = users.find(user => user.name === 'Bob');
const bobIndex = users.findIndex(user => user.name === 'Bob');
const nonExistentUser = users.find(user => user.name === 'David');
const nonExistentIndex = users.findIndex(user => user.name === 'David');

console.log(bob); // เอาต์พุต: { id: 2, name: 'Bob' }
console.log(bobIndex); // เอาต์พุต: 1
console.log(nonExistentUser); // เอาต์พุต: undefined
console.log(nonExistentIndex); // เอาต์พุต: -1

6. Array.prototype.some() และ Array.prototype.every()

วิธีการเหล่านี้ทดสอบว่าองค์ประกอบทั้งหมดในอาร์เรย์ผ่านการทดสอบที่ดำเนินการโดยฟังก์ชันที่ให้ไว้

ตัวอย่าง: การตรวจสอบสถานะผู้ใช้

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true }
];

const hasInactiveUser = users.some(user => !user.isActive);
const allAreActive = users.every(user => user.isActive);

console.log(hasInactiveUser); // เอาต์พุต: true (เพราะ Bob ไม่ได้ใช้งาน)
console.log(allAreActive); // เอาต์พุต: false (เพราะ Bob ไม่ได้ใช้งาน)

const allUsersActive = users.filter(user => user.isActive).length === users.length;
console.log(allUsersActive); // เอาต์พุต: false

// ทางเลือกอื่นโดยใช้ every โดยตรง
const allUsersActiveDirect = users.every(user => user.isActive);
console.log(allUsersActiveDirect); // เอาต์พุต: false

การเชื่อมโยงวิธีการ Array สำหรับการดำเนินการที่ซับซ้อน

พลังที่แท้จริงของการเขียนโปรแกรมเชิงฟังก์ชันด้วย JavaScript arrays จะเปล่งประกายเมื่อคุณเชื่อมโยงวิธีการเหล่านี้เข้าด้วยกัน เนื่องจากวิธีการส่วนใหญ่เหล่านี้ส่งคืนอาร์เรย์ใหม่ (ยกเว้น forEach) คุณจึงสามารถส่งออกผลลัพธ์ของวิธีหนึ่งไปยังอินพุตของอีกวิธีหนึ่งได้อย่างราบรื่น สร้างไปป์ไลน์ข้อมูลที่สง่างามและอ่านง่าย

ตัวอย่าง: การค้นหาชื่อผู้ใช้ที่ใช้งานอยู่และเพิ่ม ID เป็นสองเท่า

มาค้นหาผู้ใช้ที่ใช้งานอยู่ทั้งหมด ดึงชื่อของพวกเขา จากนั้นสร้างอาร์เรย์ใหม่ที่แต่ละชื่อนำหน้าด้วยตัวเลขที่แสดงถึงดัชนีในรายการ *ที่กรองแล้ว* และ ID ของพวกเขาเพิ่มขึ้นเป็นสองเท่า

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: true },
  { id: 5, name: 'Eve', isActive: false }
];

const processedActiveUsers = users
  .filter(user => user.isActive) // รับเฉพาะผู้ใช้ที่ใช้งานอยู่
  .map((user, index) => ({      // แปลงผู้ใช้ที่ใช้งานอยู่แต่ละราย
    name: `${index + 1}. ${user.name}`,
    doubledId: user.id * 2
  }));

console.log(processedActiveUsers);
/* เอาต์พุต:
[
  { name: '1. Alice', doubledId: 2 },
  { name: '2. Charlie', doubledId: 6 },
  { name: '3. David', doubledId: 8 }
]
*/

แนวทางแบบลูกโซ่นี้เป็นแบบประกาศ: เราระบุขั้นตอน (กรอง จากนั้นทำแผนที่) โดยไม่มีการจัดการลูปที่ชัดเจน นอกจากนี้ยังไม่สามารถเปลี่ยนแปลงได้ เนื่องจากแต่ละขั้นตอนจะสร้างอาร์เรย์หรือวัตถุใหม่ โดยปล่อยให้อาร์เรย์ users ดั้งเดิมไม่เปลี่ยนแปลง

ความไม่เปลี่ยนรูปในการปฏิบัติ

การเขียนโปรแกรมเชิงฟังก์ชันอาศัยความไม่เปลี่ยนรูปเป็นอย่างมาก ซึ่งหมายความว่าแทนที่จะปรับเปลี่ยนโครงสร้างข้อมูลที่มีอยู่ คุณจะสร้างโครงสร้างข้อมูลใหม่พร้อมกับการเปลี่ยนแปลงที่ต้องการ วิธีการอาร์เรย์ของ JavaScript เช่น map, filter และ slice สนับสนุนสิ่งนี้โดยส่งคืนอาร์เรย์ใหม่

เหตุใดความไม่เปลี่ยนรูปจึงมีความสำคัญ

เมื่อคุณต้องดำเนินการที่จะเปลี่ยนแปลงอาร์เรย์แบบดั้งเดิม (เช่น การเพิ่มหรือลบองค์ประกอบ) คุณสามารถบรรลุความไม่เปลี่ยนรูปโดยใช้วิธีการต่างๆ เช่น slice ไวยากรณ์การแพร่กระจาย (...) หรือโดยการรวมวิธีการเชิงฟังก์ชันอื่นๆ

ตัวอย่าง: การเพิ่มองค์ประกอบอย่างไม่เปลี่ยนรูป

const originalArray = [1, 2, 3];

// วิธีการสั่ง (ปรับเปลี่ยน originalArray)
// originalArray.push(4);

// วิธีการเชิงฟังก์ชันโดยใช้ไวยากรณ์การแพร่กระจาย
const newArrayWithPush = [...originalArray, 4];
console.log(originalArray); // เอาต์พุต: [1, 2, 3]
console.log(newArrayWithPush); // เอาต์พุต: [1, 2, 3, 4]

// วิธีการเชิงฟังก์ชันโดยใช้ slice และการเชื่อมต่อ (น้อยกว่าทั่วไปในตอนนี้)
const newArrayWithSlice = originalArray.slice(0, originalArray.length).concat(4);
console.log(newArrayWithSlice); // เอาต์พุต: [1, 2, 3, 4]

ตัวอย่าง: การลบองค์ประกอบอย่างไม่เปลี่ยนรูป

const originalArray = [1, 2, 3, 4, 5];

// ลบองค์ประกอบที่ดัชนี 2 (ค่า 3)

// วิธีการเชิงฟังก์ชันโดยใช้ slice และไวยากรณ์การแพร่กระจาย
const newArrayAfterSplice = [
  ...originalArray.slice(0, 2),
  ...originalArray.slice(3)
];
console.log(originalArray); // เอาต์พุต: [1, 2, 3, 4, 5]
console.log(newArrayAfterSplice); // เอาต์พุต: [1, 2, 4, 5]

// ใช้ filter เพื่อลบค่าเฉพาะ
const newValueToRemove = 3;
const arrayWithoutValue = originalArray.filter(item => item !== newValueToRemove);
console.log(arrayWithoutValue); // เอาต์พุต: [1, 2, 4, 5]

แนวทางปฏิบัติที่ดีที่สุดและเทคนิคขั้นสูง

เมื่อคุณคุ้นเคยกับวิธีการอาร์เรย์เชิงฟังก์ชันมากขึ้น ให้พิจารณาแนวทางปฏิบัติต่อไปนี้:

ตัวอย่าง: แนวทางเชิงฟังก์ชันในการรวมข้อมูล

ลองนึกภาพว่าคุณมีข้อมูลยอดขายจากภูมิภาคต่างๆ และต้องการคำนวณยอดขายรวมสำหรับแต่ละภูมิภาค จากนั้นค้นหาภูมิภาคที่มียอดขายสูงสุด

const salesData = [
  { region: 'North', amount: 100 },
  { region: 'South', amount: 150 },
  { region: 'North', amount: 120 },
  { region: 'East', amount: 200 },
  { region: 'South', amount: 180 },
  { region: 'North', amount: 90 }
];

// 1. คำนวณยอดขายรวมต่อภูมิภาคโดยใช้ reduce
const salesByRegion = salesData.reduce((acc, sale) => {
  acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
  return acc;
}, {});

// salesByRegion จะเป็น: { North: 310, South: 330, East: 200 }

// 2. แปลงวัตถุที่รวมเป็นอาร์เรย์ของวัตถุเพื่อการประมวลผลเพิ่มเติม
const salesArray = Object.keys(salesByRegion).map(region => ({
  region: region,
  totalAmount: salesByRegion[region]
}));

// salesArray จะเป็น: [
//   { region: 'North', totalAmount: 310 },
//   { region: 'South', totalAmount: 330 },
//   { region: 'East', totalAmount: 200 }
// ]

// 3. ค้นหาภูมิภาคที่มียอดขายสูงสุดโดยใช้ reduce
const highestSalesRegion = salesArray.reduce((max, current) => {
  return current.totalAmount > max.totalAmount ? current : max;
}, { region: '', totalAmount: -Infinity }); // เริ่มต้นด้วยตัวเลขที่น้อยมาก

console.log('Sales by Region:', salesByRegion);
console.log('Sales Array:', salesArray);
console.log('Region with Highest Sales:', highestSalesRegion);

/*
เอาต์พุต:
Sales by Region: { North: 310, South: 330, East: 200 }
Sales Array: [
  { region: 'North', totalAmount: 310 },
  { region: 'South', totalAmount: 330 },
  { region: 'East', totalAmount: 200 }
]
Region with Highest Sales: { region: 'South', totalAmount: 330 }
*/

บทสรุป

การเขียนโปรแกรมเชิงฟังก์ชันด้วย JavaScript arrays ไม่ได้เป็นเพียงทางเลือกด้านสไตล์เท่านั้น มันเป็นวิธีที่มีประสิทธิภาพในการเขียนโค้ดที่สะอาดกว่า คาดการณ์ได้มากขึ้น และแข็งแกร่งยิ่งขึ้น ด้วยการนำวิธีการต่างๆ เช่น map, filter และ reduce มาใช้ คุณสามารถแปลง สอบถาม และรวมข้อมูลของคุณได้อย่างมีประสิทธิภาพในขณะที่ปฏิบัติตามหลักการพื้นฐานของการเขียนโปรแกรมเชิงฟังก์ชัน โดยเฉพาะอย่างยิ่งความไม่เปลี่ยนรูปและฟังก์ชันบริสุทธิ์

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