Dexie.js представляет собой тонкий, но мощный слой над IndexedDB,
упрощающий работу с локальными базами данных в браузере. Центральным
инструментом выборки данных в Dexie является метод where(),
который позволяет выполнять индексированные запросы, фильтрацию по
диапазонам, точные совпадения и комбинированные условия через цепочки
операторов.
Метод where() применяется к таблице (Table)
и всегда опирается на индексированное поле. Это ключевая особенность: в
отличие от filter(), который выполняет постфильтрацию в
памяти, where() использует индексы IndexedDB и работает
значительно быстрее.
Базовый синтаксис:
db.table.where('fieldName')
После выбора поля возвращается объект запроса, поддерживающий дальнейшую фильтрацию через методы:
equals()above(), below()between()startsWith()anyOf(), noneOf()where() работает только с индексированными полями. Это
означает, что схема базы данных должна явно объявлять индекс:
const db = new Dexie('AppDB');
db.version(1).stores({
users: '++id, name, age, city'
});
Здесь name, age и city могут
использоваться в where().
Попытка выполнить where() по неиндексированному полю
приведёт к ошибке выполнения.
Самый базовый вариант фильтрации — поиск по точному совпадению:
db.users.where('city').equals('Almaty').toArray();
Такой запрос вернёт всех пользователей, у которых поле
city строго равно "Almaty".
Dexie поддерживает эффективные диапазонные операции, которые особенно полезны для числовых и датированных данных.
db.users.where('age').above(18).toArray();
Возвращает всех пользователей старше 18 лет.
db.users.where('age').below(30).toArray();
Возвращает пользователей младше 30 лет.
db.users.where('age').between(18, 30).toArray();
Метод between() позволяет задать диапазон. По умолчанию
границы включительные/исключительные могут быть настроены:
db.users.where('age').between(18, 30, true, false);
Параметры:
Для текстовых полей часто требуется поиск по началу строки:
db.users.where('name').startsWith('A').toArray();
Такой запрос использует лексикографический порядок индекса и возвращает всех пользователей, чьи имена начинаются на “A”.
Это особенно эффективно для автодополнения и поиска по мере ввода.
Позволяет выбрать записи, соответствующие любому значению из набора:
db.users.where('city').anyOf(['Almaty', 'Astana', 'Shymkent']).toArray();
Это аналог SQL-оператора IN.
Исключает набор значений:
db.users.where('city').noneOf(['Almaty', 'Astana']).toArray();
Такой подход полезен для фильтрации запрещённых или исключённых категорий.
Dexie поддерживает составные индексы, которые позволяют выполнять фильтрацию по нескольким полям одновременно.
Пример схемы:
db.version(1).stores({
orders: '++id, [userId+status], createdAt'
});
Запрос:
db.orders.where('[userId+status]').equals([5, 'pending']).toArray();
Составной индекс требует строгого соответствия порядку полей.
Метод where() имеет несколько архитектурных ограничений,
связанных с IndexedDB:
Любое поле, не добавленное в схему индексов, не может использоваться.
Нельзя применять callback-функции напрямую внутри
where(). Для этого используется filter(), но
он менее производителен.
IndexedDB различает типы строго:
1 !== "1"DateПосле фильтрации можно применять сортировку:
db.users.where('age').above(18).sortBy('age');
Однако сортировка возможна только по текущему индексу или после преобразования в массив.
Более гибкий вариант:
db.users.where('age').above(18).toArray().then(arr => {
arr.sort((a, b) => a.age - b.age);
});
Для обработки больших наборов данных без загрузки всего массива в
память используется each():
db.users.where('age').above(18).each(user => {
console.log(user.name);
});
Это потоковая обработка, полезная при больших объёмах данных.
Эффективность where() зависит от правильного
проектирования индексов.
filter() для больших таблицstartsWith() вместо частичного поиска
через строкиКлючевое различие:
where() — индексный доступ, быстрыйfilter() — перебор всех записей, медленныйПример filter():
db.users.filter(user => user.age > 18 && user.city === 'Almaty')
Этот подход эквивалентен, но не использует индексы.
Некоторые методы можно комбинировать в цепочке:
db.users
.where('age')
.above(18)
.and(user => user.city === 'Almaty')
.toArray();
Однако and() частично уводит выполнение в память, снижая
производительность.
При проектировании запросов важно учитывать:
Хорошо спроектированный where()-запрос превращается в
O(log n)-подобную операцию благодаря индексам, тогда как
неиндексированные подходы деградируют до O(n).
Если запрос не находит совпадений, возвращается пустой массив, а не ошибка:
db.users.where('city').equals('Nowhere').toArray()
// []
Это упрощает обработку результатов без необходимости try/catch.