Галоўная / Артыкулы / Сучасныя методы масэв JavaScript, якія заменяюць распашчатыя шаблоны коду

Сучасныя методы масэв JavaScript, якія заменяюць распашчатыя шаблоны коду

Дазвольце дазнацься, як новейшыя методы масэв JavaScript, такія як groupBy, toSorted, with і findLast, заменяюць слоўнасцовыя способы выкарыстоўвання reduce, spread і reverse.

1432 слоў

Групаванне дадзейнаў без бібліятэкі: Object.groupBy() і Map.groupBy()

Працаваючы гады, каб раздзеліць масэвую структуру элементаў на катагорыі на аднойчынай праметры, трэба было пісаць сабе ўласны акумулятор reduce() або выкарыстоўваць lodash.groupBy толькі для гэтага заведама задання.

У спефікацыі ECMAScript тепер є вбудованыя механізмы групавання через Object.groupBy() і Map.groupBy().

Якую проблему гэта рашае

Калі вы отрымляеце простую колекцыю звярэнняў з API — транзакцыі, задачы, данні корыстнікаў і т.п. — часта патрэбна ўпорядакаваць іх у групы прытымкі перад адображэнням або выкарыстоўванням на іх базе бізнес-логікі.

Раней: рэалізацыя за дапамою reduce()

const inventory = [
  { name: 'Apples', type: 'fruit', quantity: 10 },
  { name: 'Bananas', type: 'fruit', quantity: 0 },
  { name: 'Carrots', type: 'vegetable', quantity: 14 },
  { name: 'Broccoli', type: 'vegetable', quantity: 5 }
];

const groupedByReduce = inventory.reduce((acc, item) => {
  const key = item.type;
  if (!acc[key]) {
    acc[key] = [];
  }
  acc[key].push(item);
  return acc;
}, {});

Пазней: чысты, спецыяльна створаны вызов Object.groupBy

const inventory = [
  { name: 'Apples', type: 'fruit', quantity: 10 },
  { name: 'Bananas', type: 'fruit', quantity: 0 },
  { name: 'Carrots', type: 'vegetable', quantity: 14 },
  { name: 'Broccoli', type: 'vegetable', quantity: 5 }
];

const grouped = Object.groupBy(inventory, item => item.type);

/*
Output:
{
  fruit: [
    { name: 'Apples', type: 'fruit', quantity: 10 },
    { name: 'Bananas', type: 'fruit', quantity: 0 }
  ],
  vegetable: [
    { name: 'Carrots', type: 'vegetable', quantity: 14 },
    { name: 'Broccoli', type: 'vegetable', quantity: 5 }
  ]
}
*/

У замену выкарыстоўваеце Map.groupBy()

Калі ключ групавання — не проста строка чы сімбал, а болей складны об’ект чы дынамічны рэферэнс, Map.groupBy() ў такім случае яўляецца кращым выборам:

const vipTier = { tier: 'VIP' };
const standardTier = { tier: 'Standard' };

const customers = [
  { name: 'Alice', plan: vipTier },
  { name: 'Bob', plan: standardTier },
  { name: 'Charlie', plan: vipTier }
];

const groupedByPlan = Map.groupBy(customers, user => user.plan);

console.log(groupedByPlan.get(vipTier));
// Returns Alice and Charlie's records directly keyed by reference

Незменныя трансфармыціі, якім можна даверыць: toSorted(), toReversed(), toSpliced()

Класычная проблема ў JavaScript — це тое, што вядомыя методы .sort(), .reverse() і .splice() всі зменяюць масэвы на месцы, а не вяртаюць новы об’ект.

У фрэймворках, пабудаваных на прыглядным стацыянарным стане — React, Redux, Vue і падобных — безпасэчнае зміненне масэвы часта прыводзіць да тонкіх багоў і до ситуацый, калі відрасліванне элементаў тыхчэй выходзить з сінхроназаціі.

Якую проблему гэта рашае

Ёнколі ўбачаць гэтыя падступы, разработчыкі раней запісвалі захоўнія копіі спачатку, такія як [...array].sort() або array.slice().reverse(). Новая група методаў "змена массиву праз копію" цэлкам адмывае гэтыя зайвыя коды.

