Latviešu

Visaptveroša rokasgrāmata JavaScript iteratora protokola izpratnei un ieviešanai, kas ļauj jums izveidot pielāgotus iteratorus uzlabotai datu apstrādei.

JavaScript iteratora protokola un pielāgoto iteratoru demistifikācija

JavaScript iteratora protokols nodrošina standartizētu veidu, kā šķērsot datu struktūras. Izprotot šo protokolu, izstrādātāji var efektīvi strādāt ar iebūvētiem iterējamiem objektiem, piemēram, masīviem un virknēm, un veidot savus pielāgotos iterējamus objektus, kas pielāgoti konkrētām datu struktūrām un lietojumprogrammu prasībām. Šī rokasgrāmata sniedz visaptverošu ieskatu iteratora protokolā un pielāgoto iteratoru ieviešanā.

Kas ir iteratora protokols?

Iteratora protokols nosaka, kā objektu var iterēt, t.i., kā tā elementiem var piekļūt secīgi. Tas sastāv no divām daļām: iterējama objekta (Iterable) protokola un iteratora (Iterator) protokola.

Iterējama objekta protokols

Objekts tiek uzskatīts par iterējamu (Iterable), ja tam ir metode ar atslēgu Symbol.iterator. Šai metodei jāatgriež objekts, kas atbilst iteratora protokolam.

Būtībā iterējams objekts zina, kā sev izveidot iteratoru.

Iteratora protokols

Iteratora protokols nosaka, kā iegūt vērtības no secības. Objekts tiek uzskatīts par iteratoru, ja tam ir next() metode, kas atgriež objektu ar divām īpašībām:

next() metode ir iteratora protokola galvenais dzinējspēks. Katrs next() izsaukums pārvieto iteratoru uz priekšu un atgriež nākamo vērtību secībā. Kad visas vērtības ir atgrieztas, next() atgriež objektu ar done, kas iestatīts uz true.

Iebūvēti iterējami objekti

JavaScript nodrošina vairākas iebūvētas datu struktūras, kas ir pašas par sevi iterējamas. Tās ietver:

Šos iterējamos objektus var tieši izmantot ar for...of ciklu, izvēršanas sintaksi (...) un citām konstrukcijām, kas balstās uz iteratora protokolu.

Piemērs ar masīviem:


const myArray = ["apple", "banana", "cherry"];

for (const item of myArray) {
  console.log(item); // Izvade: apple, banana, cherry
}

Piemērs ar virknēm:


const myString = "Hello";

for (const char of myString) {
  console.log(char); // Izvade: H, e, l, l, o
}

for...of cikls

for...of cikls ir spēcīga konstrukcija iterējamu objektu šķērsošanai. Tas automātiski apstrādā iteratora protokola sarežģītību, padarot vieglu piekļuvi secības vērtībām.

for...of cikla sintakse ir:


for (const element of iterable) {
  // Kods, kas jāizpilda katram elementam
}

for...of cikls iegūst iteratoru no iterējamā objekta (izmantojot Symbol.iterator) un atkārtoti izsauc iteratora next() metodi, līdz done kļūst true. Katrā iterācijā mainīgajam element tiek piešķirta value īpašība, ko atgriež next().

Pielāgotu iteratoru veidošana

Lai gan JavaScript nodrošina iebūvētus iterējamus objektus, iteratora protokola patiesais spēks slēpjas tā spējā definēt pielāgotus iteratorus jūsu pašu datu struktūrām. Tas ļauj jums kontrolēt, kā jūsu dati tiek šķērsoti un kā tiem piekļūst.

Šeit ir aprakstīts, kā izveidot pielāgotu iteratoru:

  1. Definējiet klasi vai objektu, kas pārstāv jūsu pielāgoto datu struktūru.
  2. Implementējiet Symbol.iterator metodi savā klasē vai objektā. Šai metodei jāatgriež iteratora objekts.
  3. Iteratora objektam jābūt next() metodei, kas atgriež objektu ar value un done īpašībām.

Piemērs: Iteratora izveide vienkāršam diapazonam

Izveidosim klasi ar nosaukumu Range, kas pārstāv skaitļu diapazonu. Mēs implementēsim iteratora protokolu, lai ļautu iterēt pār skaitļiem diapazonā.


class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  [Symbol.iterator]() {
    let currentValue = this.start;
    const that = this; // Saglabā 'this', lai to izmantotu iteratora objektā

    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); // Izvade: 1, 2, 3, 4, 5
}

