可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Using Underscore.js, I can write the following which returns 42
:
_([42, 43]).chain()
.first()
.value()
I have custom function, not part of Underscore.js called double()
:
function double(value) { return value * 2; };
I would like to be able to call this function in an Underscore chain, as if it was part of Underscore. I would like to write the following, which I'd like to return 84
:
_([42, 43]).chain()
.first()
.double()
.value()
This can't work since Underscore doesn't define double()
. I could use tap()
as in:
_([42, 43]).chain()
.first()
.tap(double)
.value()
This is valid, but tap
applies the function to its argument and returns the argument, not the result of the function. So it looks to me like I would need a sort of tap
that returns the result of the function applied to its argument. Is there anything like this in Underscore.js? Am I missing something terribly obvious?
回答1:
Not finding a tap
that returns the value returns by the function is runs, I define one which I can take
and add to _
:
_.mixin({take: function(obj, interceptor) {
return interceptor(obj);
}});
Then assuming I have:
function double(value) { return value * 2; };
I can write:
_([42, 43]).chain()
.first() // 42
.take(double) // Applies double to 42
.value() // 84
You can look at take
as map
on objects, instead of lists. Want to experiment with this? See this example on jsFiddle.
回答2:
So you have a custom function:
function double(value) { return value * 2; }
You can use mixin
to extend Underscore with it:
_.mixin({ double:double });
Now you can call your function from the Underscore object _
:
_.double(42); // 84
and from the wrapped object returned from chain
:
_([42, 43]).chain()
.first()
.double() // double made it onto the wrapped object too
.value(); // 84
回答3:
Alright, I'm fresh off of reading the underscore annotated source code for the first time. But I think you can do something like this:
function double(value) { return value * 2; };
var obj = _([42, 43]).addToWrapper({double:double});
obj.chain()
.first()
.double()
.value();
The syntax/details might not be right, but the core point is this: when you call _([42,43])
, you're calling underscore as a function. When you do so, it instantiates a new object and then mixes into that object most of the underscore functions. Then, it returns that object to you. You can then add your own functions to that object, and none of this pollutes the "_" namespace itself.
That's what the underscore.js code looked like to me. If I'm wrong, I'd like to find out and hopefully someone will explain why.
EDIT: I've actually been using underscore.js heavily for about a month now, and I have gotten pretty familiar with it. I now know it behaves like I said here. When you call _ as a Constructor function, you get back your own "namespace" (just an object), and you can add things to it with addToWrapper() that show up in your namespace but not in the "global" "_" namespace. So the feature the OP wanted is already built in. (And I have been really impressed with underscore, btw, it is very very nicely done).
回答4:
Many ways to easily achieve this, here is one such solution:
_.chain([42,43])
.first(1)
.map(double)
.first()
.value();
// 84
However, I would recommend using Ramda then with auto-curry and pipe / compose these kinds of tasks are trivial:
R.pipe(R.head, R.multiply(2))([42, 43]); // 84
equivalent to:
R.compose(R.multiply(2), R.head)([42, 43]); // 84
If you wanted to extend the solution to take say the first 2 items, instead of a single value, as requested by the OP, then:
R.pipe(R.take(2), R.map(R.multiply(2)))([42, 43]) // [84, 86]
However, in Ramda R.compose is preferred. Either way, for trivial tasks like this, it does tend to be more convenient and easy to use.
回答5:
Looks like lodash has implemented exactly what you are looking for:
_.thru(value, interceptor)
from the docs:
This method is like _.tap except that it returns the result of
interceptor
https://lodash.com/docs#thru
回答6:
Does map
work for this?
_([42, 43]).chain()
.first()
.map(double)
.value()
edit
from the documentation, it looks like map
would only work if you place it before the call to first
:
_([42, 43]).chain()
.map(double)
.first()
.value()
回答7:
Using compose
is another way dealing with the situation, but I think adding a function such as take
as I suggested earlier is a better solution. Still, here is how the code would look like with compose
:
function double(value) { return value * 2; };
_.compose(
double,
_.first,
_.bind(_.identity, _, [42, 43])
)();
The initial value needs to be provided through a function which returns that value (here done by currying identity
), and the functions need to be listed in an other which is the reverse of what you have with a chain, which appears as pretty unnatural to me.