JavaScript-ൽ അറേകളുമായി (arrays) പ്രവർത്തിക്കുമ്പോൾ ഇനി അധികം for ലൂപ്പുകൾ എഴുതേണ്ടി വരില്ല. സങ്കീർണ്ണമായ ഡാറ്റയെ ലളിതവും വ്യക്തവുമായ രീതിയിൽ മാറ്റാൻ സഹായിക്കുന്ന മികച്ച ടൂളുകൾ ഇപ്പോൾ ഈ ഭാഷയിലുണ്ട്. അടിസ്ഥാന കാര്യങ്ങൾ കഴിഞ്ഞാൽ അടുത്ത ഘട്ടമായ flattening, reducing, querying, കൂടാതെ ഒറിജിനൽ ഡാറ്റയിൽ മാറ്റം വരുത്താത്ത പുതിയ immutable patterns എന്നിവയെക്കുറിച്ചാണ് ഈ ഗൈഡ് ചർച്ച ചെയ്യുന്നത്.
flat() ഉം flatMap() ഉം ഉപയോഗിച്ച് നെസ്റ്റഡ് സ്ട്രക്ചറുകൾ ഫ്ലാറ്റൻ ചെയ്യുക
യഥാർത്ഥ ലോകത്തെ ഡാറ്റ പലപ്പോഴും നെസ്റ്റഡ് (nested) ആയിരിക്കും. ഒരു API-ൽ നിന്ന് റെക്കോർഡുകൾ എടുക്കുമ്പോഴോ, ഒരു CSV പാഴ്സ് ചെയ്യുമ്പോഴോ, അല്ലെങ്കിൽ ഒരു കോൺഫിഗറേഷൻ ഫയൽ വായിക്കുമ്പോഴോ പലപ്പോഴും അറേകൾക്കുള്ളിൽ അറേകൾ കാണാൻ സാധിക്കും. ഇത്തരം ലെയറുകളെ ഒരു സിംഗിൾ ലിസ്റ്റാക്കി മാറ്റാൻ flat() സഹായിക്കുന്നു.
ഒരു നെസ്റ്റഡ് അറേയിൽ flat() വിളിച്ചാൽ അത് ഡിഫോൾട്ട് ആയി ഒരു ലെവൽ മാത്രം ഫ്ലാറ്റൻ ചെയ്യും:
const nested = [[1, 2], [3, 4]];
const flat = nested.flat();
// [1, 2, 3, 4]
ഒറിജിനൽ nested അറേ മാറ്റമില്ലാതെ തന്നെ ഇരിക്കും. നെസ്റ്റിംഗ് കൂടുതൽ ആഴത്തിലുള്ളതാണെങ്കിൽ, flat(2) എന്നോ അല്ലെങ്കിൽ എത്ര ലെയറുകൾ ഉണ്ടെന്ന് അറിയില്ലെങ്കിൽ flat(Infinity) എന്നോ ഉപയോഗിക്കാം. ഇത് റിക്കേഴ്സീവ് (recursive) ഫംഗ്ഷനുകൾ കൈകൊണ്ട് എഴുതുന്നതിനേക്കാൾ എളുപ്പമാണ്.
എലമെന്റുകളെ മാറ്റുന്നതിനോടൊപ്പം (transform) ഫലം ഫ്ലാറ്റൻ ചെയ്യണമെന്നുണ്ടെങ്കിൽ, flatMap() ഒരൊറ്റ പാസ്സിലൂടെ ഇത് രണ്ടും ചെയ്യുന്നു. ഇത് ഓരോ എലമെന്റിലും ഒരു മാപ്പിംഗ് ഫംഗ്ഷൻ പ്രവർത്തിപ്പിക്കുകയും, ലഭിക്കുന്ന അറേകളെ ഒരു ലെവൽ ഫ്ലാറ്റൻ ചെയ്യുകയും ചെയ്യുന്നു. .map().flat() എന്ന് പ്രത്യേകം ഉപയോഗിക്കുന്നതിനേക്കാൾ മെമ്മറി ലാഭിക്കാനും വേഗത കൂട്ടാനും flatMap() സഹായിക്കുന്നു. വാചകങ്ങൾ അടങ്ങിയ ഒരു അറേയെ ഓരോ വാക്കുകളായി വേർതിരിക്കുന്നതിന് ഇത് ഉപയോഗിക്കാം:
const lines = ['hello world', 'foo bar'];
const words = lines.flatMap(line => line.split(' '));
// ['hello', 'world', 'foo', 'bar']
ഫിൽട്ടറിംഗും മാപ്പിംഗും: വ്യത്യാസം മനസ്സിലാക്കുക
നിങ്ങളുടെ ടെസ്റ്റ് വിജയിക്കുന്ന ഐറ്റങ്ങൾ മാത്രം ഉൾപ്പെടുത്തിക്കൊണ്ട് filter() ഒരു പുതിയ അറേ നിർമ്മിക്കുന്നു. എലമെന്റിനെ നിലനിർത്താൻ കോൾബാക്ക് (callback) ഒരു truthy വാല്യൂവും, ഒഴിവാക്കാൻ ഒരു falsy വാല്യൂവും നൽകണം. ഇത് ഒറിജിനൽ അറേയെ ഒരിക്കലും മാറ്റുന്നില്ല.
const users = [
{ name: 'Ajay', active: true },
{ name: 'Bina', active: false }
];
const activeUsers = users.filter(u => u.active);
filter() ഉം map() ഉം രണ്ടും പുതിയ അറേകൾ നൽകുന്നതിനാലും കോൾബാക്കുകൾ സ്വീകരിക്കുന്നതിനാലും ഇവ തമ്മിൽ മാറിപ്പോകാൻ സാധ്യതയുണ്ട്. എന്നാൽ ഇവയുടെ പ്രവർത്തനരീതിയിൽ വ്യത്യാസമുണ്ട്. map() എപ്പോഴും അതേ നീളമുള്ള (same length) ഒരു പുതിയ അറേ നൽകുന്നു. ഓരോ എലമെന്റും മാറ്റം വരുത്തുമ്പോൾ, കണ്ടീഷൻ ചെക്ക് ചെയ്താലും true അല്ലെങ്കിൽ false ആയി ഓരോ സ്ഥാനത്തും വരും, കാരണം map() ഐറ്റങ്ങളെ ഒഴിവാക്കുന്നില്ല—മാറ്റുക മാത്രമാണ് ചെയ്യുന്നത്. എന്നാൽ filter(), നിങ്ങളുടെ ടെസ്റ്റ് വിജയിക്കുന്ന എലമെന്റുകൾ മാത്രം ഉൾക്കൊള്ളുന്ന, കുറഞ്ഞ നീളമുള്ള ഒരു അറേ നൽകുന്നു. ഡാറ്റ ഒഴിവാക്കണമെന്നുണ്ടെങ്കിൽ filter() ഉപയോഗിക്കുക. ഡാറ്റയുടെ എണ്ണം മാറ്റാതെ അവയെ മാറ്റം വരുത്തണമെങ്കിൽ map() ഉപയോഗിക്കുക.
reduce() ഉപയോഗിച്ച് അറേകളെ ചുരുക്കുക
map(), filter() എന്നിവ അറേകൾ നൽകുന്നിടത്ത്, reduce() ഒരു വലിയ ലിസ്റ്റിനെ ഒരു വാല്യൂവിലേക്ക് ചുരുക്കുന്നു. ഇത് ഇടത്തുനിന്ന് വലത്തോട്ട് ഓരോ ഘട്ടത്തിലൂടെയും ഒരു അക്യുമുലേറ്റർ (accumulator) ഉപയോഗിച്ച് മുന്നോട്ട് പോകുന്നു.
const totals = [10, 20, 30];
const sum = totals.reduce((acc, current) => acc + current, 0);
// 60
മുകളിലെ ഉദാഹരണത്തിലെ രണ്ടാമത്തെ ആർഗ്യുമെന്റ്—0 ആണ്—ഇനീഷ്യൽ അക്യുമുലേറ്റർ വാല്യൂ. ഇത് നൽകിയില്ലെങ്കിൽ, JavaScript ആദ്യത്തെ എലമെന്റിനെ സ്റ്റാർട്ടിംഗ് പോയിന്റായി എടുക്കുകയും ഇൻഡക്സ് 1 മുതൽ പ്രവർത്തനം തുടങ്ങുകയും ചെയ്യും. അറേ കാലിയാണെങ്കിൽ, ഇനീഷ്യൽ വാല്യൂ ഇല്ലാതെ reduce() വിളിക്കുന്നത് TypeError ഉണ്ടാക്കും.
reduce() കണക്കുകൂട്ടലുകൾക്ക് വേണ്ടി മാത്രമല്ല. ഫ്രീക്വൻസി ടേബിളുകൾ നിർമ്മിക്കാനും, ഒബ്ജക്റ്റുകൾ യോജിപ്പിക്കാനും (merge), പരമാവധി മൂല്യങ്ങൾ (maximum values) കണ്ടെത്താനും ഇത് ഉപയോഗിക്കാം. ഇത് വളരെ ഫ്ലെക്സിബിൾ ആയതുകൊണ്ട്, കോൾബാക്ക് ചെറുതാക്കി വാല്യബിളുകൾക്ക് വ്യക്തമായ പേര് നൽകുന്നത് നല്ലതാണ്. ഒരു സങ്കീർണ്ണമായ reduce() ബ്ലോക്ക്, അത് റീപ്ലേസ് ചെയ്ത ലൂപ്പിനേക്കാൾ വായിക്കാൻ പ്രയാസമുള്ളതാകാൻ സാധ്യതയുണ്ട്.
ബൂലിയൻ ക്വറികൾ: every() ഉം some() ഉം
ചിലപ്പോൾ നിങ്ങൾക്ക് ഒരു പുതിയ അറേ ആവശ്യമില്ല; പകരം ആ കളക്ഷനെക്കുറിച്ച് ഒരു 'അതെ' അല്ലെങ്കിൽ 'അല്ല' എന്ന മറുപടി മതിയാകും. എല്ലാ എലമെന്റുകളും നിങ്ങളുടെ കണ്ടീഷൻ പാലിക്കുന്നുണ്ടെങ്കിൽ മാത്രമേ every() true നൽകുകയുള്ളൂ. ആദ്യത്തെ എലമെന്റ് തന്നെ പരാജയപ്പെട്ടാൽ അത് ഉടൻ തന്നെ false നൽകി നിർത്തും.
const scores = [85, 92, 78];
const allPassed = scores.every(s => s >= 50);
some() ഇതിന് നേരെ വിപരീതമാണ്. കുറഞ്ഞത് ഒരു എലമെന്റ് എങ്കിലും ടെസ്റ്റ് വിജയിച്ചാൽ അത് true നൽകുന്നു. ഒരു മാച്ച് കണ്ടെത്തിയാൽ ഉടൻ തന്നെ അത് പ്രവർത്തനം നിർത്തും.
const hasPerfectScore = scores.some(s => s === 100);
വാലിഡേഷൻ ചെക്കുകൾക്കായി മാനുവൽ ലൂപ്പുകൾക്ക് പകരം ഈ മെത്തേഡുകൾ ഉപയോഗിക്കാം. എല്ലാ ഫോം ഫീൽഡുകളും പൂരിപ്പിച്ചിട്ടുണ്ടോ എന്ന് പരിശോധിക്കണോ? every() ഉപയോഗിക്കുക. ഏതെങ്കിലും യൂസർക്ക് അഡ്മിൻ റൈറ്റ്സ് ഉണ്ടോ എന്ന് അറിയണോ? some() ഉപയോഗിക്കുക.
മാറ്റങ്ങളില്ലാതെ പരിശോധിക്കാനും അപ്ഡേറ്റ് ചെയ്യാനും
മോഡേൺ JavaScript ഇമ്മ്യൂട്ടബിലിറ്റിക്ക് (immutability) വലിയ പ്രാധാന്യം നൽകുന്നു. ഒറിജിനൽ ഡാറ്റയിൽ മാറ്റം വരുത്താതെ തന്നെ അറേകൾ പരിശോധിക്കാനും അപ്ഡേറ്റ് ചെയ്യാനും ഈ മെത്തേഡുകൾ സഹായിക്കുന്നു.
keys() അറേയിലെ ഇൻഡക്സുകൾ അടങ്ങിയ ഒരു ഇറ്ററേറ്റർ ഒബ്ജക്റ്റ് നൽകുന്നു. entries() [index, value] ജോഡികൾ അടങ്ങിയ ഒരു ഇറ്ററേറ്റർ നൽകുന്നു. ക്ലീൻ ആയ ഡെസ്ട്രക്ചറിംഗിനായി (destructuring) for...of ലൂപ്പുകളിൽ ഇവ മികച്ച രീതിയിൽ ഉപയോഗിക്കാം:
const fruits = ['apple', 'banana'];
for (const [index, fruit] of fruits.entries()) {
console.log(index, fruit);
}
with() എന്നത് ഭാഷയിലേക്ക് പുതുതായി വന്ന ഒരു ഫീച്ചറാണ്, ഇത് ഒരു പ്രത്യേക ഇൻഡക്സിലുള്ള എലമെന്റിനെ മാറ്റി പകരം ഒരു പുതിയ അറേ (array) നൽകുന്നു. ബ്രാക്കറ്റ് അസൈൻമെന്റ്—arr[index] = value—ചെയ്യുന്ന അതേ ആവശ്യത്തിനാണ് ഇത് ഉപയോഗിക്കുന്നത്, എന്നാൽ ഇത് ഒറിജിനൽ അറേയിൽ മാറ്റം വരുത്തുന്നില്ല. ഡയറക്ട് മ്യൂട്ടേഷൻ (direct mutation) മാറ്റങ്ങൾ തിരിച്ചറിയുന്നതിനെ (change detection) തടസ്സപ്പെടുത്തുന്ന React പോലുള്ള ഫ്രെയിംവർക്കുകളിൽ, with() ഉപയോഗിക്കുന്നതാണ് കൂടുതൽ സുരക്ഷിതം:
const original = [10, 20, 30];
const updated = original.with(1, 99);
// original: [10, 20, 30]
// updated: [10, 99, 30]
Spread and Rest: ...-ന്റെ രണ്ട് ദിശകൾ
സ്പ്രെഡ് സിന്റാക്സും (spread syntax) റെസ്റ്റ് പാരാമീറ്ററും (rest parameter) ഒരേ മൂന്ന് ഡോട്ടുകൾ ഉപയോഗിക്കുന്നുണ്ടെങ്കിലും, അവ ഡാറ്റയെ കൈകാര്യം ചെയ്യുന്നത് വിപരീത ദിശകളിലാണ്.
ഒരു അറേയെ ഓരോ എലമെന്റുകളായി വികസിപ്പിക്കാൻ spread operator ഉപയോഗിക്കുക. അറേകൾ യോജിപ്പിക്കാനോ (merge) അവയുടെ ക്ലോൺ (clone) നിർമ്മിക്കാനോ ഉള്ള ഏറ്റവും ലളിതമായ മാർഗമാണിത്:
const front = [1, 2];
const back = [3, 4];
const merged = [...front, ...back];
const copy = [...merged];
നിങ്ങൾക്ക് എളുപ്പത്തിൽ അധിക ഐറ്റങ്ങൾ കൂടി ചേർക്കാം: [0, ...front, 99, ...back].
rest parameter ഒന്നിലധികം എലമെന്റുകളെ ഒരു ഒറ്റ അറേയിലേക്ക് ശേഖരിക്കുന്നു. ഒരു ഫംഗ്ഷൻ സിഗ്നേച്ചറിനുള്ളിൽ (function signature), ബാക്കിയുള്ള എല്ലാ ആർഗ്യുമെന്റുകളെയും (arguments) ഇത് ശേഖരിക്കുന്നു:
function logFirst(first, ...rest) {
console.log('First:', first);
console.log('Rest:', rest);
}
logFirst('a', 'b', 'c');
// First: a
// Rest: ['b', 'c']
വ്യത്യസ്തമായ അളവിലുള്ള ഇൻപുട്ടുകൾ (variadic input) സ്വീകരിക്കുക, അവയെ ഒരു അറേയായി പ്രോസസ്സ് ചെയ്യുക, തുടർന്ന് അവയെ ഫംഗ്ഷൻ കോളുകളിലേക്കോ പുതിയ ഡാറ്റാ സ്ട്രക്ചറുകളിലേക്കോ തിരികെ വിന്യസിക്കുക തുടങ്ങിയ ജോലികൾ ഇവ രണ്ടും ചേർന്ന് ചെയ്യുന്നു.
യഥാർത്ഥ പാഠം
ഈ മെത്തേഡുകളുടെ ലക്ഷ്യം വൺ-ലൈനറുകൾ (one-liners) ഉപയോഗിച്ച് സഹപ്രവർത്തകരെ അത്ഭുതപ്പെടുത്തുക എന്നതല്ല. മറിച്ച്, നിങ്ങളുടെ ഉദ്ദേശ്യം വ്യക്തമാക്കുക എന്നതാണ്. മറ്റൊരു ഡെവലപ്പർ filter() കാണുമ്പോൾ, നിങ്ങൾ ഐറ്റങ്ങൾ നീക്കം ചെയ്യുകയാണെന്ന് അവർക്ക് മനസ്സിലാകും. അവർ reduce() കാണുമ്പോൾ, നിങ്ങൾ ഡാറ്റയെ ചുരുക്കുകയാണെന്ന് അവർക്ക് മനസ്സിലാകും. അവർ with() കാണുമ്പോൾ, ഒറിജിനൽ അറേ എവിടെയോ മാറ്റമില്ലാതെ ഇരിപ്പുണ്ടെന്ന് അവർക്ക് മനസ്സിലാകും. ഈ ടൂളുകളിൽ വൈദഗ്ധ്യം നേടുക എന്നാൽ സ്വയം വിശദീകരിക്കുന്ന കോഡ് എഴുതുക എന്നാണ് അർത്ഥം; ഇത് ഏതൊരു ബുദ്ധിപരമായ ലൂപ്പിനേക്കാളും (loop) കൂടുതൽ സമയം ലാഭിക്കാൻ സഹായിക്കും.
