JavaScript Method Context With Circular Invocation In Conjunction With Call() Or Apply()
In JavaScript, you can change part of the execution context using the call() and apply() methods. These allow you to explicitly define the binding of "this" at the time of method invocation. While I use these functions all the time, I realized recently that I was unsure what would happen if you created a circular invocation chain using an overridden method. To ease my mind, I created a small experiment:
<!DOCTYPE html>
<html>
<head>
<title>Testing JavaScript Context</title>
<script type="text/javascript">
// For this test, we are going to look at how circular
// method invocations work in the context of a method
// that has been invoked with either call() or apply().
var testHarness = {
one: function(){
console.log( "Real Context!" );
},
two: function(){
this.one();
}
};
// -------------------------------------------------- //
// -------------------------------------------------- //
// To test the circular invocation and this-based context,
// we need to override the "one" method, and have it invoke
// the "two" method so we can then see what version of "one"
// it will turn around and invoke.
function one(){
console.log( "FAKE Context!" );
// Create circular invocation.
this.two();
}
// -------------------------------------------------- //
// -------------------------------------------------- //
// Invoke "fake" ONE in the context of the test harness.
one.call( testHarness );
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>
Here, we have a test object with two methods: one() and two(). In its natural state, two() invokes one() but, one() never invokes two(). Then, we create a completely separate function which overrides the "one" method and invokes two(). Does this create a circular reference problem?
When we run the above code, we get the following console output:
FAKE Context!
Real Context!
When we run the the override method, we clearly get the fake (ie. explicitly changed) context. But, when that function invokes two(), which subsequently invokes one(), the "second" invocation of one() reveals that its context is the original context.
No circular references were created.
When I saw this, I suddenly had clarity - I realized that my previous mental model of call() and apply() was completely wrong. I kept thinking about it as some sort of temporary, runtime method injection magic. I kept thinking about it as overriding part of the target context's object definition. In reality, it's nothing more than an overriding of the given method's "this" binding. There's nothing more to it. There's no relationship between the method being invoked and the context that is being applied.
In retrospect, this should have been completely obvious. I guess at some point, I had just created a false mental model of how things worked and never stopped to question it. Clarity for the win!
Want to use code from this post? Check out the license.
Reader Comments
Thanks for this clarity.
I have not tested it yet but yeah, it's obvious. Apply and call simply override the context and do not create a "mixin context" who contains both the second one() and the two().
I don't remember if it was detailled in, but I found a very well explanation of the javascript core: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
@Loic,
I think part of my distortion came from the fact that my primary programming language - ColdFusion - doesn't have a call() or apply() equivalent. As such, in that language, you actually DO have to fine way to use method injection rather than overriding the "this" binding. So, while I know how call() and apply() work, it was being colored improperly by my understanding of a different language altogether.