Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

JavaScript ES2025+: Cú pháp mới, array methods và patterns thay thế lodash

Ếch Trendy
Ếch Trendy

2 tháng trước · 6 phút đọc

Mình từng import lodash vào mọi dự án

Hồi mới làm việc, mình nghĩ lodash là một phần không thể thiếu của JavaScript. _.groupBy, _.sortBy, _.cloneDeep... cứ cần gì là npm install lodash. Hậu quả? Bundle size phình to, thêm một dependency phải maintain, và đặc biệt là toàn bộ team phải biết lodash API.

Rồi ES2015 ra Array.prototype.find(). ES2019 ra flat(), flatMap(). Và bây giờ, ES2023-2025 đang dần lấp đầy những chỗ trống cuối cùng. Thực tế là phần lớn các tính năng lodash phổ biến nhất giờ đã có native equivalent trong JavaScript.

Bài này mình sẽ đi qua các method mới nhất, so sánh code cũ vs mới, và chỉ khi nào vẫn nên giữ lodash.

345972 Bundle lodash đầy đủ nặng ~70KB - không phải con số nhỏ cho một utility library


Object.groupBy() - method mình chờ nhất

Array.prototype.groupBy từng là đề xuất gây tranh cãi vì xung đột prototype với Sugar.js. Cuối cùng TC39 chọn cách an toàn hơn: đặt nó ở Object.groupBy() thay vì gắn trực tiếp vào Array. Kết quả ra mắt chính thức trong ES2024.

So sánh nhanh:

const products = [
  { name: 'Táo', category: 'trái cây' },
  { name: 'Chuối', category: 'trái cây' },
  { name: 'Cà rốt', category: 'rau củ' },
  { name: 'Xoài', category: 'trái cây' },
];

// Cách cũ với lodash
import _ from 'lodash';
const grouped = _.groupBy(products, 'category');

// ES2024 - không cần import gì
const grouped = Object.groupBy(products, ({ category }) => category);
// Kết quả:
// {
//   'trái cây': [{ name: 'Táo', ... }, ...],
//   'rau củ': [{ name: 'Cà rốt', ... }]
// }

Một biến thể nữa là Map.groupBy() - hữu ích khi key không phải string:

// Nhóm theo điều kiện phức tạp hơn
const byStock = Map.groupBy(products, ({ quantity }) =>
  quantity > 0 ? 'còn hàng' : 'hết hàng'
);
// Trả về Map thay vì plain object - key có thể là bất kỳ type nào

345973 Object.groupBy() thay thế hoàn toàn _.groupBy - không cần thêm dependency

Lưu ý nhỏ: object trả về từ Object.groupBy() có prototype là null, tức là không có hasOwnProperty hay các method thông thường. Nếu code của bạn check obj.hasOwnProperty(key), cần điều chỉnh lại.


.toSorted(), .toReversed(), .toSpliced() - sort mà không mutate

Đây là nhóm method mình dùng nhiều nhất sau khi cập nhật. Vấn đề cũ là sort()reverse() mutate mảng gốc, gây bug âm thầm, đặc biệt trong React khi bạn sort state trực tiếp.

const scores = [3, 1, 4, 1, 5, 9, 2, 6];

// Cách cũ - NGUY HIỂM: mutate mảng gốc
const sorted = scores.sort((a, b) => a - b);
console.log(scores); // [1, 1, 2, 3, 4, 5, 6, 9] - mảng gốc đã đổi!

// Workaround cũ với lodash hoặc spread
const safeSorted = [...scores].sort((a, b) => a - b);
const lodashSorted = _.sortBy(scores);

// ES2023 - immutable by default
const sorted = scores.toSorted((a, b) => a - b);
console.log(scores); // [3, 1, 4, 1, 5, 9, 2, 6] - nguyên vẹn!
console.log(sorted); // [1, 1, 2, 3, 4, 5, 6, 9]

.toReversed() hoạt động tương tự:

const items = ['a', 'b', 'c'];
const reversed = items.toReversed(); // ['c', 'b', 'a']
// items vẫn là ['a', 'b', 'c']

.toSpliced() - phiên bản immutable của splice():

const fruits = ['táo', 'chuối', 'xoài'];
// Thay 'chuối' bằng 'dâu' tại index 1
const updated = fruits.toSpliced(1, 1, 'dâu');
// fruits không thay đổi, updated = ['táo', 'dâu', 'xoài']

