Closed Konrud closed 7 years ago
In your example, the "nested" call to Array.prototype.filter(..)
via call(..)
does in fact get the this
passed along to it, which is how it's able to filter on the array in question. So that's not actually broken.
What you're pointing out is a separate behavior, which is that when a third nesting of call happens, when filter(..)
itself calls your predicate function, that this binding isn't automatically passed along. The design of those utilities is to not by-default leak the parent this
to those user-function delegated calls. You could argue they should automatically do that, in the same way that event handlers do. But they intentionally chose not to, for some various reasons.
However, those built-in utilities all include a third optional argument, which lets you override and tell them to pass the this
binding along. Change your code to:
Array.prototype.unique = function() {
/// this -> will be the calling array
//// now let's try to pass "this" as the first argument to the "call" function
return Array.prototype.filter.call(this, function(v, i, arr) {
var thisVal = this; /// will be `arr`
return Array.prototype.indexOf.call(this, v) === i;
}, this ); // <---- *** inserted `this` ***
};
var arr = [1, 2, 1, 3, 2, 4, 5, 4, 6, 5, 7, 8, 9, 10, 6, 5, 1];
var uniqueArr = arr.unique();
Now your predicate function is getting the this
binding as "expected", so you can use it instead of arr
for the indexOf(..)
call. :)
Wow, thank you for your rapid and explanatory answer.
But still, don't you think this is a bit misleading?
First of all the "trick" that the call function may receive the third argument and we may use it as this
value is not documented in any place, not even in MDN.
I do know that call function may receive arguments that can be sent to the function when it is invoked.
But nonetheless I think it's a misleading design, I mean if the first argument is intended to be the this
argument why should I use some trick in order to use is as it should be?
Why in the first example (using plain functions) it does work as expected?
I mean I set the first argument to be this
in the called function and it's work fine, but when we use something more complex like Array.prototype.filter
we need to do some tricks.
But still, don't you think this is a bit misleading?
Misleading? No. Confusing? Well, maybe a tiny bit, but not in relation to what I said, rather this separate orthogonal topic of how certain built-in's methods work. I say it's separate because it would be true even if there was no nesting inside another function at all.
First of all the "trick" that the call function may receive the third argument
I think you have misunderstood the code. It's not that call(..)
takes a third parameter. fn.call(..)
takes as its first parameter a this
to use for the fn
only (not whatever fn
may call). Everything after the first argument is just passed along to fn
, one at a time. So, in this case, the this
we're passing along ends up as the second argument to Array.prototype.filter(..)
. That's not a trick, it's just that call(..)
is designed to pass along arguments to its callee.
is not documented in any place, not even in MDN.
It's definitely well documented, especially on MDN, but for filter(..)
etc; it has nothing to do with call(..)
. See the "thisArg" here:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Syntax
I thought I had covered this behavior somewhere in the YDKJS series, but I did a search just now and couldn't find any explicit references to it. TBH, I've never once used it, and probably wouldn't recommend it, so it doesn't surprise or bother me that it's not specifically in there.
Why in the first example (using plain functions) it does work as expected?
Again, this misunderstanding you seem to have is that you're forgetting that Array.prototype.filter.call( someThisObjHere, ...)
does in fact invoke filter(..)
itself with someThisObjHere
as its this
. That's how filter(..)
itself knows which array you want it to filter on.
It's a separate issue that you then provide another function to filter(..)
that you want filter(..)
to call. That's why filter(..)
takes another parameter (its second parameter) if you want filter to make sure the predicate is invoked with some particular this
.
Frankly, this design makes sense to me. I don't think, if you properly understand it, it will seem unreasonable. I think here the reason you think it's unreasonable is that you're misunderstanding what's actually going on.
Now, let's take a step back and analyze something for a minute, because I think this discussion is missing the forrest for the trees.
Array.prototype.unique = function() {
return Array.prototype.filter.call(this, ..., this );
};
That is fine, but it's also unnecessary. Since this
here is an array, we don't need to indirectly invoke filter(..)
, we can directly invoke it:
Array.prototype.unique = function() {
return this.filter( ..., this );
};
You'll notice here that we still can pass along this
as the second argument to filter(..)
, so that the predicate function will also be invoked in the context of this
.
Thank you very much for your clarification, patience and time.
In the example, I've used it via Array.prototype
because I wanted it to be generic not only for arrays
but also for array-like objects.
BTW, I really enjoyed your books, I've finished all the books from the series.
because I wanted it to be generic not only for arrays but also for array-like objects.
Good point! :)
IMHO, there is some mislead behavior using
Function.call
. That is when you usefunction.call
inside otherfunction.call
and passthis
as the first parameter to thecall
function, e.g.The code above works fine and as expected, but if you try to do the same using, for example,
Array.prototype
method inside anotherArray.prototype
method thethis
becomesundefined
(in thestrict mode
).Example:
I think this "trap/behavior" should be explained/mentioned.