Bound Functions and Function Imports in JavaScript

After playing with the code in my last post, and asking some apparently silly questions about the ES4 spec, I’ve found some techniques which can be used to implement python-style “import” using current JavaScript.

References to Bound Functions

In python, it is possible to hold a reference to an unbound method, or to a method which is bound to a particular object:

>>>class Foo:
...  def dumpMe(self, arg):
...    print "self: %s\narg: %s" % (self, arg)
...
>>> Foo.dumpMe
<unbound method Foo.dumpMe>
>>> Foo().dumpMe
<bound method Foo.dumpMe of <__main__.Foo instance at 0x2aaaaab33680>>
>>> Foo.dumpMe("something")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unbound method dumpMe() must be called with Foo instance as first argument (got str instance instead)
>>> Foo().dumpMe("something")
self: <__main__.Foo instance at 0x2aaaaab33d40>
arg: something

The JavaScript language does not have a built-in syntax to obtain a bound method:

js> function Foo() { }
js> Foo.prototype.dumpMe = function(arg) {
  dump("this: " + this + "\narg: " + arg);
}
js> new Foo().dumpMe
function (arg) {
    print("this: " + this + "\narg: " + arg);
}
js> f = new Foo();
js> f.dumpMe("test")
this: [object Object]
arg: test
js> unbound = new Foo().dumpMe
js> unbound("test")
this: [object BackstagePass @ 0x6214e0 (native @ 0x513fc8)]
arg: test

(The BackstagePass object is the global object in xpcshell).

However, with a little extra code it is possible to emulate bound functions in JavaScript:

js> Function.prototype.bind = function(object) {
  var func = this;
  return function() { return func.apply(object, arguments); }
}
js> boundFunc = f.dumpMe.bind(f);
js> boundFunc("test")
this: [object Object]
arg: test

We can then use bound functions to emulate the from module import x, y, x statement of python:

js> function jsimport(module) {
  // this function has two forms:
  // jsimport(module) is equivalent to python "from module import *"
  // jsimport(module, "name" [, "name2"]) is equivalent to python "from module import name, name2"

  // NOTE: "this" is the global object
  function internal_import(name) {
    if (module[name] instanceof Function) {
      this[name] = module[name].bind(module);
    }
    else {
      this[name] = module[name];
    }
  }

  if (arguments.length == 1) {
    for (name in module) {
      if (module.hasOwnProperty(name)) {
        internal_import(name);
      }
    }
  }
  else {
    for (i = 1; i < arguments.length; ++i) {
      internal_import(arguments[i]);
    }
  }
}

You could use this technique today to hide away the IOService or PrefService a little:

jsimport(Components.classes["@mozilla.org/preferences/pref-service;1"].getService(Components.interfaces.nsIPrefBranch),
         "getCharPref", "getIntPref", "getBoolPref");

if (getBoolPref("dom.window.dump.enabled"))
  // do something dumpy

Atom Feed for Comments 4 Responses to “Bound Functions and Function Imports in JavaScript”

  1. Anders Says:

    If you added something like (leading dots added to preserve indentation):

    if (typeof module == ‘string’) {
    . var pos = module.indexOf(‘#’);
    . var className = module.substring(pos + 1);
    . module = module.substring(0, pos);
    . if (module.substring(0, 1) != ‘@’) {
    . . module = “@mozilla.org/” + module;
    . }
    . if (module.indexOf(‘;’) == -1) {
    . . module += “;1”;
    . }
    . module = Components.classes[module].getService(Components.interfaces[className])
    }

    You could use the slightly less verbose:
    jsimport(“preferences/pref-service#nsIPrefBranch”, “getCharPref”, “getIntPref”, “getBoolPref”);

  2. Dao Says:

    // NOTE: “this” is the global object

    Why don’t you write window then? Imho, this is a bit irritating here.

    Anders: you can have lines 2-4 easier: var className; [module, className] = module.split("#",2);. Also .substring(0, 1) equals .charAt(0) and [0].
    And since Benjamin proposed a more general solution, I would call your variant mozImport. Then again, you don’t need to split at “#” — just use two arguments for that.

  3. Benjamin Smedberg Says:

    Dao, in JS components and xpcshell, the global object is not “window”. That is specific to JavaScript in the DOM.

  4. Dao Says:

    Ah, good to know. I’m a Web guy, so that was news to me.

Leave a Reply