Vài cách thay thế vòng lặp (loop) truyền thống trong JS

Vòng lặp (loop) là thứ cơ bản nhất trong lập trình, và cũng là thứ thường xuyên được các developer sử dụng. Bên cạnh đó cũng tồn tại những function build sẵn hỗ trợ việc loop trở nên vui hơn. Ý mình là các function kiểu kiểu cao cấp như map, filter, reduce, … đấy.

Về cơ bản các function này đều liên quan tới khái niệm callback trong js, nên các bạn cần tìm hiểu callback trước khi sử dụng các function này nhé. À thêm nữa, để bắt kịp xu hướng mình sẽ đưa ví dụ sử dụng arrow function (gọi là lambda function cũng được).

Loop mảng và chỉnh sửa item nhận về mảng mới

Viết loop thường

1
2
3
4
5
6
7
8
var students = ['Mai', 'Cúc', 'Phượng', 'Hồng', 'Huệ', 'Đào'];

var studentsUpperCase = [];
for (var i = 0; i < students.length; i++) {
studentsUpperCase.push(students[i].toUpperCase());
}

console.info(studentsUpperCase); // [ "MAI", "CÚC", "PHƯỢNG", "HỒNG", "HUỆ", "ĐÀO" ]

Giải pháp sử dụng map:

1
2
3
4
5
6
7
8
9
var students = ['Mai', 'Cúc', 'Phượng', 'Hồng', 'Huệ', 'Đào'];

var studentsUpperCase = students.map(value => value.toUpperCase());
// or
var studentsUpperCase = students.map(value => {
return value.toUpperCase();
});

console.info(studentsUpperCase); // [ "MAI", "CÚC", "PHƯỢNG", "HỒNG", "HUỆ", "ĐÀO" ]

Map sẽ trả về một mảng mới, mỗi giá trị trong mảng mới này được return từ callback truyền vào map. Khi sử dụng map thì không dùng continue hay break được, có thể xài reduce để khắc chế được điều này. À nếu bạn không return về thì undefined sẽ được return về.

Loop mảng và gọi function nào đó

Viết loop thường

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function showNameStudent(student) {
console.info(student.name);
}

var students = [
{ name: 'Mai', age: 24 },
{ name: 'Cúc', age: 20 },
{ name: 'Phượng', age: 18 },
{ name: 'Hồng', age: 25 },
{ name: 'Huệ', age: 29 },
{ name: 'Đào', age: 19 }
];

for (var i = 0; i < students.length; i++) {
showNameStudent(students[i]);
}

// Mai
// Cúc
// Phượng
// Hồng
// Huệ
// Đào

Dùng forEach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function showNameStudent(student) {
console.info(student.name);
}

var students = [
{ name: 'Mai', age: 24 },
{ name: 'Cúc', age: 20 },
{ name: 'Phượng', age: 18 },
{ name: 'Hồng', age: 25 },
{ name: 'Huệ', age: 29 },
{ name: 'Đào', age: 19 }
];

students.forEach(showNameStudent);
//or
students.forEach(student => showNameStudent(student));

// Mai
// Cúc
// Phượng
// Hồng
// Huệ
// Đào

Dùng map ở case này vẫn được, nhưng map sẽ trả về mảng, mà chúng ta chỉ cần loop rồi gọi hàm thôi, thì forEach là chuẩn nhất.

Chọn lọc, lọc phần tử trong mảng

Giả sử mình có mảng students và mình chỉ muốn lấy student nào có tuổi trên 20 mà thôi.

Viết loop thường

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var students = [
{ name: 'Mai', age: 24 },
{ name: 'Cúc', age: 20 },
{ name: 'Phượng', age: 18 },
{ name: 'Hồng', age: 25 },
{ name: 'Huệ', age: 29 },
{ name: 'Đào', age: 19 }
];

var olderStudents = [];
for (var i = 0; i < students.length; i++) {
var student = students[i];

if (student.age > 20) {
olderStudents.push(student);
}
}

console.info(olderStudents);
// { name: "Mai", age: 24 }
// { name: "Hồng", age: 25 }
// { name: "Huệ", age: 29 }

Dùng filter gọn gàng

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var students = [
{ name: 'Mai', age: 24 },
{ name: 'Cúc', age: 20 },
{ name: 'Phượng', age: 18 },
{ name: 'Hồng', age: 25 },
{ name: 'Huệ', age: 29 },
{ name: 'Đào', age: 19 }
];

var olderStudents = students.filter(student => student.age > 20);

console.log(olderStudents);
// { name: "Mai", age: 24 }
// { name: "Hồng", age: 25 }
// { name: "Huệ", age: 29 }

