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:

  1. function foo() {
  2. console.log( this.a );
  3. }
  4. var obj1 = {
  5. a: 2,
  6. foo: foo
  7. };
  8. var obj2 = {
  9. a: 3,
  10. foo: foo
  11. };
  12. obj1.foo(); // 2
  13. obj2.foo(); // 3
  14. obj1.foo.call( obj2 ); // 3
  15. 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.

  1. function foo(something) {
  2. this.a = something;
  3. }
  4. var obj1 = {
  5. foo: foo
  6. };
  7. var obj2 = {};
  8. obj1.foo( 2 );
  9. console.log( obj1.a ); // 2
  10. obj1.foo.call( obj2, 3 );
  11. console.log( obj2.a ); // 3
  12. var bar = new obj1.foo( 4 );
  13. console.log( obj1.a ); // 2
  14. 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:

  1. function foo(something) {
  2. this.a = something;
  3. }
  4. var obj1 = {};
  5. var bar = foo.bind( obj1 );
  6. bar( 2 );
  7. console.log( obj1.a ); // 2
  8. var baz = new bar( 3 );
  9. console.log( obj1.a ); // 2
  10. 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:

  1. function bind(fn, obj) {
  2. return function() {
  3. fn.apply( obj, arguments );
  4. };
  5. }

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(..):

  1. if (!Function.prototype.bind) {
  2. Function.prototype.bind = function(oThis) {
  3. if (typeof this !== "function") {
  4. // closest thing possible to the ECMAScript 5
  5. // internal IsCallable function
  6. throw new TypeError( "Function.prototype.bind - what " +
  7. "is trying to be bound is not callable"
  8. );
  9. }
  10. var aArgs = Array.prototype.slice.call( arguments, 1 ),
  11. fToBind = this,
  12. fNOP = function(){},
  13. fBound = function(){
  14. return fToBind.apply(
  15. (
  16. this instanceof fNOP &&
  17. oThis ? this : oThis
  18. ),
  19. aArgs.concat( Array.prototype.slice.call( arguments ) )
  20. );
  21. }
  22. ;
  23. fNOP.prototype = this.prototype;
  24. fBound.prototype = new fNOP();
  25. return fBound;
  26. };
  27. }

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:

  1. this instanceof fNOP &&
  2. oThis ? this : oThis
  3. // ... and:
  4. fNOP.prototype = this.prototype;
  5. 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:

  1. function foo(p1,p2) {
  2. this.val = p1 + p2;
  3. }
  4. // using `null` here because we don't care about
  5. // the `this` hard-binding in this scenario, and
  6. // it will be overridden by the `new` call anyway!
  7. var bar = foo.bind( null, "p1" );
  8. var baz = new bar( "p2" );
  9. 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.

  1. Is the function called with new (new binding)? If so, this is the newly constructed object.

    var bar = new foo()

  2. Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object.

    var bar = foo.call( obj2 )

  3. 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()

  4. Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global 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.