Trong JavaScript, việc xử lý dữ liệu mảng một cách hiệu quả là rất quan trọng, đặc biệt khi làm việc với dữ liệu từ API hoặc cần thao tác trên danh sách phần tử. Ba phương thức phổ biến .map(), .reduce(), và .filter() giúp thực hiện các thao tác như biến đổi, lọc và tổng hợp dữ liệu một cách dễ dàng, ngắn gọn và hiệu quả hơn so với các vòng lặp truyền thống như for hay forEach.

Trong bài viết này, VietnamWorks inTECH sẽ giúp bạn hiểu rõ cách sử dụng .map(), .reduce(), và .filter() thông qua các ví dụ trực quan, từ đó giúp tối ưu hóa code JavaScript và nâng cao kỹ năng lập trình.

1. .map()

Hãy cùng tìm hiểu cách hoạt động của .map() thông qua một ví dụ đơn giản. Giả sử bạn có một mảng chứa nhiều đối tượng, mỗi đối tượng đại diện cho một người. Tuy nhiên, thứ bạn thực sự cần là một mảng chỉ chứa id của mỗi người.

Dữ liệu bạn có

var officers = [

  { id: 20, name: 'Captain Piett' },

  { id: 24, name: 'General Veers' },

  { id: 56, name: 'Admiral Ozzel' },

  { id: 88, name: 'Commander Jerjerrod' }

];

Kết quả mong muốn

[20, 24, 56, 88]

Có nhiều cách để đạt được điều này. Bạn có thể tạo một mảng rỗng, sau đó sử dụng .forEach(), .for(...of) hoặc một vòng lặp for() thông thường để thêm từng id vào mảng.

Sử dụng .forEach()

var officersIds = [];

officers.forEach(function (officer) {

  officersIds.push(officer.id);

});

Lưu ý rằng bạn phải tạo một mảng rỗng trước đó.

Sử dụng .map()

var officersIds = officers.map(function (officer) {

  return officer.id;

});

 

Viết ngắn gọn hơn với arrow function (ES6)

const officersIds = officers.map(officer => officer.id);

Phương thức .map() nhận vào hai đối số:

1. Một hàm callback (chạy trên từng phần tử của mảng)

2. Một giá trị context tùy chọn (sẽ được dùng làm this trong callback, nhưng không được sử dụng trong ví dụ trên)

Lưu ý:

  • .map() sẽ tạo một mảng mới mà có cùng số phần tử với mảng ban đầu.

  • Hàm callback sẽ trả về giá trị mới cho từng phần tử trong mảng kết quả.

2. .reduce()

Cũng giống như .map(), phương thức .reduce() cũng chạy một hàm callback trên từng phần tử của mảng. Tuy nhiên, điểm khác biệt là reduce truyền kết quả của callback (gọi là accumulator) từ phần tử này sang phần tử khác.

Accumulator có thể là bất cứ kiểu dữ liệu nào (số, chuỗi, đối tượng, v.v.) và phải được khởi tạo hoặc truyền vào khi gọi .reduce().

Ví dụ 1: Tính tổng số năm kinh nghiệm

Giả sử bạn có một mảng chứa danh sách phi công cùng số năm kinh nghiệm của họ:

var pilots = [

  {

    id: 10,

    name: "Poe Dameron",

    years: 14,

  },

  {

    id: 2,

    name: "Temmin 'Snap' Wexley",

    years: 30,

  },

  {

    id: 41,

    name: "Tallissan Lintra",

    years: 16,

  },

  {

    id: 99,

    name: "Ello Asty",

    years: 22,

  }

];

 

Bạn cần tính tổng số năm kinh nghiệm của tất cả phi công. Với .reduce(), điều này trở nên cực kỳ đơn giản:

var totalYears = pilots.reduce(function (accumulator, pilot) {

  return accumulator + pilot.years;

}, 0);

 

Ở đây, chúng ta đặt giá trị khởi tạo của accumulator là 0. Sau khi hàm callback chạy trên tất cả phần tử trong mảng, reduce sẽ trả về giá trị cuối cùng của accumulator (trong trường hợp này là 82).

Viết ngắn gọn hơn với arrow function (ES6):

const totalYears = pilots.reduce((acc, pilot) => acc + pilot.years, 0);

Ví dụ 2: Tìm phi công có nhiều kinh nghiệm nhất

Bây giờ, giả sử bạn muốn tìm phi công có nhiều năm kinh nghiệm nhất. Bạn cũng có thể sử dụng reduce() để làm điều này:

var mostExpPilot = pilots.reduce(function (oldest, pilot) {

  return (oldest.years || 0) > pilot.years ? oldest : pilot;

}, {});

 

  • Ở đây, oldest đóng vai trò là accumulator.

  • Callback sẽ so sánh oldest với từng phi công trong danh sách.

  • Nếu một phi công có số năm kinh nghiệm cao hơn oldest, thì phi công đó trở thành giá trị mới của oldest.

Kết quả cuối cùng sẽ là phi công có nhiều năm kinh nghiệm nhất trong danh sách.

Tóm lại

  • .reduce() là một cách mạnh mẽ để tạo ra một giá trị duy nhất hoặc một đối tượng từ một mảng.

  • Bạn có thể dùng nó để tính toán tổng, tìm giá trị lớn nhất/nhỏ nhất, gom nhóm dữ liệu, v.v.

  • Để tối ưu code, có thể sử dụng arrow function trong ES6.

