Yet another Call vs Apply query - javascript

When I tried to run the following piece of code:
function flatten(a) {
return [].slice.call(arguments).reduce(function(acc, val) {
return acc.concat(Array.isArray(val) ? flatten.call(null, val) : val);
}, []);
}
I got the following error:
Uncaught RangeError: Maximum call stack size exceeded
But if I used flatten.apply instead of flatten.call, it works perfectly. (deep flattens the input array).
In addition to this link1 and link2, I read few other blogs and articles to understand why it behaves so. I either couldn't find the answer or I must have overlooked it. (pardon my soar eyes if in latter case)
Of course, the fundamental difference between these 2 is:
apply - requires the optional parameter be an array
call - requires the optional parameters be listed explicitly
Usually, a function may take any type of parameters - primitives and non-primitives. So, my questions are:
Could one of the optional parameters to the call method be of type Array or other non-primitive types?
Why does the above code exceeds call stack, when call is used?
Edit: Since there were 2 call methods used, my description was ambiguous. I made changes to clarify.

Your mistake is here:
[].slice.call(arguments).reduce
^^^^^^^^^
so when you pass [x], you're calling reduce on [[x]] and the first val becomes [x]. Then you call flatten once again with [x] and the story repeats itself.
On the other side, apply(..., val) will pass just x to flatten, thus reducing the nesting level by one.
If you're interested on how to use apply to flatten a deeply-nested array, this is possible without recursion:
while(ary.some(Array.isArray))
ary = [].concat.apply([], ary);
Here's a small illustration of call vs apply:
function fun() {
var len = arguments.length;
document.write("I've got "
+ len
+ " "
+ (len > 1 ? " arguments" : "argument")
+ ": "
+ JSON.stringify(arguments)
+ "<br>");
}
fun.call(null, 123);
// fun.apply(null, 123); <-- error, won't work
fun.call(null, [1,2,3]);
fun.apply(null, [1,2,3]);

