This is a small change to EventEmitter.addListener() to allow
specifying the 'this' reference for the listener function.
It is inspired by similar functions in ExtJS and Qooxdoo, it makes it
really convenient to change the 'owner' of the listener
leading to simpler code.
For example:
var sys = require('sys')
process.on('exit', function() { this.debug("EXITED THIS IS SYS"); }, sys);
Regards,
Nikhil
I still believe allowing it as a third parameter makes it 'look
better' than using bind.
Of course this is highly subjective, if most people have an opinion
that Object.bind() should be explicitly used, then you may reject this
patch :)
Nikhil
> --
> You received this message because you are subscribed to the Google Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com.
> To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.
>
>
--
Support open formats.
Don't send me files in proprietary formats like .doc, .xls, .ppt.
+process.EventEmitter.prototype.addListener = function (type, listener, scope) {
if ('function' !== typeof listener) {
throw new Error('addListener only takes instances of Function');
}
if (!this._events) this._events = {};
+ var theListener = listener;
+ if (scope) {
+ theListener = function() {
+ listener.apply(scope, arguments);
+ }
+ }
Hi Nikhil,
I'd just like to point out that you may want to call it (the 3rd argument) 'that' or 'thisBinding' or 'boundThis' instead of 'scope' because 'this' is !== 'scope'. There's 3 distinct components in a JavaScript function's context: its *scope*, the bound 'this' and its VariableEnvironment. And there's no way to alter the scope once the function has been created (it's lexical).
Currently there's lots of people using it wrong, soo many that I'm afraid it may end up sticking. But -believe me- it's not correct.
Please forgive me for nitpicking :-)
Cheers,
--
Jorge.
Nikhil
> --
> You received this message because you are subscribed to the Google Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com.
> To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.
> Updated to use bind and fix a small typo
>
> I still believe allowing it as a third parameter makes it 'look
> better' than using bind.
> Of course this is highly subjective, if most people have an opinion
> that Object.bind() should be explicitly used, then you may reject this
> patch :)
You've got a typo:
+ var listener = listener;
My opinion wrt this patch is: if you want to bind some 'this', it may be preferably (and it's faster) to pass it pre-bound because If not, with this patch, there's going to be zillions of gratuitous "if (scope)" executed daily, and that's going to worsen the global warming very much :-)
--
Jorge.
> Yes,
> I'm sorry about calling it 'scope', I realised that after you pointed
> it out. I know what scope is, but apparently I was thinking of
> something else while writing the patch. I've changed it to 'context'
But the object you'd pass in there is not -and there's no way for it to be- a 'context', you're passing the object to be bound to 'this', which is just a small bit of the context.
--
Jorge.
--
You received this message because you are subscribed to the Google Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com.
To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.
It should be about a 3 line patch, though:
-process.EventEmitter.prototype.addListener = function (type, listener) {
+process.EventEmitter.prototype.addListener = function (type, listener, thisp) {
if ('function' !== typeof listener) {
throw new Error('addListener only takes instances of Function');
}
if (!this._events) this._events = {};
+ thisp = thisp || this;
+ listener = listener.bind(thisp);
+
But it should be benched thoroughly to see if there are any
performance implications. That is VERY hot code. Even a 1-2%
slowdown there is probably unacceptable.
--i
> Yes, fn.bind() is standard, but it's also standard to pass a thisp
> argument to Array#map/reduce/forEach. I don't see any conceptual
> friction there.
>
> It should be about a 3 line patch, though:
>
> -process.EventEmitter.prototype.addListener = function (type, listener) {
> +process.EventEmitter.prototype.addListener = function (type, listener, thisp) {
> if ('function' !== typeof listener) {
> throw new Error('addListener only takes instances of Function');
> }
>
> if (!this._events) this._events = {};
>
> + thisp = thisp || this;
> + listener = listener.bind(thisp);
> +
>
> But it should be benched thoroughly to see if there are any
> performance implications. That is VERY hot code. Even a 1-2%
> slowdown there is probably unacceptable.
Even in two lines:
listener = listener.bind(thisp || this);
Glad to see you name it 'thisp' :-)
--
Jorge.
--
Right. If *calling* a bound function is slower than calling a
non-bound function, then it could slow down the emit-path. That's
what I'm saying.
It may be faster to not bind the function at all, but instead to keep
track of the thisp until call-time, since fn.apply(this, arguments) is
often required anyhow, so that would become an unnecessary scope
correction when an event is emitted with more than 2 arguments.
--i
http://github.com/isaacs/node-bench/blob/master/examples/bind.js
--i
The real test is seeing the effect on node's functional test benchmarks.
--i
Best Fabian
For example:
var sys = require('sys')
process.on('exit', function() { this.debug("EXITED THIS IS SYS"); }, sys);
Well, sure, that example is silly. But what about something more like:
process.on("exit", obj.logExit, obj)
Still yeah, not worth doing. Just use Function#bind if necessary.
--i
Thanks a lot for all the feedback, especially considering the
efficiency constraints and so on.
In retrospect, perhaps this patch is unnecessary, and as Fabian points
out, qooxdoo handles it more elegantly using the registry concept. So
yes, this shouldn't go in.
On a related note, how much does the need to keep around environment
contexts when javascript closure's are used so frequently in node and
refer to so many bound variables outside of the function's lexical
scope affect the memory and time benchmarks. What I mean is that how
slow is a 'normal' function compared to a closure referring to
external entities.
Nikhil
> --
> You received this message because you are subscribed to the Google Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com.
> To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.
>
>
--
> Closures are a little slower, but not by much. On my local machine a function referencing a variable in a closure takes 1/6th longer to execute:
>
> http://github.com/elarkin/nodeJS-closures-vs-non-closured-functions/blob/master/closure_tests.js
>
> Essentially, the more levels of scoping that you have to look through to find your variable, the more expensive it is to access.
Exactly. Except for the global context which seems to be an optimized case.
Here's some actual numbers, on a Mac:
1938 ms 9.69 ns 103.20 Mhz ctxGlobal
3848 ms 19.24 ns 51.98 Mhz ctx9
3424 ms 17.12 ns 58.41 Mhz ctx8
3286 ms 16.43 ns 60.86 Mhz ctx7
2920 ms 14.60 ns 68.49 Mhz ctx6
2458 ms 12.29 ns 81.37 Mhz ctx5
2117 ms 10.59 ns 94.47 Mhz ctx4
1861 ms 9.30 ns 107.47 Mhz ctx3
1627 ms 8.13 ns 122.93 Mhz ctx2
1497 ms 7.49 ns 133.60 Mhz ctx1
1440 ms 7.20 ns 138.89 Mhz ctx0
global.ctxGlobal= "ctxGlobal";
var ctx9= "ctx9";
(function () {
var ctx8= "ctx8";
(function () {
var ctx7= "ctx7";
(function () {
var ctx6= "ctx6";
(function () {
var ctx5= "ctx5";
(function () {
var ctx4= "ctx4";
(function () {
var ctx3= "ctx3";
(function () {
var ctx2= "ctx2";
(function () {
var ctx1= "ctx1";
(function () {
var ctx0= "ctx0";
get.ctx0= function () { return ctx0 };
get.ctx1= function () { return ctx1 };
get.ctx2= function () { return ctx2 };
get.ctx3= function () { return ctx3 };
get.ctx4= function () { return ctx4 };
get.ctx5= function () { return ctx5 };
get.ctx6= function () { return ctx6 };
get.ctx7= function () { return ctx7 };
get.ctx8= function () { return ctx8 };
get.ctx9= function () { return ctx9 };
get.ctxGlobal= function () { return ctxGlobal };
})();
})();
})();
})();
})();
})();
})();
})();
})();
--
Jorge.