Эта статья является продолжением "Итерируем всё вместе с Collection" и здесь я расскажу о встроенном механизме Collection — фильтрах.
Итеративные методы Collection могут принимать множество различных параметров, но один из самых интересных является filter, давайте рассмотрим на примере:
// Простой map
$C([1, 2, 3, 4, 5]).map(function (el) { return el * 2; }) // [2, 4, 6, 8, 10]
// А теперь только для тех элементов, которые больше 2
$C([1, 2, 3, 4, 5]).map(function (el) { return el * 2; }, {filter: function (el) { return el > 2; }}) // [6, 8, 10]
Т.е. на любой итеративный метод, будь то map, reduce, length и т.д. можно наложить дополнительную функцию фильтр, которая будет отсеивать «лишние» элементы.
Ещё примеры:
// Количество элементов в объекте, значение которых больше 2
$C({a: 1, b: 2, c: 3, e: 4}).length(function (el) { return el > 2; }) // 2
// Количество вхождений каждого символа строки, кроме "o"
$C('foo Bar').group(fuction (el) { return el; }, {filter: function (el) { return el !== 'o'; }})
Но это ещё не всё, Collection позволяет заранее продекларировать все необходимые фильтры, а затем вызывать их по ИД и даже комбинировать. Давайте напишем 2 фильтра: первый будет находить уникальные или не уникальные элементы в исходной коллекции, а второй «склеивать» дубликаты (сразу скажу, что я не преследую здесь цель написать наиболее оптимальный алгоритм, а просто как пример).
// Декларируем наши фильтры с помощью метода addFilter
$C().addFilter('unique', function (el, key, data, i, length) {
// Параметр this.$ содержит ссылку на пустой объект,
// который создаётся в рамках каждого итератора для использования в мемоизации (т.е. сохранение промежуточных значений)
var cache = this.$.cache = this.$.cache || new Set();
var final = this.$.final = this.$.final || new Set();
// this.$.ready не определён, значит идёт первых проход итератора
if (!this.$.ready) {
if (cache.has(el)) {
final.delete(el);
} else {
final.add(el);
cache.add(el);
}
// Параметр this._ содержит ссылку объект,
// который содержит характеристики текущей операции (тип данных, различные ограничители и т.д.)
// Если идёт последняя итерация, то сбрасываем операцию и начинаем проход заново
if (i === (this._.endIndex || (length() - 1))) {
this.reset();
this.$.ready = true;
}
// this.FALSE специальная константа, которая означает,
// что фильтр всегда должен возвращать false, даже если его инвертировали
return this.FALSE;
}
// Идёт второй проход и у нас уже есть индексирующий объект
return final.has(el);
});
Теперь давайте попробуем в действии
// Метод get возвращает массив элементов коллекции, которые подходят под фильтр
$C([1, 2, 2, 3]).get('unique') // [1, 3]
// Только не уникальные
$C([1, 2, 2, 3]).get('!unique') // [2, 2]
А теперь напишем фильтр, который будет «склеивать» одинаковые элементы в один
$C().addFilter('merge', function (el) {
this.$.tmp = this.$.tmp || new Set();
if (!this.$.tmp.has(el)) {
this.$.tmp.add(el);
// this.TRUE антоним this.FALSE
return this.TRUE;
}
return this.FALSE;
});
И сейчас можно запустить их вместе
// Только не уникальные
$C([1, 2, 2, 3, 5, 5]).get('!unique && merge') // [2, 5]
Следует заметить, что в составных фильтрах можно также использовать «или» ||
и круглые скобки для группировок, на которые также можно накладывать знак инверсии !
.
Допускается создавать фильтры, которые ссылаются на другие
$C().addFilter('mix', '!unique && merge');
Ну и напоследок: можно назначать «активные» фильтры, которые буду использовать по умолчанию всегда, а фильтры которые буду указаны явно, будут их просто дополнять.
$C([1, 2, 2, 3, 5, 5]).setFilter('unique').get() // [1, 3]
$C([1, 2, 2, 3, 5, 5]).setFilter('!unique').get('merge') // [2, 5]
Давайте напишем условие для пагинации, скажем загрузка 2-й страницы, где на одной странице не более 10 элементов.
// Исходной коллекцией пускай будет некоторый Map
$C(new Map(...)).get({
filter: '!unique && merge',
from: 10,
count: 10
})
Надеюсь, что эта коротенькая заметка была интересной и понятной, всем удачи!
Автор: kobezzza