हिन्दी

जावास्क्रिप्ट इटेरेटर प्रोटोकॉल को समझने और लागू करने के लिए एक विस्तृत गाइड, जो आपको बेहतर डेटा हैंडलिंग के लिए कस्टम इटेरेटर्स बनाने में सशक्त बनाता है।

जावास्क्रिप्ट इटेरेटर प्रोटोकॉल और कस्टम इटेरेटर्स को समझना

जावास्क्रिप्ट का इटेरेटर प्रोटोकॉल डेटा स्ट्रक्चर्स को ट्रैवर्स करने का एक मानकीकृत तरीका प्रदान करता है। इस प्रोटोकॉल को समझना डेवलपर्स को एरे और स्ट्रिंग्स जैसे बिल्ट-इन इटेरेबल्स के साथ कुशलता से काम करने और विशिष्ट डेटा स्ट्रक्चर्स और एप्लिकेशन आवश्यकताओं के अनुरूप अपने स्वयं के कस्टम इटेरेबल्स बनाने में सशक्त बनाता है। यह गाइड इटेरेटर प्रोटोकॉल और कस्टम इटेरेटर्स को लागू करने के तरीके का एक व्यापक अन्वेषण प्रदान करता है।

इटेरेटर प्रोटोकॉल क्या है?

इटेरेटर प्रोटोकॉल यह परिभाषित करता है कि किसी ऑब्जेक्ट पर कैसे इटेरेट किया जा सकता है, यानी, उसके एलिमेंट्स तक क्रमिक रूप से कैसे पहुंचा जा सकता है। इसमें दो भाग होते हैं: इटेरेबल प्रोटोकॉल और इटेरेटर प्रोटोकॉल।

इटेरेबल प्रोटोकॉल

किसी ऑब्जेक्ट को इटेरेबल माना जाता है यदि उसके पास Symbol.iterator की (key) के साथ एक मेथड हो। इस मेथड को एक ऐसा ऑब्जेक्ट लौटाना चाहिए जो इटेरेटर प्रोटोकॉल के अनुरूप हो।

संक्षेप में, एक इटेरेबल ऑब्जेक्ट जानता है कि अपने लिए एक इटेरेटर कैसे बनाया जाए।

इटेरेटर प्रोटोकॉल

इटेरेटर प्रोटोकॉल यह परिभाषित करता है कि एक सीक्वेंस से वैल्यूज कैसे प्राप्त करें। किसी ऑब्जेक्ट को इटेरेटर माना जाता है यदि उसके पास एक next() मेथड हो जो दो प्रॉपर्टीज वाला एक ऑब्जेक्ट लौटाता है:

next() मेथड इटेरेटर प्रोटोकॉल का मुख्य कार्यवाहक है। next() पर प्रत्येक कॉल इटेरेटर को आगे बढ़ाता है और सीक्वेंस में अगला मान लौटाता है। जब सभी मान लौटा दिए जाते हैं, तो next() done को true पर सेट करके एक ऑब्जेक्ट लौटाता है।

बिल्ट-इन इटेरेबल्स

जावास्क्रिप्ट कई अंतर्निहित डेटा स्ट्रक्चर्स प्रदान करता है जो स्वाभाविक रूप से इटेरेबल होते हैं। इनमें शामिल हैं:

इन इटेरेबल्स को सीधे 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 प्रॉपर्टी असाइन की जाती है।

कस्टम इटेरेटर्स बनाना

हालांकि जावास्क्रिप्ट बिल्ट-इन इटेरेबल्स प्रदान करता है, इटेरेटर प्रोटोकॉल की असली ताकत आपके अपने डेटा स्ट्रक्चर्स के लिए कस्टम इटेरेटर्स को परिभाषित करने की क्षमता में निहित है। यह आपको यह नियंत्रित करने की अनुमति देता है कि आपके डेटा को कैसे ट्रैवर्स और एक्सेस किया जाता है।

यहां एक कस्टम इटेरेटर बनाने का तरीका बताया गया है:

  1. एक क्लास या ऑब्जेक्ट को परिभाषित करें जो आपके कस्टम डेटा स्ट्रक्चर का प्रतिनिधित्व करता है।
  2. अपनी क्लास या ऑब्जेक्ट पर Symbol.iterator मेथड लागू करें। इस मेथड को एक इटेरेटर ऑब्जेक्ट लौटाना चाहिए।
  3. इटेरेटर ऑब्जेक्ट में एक 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
}

स्पष्टीकरण:

उदाहरण: एक लिंक्ड लिस्ट के लिए इटेरेटर बनाना

चलिए एक और उदाहरण पर विचार करते हैं: एक लिंक्ड लिस्ट डेटा स्ट्रक्चर के लिए एक इटेरेटर बनाना। एक लिंक्ड लिस्ट नोड्स का एक अनुक्रम है, जहां प्रत्येक नोड में एक मान और सूची में अगले नोड का एक संदर्भ (पॉइंटर) होता है। सूची के अंतिम नोड में 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
}

स्पष्टीकरण:

जनरेटर फंक्शन्स

जनरेटर फंक्शन्स इटेरेटर्स बनाने का एक अधिक संक्षिप्त और सुंदर तरीका प्रदान करते हैं। वे मांग पर मान उत्पन्न करने के लिए 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
}

स्पष्टीकरण:

जनरेटर फंक्शन्स 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
}

स्पष्टीकरण:

इटेरेटर प्रोटोकॉल का उपयोग करने के लाभ

उन्नत इटेरेटर तकनीकें

इटेरेटर्स को मिलाना

आप कई इटेरेटर्स को एक ही इटेरेटर में मिला सकते हैं। यह तब उपयोगी होता है जब आपको कई स्रोतों से डेटा को एक एकीकृत तरीके से संसाधित करने की आवश्यकता होती है।


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` एक इटेरेबल और एक ट्रांसफ़ॉर्म फंक्शन लेता है। यह प्रत्येक आइटम पर ट्रांसफ़ॉर्म फंक्शन लागू करने का परिणाम यील्ड करता है।

वास्तविक-दुनिया के अनुप्रयोग

इटेरेटर प्रोटोकॉल का व्यापक रूप से जावास्क्रिप्ट लाइब्रेरी और फ्रेमवर्क में उपयोग किया जाता है, और यह विभिन्न प्रकार के वास्तविक-दुनिया के अनुप्रयोगों में मूल्यवान है, खासकर जब बड़े डेटासेट या एसिंक्रोनस ऑपरेशंस से निपटना हो।

सर्वोत्तम अभ्यास

निष्कर्ष

जावास्क्रिप्ट इटेरेटर प्रोटोकॉल डेटा स्ट्रक्चर्स को ट्रैवर्स करने का एक शक्तिशाली और लचीला तरीका प्रदान करता है। इटेरेबल और इटेरेटर प्रोटोकॉल को समझकर, और जनरेटर फंक्शन्स का लाभ उठाकर, आप अपनी विशिष्ट आवश्यकताओं के अनुरूप कस्टम इटेरेटर्स बना सकते हैं। यह आपको डेटा के साथ कुशलता से काम करने, कोड पठनीयता में सुधार करने और अपने अनुप्रयोगों के प्रदर्शन को बढ़ाने की अनुमति देता है। इटेरेटर्स में महारत हासिल करना जावास्क्रिप्ट की क्षमताओं की एक गहरी समझ को खोलता है और आपको अधिक सुंदर और कुशल कोड लिखने के लिए सशक्त बनाता है।