Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Localstorage explorer (https://javascript.ru/forum/project/72087-localstorage-explorer.html)

Yesasha 08.01.2018 00:17

Localstorage explorer
 
Делали тут одну игрушку и понадобилось сохранять статус и уровень, чтоб после перезагрузки не начинать сначала. Ну и решили в localStorage сохранять. Но как дебажить? Оно же всё время записывает данные и потом начинает откуда-то не сначала! Нужно что-то типа эксплорера, чтоб ненужное удалять. Иду искать в гугл, и...ничего! Ну не то чтобы совсем ничего, есть расширения для браузеров, но это нужно для каждого браузера, да и под мобильные не будет, а нам надо на мобильном тоже. Был ещё еле еле рабочий скриптик, не буду его пиарить, можете сами по названию поискать, он примерно так и называется.

Ну, в общем, не долго думая, сажусь писать свой велосипед (или не велосипед?) и вот что получилось.

Вот тут скрипт на gist одним файлом (не считая зависимости w3.css):

https://gist.github.com/yesasha/5e8a...b0d8459c7fd616. Если нужно в свой проект этот файл кидаем в папку и испоьзуем.

Вот тут можно на codepen поиграться https://codepen.io/yesasha/pen/ppdyWL. За одно увидите что другие проекты от codepen хранят в вашем localStorage.

Немного поясню как оно работает.
  • Решил использовать таблицу, так как по моему мнению для таких данных таблица в самый раз, тем более я планировал её сортировать по разным столбцам.
  • Выглядит она так как выглядит, потому что вот https://www.w3schools.com/w3css/w3css_tables.asp.
  • Первый столбец это индекс. Это то в каком порядке записи находятся в localStarage. Во всяком случае в таком порядке браузер проходит по значениям при переборе. Это зависит от браузера, но может быть полезно это видеть.
  • Второй столбец - это ключ, третий - значение. Числа сортируются как числа, строки как строки. Есть функция isNumeric, взятая с этого сайта, чтобы отличить числа от строк.
  • При добавлении/удалении ключей, копируем содержимое в новый массив, потом сортируем его одной из 6 возможных функций (3 столбца, по 2 на каждый: убывание/возрастание). Потом обновляем таблицу, сравнивая старый массив с новым. Обновляем только изменившиеся значения. Не гарантирую, что данный подход самый хороший, но сделал так. По аналогии с virtual DOM. Кстати, массив не мутирует, а создаётся новый - аналогия с immutable state в redux. Но это пока всё.
  • При клике на заголовки происходит сортировка и таблица обновляется.
Добавлю новые фичи если будет интерес к проекту. Так же жду вашу критику.

Nexus 09.01.2018 12:17

Цитата:

Сообщение от Yesasha
Так же жду вашу критику

Я хоть и не middle/senior js, однако вставлю свои 5 копеек.

Обработка нажатий на определенные элементы таблицы интересно сделана.
Я бы, как минимум, вынес выполнение операций (удаление, добавление и .т.п.) в отдельные функции и избавился от повторения одного и того же кода.

Не проверяется доступен ли элемент коллекции под определенным ключом или нет.
Взаимодействие с локальным хранилищем осуществляется без проверки его доступности + вне try catch.

Функция "removeAllSortingClasses" и 6 функций "sort[\d]" вызвали пару вопросов:
1. Почему в "removeAllSortingClasses" не воспользовались циклами?
2. Зачем N функций, если их отличия минимальны, почему не 1 с несколькими агрументами для управления поведением?

Заранее прошу прощения, но я бы не стал пользоваться Вашим решением, много Индусского кода.

Yesasha 10.01.2018 02:42

Вложений: 1
Я вот тоже подумал, что индусский код, хоть и рабочий, может магическим образом оттолкнуть пользователей...
Код был ещё более индусским, но в определённый момент хочется сказать и так сойдёт. Благодаря же Вашим замечаниям провёл рефакторинг, узнал некоторые новые вещи, например про навигацию по таблице. Многие функции объединились. От многих избавился. Теперь состояние просто хранится в глобальных переменных. Теперь содержимое localStorage перечитывается и при сортировке. Более того, это помогло всё убрать в одну функцию рендеринга.

А что именно интересного в обработке нажатий?

LocalStorage не выбрасывает исключений. И ничего страшного не происходит, если удалить элемент дважды. С classList тоже самое. Какие ещё коллекции не проверяются?

Обновил версии по ссылкам, теперь там новые версии. Старую же прикрепил сюда для сравнения.

Nexus 10.01.2018 09:46

Цитата:

Сообщение от Yesasha
LocalStorage не выбрасывает исключений. И ничего страшного не происходит, если удалить элемент дважды. С classList тоже самое.

Почитайте
Цитата:

Сообщение от Yesasha
А что именно интересного в обработке нажатий?

Все сценарии были в одном методе, плохо читается + было ненужное повторение в коде.

Не особо разбирался в вашем коде, но более чем уверен, что строки 93-111 можно сократить.
} else if (classList.contains('index') && !classList.contains('asc') && !classList.contains('dsc')) {
    render(0, 'asc');
  } else if (classList.contains('key') && !classList.contains('asc') && !classList.contains('dsc')) {
    render(1, 'asc');
  } else if (classList.contains('value') && !classList.contains('asc') && !classList.contains('dsc')) {
    render(2, 'asc');
  } else if (classList.contains('index') && !classList.contains('asc') && classList.contains('dsc')) {
    render(0, 'asc');
  } else if (classList.contains('key') && !classList.contains('asc') && classList.contains('dsc')) {
    render(1, 'asc');
  } else if (classList.contains('value') && !classList.contains('asc') && classList.contains('dsc')) {
    render(2, 'asc');
  } else if (classList.contains('index') && classList.contains('asc') && !classList.contains('dsc')) {
    render(0, 'dsc');
  } else if (classList.contains('key') && classList.contains('asc') && !classList.contains('dsc')) {
    render(1, 'dsc');
  } else if (classList.contains('value') && classList.contains('asc') && !classList.contains('dsc')) {
    render(2, 'dsc');
  }