Порэшанне двух падходаў

// --- The Old Mutating Way ---
const scores = [88, 92, 79, 95];
const sortedMutated = scores.sort((a, b) => a - b);

console.log(scores); // [79, 88, 92, 95] -> Original data was destroyed!

// --- The Modern Non-Mutating Way ---
const originalScores = [88, 92, 79, 95];
const cleanSorted = originalScores.toSorted((a, b) => a - b);

console.log(originalScores); // [88, 92, 79, 95] -> Unchanged
console.log(cleanSorted);    // [79, 88, 92, 95] -> Fresh array

Замена splice() на toSpliced()

toSpliced() дазволяе выдаліць, дадаць аб заменіць элементы на будзь-яй пазіцыі, не чапаючы заўсёды ўвесь первісны массив:

const tabs = ['Home', 'About', 'Pricing', 'Contact'];

// Remove 'Pricing' (index 2) and insert 'Services' & 'Blog'
const updatedTabs = tabs.toSpliced(2, 1, 'Services', 'Blog');

console.log(tabs);        // ['Home', 'About', 'Pricing', 'Contact']
console.log(updatedTabs); // ['Home', 'About', 'Services', 'Blog', 'Contact']

Абнавленне аднае элемента без змены: with()

Якшчо вы хочаце зменіць значэнне на паведамленай індэксе, не чапаючы заўсёды заўсёды ўвесь первісны массив, звычайны падход — запісаць массив у копію, перазапісаць элемент на паведамленай індэксе і вернуць рызультат. with() адразу з’еднае ўсё гэта ў адну чытальную выраз.

Яка проблема рашыяецца

Пад час апдэйтаў масэвых данаёнаў часта патрабуецца заменіць адзін элемент па його пазычным номеру, заўсёды залікаючы абаковую масэ незменной.

Порэванне двух падходаў

const months = ['Jan', 'Mar', 'Mar', 'Apr'];

// Old way: Spread and mutate copy
const fixedMonthsOld = [...months];
fixedMonthsOld[1] = 'Feb';

// Modern way: array.with(index, value)
const fixedMonthsNew = months.with(1, 'Feb');

console.log(months);         // ['Jan', 'Mar', 'Mar', 'Apr']
console.log(fixedMonthsNew);  // ['Jan', 'Feb', 'Mar', 'Apr']

with() таксама падтрымлівае ад’ёмныя індэксы, таму замена элемента, які лічыцца ад канца, таксама ўсё проста:

const tags = ['v1.0.0', 'v1.1.0', 'v1.2.0-beta'];
const stableTags = tags.with(-1, 'v1.2.0');

console.log(stableTags); // ['v1.0.0', 'v1.1.0', 'v1.2.0']

Эфектывае пошук у спакою: findLast() і findLastIndex()

Аднаходжэнне элемента за дапамою .find() працуе нормальна, калі сканаваць даныя ад пачатку, але ён завжды пачынаецца з індэкса 0. Калі насправды патрабуецца апошнія знаходка, распашчальны спосаб — гэта ператворыць масэю у зворачаны стація, што вядзе да втраты часу і рызыку змены данаёнаў, якіх не хацелася чыгнуць.

Яка проблема рашыяецца

Даныя, такія як запісы журналу, історыя відкату, ленты актывацыі і записы транзакцый, па свайбе сортаваныя па часу, і частаючы вы хочаце побачыць найсвежэйшы запіс, які падпадае пад адзіныя з умоў.

Порэшчанне двух падходаў

const logs = [
  { id: 1, action: 'LOGIN', timestamp: 1000 },
  { id: 2, action: 'UPLOAD', timestamp: 1010 },
  { id: 3, action: 'ERROR', timestamp: 1020 },
  { id: 4, action: 'UPLOAD', timestamp: 1030 },
  { id: 5, action: 'LOGOUT', timestamp: 1040 }
];

