जावास्क्रिप्ट इटेरेटर प्रोटोकॉल को समझने और लागू करने के लिए एक विस्तृत गाइड, जो आपको बेहतर डेटा हैंडलिंग के लिए कस्टम इटेरेटर्स बनाने में सशक्त बनाता है।
जावास्क्रिप्ट इटेरेटर प्रोटोकॉल और कस्टम इटेरेटर्स को समझना
जावास्क्रिप्ट का इटेरेटर प्रोटोकॉल डेटा स्ट्रक्चर्स को ट्रैवर्स करने का एक मानकीकृत तरीका प्रदान करता है। इस प्रोटोकॉल को समझना डेवलपर्स को एरे और स्ट्रिंग्स जैसे बिल्ट-इन इटेरेबल्स के साथ कुशलता से काम करने और विशिष्ट डेटा स्ट्रक्चर्स और एप्लिकेशन आवश्यकताओं के अनुरूप अपने स्वयं के कस्टम इटेरेबल्स बनाने में सशक्त बनाता है। यह गाइड इटेरेटर प्रोटोकॉल और कस्टम इटेरेटर्स को लागू करने के तरीके का एक व्यापक अन्वेषण प्रदान करता है।
इटेरेटर प्रोटोकॉल क्या है?
इटेरेटर प्रोटोकॉल यह परिभाषित करता है कि किसी ऑब्जेक्ट पर कैसे इटेरेट किया जा सकता है, यानी, उसके एलिमेंट्स तक क्रमिक रूप से कैसे पहुंचा जा सकता है। इसमें दो भाग होते हैं: इटेरेबल प्रोटोकॉल और इटेरेटर प्रोटोकॉल।
इटेरेबल प्रोटोकॉल
किसी ऑब्जेक्ट को इटेरेबल माना जाता है यदि उसके पास Symbol.iterator
की (key) के साथ एक मेथड हो। इस मेथड को एक ऐसा ऑब्जेक्ट लौटाना चाहिए जो इटेरेटर प्रोटोकॉल के अनुरूप हो।
संक्षेप में, एक इटेरेबल ऑब्जेक्ट जानता है कि अपने लिए एक इटेरेटर कैसे बनाया जाए।
इटेरेटर प्रोटोकॉल
इटेरेटर प्रोटोकॉल यह परिभाषित करता है कि एक सीक्वेंस से वैल्यूज कैसे प्राप्त करें। किसी ऑब्जेक्ट को इटेरेटर माना जाता है यदि उसके पास एक next()
मेथड हो जो दो प्रॉपर्टीज वाला एक ऑब्जेक्ट लौटाता है:
value
: सीक्वेंस में अगला मान।done
: एक बूलियन मान जो यह बताता है कि क्या इटेरेटर सीक्वेंस के अंत तक पहुंच गया है। यदिdone
true
है, तोvalue
प्रॉपर्टी को छोड़ा जा सकता है।
next()
मेथड इटेरेटर प्रोटोकॉल का मुख्य कार्यवाहक है। next()
पर प्रत्येक कॉल इटेरेटर को आगे बढ़ाता है और सीक्वेंस में अगला मान लौटाता है। जब सभी मान लौटा दिए जाते हैं, तो next()
done
को true
पर सेट करके एक ऑब्जेक्ट लौटाता है।
बिल्ट-इन इटेरेबल्स
जावास्क्रिप्ट कई अंतर्निहित डेटा स्ट्रक्चर्स प्रदान करता है जो स्वाभाविक रूप से इटेरेबल होते हैं। इनमें शामिल हैं:
- एरे (Arrays)
- स्ट्रिंग्स (Strings)
- मैप्स (Maps)
- सेट्स (Sets)
- फंक्शन का आर्गुमेंट्स ऑब्जेक्ट
- टाइप्डएरे (TypedArrays)
इन इटेरेबल्स को सीधे for...of
लूप, स्प्रेड सिंटैक्स (...
), और अन्य कंस्ट्रक्ट्स के साथ उपयोग किया जा सकता है जो इटेरेटर प्रोटोकॉल पर निर्भर करते हैं।
एरे के साथ उदाहरण:
const myArray = ["apple", "banana", "cherry"];
for (const item of myArray) {
console.log(item); // आउटपुट: apple, banana, cherry
}
स्ट्रिंग्स के साथ उदाहरण:
const myString = "Hello";
for (const char of myString) {
console.log(char); // आउटपुट: H, e, l, l, o
}
for...of
लूप
for...of
लूप इटेरेबल ऑब्जेक्ट्स पर इटेरेट करने के लिए एक शक्तिशाली कंस्ट्रक्ट है। यह स्वचालित रूप से इटेरेटर प्रोटोकॉल की जटिलताओं को संभालता है, जिससे एक सीक्वेंस में मानों तक पहुंचना आसान हो जाता है।
for...of
लूप का सिंटैक्स है:
for (const element of iterable) {
// प्रत्येक एलिमेंट के लिए निष्पादित किया जाने वाला कोड
}
for...of
लूप इटेरेबल ऑब्जेक्ट से इटेरेटर प्राप्त करता है (Symbol.iterator
का उपयोग करके), और इटेरेटर के next()
मेथड को तब तक बार-बार कॉल करता है जब तक कि done
true
न हो जाए। प्रत्येक इटेरेशन के लिए, element
वेरिएबल को next()
द्वारा लौटाई गई value
प्रॉपर्टी असाइन की जाती है।
कस्टम इटेरेटर्स बनाना
हालांकि जावास्क्रिप्ट बिल्ट-इन इटेरेबल्स प्रदान करता है, इटेरेटर प्रोटोकॉल की असली ताकत आपके अपने डेटा स्ट्रक्चर्स के लिए कस्टम इटेरेटर्स को परिभाषित करने की क्षमता में निहित है। यह आपको यह नियंत्रित करने की अनुमति देता है कि आपके डेटा को कैसे ट्रैवर्स और एक्सेस किया जाता है।
यहां एक कस्टम इटेरेटर बनाने का तरीका बताया गया है:
- एक क्लास या ऑब्जेक्ट को परिभाषित करें जो आपके कस्टम डेटा स्ट्रक्चर का प्रतिनिधित्व करता है।
- अपनी क्लास या ऑब्जेक्ट पर
Symbol.iterator
मेथड लागू करें। इस मेथड को एक इटेरेटर ऑब्जेक्ट लौटाना चाहिए। - इटेरेटर ऑब्जेक्ट में एक
next()
मेथड होना चाहिए जोvalue
औरdone
प्रॉपर्टीज के साथ एक ऑब्जेक्ट लौटाता है।
उदाहरण: एक सरल रेंज के लिए एक इटेरेटर बनाना
चलिए Range
नामक एक क्लास बनाते हैं जो संख्याओं की एक रेंज का प्रतिनिधित्व करती है। हम रेंज में संख्याओं पर इटेरेट करने की अनुमति देने के लिए इटेरेटर प्रोटोकॉल को लागू करेंगे।
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let currentValue = this.start;
const that = this; // इटेरेटर ऑब्जेक्ट के अंदर उपयोग के लिए 'this' को कैप्चर करें
return {
next() {
if (currentValue <= that.end) {
return {
value: currentValue++,
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
},
};
}
}
const myRange = new Range(1, 5);
for (const number of myRange) {
console.log(number); // आउटपुट: 1, 2, 3, 4, 5
}
स्पष्टीकरण:
Range
क्लास अपने कंस्ट्रक्टर मेंstart
औरend
मान लेती है।Symbol.iterator
मेथड एक इटेरेटर ऑब्जेक्ट लौटाता है। इस इटेरेटर ऑब्जेक्ट की अपनी स्टेट (currentValue
) और एकnext()
मेथड है।next()
मेथड जांचता है कि क्याcurrentValue
रेंज के भीतर है। यदि है, तो यह वर्तमान मान औरdone
कोfalse
पर सेट करके एक ऑब्जेक्ट लौटाता है। यह अगले इटेरेशन के लिएcurrentValue
को भी बढ़ाता है।- जब
currentValue
end
मान से अधिक हो जाता है, तोnext()
मेथडdone
कोtrue
पर सेट करके एक ऑब्जेक्ट लौटाता है। that = this
के उपयोग पर ध्यान दें। क्योंकि `next()` मेथड को एक अलग स्कोप में ( `for...of` लूप द्वारा) कॉल किया जाता है, `next()` के अंदर `this` `Range` इंस्टेंस को संदर्भित नहीं करेगा। इसे हल करने के लिए, हम `this` मान ( `Range` इंस्टेंस) को `next()` के स्कोप के बाहर `that` में कैप्चर करते हैं और फिर `next()` के अंदर `that` का उपयोग करते हैं।
उदाहरण: एक लिंक्ड लिस्ट के लिए इटेरेटर बनाना
चलिए एक और उदाहरण पर विचार करते हैं: एक लिंक्ड लिस्ट डेटा स्ट्रक्चर के लिए एक इटेरेटर बनाना। एक लिंक्ड लिस्ट नोड्स का एक अनुक्रम है, जहां प्रत्येक नोड में एक मान और सूची में अगले नोड का एक संदर्भ (पॉइंटर) होता है। सूची के अंतिम नोड में null (या undefined) का संदर्भ होता है।
class LinkedListNode {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
class LinkedList {
constructor() {
this.head = null;
}
append(value) {
const newNode = new LinkedListNode(value);
if (!this.head) {
this.head = newNode;
return;
}
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
[Symbol.iterator]() {
let current = this.head;
return {
next() {
if (current) {
const value = current.value;
current = current.next;
return {
value: value,
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
}
}
// उदाहरण उपयोग:
const myList = new LinkedList();
myList.append("London");
myList.append("Paris");
myList.append("Tokyo");
for (const city of myList) {
console.log(city); // आउटपुट: London, Paris, Tokyo
}
स्पष्टीकरण:
LinkedListNode
क्लास लिंक्ड लिस्ट में एक एकल नोड का प्रतिनिधित्व करती है, जिसमें एकvalue
और अगले नोड का एक संदर्भ (next
) संग्रहीत होता है।LinkedList
क्लास स्वयं लिंक्ड लिस्ट का प्रतिनिधित्व करती है। इसमें एकhead
प्रॉपर्टी होती है, जो सूची के पहले नोड को इंगित करती है।append()
मेथड सूची के अंत में नए नोड जोड़ता है।Symbol.iterator
मेथड एक इटेरेटर ऑब्जेक्ट बनाता और लौटाता है। यह इटेरेटर वर्तमान में देखे जा रहे नोड (current
) का ट्रैक रखता है।next()
मेथड जांचता है कि क्या कोई वर्तमान नोड है (current
null नहीं है)। यदि है, तो यह वर्तमान नोड से मान प्राप्त करता है,current
पॉइंटर को अगले नोड पर ले जाता है, और मान औरdone: false
के साथ एक ऑब्जेक्ट लौटाता है।- जब
current
null हो जाता है (जिसका अर्थ है कि हम सूची के अंत तक पहुँच गए हैं), तोnext()
मेथडdone: true
के साथ एक ऑब्जेक्ट लौटाता है।
जनरेटर फंक्शन्स
जनरेटर फंक्शन्स इटेरेटर्स बनाने का एक अधिक संक्षिप्त और सुंदर तरीका प्रदान करते हैं। वे मांग पर मान उत्पन्न करने के लिए yield
कीवर्ड का उपयोग करते हैं।
एक जनरेटर फंक्शन को function*
सिंटैक्स का उपयोग करके परिभाषित किया जाता है।
उदाहरण: एक जनरेटर फंक्शन का उपयोग करके एक इटेरेटर बनाना
चलिए Range
इटेरेटर को एक जनरेटर फंक्शन का उपयोग करके फिर से लिखते हैं:
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const myRange = new Range(1, 5);
for (const number of myRange) {
console.log(number); // आउटपुट: 1, 2, 3, 4, 5
}
स्पष्टीकरण:
Symbol.iterator
मेथड अब एक जनरेटर फंक्शन है (*
पर ध्यान दें)।- जनरेटर फंक्शन के अंदर, हम संख्याओं की रेंज पर इटेरेट करने के लिए एक
for
लूप का उपयोग करते हैं। yield
कीवर्ड जनरेटर फंक्शन के निष्पादन को रोकता है और वर्तमान मान (i
) लौटाता है। अगली बार जब इटेरेटर काnext()
मेथड कॉल किया जाता है, तो निष्पादन वहीं से फिर से शुरू होता है जहां से वह छूटा था (yield
स्टेटमेंट के बाद)।- जब लूप समाप्त हो जाता है, तो जनरेटर फंक्शन अप्रत्यक्ष रूप से
{ value: undefined, done: true }
लौटाता है, जो इटेरेशन के अंत का संकेत देता है।
जनरेटर फंक्शन्स next()
मेथड और done
फ्लैग को स्वचालित रूप से संभालकर इटेरेटर निर्माण को सरल बनाते हैं।
उदाहरण: फिबोनैकी सीक्वेंस जनरेटर
जनरेटर फंक्शन्स का उपयोग करने का एक और बढ़िया उदाहरण फिबोनैकी सीक्वेंस उत्पन्न करना है:
function* fibonacciSequence() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b]; // एक साथ अपडेट के लिए डीस्ट्रक्चरिंग असाइनमेंट
}
}
const fibonacci = fibonacciSequence();
for (let i = 0; i < 10; i++) {
console.log(fibonacci.next().value); // आउटपुट: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}
स्पष्टीकरण:
fibonacciSequence
फंक्शन एक जनरेटर फंक्शन है।- यह दो वेरिएबल्स,
a
औरb
, को फिबोनैकी सीक्वेंस की पहली दो संख्याओं (0 और 1) से इनिशियलाइज़ करता है। while (true)
लूप एक अनंत सीक्वेंस बनाता है।yield a
स्टेटमेंटa
का वर्तमान मान उत्पन्न करता है।[a, b] = [b, a + b]
स्टेटमेंट डीस्ट्रक्चरिंग असाइनमेंट का उपयोग करकेa
औरb
को सीक्वेंस की अगली दो संख्याओं में एक साथ अपडेट करता है।fibonacci.next().value
एक्सप्रेशन जनरेटर से अगला मान प्राप्त करता है। चूँकि जनरेटर अनंत है, आपको यह नियंत्रित करने की आवश्यकता है कि आप इससे कितने मान निकालते हैं। इस उदाहरण में, हम पहले 10 मान निकालते हैं।
इटेरेटर प्रोटोकॉल का उपयोग करने के लाभ
- मानकीकरण: इटेरेटर प्रोटोकॉल विभिन्न डेटा स्ट्रक्चर्स पर इटेरेट करने का एक सुसंगत तरीका प्रदान करता है।
- लचीलापन: आप अपनी विशिष्ट आवश्यकताओं के अनुरूप कस्टम इटेरेटर्स को परिभाषित कर सकते हैं।
- पठनीयता:
for...of
लूप इटेरेशन कोड को अधिक पठनीय और संक्षिप्त बनाता है। - दक्षता: इटेरेटर्स लेज़ी हो सकते हैं, जिसका अर्थ है कि वे केवल आवश्यकता पड़ने पर ही मान उत्पन्न करते हैं, जो बड़े डेटा सेट के लिए प्रदर्शन में सुधार कर सकता है। उदाहरण के लिए, उपरोक्त फिबोनैकी सीक्वेंस जनरेटर केवल तभी अगला मान गणना करता है जब `next()` को कॉल किया जाता है।
- संगतता: इटेरेटर्स स्प्रेड सिंटैक्स और डीस्ट्रक्चरिंग जैसी अन्य जावास्क्रिप्ट सुविधाओं के साथ आसानी से काम करते हैं।
उन्नत इटेरेटर तकनीकें
इटेरेटर्स को मिलाना
आप कई इटेरेटर्स को एक ही इटेरेटर में मिला सकते हैं। यह तब उपयोगी होता है जब आपको कई स्रोतों से डेटा को एक एकीकृत तरीके से संसाधित करने की आवश्यकता होती है।
function* combineIterators(...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
yield item;
}
}
}
const array1 = [1, 2, 3];
const array2 = ["a", "b", "c"];
const string1 = "XYZ";
const combined = combineIterators(array1, array2, string1);
for (const value of combined) {
console.log(value); // आउटपुट: 1, 2, 3, a, b, c, X, Y, Z
}
इस उदाहरण में, `combineIterators` फंक्शन किसी भी संख्या में इटेरेबल्स को आर्ग्यूमेंट्स के रूप में लेता है। यह प्रत्येक इटेरेबल पर इटेरेट करता है और प्रत्येक आइटम को यील्ड करता है। परिणाम एक एकल इटेरेटर है जो सभी इनपुट इटेरेबल्स से सभी मानों का उत्पादन करता है।
इटेरेटर्स को फ़िल्टर और ट्रांसफ़ॉर्म करना
आप ऐसे इटेरेटर्स भी बना सकते हैं जो किसी अन्य इटेरेटर द्वारा उत्पादित मानों को फ़िल्टर या ट्रांसफ़ॉर्म करते हैं। यह आपको डेटा को एक पाइपलाइन में संसाधित करने की अनुमति देता है, प्रत्येक मान पर अलग-अलग ऑपरेशन लागू करते हुए जैसे ही यह उत्पन्न होता है।
function* filterIterator(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* mapIterator(iterable, transform) {
for (const item of iterable) {
yield transform(item);
}
}
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = filterIterator(numbers, (x) => x % 2 === 0);
const squaredEvenNumbers = mapIterator(evenNumbers, (x) => x * x);
for (const value of squaredEvenNumbers) {
console.log(value); // आउटपुट: 4, 16, 36
}
यहां, `filterIterator` एक इटेरेबल और एक प्रेडिकेट फंक्शन लेता है। यह केवल उन आइटम्स को यील्ड करता है जिनके लिए प्रेडिकेट `true` लौटाता है। `mapIterator` एक इटेरेबल और एक ट्रांसफ़ॉर्म फंक्शन लेता है। यह प्रत्येक आइटम पर ट्रांसफ़ॉर्म फंक्शन लागू करने का परिणाम यील्ड करता है।
वास्तविक-दुनिया के अनुप्रयोग
इटेरेटर प्रोटोकॉल का व्यापक रूप से जावास्क्रिप्ट लाइब्रेरी और फ्रेमवर्क में उपयोग किया जाता है, और यह विभिन्न प्रकार के वास्तविक-दुनिया के अनुप्रयोगों में मूल्यवान है, खासकर जब बड़े डेटासेट या एसिंक्रोनस ऑपरेशंस से निपटना हो।
- डेटा प्रोसेसिंग: इटेरेटर्स बड़े डेटासेट को कुशलता से संसाधित करने के लिए उपयोगी होते हैं, क्योंकि वे आपको पूरे डेटासेट को मेमोरी में लोड किए बिना डेटा के टुकड़ों में काम करने की अनुमति देते हैं। ग्राहक डेटा वाली एक बड़ी CSV फ़ाइल को पार्स करने की कल्पना करें। एक इटेरेटर आपको पूरी फ़ाइल को एक बार में मेमोरी में लोड किए बिना प्रत्येक पंक्ति को संसाधित करने की अनुमति दे सकता है।
- एसिंक्रोनस ऑपरेशंस: इटेरेटर्स का उपयोग एसिंक्रोनस ऑपरेशंस को संभालने के लिए किया जा सकता है, जैसे कि एपीआई से डेटा लाना। आप डेटा उपलब्ध होने तक निष्पादन को रोकने के लिए जनरेटर फंक्शन्स का उपयोग कर सकते हैं और फिर अगले मान के साथ फिर से शुरू कर सकते हैं।
- कस्टम डेटा स्ट्रक्चर्स: विशिष्ट ट्रैवर्सल आवश्यकताओं के साथ कस्टम डेटा स्ट्रक्चर्स बनाने के लिए इटेरेटर्स आवश्यक हैं। एक ट्री डेटा स्ट्रक्चर पर विचार करें। आप ट्री को एक विशिष्ट क्रम (जैसे, डेप्थ-फर्स्ट या ब्रेड्थ-फर्स्ट) में ट्रैवर्स करने के लिए एक कस्टम इटेरेटर लागू कर सकते हैं।
- गेम डेवलपमेंट: गेम डेवलपमेंट में, इटेरेटर्स का उपयोग गेम ऑब्जेक्ट्स, पार्टिकल इफेक्ट्स और अन्य डायनेमिक एलिमेंट्स को मैनेज करने के लिए किया जा सकता है।
- यूजर इंटरफेस लाइब्रेरी: कई UI लाइब्रेरी अंतर्निहित डेटा परिवर्तनों के आधार पर घटकों को कुशलतापूर्वक अपडेट और रेंडर करने के लिए इटेरेटर्स का उपयोग करती हैं।
सर्वोत्तम अभ्यास
Symbol.iterator
को सही ढंग से लागू करें: सुनिश्चित करें कि आपकाSymbol.iterator
मेथड एक इटेरेटर ऑब्जेक्ट लौटाता है जो इटेरेटर प्रोटोकॉल के अनुरूप है।done
फ्लैग को सटीक रूप से हैंडल करें:done
फ्लैग इटेरेशन के अंत का संकेत देने के लिए महत्वपूर्ण है। सुनिश्चित करें कि आप इसे अपनेnext()
मेथड में सही ढंग से सेट करते हैं।- जनरेटर फंक्शन्स का उपयोग करने पर विचार करें: जनरेटर फंक्शन्स इटेरेटर्स बनाने का एक अधिक संक्षिप्त और पठनीय तरीका प्रदान करते हैं।
next()
में साइड इफेक्ट्स से बचें:next()
मेथड को मुख्य रूप से अगला मान प्राप्त करने और इटेरेटर की स्थिति को अपडेट करने पर ध्यान केंद्रित करना चाहिए।next()
के भीतर जटिल ऑपरेशन या साइड इफेक्ट्स करने से बचें।- अपने इटेरेटर्स का अच्छी तरह से परीक्षण करें: यह सुनिश्चित करने के लिए कि वे सही ढंग से व्यवहार करते हैं, अपने कस्टम इटेरेटर्स का विभिन्न डेटा सेट और परिदृश्यों के साथ परीक्षण करें।
निष्कर्ष
जावास्क्रिप्ट इटेरेटर प्रोटोकॉल डेटा स्ट्रक्चर्स को ट्रैवर्स करने का एक शक्तिशाली और लचीला तरीका प्रदान करता है। इटेरेबल और इटेरेटर प्रोटोकॉल को समझकर, और जनरेटर फंक्शन्स का लाभ उठाकर, आप अपनी विशिष्ट आवश्यकताओं के अनुरूप कस्टम इटेरेटर्स बना सकते हैं। यह आपको डेटा के साथ कुशलता से काम करने, कोड पठनीयता में सुधार करने और अपने अनुप्रयोगों के प्रदर्शन को बढ़ाने की अनुमति देता है। इटेरेटर्स में महारत हासिल करना जावास्क्रिप्ट की क्षमताओं की एक गहरी समझ को खोलता है और आपको अधिक सुंदर और कुशल कोड लिखने के लिए सशक्त बनाता है।