Paskaidrojums:

Piemērs: Iteratora izveide saistītajam sarakstam

Apskatīsim citu piemēru: iteratora izveide saistītā saraksta datu struktūrai. Saistītais saraksts ir mezglu secība, kur katrs mezgls satur vērtību un atsauci (rādītāju) uz nākamo mezglu sarakstā. Pēdējam mezglam sarakstā ir atsauce uz null (vai 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
                    };
                }
            }
        };
    }
}

// Lietošanas piemērs:
const myList = new LinkedList();
myList.append("London");
myList.append("Paris");
myList.append("Tokyo");

for (const city of myList) {
    console.log(city); // Izvade: London, Paris, Tokyo
}

Paskaidrojums:

Ģeneratorfunkcijas

Ģeneratorfunkcijas nodrošina kodolīgāku un elegantāku veidu, kā izveidot iteratorus. Tās izmanto yield atslēgvārdu, lai pēc pieprasījuma radītu vērtības.

Ģeneratorfunkcija tiek definēta, izmantojot function* sintaksi.

Piemērs: Iteratora izveide, izmantojot ģeneratorfunkciju

Pārrakstīsim Range iteratoru, izmantojot ģeneratorfunkciju:


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); // Izvade: 1, 2, 3, 4, 5
}

Paskaidrojums:

Ģeneratorfunkcijas vienkāršo iteratora izveidi, automātiski apstrādājot next() metodi un done karogu.

Piemērs: Fibonači virknes ģenerators

Vēl viens lielisks piemērs ģeneratorfunkciju izmantošanai ir Fibonači virknes ģenerēšana:


function* fibonacciSequence() {
  let a = 0;
  let b = 1;

  while (true) {
    yield a;
    [a, b] = [b, a + b]; // Destrukturējošā piešķiršana vienlaicīgai atjaunināšanai
  }
}

const fibonacci = fibonacciSequence();

for (let i = 0; i < 10; i++) {
  console.log(fibonacci.next().value); // Izvade: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}

Paskaidrojums:

Iteratora protokola lietošanas priekšrocības

Papildu iteratoru tehnikas

Iteratoru apvienošana

Jūs varat apvienot vairākus iteratorus vienā iteratorā. Tas ir noderīgi, ja nepieciešams apstrādāt datus no vairākiem avotiem vienotā veidā.


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); // Izvade: 1, 2, 3, a, b, c, X, Y, Z
}

Šajā piemērā `combineIterators` funkcija kā argumentus pieņem jebkādu skaitu iterējamu objektu. Tā iterē pār katru iterējamo objektu un atgriež (yield) katru elementu. Rezultāts ir viens iterators, kas rada visas vērtības no visiem ievades iterējamiem objektiem.

Iteratoru filtrēšana un transformēšana

Jūs varat arī izveidot iteratorus, kas filtrē vai transformē vērtības, ko rada cits iterators. Tas ļauj apstrādāt datus konveijera veidā, piemērojot dažādas operācijas katrai vērtībai, kad tā tiek ģenerēta.


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); // Izvade: 4, 16, 36
}

Šeit `filterIterator` pieņem iterējamu objektu un predikāta funkciju. Tas atgriež (yield) tikai tos elementus, kuriem predikāts atgriež `true`. `mapIterator` pieņem iterējamu objektu un transformācijas funkciju. Tas atgriež (yield) transformācijas funkcijas piemērošanas rezultātu katram elementam.

Reālās pasaules pielietojumi

Iteratora protokols tiek plaši izmantots JavaScript bibliotēkās un ietvaros, un tas ir vērtīgs dažādos reālās pasaules pielietojumos, īpaši strādājot ar lielām datu kopām vai asinhronām operācijām.

Labākās prakses

Noslēgums

JavaScript iteratora protokols nodrošina jaudīgu un elastīgu veidu, kā šķērsot datu struktūras. Izprotot iterējamo objektu un iteratoru protokolus un izmantojot ģeneratorfunkcijas, jūs varat izveidot pielāgotus iteratorus, kas pielāgoti jūsu īpašajām vajadzībām. Tas ļauj efektīvi strādāt ar datiem, uzlabot koda lasāmību un uzlabot jūsu lietojumprogrammu veiktspēju. Iteratoru apgūšana paver dziļāku izpratni par JavaScript iespējām un dod jums iespēju rakstīt elegantāku un efektīvāku kodu.