When you call flatten.call(null, val), with val being an array, the flatten function receives as argument val what you passed as optional argument to call (the type isn't checked), so arguments is [val].
You see your val array is inside an array. When you call reduce, val, inside the callback, will still be an array, you flattened nothing, that's why it never stops.
When you call flatten.apply(null, val), with val being an array, the flatten function receives as arguments the elements of val, so arguments is identical to val (not [val]) : you've effectively unwrapped the array. That's why it works.

Related

How to make sense of `f(array[i], i, array)` found in `d3.min()` source code?

I am new to d3 and try to learn it by reading its source code. I start with probably the simplest function d3.min(). However, my hair was torn apart by a seemingly very common code f(array[i], i, array).
I think f() is supposed to be a function;
array[i] is accessing the element of array at index i;
i is an index of the array;
array is an array of numbers given by user.
If the above 4 understandings are correct, then f() as a function given by the user, must have all three of array[i], i, array as its arguments. But we don't have to use all of these arguments, right?
What is the point of this f()? Can anyone offer any useful example/usage of d3.min(array, f) in which f is not null?
There is a little confusion here. First, d3.min and d3.max can be used with or without an accessor. In the API:
d3.min(array[, accessor])
This square bracket before the comma means that it is optional, not compulsory. So, you can have a simple:
var something = d3.max(someArray);
Or, using an accessor (as you asked, an example where the accessor is not null):
var something = d3.max(data, function(d) {
return d.foo
});
Now we come to your question: you can see in the code above that the accessor function has only 1 argument.
The example you provided is the source code D3 uses to deal with the accessor function. If you look at the code, you'll see array[i], i and array. Those 3 arguments are not provided by the user, but passed to the accessor function by the D3 source code.
Remember that in JS you can pass more arguments than parameters or less arguments than parameters.
The f is a callback function. We use callbacks all the time in JavaScript. So this function works the same way as the map or forEach method does on standard Arrays. It also has the same call signature of value, index and array.
d3.min([1,2,3], (v,i,arr)=>10-v ) // 7
d3.min([1,2,3]) //1
When we call the min function with a 'callback' like the above the answer is the third item in that array but gives the answer as 7 (because 10 - 3 is 7)
adding a f function to find the minimum value of the array while this minimum value being >= 6
var array = [10, 2, 3, 4, 5];
// find the minimum while >= 6
var f = function(x, y, z) {
if (x >= 6) {
return x;
}
}
var min = d3.min(array, f);
console.log(min);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

How to pass the method defined on prototype to Array.map as callback

I have an array
var arr = [' A ', ' b ', 'c'];
and I want to trim the spaces from each of the element from array.
It can be done by using Array.map as
arr.map(function(el) {
return el.trim();
});
I'm curious about passing the trim/toLowerCase function directly to the map as callback function, like arr.map(Math.max.apply.bind(Math.max, null)); to get the maximum element from each subarray or arr.map(Number); to cast each element to Number.
I've tried
arr.map(String.prototype.trim.apply);
but it is throwing error
Uncaught TypeError: Function.prototype.apply was called on undefined, which is a undefined and not a function
I expect that String.prototype.trim.apply should be called for each element in the array with the context set to the element from array(passed to apply);
I've also tried different combinations of apply, call and bind with no success.
Why the function on prototype cannot be referenced when using map
How function can be passed as parameter to map
arr.map(String.prototype.trim.call.bind(String.prototype.trim));
call uses this internally, which must point to the trim function to work properly in this case. Simply passing String.prototype.trim.call would leave call unbound to any method, resulting in the this value pointing to window instead.
It works, but when used apply instead of call it throws error,
arr.map(String.prototype.trim.apply.bind(String.prototype.trim));
The problem is that map will pass 2 arguments, the item and the index. Therefore it ends up calling something like 'String.prototype.trim.apply('test', 0) which fails since the second argument must be an array.
one more thing [' A ', ' B ',
'c'].map(String.prototype.trim.call.bind(String.prototype.toLowerCase));,
in this, I've used trim.call and passed toLowerCase as context then
why we need trim here, why trim is not called
When using call.bind the path that you chose to access the call function reference becomes irrelevant. The function that will get called is the one that is bound.
If you want to compose functions together you will need a different approach:
var call = Function.prototype.call,
trim = call.bind(String.prototype.trim),
toLowerCase = call.bind(String.prototype.toLowerCase),
trimAndLowerCase = pipelineFrom(trim, toLowerCase);
[' TeST '].map(trimAndLowerCase);
function pipelineFrom(fn1, fn2) {
return function (val) {
return fn2(fn1(val));
};
}
However at this point you're better off with:
arr.map(function (val) {
return val.trim().toLowerCase();
});
This works, it sure is long-winded though:
var t = String.prototype.trim.call.bind(String.prototype.trim);
arr.map(t);
Because it's longwinded there are blog posts and modules devoted to uncurrying, which is what you are trying to do here.
I did ask about this here once...

js reduce of an array with only empty values

Why doesn't this iterate?
(Array(5)).reduce(function(cur,prv,n,a){alert(n)},'');
I never reached the function body, seemingly ignoring all of the empty values, which is not what I want it to do.
As far as I understand Array(5) returns an instance of Array Object with a property length (value 5), but without values. One way to be able to use reduce (or other array methods like map) here is:
String(Array(5)).split(',').reduce(function (cur, prv, n, a) { /* ... */ })
An alternative could be:
Array.apply(null,{length: 5}).map( [callback] );
// or
Array.apply(null,Array(5)).map( [callback] );
But that will encounter a maximum call stack error at some point. The string/split method keeps working (albeit a bit slower) for large arrays. Using node.js on my (not so fast) computer mapping Array(1000000) with the string/split-method lasts 0.47 seconds, the array.apply method for the same crashes with a RangeError (Maximum call stack size exceeded).
You can also write a 'static' Array method to create an Array with n values and all usable/applicable Array methods in place:
Array.create = function (n, mapCB, method, initial) {
method = method in [] ? method : 'map';
var nwArr = ( function(nn){ while (nn--) this.push(undefined); return this; } )
.call([], n);
return mapCB ? nwArr[method](mapCB, initial) : nwArr;
};
// usages
Array.create(5, [callback], 'reduce', '');
Array.create(5).reduce( [callback], '');
Array.create(5).map( [callback] ).reduce( [callback] );
// etc.
As others have stated, the implementation of reduce causes this to throw a TypeError because the calling array has no initial values. To initialize an array with initial values of undefined and successfully invoke the function, try something like this:
Array.apply(undefined, Array(5)).reduce(function(cur,prv,n,a){alert(n)},'');

No concept of overloading in JavaScript

Check this fiddle or the code below:
function abc(s) {
console.log('in abc(s)');
}
function abc(s, t) {
console.log('in abc(s,t)');
}
abc('1');
The output of this question is always in abc(s,t)
Can someone please explain me whats going on here and why ?
In Javascript there is no overload concept.
You can however write a function that checks how many arguments have been passed by using the arguments value.
function foo(s, t) {
if (arguments.length == 2) {
...
} else {
...
}
}
all arguments that the function expects in the signature but that are not passed by the caller are received as undefined. You can also write variadic functions by simply accessing the n-th argument passed with arguments[i]. Note however that arguments is not a Javascript array, so not all array methods are available for it.
About being able to redefine the same function multiple times without errors things are a bit complex to explain because the rules are strange.
A simple explanation is you could think of is that function is an executable statement like it is in Python and so the last function definition wins. This would be wrong however because, differently from Python, the following is legal Javascript code:
console.log(square(12));
function square(x) { return x*x; }
i.e. you can call a function in lines that are preceding the definition (in a script: of course typing those two lines in a Javascript console wouldn't work).
A slightly more correct explanation is that the compiler first parses all the function definitions (last wins) and then starts executing the code. This mental model works if you don't put functions inside if because what happens in practice in that case is implementation dependent (and I'm not talking about crazy IE, but even that FF and Chrome will do different things). Just don't do that.
You can even use the form
var square = function(x) { return x*x; }
and in this case it's a simple assignment of a "function expression" to a variable that is executed when the flow passed through it (so it's ok to place different implementations of a function inside different if branches, but you cannot call the function before assigning it an implementation).
First, no method overload support in JavaScript (see #6502 workaround).
Second, to describe what you're experiencing, in JavaScript, the last declared function (with the same name) is invoked because the former has been overwritten, It relates to JavaScript Hoisting.
Try to reorder the functions declarations and see the output result again:
function abc(s, t) {
console.log('in abc(s,t)');
}
function abc(s) {
console.log('in abc(s)');
}
abc('1');
In javascript, there is only one function with any given name and if multiple functions with the same name are declared, the last one declared will be the one that is active.
You can however test the arguments that are passed to your function and implement many of the same types of behaviors that function overloading is designed to handle. In fact, in some cases you can do even more.
In your specific example:
function abc(s, t) {
// test to see if the t argument was passed
if (t !== undefined) {
console.log('was called as abc(s,t)');
} else {
console.log('was called as abc(s)');
}
}
abc('1'); // outputs 'was called as abc(s)'
abc('1', '2'); // outputs 'was called as abc(s,t)'
But, you can also get much, much more creative (and useful).
For example, the jQuery .css() method can be called five different ways.
.css( propertyName )
.css( propertyNames )
.css( propertyName, value )
.css( propertyName, function(index, value) )
.css( properties )
The code inside the .css() method examines the type and number of the arguments to figure out which way it is being called and therefore exactly what operation to carry out.
Let's look at how this could be done to figure out which of the 5 forms of this function are being used:
css: function(prop, value) {
// first figure out if we only have one argument
if (value === undefined) {
if (typeof prop === "string") {
// we have a simple request for a single css property by string name
// of this form: .css( propertyName )
} else if (Array.isArray(prop)) {
// we have a request for an array of properties
// of this form: .css( propertyNames )
} else if (typeof prop === "object") {
// property-value pairs of css to set
// of this form: .css( properties )
}
} else {
if (typeof value === "function") {
// of this form: .css( propertyName, function(index, value) )
} else {
// of this form: .css( propertyName, value )
}
}
}
You can also implement optional arguments. For example, jQuery's .hide() can accept many forms. One of the forms is .hide( [duration ] [, complete ] ) where both the duration and the completion function are optional. You can pass nothing, just a duration or both a duration and completion callback function. That could be implemented like this:
hide: function(duration, fn) {
// default the duration to zero if not present
duration = duration || 0;
// default the completion function to a dummy function if not present
fn = fn || function() {};
// now the code can proceed knowing that there are valid arguments for both
// duration and fn whether they were originally passed or not
}
I find one of the most useful ways of using these variable arguments are to allow code to support a variety of different argument types so that no matter what state your arguments are in, you can just pass them as you have them without having to convert them to some universal type. For example, in this implementation of a set object in javascript, the .add() method can take all of these different forms of arguments:
s.add(key)
s.add(key1, key2, key3)
s.add([key1, key2, key3])
s.add(key1, [key8, key9], key2, [key4, key5])
s.add(otherSet) // any other set object
s.add(arrayLikeObject) // such as an HTMLCollection or nodeList
This both accepts a variable number of arguments and it accepts a number of different types for each argument and it will adapt based on what is passed to it. So, you can initialize a set via a list of keys, an array of keys, from another set, from a pseudo array or any mixture of those types. Internally, the code just iterates through each argument that was passed to the function, checks the type of the argument and acts accordingly.
You can see the code here on GitHub for further info on how this is done.

Javascript: confuse about usage of function call method

I achieve a forEach function:
function forEach(arr, fn) {
for (var i = 0; i < arr.length; i++) {
fn.call({}, arr[i], i);
}
}
what I confused is about fn.call({}, arr[i], i);
the first parameter is pass empty just like above {} is better
or pass this in: fn.call(this, arr[i], i); is better?
Or it doesn't matter
It matters quite a bit. The first parameter to .call() is the value to be used for this inside the called function. Thus, it doesn't make sense to talk about what value is "better"; the right value to pass is the one you need in order for the called function to operate properly.
For example, if you want to call a function on the Array prototype, then the value of this inside that function has to be something that "feels like" an array (a "length" property and numerically-indexed properties). Thus:
var sneaky = {
"0": "hello",
"1": "world",
"length": 2
};
alert( Array.prototype.join.call(sneaky, " - ") ); // "hello - world"
That works because that function expects this to refer to the array to be joined.
There are as many other examples as there are functions that have expectations about this. In your sample code, passing {} gives the called function a this reference to that newly-created empty object. Will that work? I don't know, because that function could expect anything. There's no way to find out, either, except by looking at the code (or trusting documentation). If all you know is that it's some random arbitrary function, then {} is a reasonable guess, though undefined might be better, to force early failure.
Personally I would go with passing this. By passing {} you are limiting the flexibility of your function. You will never be able to bind another object to this function the way it is currently written. This won't work:
forEach.call(newContext, array, fn)
Neither will this:
forEach(array, fn.bind(newContext));
By binding {} inside your forEach you are adding unexpected behavior.

Categories