345974 Mutation là nguồn gốc của nhiều bug React - immutable methods giải quyết tận gốc

Nếu bạn đang học JavaScript từ đầu và chưa quen khái niệm mutation vs immutable, khóa Lập Trình JavaScript Nâng Cao có giải thích chi tiết về reference types - phần nền tảng để hiểu tại sao các method này quan trọng.


.with() - thay phần tử tại index mà không mutate

Method nhỏ nhưng xịn. Trước đây để thay một phần tử trong mảng mà không mutate, bạn phải map hoặc spread khá dài:

const cart = ['áo', 'quần', 'giày'];
const targetIndex = 1;

// Cách cũ - verbose
const updated = cart.map((item, i) =>
  i === targetIndex ? 'dép' : item
);
// Hoặc với spread
const updated = [
  ...cart.slice(0, targetIndex),
  'dép',
  ...cart.slice(targetIndex + 1)
];

// ES2023 - clean hơn nhiều
const updated = cart.with(1, 'dép');
// ['áo', 'dép', 'giày'] - cart không thay đổi

.with() cũng hỗ trợ negative index như at():

// Thay phần tử cuối cùng
const last = cart.with(-1, 'tất');
// ['áo', 'quần', 'tất']

345975 Kết hợp .with(), .toSorted(), .toReversed() cho functional-style array manipulation không cần lodash

Chaining các immutable method:

// Sắp xếp rồi thay phần tử đầu - chain gọn
const result = products
  .toSorted((a, b) => a.price - b.price)
  .with(0, { ...products[0], featured: true });

Promise.withResolvers() và các cải tiến async

ES2024 mang đến Promise.withResolvers() - pattern mà trước đây bạn phải tự implement khá awkward:

// Pattern cũ - phải expose resolve/reject ra ngoài
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});
// Sau đó dùng resolve/reject ở nơi khác...

// ES2024 - clean và rõ ràng hơn
const { promise, resolve, reject } = Promise.withResolvers();

// Ứng dụng thực tế: deferred loading
function createDeferredTask() {
  const { promise, resolve, reject } = Promise.withResolvers();

  // Chạy task async ở nơi khác
  someEventEmitter.once('done', resolve);
  someEventEmitter.once('error', reject);

  return promise;
}

345976 Promise.withResolvers() giải quyết pattern deferred mà không cần wrapper library

Cải tiến khác đáng chú ý trong ES2024-2025:

ArrayBuffer.prototype.transfer() - move memory buffer mà không copy, quan trọng cho WebAssembly và Web Workers.

Regex /v flag (Unicode Sets) - thay thế /u, hỗ trợ set operations trong regex như [A--Z] (difference), [A&&Z] (intersection). Regex phức tạp giờ đọc được hơn nhiều.


Khi nào vẫn nên giữ lodash?

Mình không bảo bạn xóa lodash ngay khỏi mọi dự án. Có những trường hợp nó vẫn xứng đáng.

_.cloneDeep() - structuredClone() đã có native từ ES2022 và handle hầu hết cases. Nhưng nó không clone function, DOM nodes, hay circular reference phức tạp theo cách lodash làm. Nếu bạn clone objects có method, lodash vẫn tốt hơn.

_.debounce()_.throttle() - không có native equivalent. Bạn phải tự implement hoặc dùng library nhỏ hơn như lodash-es (tree-shakeable) thay vì cả package.

_.merge() deep merge - Object.assign() chỉ shallow merge. structuredClone merge theo cách riêng. Nếu cần deep merge objects lồng nhau, lodash vẫn là lựa chọn đáng tin.

345977 Không phải cuộc chiến thắng-thua - chọn đúng tool cho từng use case

Nếu vẫn muốn dùng lodash, hãy dùng lodash-es với tree-shaking thay vì import toàn bộ:

// Tệ: import cả 70KB
import _ from 'lodash';
_.debounce(fn, 300);

// Tốt: chỉ import hàm cần dùng
import debounce from 'lodash-es/debounce';
debounce(fn, 300);
// Hay với named import
import { debounce, throttle } from 'lodash-es';

Tree-shaking với lodash-es giúp bundle size giảm đáng kể so với import default.