Dalam skenario tertentu, upaya untuk mengulangi kunci atau nilai Peta JavaScript gagal. Script tidak masuk ke loop.

function pushAndLog(array, values) {
    array.push(...values);
    for (const value of values) {
        console.log(value);
    }
}

const array = [];
const map = new Map([["a", 1], ["b", 2]]);
pushAndLog(array, map.keys());

Dalam contoh ini, array diperbarui dengan benar, tetapi kunci peta tidak dicatat.

0
Egor Nepomnyaschih 7 November 2020, 23:46

1 menjawab

Jawaban Terbaik

Alasan

Ini terjadi karena Map.keys() dan Map.values() metode mengembalikan Iterator alih-alih < a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol" rel="nofollow noreferrer">Iterable objek.

Bagaimana cara memperbaiki

Hindari menggunakan kembali iterator

Berbeda dengan Iterable, Iterator adalah objek stateful. Anda tidak dapat menggunakannya kembali. Secara khusus, jika Anda meneruskan Iterator ke suatu fungsi, Anda tidak dapat menggunakannya setelah itu - Anda harus membuat Iterator baru. Ini tidak selalu mungkin.

Gunakan iterable

Cara termudah untuk memperbaiki kode ini adalah dengan meneruskan koleksi ke fungsi alih-alih iterator:

pushAndLog(array, [...map.keys()]);

Tetapi solusi ini umumnya bukan yang paling efisien, karena pembuatan koleksi baru menghabiskan memori dan waktu CPU. Ada solusi yang lebih baik. Tambahkan utilitas berikut ke kode Anda:

export function getIterableKeys<K, V>(map: Iterable<readonly [K, V]>): Iterable<K> {
    return {
        [Symbol.iterator]: function* () {
            for (const [key, _] of map) {
                yield key;
            }
        }
    };
}

export function getIterableValues<K, V>(map: Iterable<readonly [K, V]>): Iterable<V> {
    return {
        [Symbol.iterator]: function* () {
            for (const [_, value] of map) {
                yield value;
            }
        }
    };
}

Gunakan mereka sebagai ganti metode Map.keys() dan Map.values() asli:

pushAndLog(array, getIterableKeys(map));

Ini bekerja lebih baik untuk peta yang lebih besar.

Catatan:

  • Fungsi ditulis dalam TypeScript. Untuk menerjemahkannya ke JavaScript, cukup hapus definisi tipe dari header fungsi.
  • Fungsi ditulis sebagai modul ES6. Untuk menerjemahkannya ke JavaScript biasa, cukup hapus kata kunci export.
  • Fungsi bersarang adalah fungsi generator, yaitu mengembalikan Generator yang juga dapat berfungsi sebagai Iterator. Untuk menggunakan kode ini, pastikan penerjemah Anda mendukung generator atau gunakan transpiler TypeScript/Babel dalam kombinasi dengan regenerator-runtime perpustakaan.

Beberapa pemikiran

Mengembalikan objek Iterator dari metode Map.keys() dan Map.values() adalah keputusan desain yang buruk yang harus kita jalani dan selalu ingat. Kesalahan seperti itu terkadang tidak terduga dan sangat sulit dilacak, karena iterator berfungsi dengan baik hingga Anda mencoba menggunakannya kembali. Jadi Anda harus tetap waspada.

Dalam bahasa pemrograman dewasa lainnya, metode seperti itu dengan cerdas mengembalikan objek yang dapat diubah:

Baik Java Iterable dan .NET IEnumerable dapat digunakan kembali, sehingga penggunanya tidak dapat mengalami masalah yang sama. Pendekatan ini jauh lebih aman. Ini adalah pertanyaan bagi saya mengapa komunitas JavaScript yang relatif muda menciptakan solusi mereka sendiri alih-alih mewarisi praktik terbaik dari bahasa pemrograman dewasa yang ada.

Meskipun JavaScript Iterator bekerja kurang lebih seperti objek Iterable (Anda dapat menggunakannya dalam for..of loop, operator spread, konstruktor koleksi, dll.), Ini secara resmi memutus kontrak kelas Iterable yang dimaksudkan untuk dapat digunakan kembali, sementara Iterator adalah tidak.

Masalahnya diperkuat oleh definisi TypeScript yang berpura-pura bahwa Map.keys() dan Map.values() mengembalikan apa yang disebut objek IterableIterator yang memperluas antarmuka Iterable, yang salah karena alasan yang sama.

1
Egor Nepomnyaschih 8 November 2020, 10:32