Function calls
Functions are just a type of Object.
All Function objects have call and apply methods which execute the Function object they're called on.
When called, the first argument to these methods specifies the object which will be referenced by the this
keyword during execution of the Function - if it's null
or undefined
, the global object, window
, is used for this
.
Thus, calling a Function...
whereAmI = "window";
function foo()
{
return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}
...with parentheses - foo()
- is equivalent to foo.call(undefined)
or foo.apply(undefined)
, which is effectively the same as foo.call(window)
or foo.apply(window)
.
>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"
Additional arguments to call
are passed as the arguments to the function call, whereas a single additional argument to apply
can specify the arguments for the function call as an Array-like object.
Thus, foo(1, 2, 3)
is equivalent to foo.call(null, 1, 2, 3)
or foo.apply(null, [1, 2, 3])
.
>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"
If a function is a property of an object...
var obj =
{
whereAmI: "obj",
foo: foo
};
...accessing a reference to the Function via the object and calling it with parentheses - obj.foo()
- is equivalent to foo.call(obj)
or foo.apply(obj)
.
However, functions held as properties of objects are not "bound" to those objects. As you can see in the definition of obj
above, since Functions are just a type of Object, they can be referenced (and thus can be passed by reference to a Function call or returned by reference from a Function call). When a reference to a Function is passed, no additional information about where it was passed from is carried with it, which is why the following happens:
>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"
The call to our Function reference, baz
, doesn't provide any context for the call, so it's effectively the same as baz.call(undefined)
, so this
ends up referencing window
. If we want baz
to know that it belongs to obj
, we need to somehow provide that information when baz
is called, which is where the first argument to call
or apply
and closures come into play.
Scope chains
function bind(func, context)
{
return function()
{
func.apply(context, arguments);
};
}
When a Function is executed, it creates a new scope and has a reference to any enclosing scope. When the anonymous function is created in the above example, it has a reference to the scope it was created in, which is bind
's scope. This is known as a "closure."
[global scope (window)] - whereAmI, foo, obj, baz
|
[bind scope] - func, context
|
[anonymous scope]
When you attempt to access a variable this "scope chain" is walked to find a variable with the given name - if the current scope doesn't contain the variable, you look at the next scope in the chain, and so on until you reach the global scope. When the anonymous function is returned and bind
finishes executing, the anonymous function still has a reference to bind
's scope, so bind
's scope doesn't "go away".
Given all the above you should now be able to understand how scope works in the following example, and why the technique for passing a function around "pre-bound" with a particular value of this
it will have when it is called works:
>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"