3. .filter()

Giả sử bạn có một mảng nhưng chỉ muốn lấy một số phần tử trong đó. Đây chính là lúc .filter() phát huy tác dụng!

Ví dụ: Lọc phi công theo team

Dưới đây là danh sách các phi công:

var pilots = [

  {

    id: 2,

    name: "Wedge Antilles",

    faction: "Rebels",

  },

  {

    id: 8,

    name: "Ciena Ree",

    faction: "Empire",

  },

  {

    id: 40,

    name: "Iden Versio",

    faction: "Empire",

  },

  {

    id: 66,

    name: "Thane Kyrell",

    faction: "Rebels",

  }

];

 

Giả sử bạn muốn chia danh sách này thành hai mảng:

  • Một mảng chỉ chứa phi công của team Rebels.

  • Một mảng chỉ chứa phi công của team Empire.

Bạn có thể sử dụng .filter() như sau:

var rebels = pilots.filter(function (pilot) {

  return pilot.faction === "Rebels";

});

var empire = pilots.filter(function (pilot) {

  return pilot.faction === "Empire";

});

Viết ngắn gọn hơn với arrow function (ES6):

const rebels = pilots.filter(pilot => pilot.faction === "Rebels");

const empire = pilots.filter(pilot => pilot.faction === "Empire");

Cách hoạt động của .filter()

  • .filter() chạy qua từng phần tử trong mảng.

  • Nếu callback function trả về true, phần tử đó sẽ được đưa vào mảng mới.

  • Nếu callback function trả về false, phần tử đó sẽ bị loại bỏ.

Vậy nên, .filter() rất hữu ích khi bạn cần lọc dữ liệu theo điều kiện nhất định!

4. Kết hợp .map(), .reduce(), và .filter()

Vì cả ba phương thức này đều được gọi trên mảng, và .map() cùng .filter() đều trả về mảng mới, chúng ta có thể kết hợp chúng lại trong một chuỗi (chaining) để xử lý dữ liệu một cách hiệu quả.

Ví dụ: Tính tổng điểm của những người có Thần lực (Force Users)

Dưới đây là danh sách nhân sự:

var personnel = [

  {

    id: 5,

    name: "Luke Skywalker",

    pilotingScore: 98,

    shootingScore: 56,

    isForceUser: true,

  },

  {

    id: 82,

    name: "Sabine Wren",

    pilotingScore: 73,

    shootingScore: 99,

    isForceUser: false,

  },

  {

    id: 22,

    name: "Zeb Orellios",

    pilotingScore: 20,

    shootingScore: 59,

    isForceUser: false,

  },

  {

    id: 15,

    name: "Ezra Bridger",

    pilotingScore: 43,

    shootingScore: 67,

    isForceUser: true,

  },

  {

    id: 11,

    name: "Caleb Dume",

    pilotingScore: 71,

    shootingScore: 85,

    isForceUser: true,

  },

];

 

Bước 1: Lọc ra những người có Thần lực (Force Users)

Dùng .filter() để giữ lại những người có isForceUser === true:

var jediPersonnel = personnel.filter(function (person) {

  return person.isForceUser;

});

Kết quả: Chúng ta còn lại 3 Jedi (Luke, Ezra, Caleb).

Bước 2: Tạo mảng mới chứa tổng điểm của mỗi Jedi

Dùng .map() để tính tổng điểm (pilotingScore + shootingScore) của từng Jedi:

var jediScores = jediPersonnel.map(function (jedi) {

  return jedi.pilotingScore + jedi.shootingScore;

});

Kết quả: [154, 110, 156]

Bước 3: Tính tổng điểm của tất cả Jedi

Dùng .reduce() để tính tổng tất cả các điểm trong mảng jediScores:

var totalJediScore = jediScores.reduce(function (acc, score) {

  return acc + score;

}, 0);

Kết quả: 420

Tối ưu bằng cách kết hợp (chaining) tất cả trong một dòng

var totalJediScore = personnel

  .filter(function (person) {

    return person.isForceUser;

  })

  .map(function (jedi) {

    return jedi.pilotingScore + jedi.shootingScore;

  })

  .reduce(function (acc, score) {

    return acc + score;

  }, 0);

Rút gọn hơn với Arrow Function (ES6)

const totalJediScore = personnel

  .filter(person => person.isForceUser)

  .map(jedi => jedi.pilotingScore + jedi.shootingScore)

  .reduce((acc, score) => acc + score, 0);

Lời kết

Ba phương thức .map(), .reduce(), và .filter() không chỉ giúp viết code ngắn gọn hơn mà còn tăng hiệu suất và khả năng đọc hiểu của chương trình. Thay vì sử dụng các vòng lặp truyền thống và phải thao tác thủ công với dữ liệu, hãy tận dụng sức mạnh của những phương thức này để xử lý mảng một cách hiệu quả.

Việc thành thạo .map(), .reduce(), và .filter() sẽ giúp nâng cao kỹ năng lập trình JavaScript, đặc biệt trong việc xử lý dữ liệu và làm việc với API. Hãy thử áp dụng chúng vào các dự án thực tế để thấy rõ sự khác biệt!

VietnamWorks inTECH