Everything In Order
So, now we’ve uncovered the 4 rules for binding this
in function calls. All you need to do is find the call-site and inspect it to see which rule applies. But, what if the call-site has multiple eligible rules? There must be an order of precedence to these rules, and so we will next demonstrate what order to apply the rules.
It should be clear that the default binding is the lowest priority rule of the 4. So we’ll just set that one aside.
Which is more precedent, implicit binding or explicit binding? Let’s test it:
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
So, explicit binding takes precedence over implicit binding, which means you should ask first if explicit binding applies before checking for implicit binding.
Now, we just need to figure out where new binding fits in the precedence.
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
OK, new binding is more precedent than implicit binding. But do you think new binding is more or less precedent than explicit binding?
Note: new
and call
/apply
cannot be used together, so new foo.call(obj1)
is not allowed, to test new binding directly against explicit binding. But we can still use a hard binding to test the precedence of the two rules.
Before we explore that in a code listing, think back to how hard binding physically works, which is that Function.prototype.bind(..)
creates a new wrapper function that is hard-coded to ignore its own this
binding (whatever it may be), and use a manual one we provide.
By that reasoning, it would seem obvious to assume that hard binding (which is a form of explicit binding) is more precedent than new binding, and thus cannot be overridden with new
.
Let’s check:
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
Whoa! bar
is hard-bound against obj1
, but new bar(3)
did not change obj1.a
to be 3
as we would have expected. Instead, the hard bound (to obj1
) call to bar(..)
is able to be overridden with new
. Since new
was applied, we got the newly created object back, which we named baz
, and we see in fact that baz.a
has the value 3
.
This should be surprising if you go back to our “fake” bind helper:
function bind(fn, obj) {
return function() {
fn.apply( obj, arguments );
};
}
If you reason about how the helper’s code works, it does not have a way for a new
operator call to override the hard-binding to obj
as we just observed.
But the built-in Function.prototype.bind(..)
as of ES5 is more sophisticated, quite a bit so in fact. Here is the (slightly reformatted) polyfill provided by the MDN page for bind(..)
:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError( "Function.prototype.bind - what " +
"is trying to be bound is not callable"
);
}
var aArgs = Array.prototype.slice.call( arguments, 1 ),
fToBind = this,
fNOP = function(){},
fBound = function(){
return fToBind.apply(
(
this instanceof fNOP &&
oThis ? this : oThis
),
aArgs.concat( Array.prototype.slice.call( arguments ) )
);
}
;
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
Note: The bind(..)
polyfill shown above differs from the built-in bind(..)
in ES5 with respect to hard-bound functions that will be used with new
(see below for why that’s useful). Because the polyfill cannot create a function without a .prototype
as the built-in utility does, there’s some nuanced indirection to approximate the same behavior. Tread carefully if you plan to use new
with a hard-bound function and you rely on this polyfill.
The part that’s allowing new
overriding is:
this instanceof fNOP &&
oThis ? this : oThis
// ... and:
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
We won’t actually dive into explaining how this trickery works (it’s complicated and beyond our scope here), but essentially the utility determines whether or not the hard-bound function has been called with new
(resulting in a newly constructed object being its this
), and if so, it uses that newly created this
rather than the previously specified hard binding for this
.
Why is new
being able to override hard binding useful?
The primary reason for this behavior is to create a function (that can be used with new
for constructing objects) that essentially ignores the this
hard binding but which presets some or all of the function’s arguments. One of the capabilities of bind(..)
is that any arguments passed after the first this
binding argument are defaulted as standard arguments to the underlying function (technically called “partial application”, which is a subset of “currying”).
For example:
function foo(p1,p2) {
this.val = p1 + p2;
}
// using `null` here because we don't care about
// the `this` hard-binding in this scenario, and
// it will be overridden by the `new` call anyway!
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2
Determining this
Now, we can summarize the rules for determining this
from a function call’s call-site, in their order of precedence. Ask these questions in this order, and stop when the first rule applies.
Is the function called with
new
(new binding)? If so,this
is the newly constructed object.var bar = new foo()
Is the function called with
call
orapply
(explicit binding), even hidden inside abind
hard binding? If so,this
is the explicitly specified object.var bar = foo.call( obj2 )
Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so,
this
is that context object.var bar = obj1.foo()
Otherwise, default the
this
(default binding). If instrict mode
, pickundefined
, otherwise pick theglobal
object.var bar = foo()
That’s it. That’s all it takes to understand the rules of this
binding for normal function calls. Well… almost.