// Old way: Reversing creates overhead and array allocation
const lastUploadOld = [...logs].reverse().find(log => log.action === 'UPLOAD');

// Modern way: findLast iterates backwards directly
const lastUpload = logs.findLast(log => log.action === 'UPLOAD');
const lastUploadIdx = logs.findLastIndex(log => log.action === 'UPLOAD');

console.log(lastUpload);    // { id: 4, action: 'UPLOAD', timestamp: 1030 }
console.log(lastUploadIdx); // 3

Паколькі гэты падход скануе з канца і зупыняецца як толькі знаходзіць падходящы элемент, ён выконваецца за час O(k), дзе k — колькісць крокаў назад да падходящага элемента — не трэба спачатку копіюваць чы пертвараць весь масэвак.

5. Адрабатка Map і Filter адразу: flatMap()

Часта выкарыстоўваецца комбінацыя .map() з .filter() чы .flat(), калі кожны элемент выхіднага масэвака можа даць нуль, адзін чы калькі рэзультатаў. flatMap() з’едынае гэтыя каскады ў адну процедуру.

Што гэта рашыяе: гэта спакоюе стварэнне тымчасовых масэў, якія празьліваюцца пасля выкарыстоўвання, калі трэба перакаштаваць вярстакаваныя данні або фільтруваць і трансфармаваць іх у аднам кроку.

Прыклад: сплюшчэнне вярстака з вярстакаванымі элементамі

const authors = [
  { name: 'Author A', books: ['Book 1', 'Book 2'] },
  { name: 'Author B', books: ['Book 3'] },
  { name: 'Author C', books: [] }
];

// Map + Flat produces 2 array allocations
const booksNested = authors.map(a => a.books).flat();

// flatMap runs the map and flattens depth-1 in one pass
const allBooks = authors.flatMap(a => a.books);
console.log(allBooks); // ['Book 1', 'Book 2', 'Book 3']

Прыклад: адночасная трансфармацыя і фільтрацыя

Вы можете адмовіцца ад элемента, вярнуўшы порожній масэвак, і застацься з яным, вярнуўшы масэвак з адним элементам:

const rawInputs = ['42', 'invalid', '100', 'undefined', '256'];

const validNumbers = rawInputs.flatMap(input => {
  const num = parseInt(input, 10);
  return Number.isNaN(num) ? [] : [num];
});

console.log(validNumbers); // [42, 100, 256]

6. Чыстэйшы доступ да элементаў: at()

Для выявлення апошньага элемента масэвака традыцыйна выкарыстоўваліся array[array.length - 1], што є незручным і лёгка прыводзіць да памылак.

at() адбірае прыем паўнегатывых індэксаў, вядомы з Python, чым дазволяе спісваць назад ад канца масэвакоў і страк.

Да і пасля

const queue = ['Alice', 'Bob', 'Charlie', 'Dana'];

// Old way
const lastPersonOld = queue[queue.length - 1];
const secondLastOld = queue[queue.length - 2];

// Modern way
const lastPerson = queue.at(-1);    // 'Dana'
const secondLast = queue.at(-2);    // 'Charlie'

Рэзультат ўсьмохнейшы, менее схильны да памылак типу «одна единіца занадта» у вырахунавальных выразах, і лепа працюе з абэрнутымі вызывамі методаў, адколі не трэба спачатку зберагаць масэвую структуру ў тымчасовай зменнай.

Калі яго вжываць

Спіс перагляду для модернізацыі

  • Перагляньце, як вы карыстаецеся станам: заменіце .sort() і .reverse() на .toSorted() і .toReversed(), ўбачыць багі з тыхамі мутацый.
  • Адменшыце стандартныя функціі-редактары: вжывайце Object.groupBy() замест аддатковых об’ектаў-акумулятораў.
  • Праспяшыце пошук: заменіце патэрны «спачатку адварот, потым пошук» на findLast() для дадзеных у часовым порядку.
  • Апростацюйце кождадзённую сінтаксіс: выкорыстоўвайце .at(-1) і .with(index, value) замест ручных вычыслаў дужыны і захоўніх копій масэв.
  • Спаднёе чытанне