Yesasha 11.01.2018 01:44

Те строчки действительно получилось сократить и очень сильно. Но вот стоило ли оно того? Ведь было потрачено время, а оно и так работало!

Насчёт доступности хранилища, замечание полезное. Добавил проверку доступности в функцию рендеринга. Не уверен насчёт полезности данного подхода.

Плюс добавил ещё несколько полезных вещей.
Например фокус возвращается на поле ввода после клика по кнопке, что позволяет быстрее добавлять новые записи.
В панели статуса отображается доступность хранилища и кол-во записей.

Nexus 11.01.2018 09:21

Цитата:

Сообщение от Yesasha
Но вот стоило ли оно того? Ведь было потрачено время, а оно и так работало!

Ну-с, даже не знаю как прокомментировать. С таким мнением можно было оставить и первоначальную версию вашего творения, она же работала.

Yesasha 11.01.2018 12:31

Ну, некоторые изменения были важны, например проверка доступности. Странно, что я не замечал ту статью.
А на остальное нет однозначного ответа для всех. Это каждый для себя должен ответить, в зависимости от того кем он хочет стать! Программистом или кодописцем)
Но в целом если код более понятен, то потом проще выявить баги и добавлять новые функции. Понятен, не значит меньше кода, а то писал я тут букмарклет...

Nexus 11.01.2018 13:18

Yesasha, если код недостаточно ясен для понимания, в любом яп есть такая вещь, как комментарии, которые эту неясно могут легко исключить.

Никого до вас не встречал, кто бы говорил, что Индусский стиль написания кода улучшает его общую понятность и скорость написания.
Глупость, имхо.

Поддерживать такой код еще то удовольствие...

Yesasha 11.01.2018 19:08

