Skip to content

Instantly share code, notes, and snippets.

@cowboy
Last active March 30, 2023 01:59
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save cowboy/4477847 to your computer and use it in GitHub Desktop.
Save cowboy/4477847 to your computer and use it in GitHub Desktop.
JavaScript: call invo-cursion?
// OOP
console.log( 'OHAI'.blink() );
// Call invocation
console.log( String.prototype.blink.call('OHAI') );
// $ always makes things look awesome.
var $ = Function.prototype.call;
// Very explicit call invocation
console.log( $.call(String.prototype.blink, 'OHAI') );
// Very, very explicit call invocation, ie. call invo-cursion?
console.log( $.call($,$,$,$,$,$,$,$,$,$,$,$, String.prototype.blink, 'OHAI') );
// ^^^^^^^^^^^^^^^^^^^^^^^ "bonus" calls
// You can have fun with apply invocation and _ too.
var _ = Function.prototype.apply;
// Very, very explicit apply invocation, ie. apply invo-cursion.
console.log( _.apply(_,[_,[_,[_,[_,[_,[_,[_, [ String.prototype.blink, ['OHAI'] ]]]]]]]]) );
// ^^^^^^^^^^^^^^^^^^^^^^ "bonus" applies, and fun w/brackets ^^^^^^^^
@luizfilipe
Copy link

@vsternbach {length: 5} is an array-like for apply function

From MDN docs:

Syntax

fun.apply(thisArg, [argsArray])

Parameters

thisArg
The value of this provided for the call to fun. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode code, null and undefined will be replaced with the global object, and primitive values will be boxed.
argsArray
An array-like object, specifying the arguments with which fun should be called, or null or undefined if no arguments should be provided to the function. Starting with ECMAScript 5 these arguments can be a generic array-like object instead of an array. See below for browser compatibility information.

@jackocnr
Copy link

In case this helps anyone else: I was confused as to why you needed that last Number passed in as the 2nd arg to map. I know that map invokes the given callback with 3 args: currentVal, index and array, and that Number just takes 1 arg, so instead of directly using Number as the callback (it would receive the first arg currentVal (undefined)), we use Number.call as the callback, which swallows the first arg as the this value and instead invokes Number with the second arg index, which is what we want.

But it turns out call doesn't just magically know which function it was called on, it just executes whichever function is in it's this value (I believe). Normally that will indeed be the function it was called on (it is effectively an Object method - see the MDN article on this), but in this case, we pass Number.call into map, which is then stored internally simply as callback, so it is no longer an Object method, it is now just a simple function, so (I assume) it's this defaults to the global object, which it then tries to invoke as a function, which throws an error. So in this case the Number from Number.call is lost and never actually gets called. That is why we have to pass in Number as the 2nd arg to map because it is then set as the this for the callback (which in this case is Number.call), and so based on our past assertions, call will then invoke it.

As mentioned earlier in this thread: an interesting consequence of all this is that it doesn't matter how you invoke call - it works just as well if you do map(Function.prototype.call, Number).

Props to these articles for helping me understanding this: Some interesting aspects of “call” method in JavaScript and Partial Application in JavaScript.

Feedback welcome on these wild theories!

FYI if you remove the last Number argument, and instead run Array.apply(null, {length: 5}).map(Number.call) then in Chrome you get the error: Uncaught TypeError: Array.apply(...).map is not a function, which is confusing. Anyone understand this?

@haocong
Copy link

haocong commented Sep 7, 2016

@jackocnr For the last FYI, I thought it would be like this: when you pass Number.call(or Function.call) to Array.prototype.map, the Number.call(or Function.call) is no longer an Object method, it is now just a simple function, with the call missing this then JavaScript runtime call an undefined function and will throw an error.

Array.apply(null, {length: 5}).map(Number.call) // TypeError: undefined is not a function

For better understanding, here is another example:

var call = Function.call;
call() // TypeError: call is not a function
call.call(Number, undefined, 1) // 1
call.call(String, undefined, 'foo') // foo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment