localStorage — Размер имеет значение

Речь пойдет о размере хранимых в localStorage объектов. Да, именно объектов (массивов, коллекций и т.д), а не строк или чисел. Для записи элемента в localStorage есть метод setItem. Он принимает два параметра — ключ и значение. Что примечательно, оба параметра являются строками. Для того что бы записать объект, его надо вначале пропустить через JSON.stringify. Соответственно, чтоб потом его прочитать и распарсить используются getItem и JSON.parse. Ничего хитрого.

Хитрости начинаются тогда, когда в localStorage скапливается большое количество данных (которые все одновременно нужны). Я имею ввиду ситуации, когда общий объем данных больше 2МБ. Так бывает в SPA-приложениях, в которых идет мониторинг долго идущих процессов и обновление страницы не должно влиять на отображение данных пользователю. В таких ситуациях остро встают вопросы организации объектов в хранилище и количество обращений к нему. Оба этих аспекта тесно связаны между собой.

Есть несколько возможных ситуаций:

  • Ситуация 1: в localStorage хранится один большой объект с данными для вашего приложения. И каждый раз, когда надо получить какую-то часть данных, вытягивается весь объект.
  • Ситуация 2: тоже самое, что и Ситуация 1, однако количество обращений к localStorage минимизировано (за каждое обращение вытягивается несколько частей данных).
  • Ситуация 3: в localStorage хранится много объектов — каждый соответствует своей части приложения. Тут уже не сильно играет роль кол-во обращений к хранилищу.

Рассмотрим код для каждой ситуации.

Ситуация1 (obj — имя объекта в localStorage, который используется приложением):

// getter
function getKey(key) {
    var obj = JSON.parse(localStorage.getItem('obj'));
    return obj[key];
}
// setter
function setKey(key, value) {
    var obj = JSON.parse(localStorage.getItem('obj'));
    obj[key] = value;
    localStorage.setItem('obj', JSON.stringify(obj));
}

В коде выше быстро отработают только строки return obj[key] и obj[key] = value. Чтение/запись в localStorage будут работать медленно. И чем больше будет obj, тем медленнее будут работать эти функции. Потому что если надо выбрать 5 свойств, то придется делать так:

getKey('a');
getKey('b');
getKey('c');
getKey('d');
getKey('e');

Ситуация2 будет чуть лучше в плане производительности, потому что за одно обращение можно записать/прочитать несколько ключей:

// getter
function getKeys(keys) {
    var obj = JSON.parse(localStorage.getItem('obj'));
    var ret = {};
    for (var i = 0; i < keys.length; i++) {
        ret[keys[i]] = obj[keys[i]];
    }
    return ret;
}
// setter
function setKeys(hash) {
    var obj = JSON.parse(localStorage.getItem('obj'));
    var keys = Object.keys(hash);
    for (var i = 0; i < keys.length; i++) {
        obj[keys[i]] = hash[keys[i]];
    }
    localStorage.setItem('obj', JSON.stringify(obj));
}

Теперь чтение 5 ключей будет выглядеть так:

getKeys(['a', 'b', 'c', 'd', 'e']);

В плане кода Ситуация3 отличается только тем, что дополнительным параметром в getKeys/setKeys передается имя еще и имя объекта в localStorage (первым параметром):

// getter
function getKeysFromObject(objName, keys) {
    var obj = JSON.parse(localStorage.getItem(objName));
    var ret = {};
    for (var i = 0; i < keys.length; i++) {
        ret[keys[i]] = obj[keys[i]];
    }
    return ret;
}
// setter
function setKeysToObject(objName, hash) {
    var obj = JSON.parse(localStorage.getItem(objName));
    var keys = Object.keys(hash);
    for (var i = 0; i < keys.length; i++) {
        obj[keys[i]] = hash[keys[i]];
    }
    localStorage.setItem(objName, JSON.stringify(obj));
}

Если смотреть только на код, то разницы между Ситуация2 и Ситуация3 нет никакой. Однако, все дело в размере хранимых отдельно друг от друга объектов в localStorage. И потому в Ситуации3 скорость работы будет выше.

Рассмотрим тест на jsperf. Он состоит из 8 сценариев, которые так или иначе моделируют ситуации, описанные выше:

  • «1. Get one by one from the big object» и «4. Set all in one to the big object» соответствуют Ситуации1. В сценарии1 в localStorage хранится один большой объект (на 1000 ключей) из которого по очереди берется 5 ключей. В сценарии4 один за одним записываются 5 ключей в этот же объект.
  • «2. Get all in one from the big object» и «3. Set one by one to the big object» соответствуют Ситуации2. В сценарии2 в localStorage так же хранится один большой объект (на 1000 ключей), но 5 ключей из него берутся за один «заход». В сценарии3 за один «заход» осуществляется запись 5 ключей в этот же объект.
  • Сценарии 5-8 относятся к Ситуации3. В каждом из них в localStorage хранится маленький объект (на 6 ключей), из которого по очереди берутся 5 ключей (5), 5 ключей берутся за один «заход» (6), 5 ключей записываются по очереди (7) и 5 ключей записываются за один «заход» (8).

Сравним сценарии 1 и 2. В среднем сценарий2 раз в 5 быстрее сценария1. Логично, так как операции getItem, setItem выполняются по одному разу в сценарии2, а не по 5 (как в первом сценарии). Тоже самое и в сравнении сценариев 3 и 4. Запись за одно обращение осуществляется где-то в 5 раз быстрее (#4), чем когда таких обращений 5 (#3).

Однако, если объект в localStorage мал, то время доступа к нему в разы меньше. Достаточно сравнить попарно сценарии 5-1, 6-2, 7-3, 8-4 (во сколько раз один сценарий быстрее другого):

  • #5 / #1 = ~200
  • #6 / #2 = ~130
  • #7 / #3 = ~55
  • #8 / #4 = ~42

Некоторые выводы:

  • Хранить в localStorage все данные в виде одного объекта — очень затратно из-за времени доступа к ним.
  • Если сложилось так, что в localStorage все данные в одном объекте, то необходимо минимизировать количество обращений к хранилищу.
  • Если сложилось так, что в localStorage все данные в одном объекте, то необходимо время от времени (зависит от задач, выполняемых приложением) чистить его от устаревших данных. Делать это надо не в момент, когда на их место пришли новые данные, а в момент, когда текущие данные уже стали ненужными/устаревшими.
  • Лучше всего, чтоб данные в localStorage хранились в отдельных объектах, каждый из которых соответствует своей части приложения.

,

Оставить комментарий

Top ↑ | Main page | Back