My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Wednesday, November 28, 2012

My Name Is Bound, Method Bound ...

it seems to me the most obvious thing ever but I keep finding here and there these common anti pattern:
// 1. The timer Case
this.timer = setTimeout(
  this.doStuff.bind(this), 500
);

// 2. The Listener Case
(objOrEl || this).addEventListener(
  type, this.method.bind(this), false
);

What Is Wrong

I have explained a while ago what's wrong and how to improve these cases but I understand the code was too scary and the post was too long so here the summary:
  1. every setTimeout() call will create a new bound object and this is both redundant and inefficient plus it's not GC friendly
  2. there is no way to retrieve that listener created inline so it's impossible to remove the listener any time is necessary ... yes, even after, when you do refactoring 'cause you will need to clean up listeners

What You Need

The short version is less than a tweet, 64bytes: function b(o,s,t){return(t=this)[s="@"+o]||(t[s]=t[o].bind(t))} This is the equivalent of:
// as generic prototype method
function bound(methodName) {
  var boundName = "__bound__" + methodName;
  return this[boundName] || (
    this[boundName] = this[methodName].bind(this)
  );
}

// as generic utility
function bound(object, methodName) {
  var boundName = "__bound__" + methodName;
  return object[boundName] || (
    object[boundName] = object[methodName].bind(object)
  );
}

// as stand alone dependencies free prototype method
var bound = function(){
  function bound(methodName) {
    var boundName = uniqueId + methodName;
    return this[boundName] || (
      this[boundName] = bind.call(this[methodName], this)
    );
  }
  var
    uniqueId = "__bound__", // + Math.random(),
    bind = bound.bind || function bind(self) {
      var callback = this;
      return function bound() {
        return callback.apply(self, arguments);
      };
    }
  ;
  return bound;
}();

// really ... you can write
// this little thing in many ways

Pick One And Use It !

These are benefits over the simple, ultra fast, performance oriented, and memory safe approach:
// 1. The timer Case
this.timer = setTimeout(
  this.bound("doStuff"), 500
);

// 2. The Listener Case
(objOrEl || this).addEventListener(
  type, this.bound("method"), false
);
  1. every setTimeout() call, if any, will create one single bound object
  2. you can remove the listener at any time through the same call, i.e. (objOrEl || this).removeEventListener(type, this.bound("method"), false);

Is That It ?

You are free to over complicate this concept as much as you want as long as you got the point: there's no need to create a new bound Object per each call plus your life will be much easier following this pattern focusing on logic rather than possible typos, boring variable assignments, etc etc ... this works and it's what you need and what you want now. I'm also moving my hands as a Jedi so I hope I've convinced you!

4 comments:

Adam Silver said...

Or just save a reference to the bound listener for removal later and save having to keep a registry as follows:

var boundListener = (function() {}).bind(this);
el.addEventListener('click', boundListener);
el.removeEventListener('click', boundListener);

?

(btw I got this idea from David Mark - credit where it is due :))

Adam

Andrea Giammarchi said...

You still need a reference while with this pattern you don't ! No need to share scope or anything else, just use this pattern to speed up things and scale ;)

Adam Silver said...

I understand your reasons for doing so and some people may prefer it for it looks a little tidier but for me simple is most often better and of course it's about context...

How often do you need that reference to a bound function? In my experience not very often. And when I have needed to for example, remove an attached event listener I just save the reference.

Adam

Andrea Giammarchi said...

if you work with classes or interfaces and you inherit and you want to be able to overwrite a method removing listeners your pattern won't work/scale, mine will.

My suggested pattern does not depend on shared scope or current closure and it's portable across objects as mixins too.

In any case, as I have said, you can write the pattern in many ways, and your is one of them, as long as you don't create more than once a bound object ... so you are good.