Search code examples
javascriptlocal-storagefor-in-loop

Using for-in loop to iterate localStorage returns spurious entries


I am trying to make a basic notes app using JS.

I am storing notes in localStorage, and I am trying to print those notes using for-in loop. It works mostly fine, but I don’t know why I am getting extra values like length, getItem, key, etc. Can anyone help?

My code:

for (const key in localStorage) {
    let notes = document.getElementById("notes");
    let value = localStorage.getItem(key);
    notes.innerHTML = notes.innerHTML + `${key}: ${value} <br>`;
}

Solution

  • localStorage (and likewise sessionStorage) is an object similar to Map, in that its own properties and methods don’t directly correspond to values actually kept in the storage. Enumerating its properties and accessing missing properties on it will fall back to returning items from the underlying storage, but that is actually a design flaw that is best not relied on.

    To enumerate the contents of a storage object (localStorage or sessionStorage), you shouldn’t use for-in, Object.keys or Object.entries. Use key, length and getItem:

    function* storageEntries(storage) {
        for (let i = 0; i < storage.length; ++i) {
            const key = storage.key(i); 
            yield [key, storage.getItem(key)];
        }
    }
    
    for (const [key, val] of storageEntries(localStorage)) {
        console.info(
            `localStorage ${JSON.stringify(key)} maps to ${JSON.stringify(val)}`);
    }
    

    Using the for-in loop and property accesses will return properties of the localStorage object itself, while using Object.entries or Object.keys will skip them even if there actually are storage items with the same keys. For example:

    localStorage.clear();
    localStorage.setItem('length', -69420);
    localStorage.setItem('getItem', 'where is your god now?');
    
    // returns wrong values
    const result0 = [];
    for (var key in localStorage) {
        result0.push(key, localStorage[key]);
    }
    console.log('WRONG:', result0);
    
    // returns right values, but also spurious keys
    const result1 = [];
    for (var key in localStorage) {
        result1.push(key, localStorage.getItem(key));
    }
    console.log('WRONG:', result1);
    
    // returns an empty array
    console.log('WRONG:', Object.entries(localStorage));
    
    // returns two key-value pairs
    console.log('RIGHT:', Array.from(storageEntries(localStorage)));
    

    As an aside though, keep in mind that each storage object provides a single namespace shared by all scripts that happen to be running on your page. If any other script you use decides to store items in localStorage as well, those may appear as ‘notes’ in your app, and modifying them may corrupt those other scripts’ data. You may be better off at the very least prefixing (or suffixing) storage keys you use with something specific to your app, to section them off and minimise the risk of clashes.