А я не говорил что индусский стиль улучшает! Я сказал, что мне удалось сократить код и он СТАЛ понятнее. Но я мог его не сокращать и оставить как есть, если бы решил, что оно того не стоит, в смысле тратить время на проект, который никому не нужен, к примеру. Более того, я не видел изначально, что получится сократить на столько! А индусский стиль не столько понятен, сколько он сам по себе возникает, когда добавляешь много новых функций, просто дописывая новый код, не ориентируясь на уже написанный. А потом, есть такая вещь, как рефакторинг, как раз для того, чтобы взглянуть со стороны и переписать по другому. И вряд ли можно всегда сразу писать идеально.

Nexus 11.01.2018 19:40

Цитата:

Сообщение от Yesasha
И вряд ли можно всегда сразу писать идеально.

Нужно стараться к этому стремиться :)

Yesasha 12.01.2018 20:05

Интернет експлорер преподнёс пару сюрпризов. Оказалось, что методы add и remove у classList не поддерживают множественные аргументы.
Добавил реакцию на событие storage. Теперь изменения сделанные в одной вкладке/окне, сразу видны во всех других. И тут скрывался очередной сюрприз! Все ие от этого страдают, пришлось добавить throttler. В принципе, я и так собирался это сделать, чтоб перерисовывать DOM через requestAnimationFrame.

Yesasha 18.01.2018 04:41

Хочу рассказать ещё несколько вещей на тему локального хранилища.
У него есть ограничения на размер хранимой информации и этот размер равен примерно 5 мегабайт для каждого origin. Может отличаться от браузера к браузеру, но мы же хотим чтобы всё работало в разных браузерах, значит нужно ориентироваться на минимум. Но что значит эти 5 мегабайт? Это значит что мы примерно можем сохранить строку из 5242880 англ. символов. И вот тут всплывает не самая очевидная деталь - ключи тоже занимают место. То есть мы можем сохранить 5 мег строку под пустым ключом. Или под ключом длиной 5 мег пустую строку. Ну и все промежуточные варианты, конечно. И логично было бы хранить всё одной строкой и не тратить место на ключи, но тогда возникает проблема производительности, ведь эту длинную строку придётся каждый раз перезаписывать целиком! Поэтому нужно искать компромисс между кол-вом ключей и размером значений.
Кстати, размер в 5 мегабайт можно и обойти, и я давно пришёл к этой идее и хотел даже библиотечку написать, но потом нашёл ещё людей, которые пришли к тому же!
Надёжный localStorage для букмарклетов,
Cross-Domain LocalStorage.

Что касаемо моего эксплорера, то во время теста на максимальное заполнение (посмотреть его можно тут, только он совсем не юзерфрендли, не совсем понятно что он делает, если не заглядывать в код, но я создавал его для себя, а не для общего пользования), выяснилось, что браузер то не справляется с таблицей на тысячи строк... :cray:
И тут я пустился в размышления и понял, что единственный способ "отобразить" тысячи и больше единиц информации - это не отображать её в DOM, а держать в переменной javascript, и отображать только видимую часть. И это решит и проблему производительности и проблему памяти. Только вот думаю стоит использовать какой-нибудь фреймворк для таких манипуляций.

Если же смотреть только на производительность, то мой подход с переиспользованием элементов оказался не плохим вариантом! Дело в том, что это единственное, что смог осилить Интернет Эксплорер! И конкатенация строк в цикле с последующей записью в innerHTML таблицы, и insertAdjacentHTML в цикле, показали в десятки(!) раз худшую производительность! И если бы не IE, то я бы сказал, что конкатенация строк, то есть пересоздание таблицы в памяти с последующей записью в innerHTML нормальный и производительный способ! Он показал в 2 раза лучшую производительность в Хроме. И в новой версии я бы использовал именно этот подход. А так пришлось оставить всё как было...
Вот тут и эксперименты: 1, 2, 3

Nexus 18.01.2018 10:07

Что можно хранить в локальном хранилище в таких объемах?
Почему именно в локальном хранилище?

Yesasha 18.01.2018 11:12

