Sytuacja kobiet w IT w 2024 roku
20.05.20204 min
Kasra

KasraSoftware DeveloperTidal Music

Zbuduj własną funkcję map w JavaScript

Dowiedz się, jak można zbudować własną funkcję map w JavaScript przy użyciu m.in pętli for, forEach oraz rekurencji.

Zbuduj własną funkcję map w JavaScript

Mapowanie to proces przekształcania tablicy elementów w nową tablicę za pomocą mappera. W wyniku tego procesu otrzymujemy taką samą długość danych, jak podaliśmy. Spójrz na poniższą ilustrację. Ukazuje ona proces mapowania z podejściem deklaratywnym. Nie dyktujemy tutaj, w jaki sposób należy wykonać mapowanie, a raczej, co z nim zrobić.

Podajemy tablicę [?, ?, ?] i mapper cook oraz pozwalamy, aby funkcja map zajęła się iteracją po elementach tablicy i wywołała funkcję mapującą na każdym z nich. Na koniec dostaniemy nową tablicę [?, ?, ?].


Zdjęcie autora (zainspirowane Overflowjs)


Jeśli chodzi o mapowanie, mamy kilka opcji (zarówno z podejściem deklaratywnym, jak i imperatywnym).

Pętla For

Możemy użyć prostej pętli for do przejścia po elementach tablicy:

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;
const result = [];

for (let i = 0; i < items.length; i++) {
 result.push(double(items[i]));
}


console.log(result);
// Result: [2, 4, 6, 8, 10]


Jak widać, spoczywa na nas zadanie śledzenia indeksów, inicjowania i przekazywania wyników do tablicy. Jest to wyraźny przykład programowania imperatywnego, które mówi komputerowi, w jaki sposób chcemy osiągnąć coś krok po kroku.

forEach

Inną opcją jest forEach, która iteruje po każdym elemencie tablicy oddzielnie:

let items = [1, 2, 3, 4, 5];
let double = item => item * 2;
const result = [];

items.forEach(item => {
    const doubledItem = double(item);
    result.push(doubledItem);
});


console.log(result);
// Result: [2, 4, 6, 8, 10]

Wygląda lepiej, prawda? Nie musimy już śledzić indeksów elementów. Możemy się jednak zgodzić, że mutowanie elementu poza zasięgiem funkcji (w tym przypadku rezultatu) nie jest idealne. Byłoby super, gdybyśmy mogli znaleźć lepszą abstrakcję.

Lepszą alternatywą jest natywna mapa JavaScript.

Natywna mapa JS

Użyjmy natywnej metody mapy JavaScript. Wszystko, czego potrzebujemy to tablica danych i mapper. map otrzyma tablicę i przeiteruje po każdym jej elemencie, jednocześnie stosując na nich funkcję mapera. Na koniec zwróci skonwertowaną tablicę o tej samej długości.

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

const result = items.map(double);

console.log(result);
// Result: [2, 4, 6, 8, 10]

Pod względem czytelności, jest to znacznie lepsze w porównaniu do forEach lub pętli for. Wydajność jest jednak bardzo istotna i trzeba ją wziąć pod uwagę, podejmując decyzję. 

Budowanie własnej funkcji map

A teraz ta przyjemna część. Czy wiesz, że zbudowanie funkcji map nie jest takie trudne? Zobaczmy, jak to działa.


Nasza funkcja map (wersja z pętlą for)

Tutaj ubieramy w abstrakcję śledzenie indeksu i inicjalizowanie tablicy. Wszystko, co musimy przekazać, to mapper i tablicę elementów.

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

// Loop Version of Map
let MapLoop = (fn, arr) => {
    const mappedArr = [];
    for (let i = 0; i < arr.length; i++) {
        let mapped = fn(arr[i]);
        mappedArr.push(mapped);
    }
    return mappedArr;
};

console.log(MapLoop(double, items));
// Result: [2, 4, 6, 8, 10]


Nasza funkcja map (wersja rekurencyjna)

Zbudowanie rekurencyjnej wersji funkcji map jest interesujące. Jak to działa? Nadal przekazujemy zarówno mapper, jak i tablicę do funkcji, ale używamy ES6 destructuring assignment, aby rozbić tablicę na dwa parametry o nazwie head oraz tail.

Stosujemy podejście krok po kroku i rekurencyjnie wykonujemy mapper na każdym z elementów tablicy. W tym procesie używamy składni spread, aby połączyć wynik każdego wywołania MapRecursive ze zmapowanym wynikiem fn(head).

Trwa to do momentu, gdy head stanie się undefined, co oznacza, że nie ma już więcej elementów w tablicy. Wtedy wydostaniemy się z funkcji rekurencyjnej pokazanej w line 8 i zaczynamy zwracać nową transformowaną tablicę.

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

// Recursive Version of Map
let MapRecursive = (fn, [head, ...tail]) => {


    // bailout
    if (head === undefined) {
       return [];
    }
    return[fn(head), ...MapRecursive(fn, tail)];
};

console.log(MapRecursive(double, items));
// Step 1: head: 1, tail: [2,3,4,5], newArray: [2, ...MapRecursive(double, [2,3,4,5])]
// Step 2: head: 2, tail: [3,4,5], newArray: [2,4, ...MapRecursive(double, [3,4,5])]
// Step 3: head: 3, tail: [4,5], newArray: [2,4,6, ...MapRecursive(double, [4,5])]
// Step 4: head: 4, tail: [5], newArray: [2,4,6,8 ...MapRecursive(double, [5])]
// Step 5: head: 5, tail: [], newArray: [2,4,6,8,10 ...MapRecursive(double, [])]
// Step 6: head: undefined -> return newArray: [2,4,6,8,10]


Nasz funkcja map (wersja z generatorem)

Możesz także zbudować funkcję mapy za pomocą funkcji generatora. Nie jest to idealny sposób na obsługę mapowania i nie daje takiego samego rezultatu jak poprzednie przykłady, ponieważ funkcje generatora zwracają iterator.

Ta wersja ma charakter edukacyjny, żebyśmy mogli zobaczyć, jak podobną koncepcję można zastosować również w funkcjach generatora.

Poniżej możesz zobaczyć końcowy wynik wywołania MapGenerator:

let items = [1, 2, 3, 4, 5];
let double = (item) => item * 2;

// Generator version of Map
let MapGenerator = function * (fn, arr) {
    for (let x of arr) {
        yield fn(x);
    }
};

const result = MapGenerator(double, items);

console.log(result.next());
// Object {value: 2, done: false}
console.log(result.next());
// Object {value: 4, done: false}
console.log(result.next());
// Object {value: 6, done: false}
console.log(result.next());
// Object {value: 8, done: false}
console.log(result.next());
// Object {value: 10, done: false}
console.log(result.next());
// Object {value: undefined, done: true}

Źródła



Jeśli ten artykuł okazał się dla Ciebie przydatny, sprawdź również Przewodnik dla Junior Developera od autora tekstu.

<p>Loading...</p>