Binding Exceptions

As usual, there are some exceptions to the “rules”.

The this-binding behavior can in some scenarios be surprising, where you intended a different binding but you end up with binding behavior from the default binding rule (see previous).

Ignored this

If you pass null or undefined as a this binding parameter to call, apply, or bind, those values are effectively ignored, and instead the default binding rule applies to the invocation.

  1. function foo() {
  2. console.log( this.a );
  3. }
  4. var a = 2;
  5. foo.call( null ); // 2

Why would you intentionally pass something like null for a this binding?

It’s quite common to use apply(..) for spreading out arrays of values as parameters to a function call. Similarly, bind(..) can curry parameters (pre-set values), which can be very helpful.

  1. function foo(a,b) {
  2. console.log( "a:" + a + ", b:" + b );
  3. }
  4. // spreading out array as parameters
  5. foo.apply( null, [2, 3] ); // a:2, b:3
  6. // currying with `bind(..)`
  7. var bar = foo.bind( null, 2 );
  8. bar( 3 ); // a:2, b:3

Both these utilities require a this binding for the first parameter. If the functions in question don’t care about this, you need a placeholder value, and null might seem like a reasonable choice as shown in this snippet.

Note: We don’t cover it in this book, but ES6 has the ... spread operator which will let you syntactically “spread out” an array as parameters without needing apply(..), such as foo(...[1,2]), which amounts to foo(1,2) — syntactically avoiding a this binding if it’s unnecessary. Unfortunately, there’s no ES6 syntactic substitute for currying, so the this parameter of the bind(..) call still needs attention.

However, there’s a slight hidden “danger” in always using null when you don’t care about the this binding. If you ever use that against a function call (for instance, a third-party library function that you don’t control), and that function does make a this reference, the default binding rule means it might inadvertently reference (or worse, mutate!) the global object (window in the browser).

Obviously, such a pitfall can lead to a variety of very difficult to diagnose/track-down bugs.

Safer this

Perhaps a somewhat “safer” practice is to pass a specifically set up object for this which is guaranteed not to be an object that can create problematic side effects in your program. Borrowing terminology from networking (and the military), we can create a “DMZ” (de-militarized zone) object — nothing more special than a completely empty, non-delegated (see Chapters 5 and 6) object.

If we always pass a DMZ object for ignored this bindings we don’t think we need to care about, we’re sure any hidden/unexpected usage of this will be restricted to the empty object, which insulates our program’s global object from side-effects.

Since this object is totally empty, I personally like to give it the variable name ø (the lowercase mathematical symbol for the empty set). On many keyboards (like US-layout on Mac), this symbol is easily typed with +o (option+o). Some systems also let you set up hotkeys for specific symbols. If you don’t like the ø symbol, or your keyboard doesn’t make that as easy to type, you can of course call it whatever you want.

Whatever you call it, the easiest way to set it up as totally empty is Object.create(null) (see Chapter 5). Object.create(null) is similar to { }, but without the delegation to Object.prototype, so it’s “more empty” than just { }.

  1. function foo(a,b) {
  2. console.log( "a:" + a + ", b:" + b );
  3. }
  4. // our DMZ empty object
  5. var ø = Object.create( null );
  6. // spreading out array as parameters
  7. foo.apply( ø, [2, 3] ); // a:2, b:3
  8. // currying with `bind(..)`
  9. var bar = foo.bind( ø, 2 );
  10. bar( 3 ); // a:2, b:3

Not only functionally “safer”, there’s a sort of stylistic benefit to ø, in that it semantically conveys “I want the this to be empty” a little more clearly than null might. But again, name your DMZ object whatever you prefer.

Indirection

Another thing to be aware of is you can (intentionally or not!) create “indirect references” to functions, and in those cases, when that function reference is invoked, the default binding rule also applies.

One of the most common ways that indirect references occur is from an assignment:

  1. function foo() {
  2. console.log( this.a );
  3. }
  4. var a = 2;
  5. var o = { a: 3, foo: foo };
  6. var p = { a: 4 };
  7. o.foo(); // 3
  8. (p.foo = o.foo)(); // 2

The result value of the assignment expression p.foo = o.foo is a reference to just the underlying function object. As such, the effective call-site is just foo(), not p.foo() or o.foo() as you might expect. Per the rules above, the default binding rule applies.

Reminder: regardless of how you get to a function invocation using the default binding rule, the strict mode status of the contents of the invoked function making the this reference — not the function call-site — determines the default binding value: either the global object if in non-strict mode or undefined if in strict mode.

Softening Binding

We saw earlier that hard binding was one strategy for preventing a function call falling back to the default binding rule inadvertently, by forcing it to be bound to a specific this (unless you use new to override it!). The problem is, hard-binding greatly reduces the flexibility of a function, preventing manual this override with either the implicit binding or even subsequent explicit binding attempts.

It would be nice if there was a way to provide a different default for default binding (not global or undefined), while still leaving the function able to be manually this bound via implicit binding or explicit binding techniques.

We can construct a so-called soft binding utility which emulates our desired behavior.

  1. if (!Function.prototype.softBind) {
  2. Function.prototype.softBind = function(obj) {
  3. var fn = this,
  4. curried = [].slice.call( arguments, 1 ),
  5. bound = function bound() {
  6. return fn.apply(
  7. (!this ||
  8. (typeof window !== "undefined" &&
  9. this === window) ||
  10. (typeof global !== "undefined" &&
  11. this === global)
  12. ) ? obj : this,
  13. curried.concat.apply( curried, arguments )
  14. );
  15. };
  16. bound.prototype = Object.create( fn.prototype );
  17. return bound;
  18. };
  19. }

The softBind(..) utility provided here works similarly to the built-in ES5 bind(..) utility, except with our soft binding behavior. It wraps the specified function in logic that checks the this at call-time and if it’s global or undefined, uses a pre-specified alternate default (obj). Otherwise the this is left untouched. It also provides optional currying (see the bind(..) discussion earlier).

Let’s demonstrate its usage:

  1. function foo() {
  2. console.log("name: " + this.name);
  3. }
  4. var obj = { name: "obj" },
  5. obj2 = { name: "obj2" },
  6. obj3 = { name: "obj3" };
  7. var fooOBJ = foo.softBind( obj );
  8. fooOBJ(); // name: obj
  9. obj2.foo = foo.softBind(obj);
  10. obj2.foo(); // name: obj2 <---- look!!!
  11. fooOBJ.call( obj3 ); // name: obj3 <---- look!
  12. setTimeout( obj2.foo, 10 ); // name: obj <---- falls back to soft-binding

The soft-bound version of the foo() function can be manually this-bound to obj2 or obj3 as shown, but it falls back to obj if the default binding would otherwise apply.