Ну почему, потому-что простые вещи, как уровень игрока в игре, проще хранить именно в нём, это и послужило началом для этой темы. А дальше я просто делюсь тем что ещё знаю об этом. Это, конечно, не единственный способ сохранять информацию! Простые вещи можно хранить в куках, например, но там совсем мало места и они шлются каждый раз на сервер. Ещё localStorage хорошо поддерживается браузерами и не спрашивает разрешения пользователя, и с ним проще работать чем с куками! Так что для простых вещей самое то.

А о каких "таких" объёмах речь? Пока речь идёт о тексте, кажется что 5 мегабайт это много, но как только дело дойдёт до картинок, много вы туда сможете сохранить? Сейчас эра вебаппликаций, вот вы загружаете фото в соц. сеть и вдруг раз, пропал интернет. Картинка временно сохраняется локально, а потом синхронизируется с сервером при появлении связи.

Nexus 18.01.2018 12:00

Yesasha, вы какую-то глупость написали.
Браузер самостоятельно кеширует загруженные изображения, достаточно настроить заголовки отдаваемые сервером, поэтому нет абсолютно никакой необходимости сохранять их в localStorage.

Все используемые в работе данные обязаны храниться только на сервере.
Если lvl персонажа хранится на клиенте и используется для каких-либо расчетов, то это колоссальная дыра.
Данные персонажа можно передать клиенту при инициализации и сохранить их в ram.
Т.е. опять же нет необходимости хранить данные персонажа на клиенте в localStorage.

PS. а изображения в localStorage вы храните в base64?

Yesasha 18.01.2018 17:24

Цитата:

Сообщение от Nexus (Сообщение 475560)
PS. а изображения в localStorage вы храните в base64?

В простейшем случае да, тем более что канвас поддерживает экспорт в base64.

Цитата:

Сообщение от Nexus (Сообщение 475560)
вы какую-то глупость написали.

Цитата:

Сообщение от Nexus (Сообщение 475560)
Все используемые в работе данные обязаны храниться только на сервере.

Это Вы в конституции интернета прочитали?
Вон товарищ зачем-то хочет в PNG сохранять историю действий. По моему ему идеально подошёл бы localStorage. Вот уж не знаешь что глупее, хранить текст в картинке или картинку в тексте!

Цитата:

Сообщение от Nexus (Сообщение 475560)
Браузер самостоятельно кеширует загруженные изображения

Пришедшие с сервера, да. Но я говорил про загруженные пользователем с диска, которые он хотел загрузить НА сервер, но вдруг пропал интернет. Он закрывает сайт, а потом при повторном открытии они ждут в localStorage и сами синхронизируются. Вот тут интересная вещь - https://pouchdb.com/, но я ей ещё не пользовался. Там и сервер и поддержка разных хранилищ на клиенте. Кстати, браузер кеширует, но он не гарантирует сколько он их будет хранить в кеше. Всё это, конечно, моё личное мнение о том как стоит организовать работу приложения. Для кого-то такой подход может быть не приемлем. Более того, это только теория, а реализация может по разным причинам отличаться.

Цитата:

Сообщение от Nexus (Сообщение 475560)
Если lvl персонажа хранится на клиенте и используется для каких-либо расчетов, то это колоссальная дыра.

Да, а ещё если сохранять место до которого пользователь дочитал в книге в localStorage, то это, получается, тоже дыра? В игре уровень хранится исключительно для удобства пользователя.
А Вы для каждого простого приложения держите сервер и проводите регистрацию пользователей?
В любом случае речь идёт об offline first web app, то есть приложение, которое должно в первую очередь работать без наличия интернета. И многие принципы, которые были актуальны раньше, тут уже не подходят.

Yesasha 05.02.2018 21:26

А тем временем я написал "полнофункциональное" приложение для просмотра и редактирования localStorage, sessionStorage и cookie на своём сайте и с возможностью кроссдоменного доступа через букмарклет. Так же приложение доступно оффлайн через технологию application cache.
Storage Explorer
UPD: Ссылка на репозиторий https://github.com/yesasha/storage-explorer


Часовой пояс GMT +3, время: 09:57.