18 07 2013
Some interesting aspects of “call” method in JavaScript
Hi all, today is another javascript experiment where I use Array’s iteration methods forEach and map to look how call function is implemented and how we can use call
to implement something ‘not-standard’.
You know often I use forEach
or map
on an array to execute one method of the instances in this array, e.g.,
var json = arr.map(function (obj) { return obj.serialize(); });
Today I thought: “hey, but actually, that would be cool to execute "serialize"
method not directly in a anonymous function, but pass it as a parameter to the map
method in a more declarative style”. Something like this one:
// arr is an instance of Array field with Zork's var json = arr.map(obj.serialize);
To say frankly, one of the reasons why I love javascript so much, is because there is always a way to implement something in an obscure manner and I think this case is not any different.
So, let’s start our small journey and imagine that objects in the arr
variable are of the type Zork
.
When I first thought about how would I achieve this, my first thought was: “Ok, that’s nice, but where do I take the reference to the serialize method from if it’s on every obj, which is available at the moment of iteration? But it’s avaliable also on the prototype of the obj instance, on the prototype of the Zork
“, pseudo-code:
var json = arr.map( Zork.prototype.serialize );
But, we all know that it wont work, since map
will pass instance of every object as a first argument and scope of serialize
method would be wrong.
Luckily, javascript has the ability to change scope of any function/method to the desired one. This can be achieved by using call
or apply method which is a member of any normal function in javascript.
“Call
” method receives scope as the first argument, this means that passing any Zork
instance to the call will change scope and execute serialize
with a correct scope. Here the example code:
var json = arr.map(function (obj) { return Zork.prototype.serialize.call(obj); });
Check, that’s exactly what I need. So, theoretically this means, that I can also pass “call
” function directly to the map
var json = arr.map( Zork.prototype.serialize.call );
When I tried to execute the above function Firfox gave me an error saying:
TypeError: Function.prototype.call called on incompatible undefined
This is not very explanatory message, but after making a couple of tests the answer was a bit shocking for me. I will show you this tests later on, but first my conclusion: “It appears that call
function is using the same context resolution principles as for normal “method” calls, but instead call
uses its ‘this’ variable for resolving referenced function and not associated object.”
Before I continue let’s remain the basics how javascript resolves this
variable for functions.
Function can be called at least in five different ways: “invoke as method”, “invoke as baseless function”, “invoke using “Function.protoype.call”, “function.prototype.apply” and “invoke a constructor using new”. Currently we are interested in two of them: “invoke as method” and “invoke as baseless function”.
When we are caching method and invoking it separatly we are dealing with “invoke as baseless function”. At this moment, execution context ( with other words this
variable ) is resolved to the global. Here is example code:
var myObject = { some_method: function () { return this; }; }; // var cached = myObject.some_method; cached();
In this code this
variable doesn’t point to the myObject
object, but instead points to the global something. This is due to the fact, that called function is not called as property of myObject
, but instead as anonymous baseless function.
But when function is invoked as a property of the object (“invoke as method”), this
variable will point to that object. To say with other words: when function is called “this
variable always points to the object to which this function belongs as a property at the moment of the execution. Example to make it clear:
var myObject = { some_method: function () { return this; }; }; // var second_object = {}; second_object.newMethod = myObject.some_method; second_object.newMethod();
In the code above “this” variable will point to the second_object
, because at the moment of the execution newMethod
function was property of second_object
object.
Now, after we remembered how context resolution works in javascript, let’s return to our call
story.
As I stated, call
function is using the same resolution principles and doesn’t have tight reference to it’s associated function and uses this
variable to execute associated function. Interesting, isn’t it? In the normal flow execution this
variable points to the object, but in our case it points to the function. In pseudo-code you could write call implementation something like this:
var call = function () { this(); }
To test my thoughts I’ve created a test snippets which will show below. My snippets uses Zork prototype which is defined as follows:
function Zork(name) { this.name = name; } Zork.prototype.serialize = function () { console.log(this.name); return 'My name is ' + this.name; }
I propose to run call
function as anonymous (baseless) function and ensure that it throws the same error as above:
var serializeCall = Zork.prototype.serialize.call; serializeCall({ name: 'Nemisj' });
Console gave back exactly the same error as above.
TypeError: Function.prototype.call called on incompatible undefined
Now let’s execute “call
” as a property of “serialize
” method, so that we can see whether “call
” function is using serialize
method as its execution context:
var serialize = Zork.prototype.serialize; serialize.call({ name: 'Nemisj' });
After executing that code it has printed “Nemisj” which proved that “call
” is not bound to the referenced function, but defines it at the moment of the execution. If my assumption is correct it also means that I can invoke call
function by using its own call
method, only instead of passing object to the second call
I will pass serialize
method.
var serializeCall = Zork.prototype.serialize.call; serializeCall.call( Zork.prototype.serialize, { name: 'Nemisj' });
This might look a bit weired, but it’s still working 🙂
This information led me to the next conclusion: In normal situation, if I want this
variable to point to the correct object, I use “bind“. But does this mean, that I can use bind
function to preserve the associated method for call
function? Let’s try it out:
var serializeCallBinded = Zork.prototype.serialize.call.bind( Zork.prototype.serialize ); serializeCallBinded({ name: 'Nemisj' });
And the answer is: YES 🙂 After executing this code, it still prints “Nemisj”. Great, now we know how to execute call
as anonymous function, let’s move on with our achievement.
First implementation with bind:
var serializeCallBinded = Zork.prototype.serialize.call.bind( Zork.prototype.serialize ); var json = arr.map( serializeCallBinded );
But, any iteration method of an Array supports second parameter, which defines the SCOPE of the executed function and since call
uses it’s scope to reference the parent function it means, that I can pass the “serialize
” method as the second argument and have one nice one-liner:
var json = arr.map( Zork.prototype.serialize.call, Zork.prototype.serialize );
Run…and…check ( http://jsfiddle.net/vNRjd/2 )…code is working 🙂 Well, mission is accomplished I guess.
Except that it does look a bit weird. Passing the same function twice, writing the long Zork.prototype
twice..mhee…not nice 🙂 Let’s add some sugar to it.
Because “call
” function is isolated and is not bound to its parent function until the invocation, we can pass any native call
method to the map as the first argument:
var json = arr.map( Function.prototype.call, Zork.prototype.serialize );
It’s not necessary to give the call
method of the prototype of a function, it also can be any call
method from any method/function, even the evil “eval” one:
var json = arr.map( eval.call, Zork.prototype.serialize );
But the Function constructor also has a direct “call
” method, which simplifies our invocation even more:
var json = arr.map( Function.call, Zork.prototype.serialize );
This looks much prettier and almost human readable: map an array by calling the serialize of Zork 🙂
I don’t advocate using this code in your production environment. It’s not very self explanatory and it’s a bit slow 🙂 For those who like graphs here is one http://jsperf.com/combine-foreach-and-call. There are more reasons why you shouldn’t write such unreadable code, but I leave it up to you. By making such obscure implementations I understand the working of javascript more and more and I hope you do too.
Have a happy coding.
Why I stopped using self when caching ‘this’ Vim script for Node.js and ‘gf’