Этот вопрос - хорошее упражнение в композиции и абстракции. Давайте рассмотрим некоторые функции, которые может предложить Underscore , и то, как мы можем их использовать. Постепенно мы объединим их в большее целое, пока не достигнем своей цели.
_.contains
принимает коллекцию (массив или объект) и значение. Если коллекция содержит значение, оно возвращает true
, иначе false
.
_.contains([1, 2, 3], 3) // true
_.partial
принимает функцию и любое количество аргументов, которые вы можете захотеть » pre-fill "в качестве аргументов этой функции. Затем он возвращает новую функцию с предварительно заполненными аргументами. Вы можете использовать _
в качестве заполнителя для аргументов, которые вы хотите оставить открытыми.
// a version of _.contains that has the second argument pre-filled
// with the value 3
var contains3 = _.partial(_.contains, _, 3);
// the next line is equivalent to calling _.contains([1, 2, 3], 3)
contains3([1, 2, 3]) // true
_.property
принимает имя и возвращает функцию. Эта функция принимает объект и возвращает значение свойства с переданным вами именем.
var getName = _.property('name');
getName({'name': 'test 1'}) // 'test 1'
_.compose
принимает любое количество функций и возвращает новую функцию. Аргументы, которые вы передаете этой новой функции, передаются самой правой функции, которую вы передали в _.compose
. Затем, справа налево, каждая функция вызывается с результатом функции справа. Наконец, новая функция возвращает результат самой левой функции.
// a function that takes an object, reads its class_id property,
// and checks whether that contains the value 3
var classIdContains3 = _.compose(contains3, _.property('class_id'));
classIdContains3({'class_id': [1, 2, 3]}) // true
Мы можем превратить это в функцию, которая будет возвращать такое «свойство содержит средство проверки значения» для любой комбинации имени свойства и значения.
function propertyContains(name, value) {
return _.compose(
_.partial(_.contains, _, value),
_.property(name)
);
}
var classIdContains3 = propertyContains('class_id', 3);
classIdContains3({'class_id': [1, 2, 3]}) // true
classIdContains3({'class_id': [1, 2]}) // false
var nameContainsE = propertyContains('name', 'e');
nameContainsE({'name': 'test 1'}) // true
nameContainsE({'name': 'Zorro'}) // false
_.filter
принимает набор и критерий. Он возвращает массив со всеми элементами коллекции, которые соответствуют критерию.
_.filter(data, classIdContains3)
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
Помимо передачи функции в качестве критерия фильтрации, такие функции, как _.filter
, также позволяют использовать несколько декларативных сокращенных . Для проверки свойств на соответствие точному значению действует следующая запись:
_.filter(data, {'country_id': 20})
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
// {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
Чтобы сузить результаты по нескольким критериям, просто отфильтруйте несколько раз.
// first filter pass
var hasClassId3 = _.filter(data, classIdContains3);
// second filter pass (filtering result of the above)
_.filter(hasClassId3, {'country_id': 20})
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
_.chain
и _.value
позволяют использовать более короткое обозначение вышеуказанного.
_.chain(data)
.filter(classIdContains3)
.filter({'country_id': 20})
.value()
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
Используя _.each
, мы можем превратить это в функцию, которая будет фильтровать по произвольным комбинациям критериев.
function multiFilter(data, criteria) {
var result = _.chain(data);
if (criteria.exact) result = result.filter(criteria.exact);
_.each(criteria.contains, function(value, name) {
result = result.filter(propertyContains(name, value));
});
return result.value();
}
multiFilter(data, {
exact: {
country_id: 20,
},
contains: {
class_id: 3,
},
})
//[
// {'name': 'test 1', 'address': 'New York', 'class_id': [1, 2, 3], 'country': 'USA', 'country_id': 20},
// {'name': 'test 5', 'address': 'New York', 'class_id': [3], 'country': 'USA', 'country_id': 20},
//]
multiFilter(data, {
exact: {
country_id: 20,
address: 'New York',
},
contains: {
name: '3',
},
})
//[
// {'name': 'test 3', 'address': 'New York', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//]
multiFilter(data, {
contains: {
address: 'a',
},
})
//[
// {'name': 'test 2', 'address': 'Lisbona', 'class_id': [2, 3], 'country': 'Portugal', 'country_id': 12},
// {'name': 'test 4', 'address': 'Atlanta', 'class_id': [2], 'country': 'USA', 'country_id': 20},
//]
Для получения дополнительных сведений о подчеркивании, функциональном программировании и поиске вы также можете обратиться к этому ответу .