JavaScript मध्ये arrays सोबत काम करताना आता क्वचितच तुम्हाला for loop लिहिण्याची गरज पडते. ही भाषा आता तुम्हाला अशी प्रभावी साधने देते जी गुंतागुंतीचा आणि गोंधळलेला डेटा स्वच्छ आणि वाचनीय स्वरूपात बदलू शकतात. हा मार्गदर्शक मूलभूत गोष्टींच्या पुढे जाऊन—flattening, reducing, querying आणि नवीन immutable patterns वर लक्ष केंद्रित करतो, ज्यामुळे तुमचा मूळ डेटा सुरक्षित राहतो.
flat() आणि flatMap() वापरून नेस्टेड स्ट्रक्चर्स फ्लॅटन (Flatten) करणे
वास्तविक जगातील डेटा अनेकदा नेस्टेड (nested) स्वरूपात असतो. तुम्ही API मधून रेकॉर्ड्स मिळवले, CSV पार्स केले किंवा कॉन्फिगरेशन फाईल वाचली, तर अनेकदा तुम्हाला arrays च्या आत arrays मिळतील. flat() हे अशा थरांना (layers) एका सिंगल लिनियर लिस्टमध्ये रूपांतरित करण्यासाठी वापरले जाते.
नेस्टेड array वर flat() कॉल केल्यास, ते डीफॉल्टनुसार फक्त एक लेव्हल फ्लॅटन करते:
const nested = [[1, 2], [3, 4]];
const flat = nested.flat();
// [1, 2, 3, 4]
मूळ nested array तसाच राहतो. जर तुमचे नेस्टिंग अधिक खोल असेल, तर flat(2) किंवा किती लेव्हर्स आहेत हे माहित नसल्यास flat(Infinity) सारखा depth argument पास करा. हे मॅन्युअली रिकर्सिव्ह हेल्पर फंक्शन्स लिहिण्यापेक्षा खूप सोपे आहे.
जेव्हा तुम्हाला एलिमेंट्स ट्रान्सफॉर्म करायचे असतात आणि रिझल्ट फ्लॅटन करायचा असतो, तेव्हा flatMap() हे दोन्ही कामे एकाच वेळी करते. ते प्रत्येक एलिमेंटवर मॅपिंग फंक्शन चालवते आणि नंतर मिळालेल्या arrays ला एक लेव्हल फ्लॅटन करते. हे .map().flat() वापरून करणे शक्य आहे, परंतु flatMap() इंटरमीडिएट array वगळते, ज्यामुळे मेमरी वाचते आणि दुसरी इटरेशन टाळता येते. वाक्यांच्या array ला स्वतंत्र शब्दांमध्ये विभागणे हे याचे एक उत्तम उदाहरण आहे:
const lines = ['hello world', 'foo bar'];
const words = lines.flatMap(line => line.split(' '));
// ['hello', 'world', 'foo', 'bar']
Filtering आणि Mapping: फरक जाणून घ्या
filter() एक नवीन array तयार करते ज्यामध्ये फक्त तुमच्या टेस्टमधून पास झालेले आयटम्स असतात. एलिमेंट ठेवण्यासाठी कॉलबॅकने truthy व्हॅल्यू आणि तो काढून टाकण्यासाठी falsy व्हॅल्यू रिटर्न करणे आवश्यक आहे. हे मूळ (source) array मध्ये कोणताही बदल करत नाही.
const users = [
{ name: 'Ajay', active: true },
{ name: 'Bina', active: false }
];
const activeUsers = users.filter(u => u.active);
filter() आणि map() मध्ये गोंधळ होणे सोपे आहे कारण दोन्ही नवीन arrays रिटर्न करतात आणि कॉलबॅक स्वीकारतात. फरक त्यांच्या कामाच्या पद्धतीत आहे. map() त्याच लांबीचा (same length) नवीन array रिटर्न करते. प्रत्येक एलिमेंट ट्रान्सफॉर्म करा, कंडिशनल चेक करा, आणि तुम्हाला प्रत्येक स्लॉटसाठी true किंवा false दिसेल, कारण map() कधीही आयटम्स काढून टाकत नाही—ते फक्त त्यांना बदलत असते. दुसरीकडे, filter() फक्त तुमच्या टेस्टमधून पास झालेले एलिमेंट्स असलेले कदाचित लहान array रिटर्न करते. जर तुम्हाला डेटा काढून टाकायचा असेल, तर filter() वापरा. जर तुम्हाला डेटाची संख्या कायम ठेवून प्रत्येक डेटा ट्रान्सफॉर्म करायचा असेल, तर map() वापरा.
reduce() वापरून arrays एकत्रित करणे (Collapsing)
जिथे map() आणि filter() arrays रिटर्न करतात, तिथे reduce() संपूर्ण लिस्टला एका सिंगल व्हॅल्यूमध्ये रूपांतरित करते. हे डावीकडून उजवीकडे चालते आणि प्रत्येक स्टेपमध्ये एक accumulator सोबत घेऊन जाते.
const totals = [10, 20, 30];
const sum = totals.reduce((acc, current) => acc + current, 0);
// 60
दुसरा आर्ग्युमेंट—वरील उदाहरणातील 0—हा इनिशियल accumulator व्हॅल्यू आहे. जर तुम्ही तो वगळला, तर JavaScript पहिल्या एलिमेंटला सुरुवातीचा बिंदू मानून इंडेक्स 1 पासून इटरेशन सुरू करते. जेव्हा array रिकामा असतो, तेव्हा हे लोकांना गोंधळात टाकते, कारण इनिशियल व्हॅल्यूशिवाय रिकामा array वर reduce() कॉल केल्यास TypeError येतो.
reduce फक्त गणिती क्रियांसाठी नाही. तुम्ही याचा वापर फ्रिक्वेन्सी टेबल्स तयार करण्यासाठी, ऑब्जेक्ट्स मर्ज करण्यासाठी, मॅक्सिमम व्हॅल्यू शोधण्यासाठी किंवा डेटाला अनेक ट्रान्सफॉर्मेशन्समधून पास करण्यासाठी करू शकता. ते खूप लवचिक असल्यामुळे, कॉलबॅक लहान ठेवणे आणि व्हेरिएबल्सना स्पष्ट नावे देणे फायदेशीर ठरते. एक गुंतागुंतीचा reduce() ब्लॉक मॅन्युअल लूपपेक्षा वाचायला अधिक कठीण होऊ शकतो.
Boolean Queries: every() आणि some()
कधीकधी तुम्हाला नवीन array ची गरज नसते; तुम्हाला कलेक्शनबद्दल फक्त 'हो' किंवा 'नाही' मध्ये उत्तर हवे असते. 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() वापरा.
Mutation न करता Inspect आणि Update करणे
मॉडर्न JavaScript मध्ये immutability ला खूप महत्त्व दिले जाते. हे मेथड्स मूळ डेटाला स्पर्श न करता arrays तपासण्यास किंवा अपडेट करण्यास मदत करतात.
keys() हे array चे इंडिसेस असलेले iterator object रिटर्न करते. entries() हे [index, value] जोड्यांचे iterator रिटर्न करते. जेव्हा तुम्हाला स्वच्छ destructuring हवे असते, तेव्हा for...of लूपमध्ये हे मेथड्स उत्तम काम करतात:
const fruits = ['apple', 'banana'];
for (const [index, fruit] of fruits.entries()) {
console.log(index, fruit);
}
with() ही भाषेतील एक नवीन भर आहे जी विशिष्ट इंडेक्सवरील घटक बदलून एक पूर्णपणे नवीन ॲरे परत करते. हे ब्रॅकेट असाइनमेंट—arr[index] = value—प्रमाणेच काम करते, फक्त फरक इतकाच की यामुळे मूळ ॲरेमध्ये कोणताही बदल होत नाही. React सारख्या फ्रेमवर्कमध्ये, जिथे थेट म्यूटेशनमुळे (direct mutation) चेंज डिटेक्शन (change detection) बिघडते, तिथे 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) स्वीकारणे, त्यावर ॲरेप्रमाणे प्रक्रिया करणे आणि नंतर ते पुन्हा फंक्शन कॉल्स किंवा नवीन डेटा स्ट्रक्चर्समध्ये पसरवणे यांसारखी सामान्य कामे हाताळतात.
मुख्य निष्कर्ष
या पद्धतींचा उद्देश 'वन-लाइनर' कोड लिहून सहकाऱ्यांना प्रभावित करणे हा नाही, तर तुमचा हेतू स्पष्ट करणे हा आहे. जेव्हा दुसरा डेव्हलपर filter() पाहतो, तेव्हा त्याला समजते की तुम्ही घटक काढून टाकत आहात. जेव्हा ते reduce() पाहतात, तेव्हा त्यांना समजते की तुम्ही डेटा एकत्रित (collapsing) करत आहात. जेव्हा ते with() पाहतात, तेव्हा त्यांना समजते की मूळ ॲरे कोठेतरी सुरक्षित आहे. या साधनांवर प्रभुत्व मिळवणे म्हणजे असा कोड लिहिणे जो स्वतःचे स्पष्टीकरण देतो, आणि यामुळे कोणत्याही हुशार लूपपेक्षा (loop) कितीतरी जास्त वेळ वाचतो.
