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