Dùng reduce, cách hoạt động của reduce bạn tham khảo ở đây

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var students = [
{ name: 'Mai', age: 24 },
{ name: 'Cúc', age: 20 },
{ name: 'Phượng', age: 18 },
{ name: 'Hồng', age: 25 },
{ name: 'Huệ', age: 29 },
{ name: 'Đào', age: 19 }
];

var olderStudents = students.reduce((accumulator, currentStudent) => {
if (currentStudent.age > 20) {
accumulator.push(currentStudent);
}

return accumulator;
}, []);

console.info(olderStudents);
// { name: "Mai", age: 24 }
// { name: "Hồng", age: 25 }
// { name: "Huệ", age: 29 }

Vậy khi nào cần sử dụng reduce, xài khi nào chuẩn xác nhất, cá nhân mình nghĩ khi bạn cần cộng dồn hoặc giá trị trong một mảng, hoặc dồn những giá trị có một điểm chung nào đó vào một mảng riêng.

Giả sử hôm nay bạn bán hàng, mỗi lần bán bạn thu về được số tiền là numbers. Đến cuối ngày bạn muốn tính tổng xem mình đã bán được bao nhiêu tiền.

1
2
3
4
5
6
7
var numbers = [123, 43, 734, 347, 445, 23, 345, 54, 65];

var total = numbers.reduce((acc, item) => acc + item, 0);

console.info(total);
// bán được
// 2179

Như đã nói, tham khảo cách `reduce` hoạt động để hiểu hơn.

Kiểm tra xem trong mảng có chứa phần tử này không?

Viết loop thường

1
2
3
4
5
6
7
8
9
10
11
var students = ['Mai', 'Cúc', 'Phượng', 'Hồng', 'Huệ', 'Đào'];

var hasMai = false;
for (var i = 0; i < students.length; i++) {
if (students[i] === 'Mai') {
hasMai = true;
break;
}
}

console.info(hasMai);

Dùng some

1
2
3
4
5
6
var students = ['Mai', 'Cúc', 'Phượng', 'Hồng', 'Huệ', 'Đào'];

var hasMai = students.some(name => name === 'Mai');

console.info(hasMai);
// true

Vậy nếu mảng object thì như thế nào.

1
2
3
4
5
6
7
8
9
10
11
12
var students = [
{ name: 'Mai', age: 24 },
{ name: 'Cúc', age: 20 },
{ name: 'Phượng', age: 18 },
{ name: 'Hồng', age: 25 },
{ name: 'Huệ', age: 29 },
{ name: 'Đào', age: 19 }
];

var hasOlderStudent = students.some(student => student.age > 20);

console.info(hasOlderStudent);

Hàm some sẽ chạy duyệt mảng tìm nếu thỏa điều kiện thì return true, không có thì false. À nếu mảng không có phần tử nào (empty array) thì auto return về false.

Ngoài ra có thể sử dụng includes hoặc indexOf để thay thế.

Duyệt hết mảng

Cũng tương tự .some() nhưng every() có vài điểm khác biệt, every sẽ duyệt hết mảng bất kể là đã tìm thấy điều kiện. Và nếu mảng là mảng rỗng thì mặc định sẽ return true.

Tóm lại

Về cơ bản chúng cũng đều loop, và chúng giúp bạn lập trình vui hơn, khỏe hơn ở vài trường hợp và chúng không dùng để thay thế loop truyền thống. Có vài đều bạn cần lưu ý khi dùng các function này.

  • Tốc độ thực thi sẽ chậm hơn loop truyền thống nên mảng gặp mảng lớn thì hơi xanh mặt.
  • Nếu cần dùng async/await thì sẽ phức tạp hơn, không như loop thường.
  • Nếu không tìm hiểu kỹ, hoặc dùng không đúng cách sẽ gây lãng phí, ví dụ xài map hoặc every để check xem có phần tử nào hay không, map chạy hết mảng, và lúc tìm thấy bạn không thể break để stop lại được.
  • Nếu callback truyền vào những function này là function thường (ở các ví dụ trên mình xài arrow function) thì this binding vào cũng khác so với this gọi trong loop thường.

À for of cũng là cách để duyệt mảng vui hơn nhiều đây, không riêng chi mấy function này đâu.

Mình thấy một repo trên github có nói về về giải pháp thay thế loop, các bạn xem thử: You don’t (may not) need loops

Cuối cùng sẽ có kha khá người bảo không nên xài cái này cái kia các kiểu. Ý mình không giống thế, hãy nghiên cứu và chọn ra cái mình cần và phù hợp nhất.

Chúc vui với coding.

Tham khảo

 Comments
Comment plugin failed to load
Loading comment plugin