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
January 4th, 2007 at 2:14 am
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”);
January 4th, 2007 at 10:57 am
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.January 4th, 2007 at 11:15 am
Dao, in JS components and xpcshell, the global object is not “window”. That is specific to JavaScript in the DOM.
January 4th, 2007 at 8:04 pm
Ah, good to know. I’m a Web guy, so that was news to me.