Подтвердить что ты не робот

Underscore.js: как настроить пользовательские функции

Используя Underscore.js, могу написать следующее, которое возвращает 42:

_([42, 43]).chain()
    .first()
    .value()

У меня есть пользовательская функция, а не часть Underscore.js, называемая double():

function double(value) { return value * 2; };

Я хотел бы иметь возможность вызвать эту функцию в цепочке Underscore, как если бы она была частью Underscore. Я хотел бы написать следующее, которое я хотел бы вернуть 84:

_([42, 43]).chain()
    .first()
    .double()
    .value()

Это невозможно, так как Underscore не определяет double(). Я мог бы использовать tap(), как в:

_([42, 43]).chain()
    .first()
    .tap(double)
    .value()

Это допустимо, но tap применяет функцию к своему аргументу и возвращает аргумент, а не результат функции. Поэтому мне кажется, что мне нужен тип tap, который возвращает результат функции, примененной к его аргументу. Есть ли что-то подобное в Underscore.js? Я пропустил что-то ужасно очевидное?

4b9b3361

Ответ 1

Не найдя tap, возвращающий значение, возвращаемое функцией, запускается, я определяю один, который я могу take, и добавляю к _:

_.mixin({take: function(obj, interceptor) {
    return interceptor(obj);
}});

Тогда, предполагая, что у меня есть:

function double(value) { return value * 2; };

Я могу написать:

_([42, 43]).chain()
    .first()             // 42
    .take(double)        // Applies double to 42
    .value()             // 84

Вы можете посмотреть take как map на объекты, а не на списки. Хотите экспериментировать с этим? См. Этот пример в jsFiddle.

Ответ 2

Итак, у вас есть пользовательская функция:

function double(value) { return value * 2; }

Вы можете использовать mixin для расширения Underscore:

_.mixin({ double:double });

Теперь вы можете вызывать свою функцию из объекта Underscore _:

_.double(42); // 84

и из обернутого объекта, возвращаемого из chain:

_([42, 43]).chain()
  .first()
  .double() // double made it onto the wrapped object too
  .value(); // 84

Ответ 3

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

function double(value) { return value * 2; };

var obj = _([42, 43]).addToWrapper({double:double});

obj.chain()
  .first()
  .double()
  .value();

Синтаксис/детали могут быть неправильными, но основная точка заключается в следующем: когда вы вызываете _([42,43]), вы вызываете подчеркивание как функцию. Когда вы это делаете, он создает новый объект, а затем смешивает с этим объектом большинство функций подчеркивания. Затем он возвращает этот объект вам. Затем вы можете добавить свои собственные функции к этому объекту, и ничто из этого не загрязняет пространство имен "_".

Что мне понравился код underscore.js. Если я ошибаюсь, я бы хотел узнать, и, надеюсь, кто-то объяснит, почему.

EDIT: я действительно использовал underscore.js в течение примерно месяца, и я хорошо знаком с ним. Теперь я знаю, что это ведет себя так, как я сказал здесь. Когда вы вызываете _ как функцию конструктора, вы возвращаете свое собственное "пространство имен" (просто объект), и вы можете добавлять к нему вещи с помощью addToWrapper(), которые отображаются в вашем пространстве имен, но не в "глобальном" "_", Пространство имен. Таким образом, функция, которую захочет OP, уже встроена. (И я был действительно впечатлен подчеркиванием, кстати, это очень хорошо сделано).

Ответ 4

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

function doubleValue(x) { return x * 2; }
var rt = _.chain([42,43])
        .first(1)
        .map(doubleValue)
        .value();
console.log(rt);   // prints out [84]

//Обратите внимание, что важно указать .first(), сколько элементов вы хотите, иначе вы можете получить пустой массив в противном случае. Сначала подумайте, как я ХОЧУ первое количество элементов, поэтому .first(2) возвращает [84], [86] и т.д.

Ответ 5

Многие способы легко достичь этого, вот одно из таких решений:

  _.chain([42,43])
    .first(1)
    .map(double)
    .first()
    .value();
    // 84

Тем не менее, я бы рекомендовал использовать Ramda, тогда с автоматическим каррированием и конвейером/составлением таких задач тривиально:

R.pipe(R.head, R.multiply(2))([42, 43]);  // 84

equivalent to:

R.compose(R.multiply(2), R.head)([42, 43]);  // 84

Если вы хотите расширить решение, чтобы взять первые 2 элемента, вместо одного значения, как запрошено OP, тогда:

R.pipe(R.take(2), R.map(R.multiply(2)))([42, 43])  // [84, 86]

Однако, в Ramda R.compose предпочтительнее. В любом случае, для таких тривиальных задач, как это, он, как правило, более удобен и прост в использовании.

Ответ 6

Похоже, что lodash внедрил именно то, что вы ищете:

_.thru(value, interceptor)

из документов:

Этот метод похож на _.tap, за исключением того, что он возвращает результат Перехватчик

https://lodash.com/docs#thru

Ответ 7

Работает ли map для этого?

_([42, 43]).chain()
    .first()
    .map(double)
    .value()

изменить

из документации, похоже, что map будет работать, только если вы поместите его перед вызовом first:

_([42, 43]).chain()
    .map(double)
    .first()
    .value()

Ответ 8

Использование compose - это еще один способ справиться с ситуацией, но я думаю, что добавление функции, такой как take , как я предложил ранее, является лучшим решением. Тем не менее, вот как выглядит код с помощью compose:

function double(value) { return value * 2; };

_.compose(
    double,
    _.first,
    _.bind(_.identity, _, [42, 43])
)();

Начальное значение должно быть предоставлено через функцию, которая возвращает это значение (здесь выполняется currying identity), а функции должны быть перечислены в другом, что является обратным тому, что у вас есть с цепочкой, что представляется мне довольно неестественным.