Proposal for improving bind() + adding "unbind()" support

36 views
Skip to first unread message

Robert Kieffer

unread,
Mar 10, 2009, 12:41:22 PM3/10/09
to Prototype & script.aculo.us
As I suspect is common with many Prototype shops, over here at
Zenbe.com, we're making HEAVY use of Function#bind(). We've been
using a custom version of the bind() method for a while now, and I've
recently added a couple enhancements that I'd like to get people's
opinions on - especially that of the Prototype team.

I've attached a commented version of our implementation below (we run
this after importing the Prototype library, so it just overwrites the
Prototype code). The motivation for this implementation is as
follows:

- bind() is so ubiquitous for us that performance is a top priority.
We're willing to have a bit more code here to make things run faster.
In particular, 99% of our use of bind() is simply to bind a function
to an object, without binding any arguments (e.g. "...bind
(someObject)"). So special-casing that situation makes sense.

- Stepping through bound functions is painful because you constantly
have to step-into/out-of the $A method. It's possible to improve
performance *and* make stepping through code easier by inlining the
code that sets up the args array.

- Inspecting functions created by bind() (e.g. "someFunction.toString
()") is never helpful. Provide a way to "unbind" a function to get at
the original method.

Aside from the obvious increase in code size (which isn't *that* much
when the code is compacted - adds ~100-150 bytes), are there any
disadvantages/weaknesses to this implementation? Would it make sense
to migrate some or all of these changes into the Prototype codebase?

=======================
Object.extend(Function.prototype, {
bind: function(object) {
var __method = this, __f;
// Special case: No args or object being bound - just return
this function
if (arguments.length < 2 && arguments[0] === undefined) return
this;

if (arguments.length == 1) {
// Special case: No args are being bound - don't bother
creating a new
// args structure
__f = function() {
return __method.apply(object, arguments);
};
} else {
// General case: Basically do what the native Prototype
implementation
// does, but be as as efficient as possible, and make it
trivial to
// step through in a debugger.
var args = $A(arguments), l = args.length-1;
args.shift();
__f = function() {
// Set up the args array. Note that we recycle the args
array,
// rather than creating a new one. Also do all this on ONE
line so
// it only takes one click to step over in a debugger.
var ll = arguments.length; args.length = l + ll; while
(ll--) args[l+ll] = arguments[ll];
return __method.apply(object, args);
};
}

// Finally, keep a reference to the original function so we can
// unbind() (below) as needed.
__f._boundFunction = this;
return __f;
},

unbind: function() {
var orig = this;
while (orig._boundFunction) orig = orig._boundFunction;
return orig;
}
});

Robert Kieffer

unread,
Mar 10, 2009, 1:42:53 PM3/10/09
to Prototype & script.aculo.us
*dooh* sorry - shoulda just stuck that code in a Pastie, like so:
http://pastie.org/412930

FWIW, I just collected some stats on our code to determine what %'age
of cases are binding arguments .vs. just binding an object. It looks
like it's ~98.5% (2170 of 2200 calls to bind pass a single object
argument, with no additional arguments to bind.)

Richard Quadling

unread,
Mar 10, 2009, 1:48:30 PM3/10/09
to prototype-s...@googlegroups.com
2009/3/10 Robert Kieffer <bro...@gmail.com>:
Are you able to provide any sort of stats comparing the amount of time
with and without your patch to bind()?

--
-----
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"

T.J. Crowder

unread,
Mar 10, 2009, 1:49:58 PM3/10/09
to Prototype & script.aculo.us
Hi,

On the face of it, I like the thought of skipping the arguments prep
in the no-args case, but have you done any profiling to be sure that
it really has a measurable benefit? Same question on inlining the per-
call arguments concatenation... Without a solid, measurable benefit,
I'd avoid the additional complexity. You can deal with the debugger
thing like this:

Replace Prototype's original:

return __method.apply(object, args.concat($A(arguments)));

with:

var newargs = args.concat($A(arguments));
return __method.apply(object, newargs);

Re: unbind. That version repeatedly unbinds until it finds a non-
bound function, which means it's not the reciprocal of bind. E.g.:

x = foo.bind(obj, 1);
y = x.bind(obj, 2);
y.unbind() === x ?
// -> false, the unbind returns foo, not x

From the name, I'd expect unbind to be the reciprocal of bind. Since
it's mostly a debug function, it may be worth just using a different
name.

FWIW,
--
T.J. Crowder
tj / crowder software / com
Independent Software Engineer, consulting services available

kangax

unread,
Mar 10, 2009, 2:18:13 PM3/10/09
to Prototype & script.aculo.us
On Mar 10, 12:41 pm, Robert Kieffer <bro...@gmail.com> wrote:
[...]
> - bind() is so ubiquitous for us that performance is a top priority.

Your version is still slower than it could be ; )
`$A`, `shift`, `apply` - all of those slow things down for no good
reason.
Take a look at http://prototype.lighthouseapp.com/projects/8886/tickets/215-optimize-bind-bindaseventlistener#ticket-215-9

[...]

--
kangax

Tobie Langel

unread,
Mar 10, 2009, 2:19:34 PM3/10/09
to Prototype & script.aculo.us
Robert, have you looked at the implementation in trunk ?

I rewrote most of it not long ago after serious benchmarking and
taking into consideration smart(er) memory management.

This kind of discussion belongs ont the core mailing list BTW, to make
sure no one misses it!

Best,

Tobie

Ryan Gahl

unread,
Mar 10, 2009, 2:44:41 PM3/10/09
to prototype-s...@googlegroups.com
I won't speak to your .bind() mods as I've not reviewed them, but in general, for the majority case of simply binding the context of a function to an object in order to fix the "this" keyword within the function, it is ALWAYS just easier and MUCH MUCH faster to "early bind" to a variable outside the function, and use that variable within the closure instead of "this". Then you have zero performance or memory issues, and debugging is unhindered by the convoluted chain of methods that results from overuse of .bind().

E.g., instead of:

var someHandlerFunction = function(blah) {
this.doSomething(blah);
}.bind(this);


Do this... (always):

var me = this;
var someHandlerFunction = function(blah) {
me.doSomething(blah);
};

I do agree there are just times when .bind() is called for, but I am fairly certain the majority of people WAY overuse it.

When in a class where I would otherwise be doing a bunch of binding, I just create the "var me = this;" variable at the top of whatever scope I'm currently in.

Ryan Gahl
CEO
Nth Penguin, LLC
http://www.nthpenguin.com
--
Inquire: 1-920-574-2218
Blog: http://www.someElement.com
LinkedIn Profile: http://www.linkedin.com/in/ryangahl

T.J. Crowder

unread,
Mar 10, 2009, 3:27:51 PM3/10/09
to Prototype & script.aculo.us
> I do agree there are just times when .bind() is called for, but I am fairly
> certain the majority of people WAY overuse it.

+1 to that, where you're defining the function inline. Where you're
not, while this may be faster:

var self = this;
handler = function() {
self.realHandler();
}

...bind is (for me) much clearer:

handler = realHandler.bind(this);

But again, yeah, when you're defining the function inline _anyway_,
take advantage of the native closure stuff.

-- T.J. :-)

On Mar 10, 6:44 pm, Ryan Gahl <ryan.g...@gmail.com> wrote:
> I won't speak to your .bind() mods as I've not reviewed them, but in
> general, for the majority case of simply binding the context of a function
> to an object in order to fix the "this" keyword within the function, it is
> ALWAYS just easier and MUCH MUCH faster to "early bind" to a variable
> outside the function, and use that variable within the closure instead of
> "this". Then you have zero performance or memory issues, and debugging is
> unhindered by the convoluted chain of methods that results from overuse of
> .bind().
>
> E.g., instead of:
>
> var someHandlerFunction = function(blah) {
> this.doSomething(blah);
>
> }.bind(this);
>
> Do this... (always):
>
> var me = this;
> var someHandlerFunction = function(blah) {
> me.doSomething(blah);
>
> };
>
> I do agree there are just times when .bind() is called for, but I am fairly
> certain the majority of people WAY overuse it.
>
> When in a class where I would otherwise be doing a bunch of binding, I just
> create the "var me = this;" variable at the top of whatever scope I'm
> currently in.
>
> Ryan Gahl
> CEO
> Nth Penguin, LLChttp://www.nthpenguin.com

Tobie Langel

unread,
Mar 10, 2009, 5:50:58 PM3/10/09
to Prototype & script.aculo.us
Function.prototype.bind() is making it's way into ES 3.1. IT will be
interesting to see it's performance once it's native.

kangax

unread,
Mar 10, 2009, 5:53:37 PM3/10/09
to Prototype & script.aculo.us
On Mar 10, 3:27 pm, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> > I do agree there are just times when .bind() is called for, but I am fairly
> > certain the majority of people WAY overuse it.
>
> +1 to that, where you're defining the function inline.  Where you're
> not, while this may be faster:
>
>     var self = this;
>     handler = function() {
>         self.realHandler();
>     }
>
> ...bind is (for me) much clearer:
>
>     handler = realHandler.bind(this);
>
> But again, yeah, when you're defining the function inline _anyway_,
> take advantage of the native closure stuff.

I absolutely agree that not using `bind` with local function works
well and works fast. I also agree that `bind` is overused. What I want
to stress, though, is that when binding a non-local function, `bind`
actually *eliminates an extra closure* that would otherwise be
implicitly created. You either save on memory, or on runtime
performance:

1) Bound event handler is augmented and is slower than original one
(at runtime), as it has to go through function call, `apply`
invocation and even worse - `$A` normalization, etc. (in older
versions of prototype)

this.element.observe('click', this.onClick.bind(this));


2) Non-bound event handler is faster at run time, as it accesses
`onClick` from the proper object directly, but presumably consumes
more memory (due to extra closure, created during function expression
evaluation)

var self = this;
this.element.observe('click', function(){ self.onClick() });

[...]

--
kangax

Ryan Gahl

unread,
Mar 10, 2009, 6:12:01 PM3/10/09
to prototype-s...@googlegroups.com
Kangax...


2) Non-bound event handler is faster at run time, as it accesses
`onClick` from the proper object directly, but presumably consumes
more memory (due to extra closure, created during function expression
evaluation) 

There is no "extra" closure because .bind() creates a closure anyway (because just calling .apply would execute the function right away);

bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
 }

So in all cases, it's less overhead to just not use .bind(). It's purely a convenience method.
 

kangax

unread,
Mar 10, 2009, 8:39:26 PM3/10/09
to Prototype & script.aculo.us
On Mar 10, 6:12 pm, Ryan Gahl <ryan.g...@gmail.com> wrote:
> Kangax...
>
> 2) Non-bound event handler is faster at run time, as it accesses
>
> > `onClick` from the proper object directly, but presumably consumes
> > more memory (due to extra closure, created during function expression
> > evaluation)
>
> There is no "extra" closure because .bind() creates a closure anyway
> (because just calling .apply would execute the function right away);

Good point, Ryan. A function is created in both cases and in both
cases it always carries a closure; After all, it needs to keep a
`context` somewhere : )

I guess what I wanted to say is that a function created with
Prototype's `bind` (internally) has a predictable closure - the one
that's got formed when a function was declared. And it is declared by
Prototype, inside a very "slim" scope (self-executing function,
surrounding `Function.prototype` extensions). That scope has no heavy
objects, no host objects (!) and so when bind internally creates a
function, that function closure consumes a relatively small amount of
memory.

Now, what happens if you `bind` manually (as in my second example) and
you're in the middle of some method with bunch of host objects, maybe
some large objects, etc.? What if that method is deeply nested
(although that's probably an indication of a bad design :)). Such
closure can obviously consume quite a lot, as all that stuff is being
closed over in some event handler that you're binding.

Don't forget that having "inline" function is not always feasible. In
such cases, using `bind` can be safer.

[...]

--
kangax

Ryan Gahl

unread,
Mar 10, 2009, 9:39:53 PM3/10/09
to prototype-s...@googlegroups.com
I guess what I wanted to say is that a function created with
Prototype's `bind` (internally) has a predictable closure - the one
that's got formed when a function was declared. And it is declared by
Prototype, inside a very "slim" scope (self-executing function,
surrounding `Function.prototype` extensions). That scope has no heavy
objects, no host objects (!) and so when bind internally creates a
function, that function closure consumes a relatively small amount of
memory.

 
That has me thinking. Do the closures really consume as much additional memory as the entire host scope's execution context, plus it's own, or just the added pointers to the variables it actually closes over (i.e. uses within it). I.e., are the interpreters smart enough to create the new execution context in any sort of optimized manner?

If not, then wouldn't it just not matter? If it duplicates the entire host context's memory footprint, then would that not lead all the way up to the main (window) context, presumably? Therefore, your statement of .bind()'s closure being "thin" isn't necessarily true, as it would derive from a function chain relative to where .bind() actually gets called anyway, ultimately closing over the entire global namespace (as do all functions). Also, does it really consume additional memory for that closed over context, or simply pass it in by reference, thus it being truly shared memory. Again, I would have to believe it's not a O(log n) operation... that would truly result in major memory consumption. My GUESS is, it's about a horse a piece (6 one way, half a dozen the other).

Interesting. I wonder if there are tools around to profile a javascript context tree and see what's going on with this.

At any rate... I used to be "bind happy", as I think most people are, but now implement a pattern that precludes having to use it almost ever. Really the only time I employ it these days is if I get lazy. The modified module pattern I described in that other thread really does away with the need to use it (i.e. create functions within the context in which they are needed, or pass the instance in)... I've become a bit more "functional" with my js these days.

Also, RE: your comment about deeply nested function definitions being an indicator of poor design... it's often a good technique to actually _save_ on resources (lazy definitions), wherein the memory for the function is not set aside until it's actually needed (assuming you use "var foo = function() {};" instead of "function foo() {}", as the latter results in memory allocation immediately with some interpreters). So it _can_ be good design.

T.J. Crowder

unread,
Mar 11, 2009, 5:07:27 AM3/11/09
to Prototype & script.aculo.us
> That has me thinking. Do the closures really consume as much additional
> memory as the entire host scope's execution context, plus it's own, or just
> the added pointers to the variables it actually closes over (i.e. uses
> within it).

According to the spec, every execution context has a "variable
object", and the vars and parameters related to that context are
properties of that object. (The "variable object" for the global
scope is the global object -- window, in our case -- but there's no
way to directly reference the ones created for each function.) This
variable object is put at the top of the scope chain in effect for
that context, which is effectively a linked list of the variable
objects of each containing context. When you create a new function
inside a context, the new function gets its own variable object linked
at the top of the containing context's scope chain. So not a copy of
the containing context, and not pointers to individual vars that
actually get used, but a pointer to the variable object and associated
scope chain.

The memory issue comes into it because the closure keeps that entire
scope chain alive where (absent any closures) it would be eligible for
cleanup when the function exited. The nice thing about bind, aside
from convenience, is that as kangax pointed out the variable object
and scope chain are quite thin. You can happily bind without worrying
(much) about what you're keeping live.

So, scenario:

function init() {
var container;

container = $('container');
hookUpHideables(container);
container.show();

function hookUpHideables(p) {
p.select('.hideable').invoke('observe', 'click', function(evt)
{
this.hide();
});
}
}

Perhaps the author was thinking: "I don't need hookUpHideables()
except when init runs, and init() only runs once, so I'll let it get
reclaimed after init() ends." hookUpHideables() doesn't do anything
with the 'container' var, nor does the anonymous closure it creates.
But they both keep 'container' alive nevertheless, indirectly via the
scope chain. It doesn't matter for hookUpHideables() since no
reference to it survives the return of init(), and so it's eligible
for cleanup when init() returns. But the anonymous closures survive,
and so the entire scope chain does, including the reference to the
host object inside 'container'. (You can break the reference to the
host object by clearing 'container' before exiting init(), but the
scope chain lives on regardless.)

Although some optimizations are presumably possible at the
implementation level, I'd have to re-read the spec to see whether they
would violate it, and in any case exec() and such make life very
difficult for the implementers in terms of figuring out what may get
referenced at runtime.

If we recast our example to use a named function for the handler:

function init() {
var container;

container = $('container');
hookUpHideables(container);
container.show();

function hookUpHideables(p) {
p.select('.hideable').invoke('observe', 'click',
hideableClick);
}
}

function hideableClick(evt) {
this.hide();
}

...no closures have outstanding references at the end of init(), and
the chain is eligible for GC.

FWIW,
--
T.J. Crowder
tj / crowder software / com
Independent Software Engineer, consulting services available


> Nth Penguin, LLChttp://www.nthpenguin.com

Robert Kieffer

unread,
Mar 11, 2009, 11:29:28 AM3/11/09
to Prototype & script.aculo.us
[Wow, wasn't expecting such a barrage of (great) responses! (I get
this list in digest mode, so find myself scrambling to catch up
here.)]

Regarding performance stats: I've just posted some test data over on
my blog...

http://www.broofa.com/blog/2009/03/prototype-bind-performance

Basically, it looks like my proposed implementation is 2-3X faster
than Prototype's current solution (stable or trunk). I'm pretty sure
the improvement comes from minimizing the number of function calls
required (by inlining the marshalling of the args array).

@tobie: sorry, my tests don't show much improvement in the trunk
version performance. Again, the problem is that it's making several
function calls, and those are relatively expensive compared to the
other operations

@ryan: I see your point about inlining the bind pattern, and I do on
occasion do that, however I've always thought much as kangax does: bind
() provides a consistent, minimal closure for a function, whereas
inlining you get a much more cluttered closure. (i.e. it seems like
bind() is less susceptible to memory leaks. But maybe I'm wrong
there? (Note, too, that "self" may not be such a good name for your
local reference to 'this'. https://developer.mozilla.org/en/DOM/window.self
. I usually do "var me = this;" instead.)

@T.J. Crowder - re: unbind() behavior. Yeah, perhaps a different name
would be more appropriate, but I have a hard time thinking of a
legitimate use for unbinging other than to debug something. The 99%
use case for this (I believe) is to do something like,
"myCallback.unbind().toString()" to see what the original method
implementation looks like so you can tell where a callback or event
handler originally came from. At least, that was my rational for the
name/behavior in my example.

@kangax, re: use of $A, shift, apply - Sure, there's room for other
performance tweaks, but what I care most about is the performance of
invoking a bound function, not in the construction of a bound
function. This is why the function that I return in my implementation
inlines everything except the one apply() call. I suspect the code
you refer to (at the bottom of bug #215) would benefit from the
strategy I use to recycle the args array and marshal the bound args
and passed in arguments together as part of the inline code.

@Prototype Team: Any suggested next steps with this? (If any?)

Thanks for all the great feedback.

Ryan Gahl

unread,
Mar 11, 2009, 11:46:05 AM3/11/09
to prototype-s...@googlegroups.com
[Wow, wasn't expecting such a barrage of (great) responses!  (I get
this list in digest mode, so find myself scrambling to catch up
here.)]

That tends to happen here :)

Rob, those are some VERY impressive numbers you put up in that blog post. I absolutely have to avoid the mailing list today as I'm in a very crunchy crunch time on a project right now... but needless to say, I'll definitely be throwing your implementation into our framework to see what gains we can get (in those places we still have .bind()'s peppered around).


Robert Kieffer

unread,
Mar 11, 2009, 11:54:23 AM3/11/09
to Prototype & script.aculo.us
*Darn* 'Just noticed a bug in my test suite. I've just rejiggered the
tests and updated the blog post. The results are more varied/
intersting than before, but still show significant improvements.
better than the current prototype implementation.

Anyone care to explain the FF behavior when passing just "(obj)" .vs.
passing "(obj, 1, 2)"?

Ryan Gahl

unread,
Mar 11, 2009, 12:00:30 PM3/11/09
to prototype-s...@googlegroups.com
If we recast our example to use a named function for the handler:

function init() {
   var container;

   container = $('container');
   hookUpHideables(container);
   container.show();

   function hookUpHideables(p) {
       p.select('.hideable').invoke('observe', 'click',
hideableClick);
   }
}

function hideableClick(evt) {
   this.hide();
}

...no closures have outstanding references at the end of init(), and
the chain is eligible for GC.

 That's true, but your example does not use .bind(), therefore I don't really see how it pertains to the discussion. When you .bind(), you are passing the the function to bind, along with it's context and all that goes with that... so I really can't see how it's any better than inlining, except just on a pure convenience level. Keep in mind we're talking about cases where you need to "correct" the scope of the bound function so that "this" is your class's object instance. Your example here is a completely different scenario where the developer doesn't care that "this" is the element itself.

Ryan Gahl

unread,
Mar 11, 2009, 12:18:17 PM3/11/09
to prototype-s...@googlegroups.com
What I meant to say was, you didn't compare the first example of inlining to another example using bind. I get that in general you were just illustrating the closure behaviors...

T.J. Crowder

unread,
Mar 11, 2009, 1:13:09 PM3/11/09
to Prototype & script.aculo.us
Hi,

> That's true, but your example does not use .bind(), therefore I don't
> really see how it pertains to the discussion.

I was addressing your question (the one I quoted), not the bind
discussion.

> When you .bind(), you are
> passing the the function to bind, along with it's context and all that goes
> with that...

Right. As I said, if you're doing an inline function *anyway*, it can
make sense to go direct rather than use bind -- you're already
creating the closure over the current context, why compound things
unnecessarily with bind's (admittedly slim) context? (Go back to my
reply to your first post in this thread.) But where you're using a
function declared *elsewhere* and your only purpose for an inline
function is setting 'this', that's where bind() makes more sense --
because you don't create a new closure over the current execution
context, which may be heavy; instead, you create a new closure over
bind()'s context, which is quite slim.

An example may help:

function outer() {
var x, y, z;

function nested() {
var q, r, s;

function moreNested() {
var a, b, c;
var self = this;

new Ajax.Request(url, {
onSuccess: function() {
self.handler()
}
}
}
}
}

With that structure, we're creating a closure over a deeply-nested
scope chain and preserving a, b, c, q, r, s, x, y, z, and self until
that request completes -- all just to provide handler() with its
'this' reference.

Whereas:

function outer() {
var x, y, z;

function nested() {
var q, r, s;

function moreNested() {
var a, b, c;

new Ajax.Request(url, {
onSuccess: this.handler.bind(this)
}
}
}
}

Here, we're *not* preserving a, b, c, q, r, s, x, y, and z, because
there's no closure. They can be GC'd as soon as the function returns,
before the request completes. We are (instead) preserving a fairly
slim context around bind().

Using bind with a function that's *already* inline for other reasons
is just wasteful. But with functions defined elsewhere, it can
provide efficiency in addition to syntactic clarity.
--
T.J. Crowder
tj / crowder software / com
Independent Software Engineer, consulting services available

Tobie Langel

unread,
Mar 11, 2009, 1:30:50 PM3/11/09
to Prototype & script.aculo.us
Your results look impressive indeed.

However, I'd like to ponder code source readability and
maintainability with performance (which was the main goal of the
recent changes).

Could you maybe benchmark this:

function bind(context) {
var __method = this, args;
if (arguments.length < 2) {
if (Object.isUndefined(context)) return this;
return function() {
return __method.apply(context, arguments);
}
}
args = slice.call(arguments, 1)
return function() {
var a = merge(args, arguments);
return __method.apply(context, a);
}
}

Thoughts and suggestions from other core members welcomed.

Best,

Tobie

Tobie Langel

unread,
Mar 11, 2009, 2:13:14 PM3/11/09
to Prototype & script.aculo.us
Actually, Robert, I took a closer look at your implementation.

And I figured out why it's so much faster.

You're relying on a single array of partially applied arguments whose
length you reset rather than cloning the array each time.

This actually turns out to be perfectly spec-compliant and avoids
significant memory consumption (one arg array instantiation upon
binding instead of one per call to the bound function).

I must admit this looks both very appealing... and scary at the same
time.

Thoughts anyone ?

Best,

Tobie

Ryan Gahl

unread,
Mar 11, 2009, 2:25:55 PM3/11/09
to prototype-s...@googlegroups.com
Has that been tested with deferral?

Robert Kieffer

unread,
Mar 11, 2009, 2:38:39 PM3/11/09
to Prototype & script.aculo.us
@Tobie: "[*Evil Cackle*]... You just now noticed that, did you?"

Yeah, it's a nice little trick. And, yes, it does look a little
fishy, but I couldn't think of any reason why it wouldn't work.

I've updated the blog post to include your implementation in the
tests. (BTW, please note that if you'd like to experiment with the
tests, you need only save the HTML source of the test page to a file
on your local disk and it should work just fine. You can hack on the
tests from there.)

I certainly understand the desire to have readable, maintainable
code. My opinion in this particular case is that bind() is such a
universal operation, and the behavior so well defined, and the
implementation so compact, that sacrificing a little readability for
the sake of optimizing performance may be justified. That said,
you're variation above addresses 98.5% of the important use cases. :-)

I would like to see the _boundFunction reference included in whatever
solution you settle on. While not essential, I believe it will allow
developers to provide useful debugging capabilities if so desired,
even if you decide not to include the unbind() method I suggested.
I'm perfectly happy to add my own extensions for stuff like that, but
having to override base implementations, as we're currently doing, has
always felt a little hackish. So having that reference built into the
native bind() implementation would be nice.

Tobie Langel

unread,
Mar 11, 2009, 7:19:28 PM3/11/09
to Prototype & script.aculo.us


On Mar 11, 7:38 pm, Robert Kieffer <bro...@gmail.com> wrote:
> Yeah, it's a nice little trick.  And, yes, it does look a little
> fishy, but I couldn't think of any reason why it wouldn't work.

For it not to work you would need the two following conditions:

1. Function.prototype.apply passing it's second argument directly to
the function object instead of using it to repopulate a new arguments
object (as per specs), and
2. the length property of array object to loose it's magical behaviour
(which, when set, deletes arguments >= to it).

Such an implementation would have a lot more issues than breaking your
implementation of Function.prototype.bind.

I'm therefore completely in favor of rewriting the whole of
function.js taking this into account.

kangax

unread,
Mar 12, 2009, 2:07:10 AM3/12/09
to Prototype & script.aculo.us
On Mar 11, 11:29 am, Robert Kieffer <bro...@gmail.com> wrote:
[...]
> @kangax, re: use of $A, shift, apply - Sure, there's room for other
> performance tweaks, but what I care most about is the performance of
> invoking a bound function, not in the construction of a bound
> function.  This is why the function that I return in my implementation

Of course runtime performance is the key here. That's why the patch in
#215 forks 4 times (twice at load time) based on whether partial
application needs to be performed. In my experience, most of the time,
bind is used *without* partial application (which was the main reason
I wrote the patch). When invoking bind without arguments, a much
faster version is returned. That faster version in its turn *also*
forks twice. Why? Because we know that `fn.apply(context, arguments)`
is slower that `fn.call(context)` (at least in Firefox, not sure about
other clients). That's why we use latter version when
`arguments.length` (of bound function) is 0. This exact (partialless)
optimization is what I was aiming for. I added my implementation to
your test right after Tobie's one:

var bind3 = (function(){
var _slice = Array.prototype.slice;
return function(context) {
var fn = this,
args = _slice.call(arguments, 1);
if (args.length) {
return function() {
return arguments.length
? fn.apply(context, args.concat(_slice.call(arguments)))
: fn.apply(context, args);
}
}
return function() {
return arguments.length
? fn.apply(context, arguments)
: fn.call(context);
};
}
})();

then called it like so: - `testBindFunction('kangax', bind3);`

The results are: http://tinyurl.com/bgbj5k

You can see that `bind3` outperforms every other opponent by a great
margin. Second time I ran the test, your version outperformed mine,
when partially applying (the one that's - "obj, 1, 2"). I would want
you to test these too and see which results you are getting. As far as
I can see, you have a good demonstration here of how slow `concat` is
and how manual concatenation can speed things up. It's a nice
optimization of a partial application case, but as you can see, we can
do better in other (more common?) cases.

When Tobie was rewriting functional extensions, I proposed using
approach similar to `bind3` [1], but for whatever reason it never
happened : )

> inlines everything except the one apply() call.  I suspect the code
> you refer to (at the bottom of bug #215) would benefit from the
> strategy I use to recycle the args array and marshal the bound args
> and passed in arguments together as part of the inline code.

[...]

[1] http://github.com/sstephenson/prototype/commit/d55932fc8c60847406789fa57c998c6ec6c46385#comments

--
kangax

Tobie Langel

unread,
Mar 12, 2009, 4:06:55 AM3/12/09
to Prototype & script.aculo.us

> When Tobie was rewriting functional extensions, I proposed using
> approach similar to `bind3` [1], but for whatever reason it never
> happened : )

Probably because no one ever wrote a patch for it ;)

T.J. Crowder

unread,
Mar 12, 2009, 6:16:50 AM3/12/09
to Prototype & script.aculo.us
And the magic length behavior is also spec'd (Section 15.4):
"...whenever the length property is changed, every property whose name
is an array index whose value is not smaller than the new length is
automatically deleted." So an implementation where that isn't true is
non-compliant.

It seems to me the spec on the first behavior is a bit less clear
(Section 15.3.4.3): "If argArray is either an array or an arguments
object, the function is passed the (ToUint32(argArray.length))
arguments argArray[0], argArray[1], ..., argArray[ToUint32
(argArray.length)–1]". That doesn't necessarily completely rule out
actually using the passed-in array to accomplish that, but I can't
*imagine* an implementation doing it (talk about dodgy practice). If
none of the current implementations do (which is easily checked; mod
the array in the called function and look for the effect in the
caller), it doesn't seem like anything to worry about.

Sounds good to me. Nice one, Robert!
--
T.J. Crowder
tj / crowder software / com
Independent Software Engineer, consulting services available


Tobie Langel

unread,
Mar 12, 2009, 6:52:01 AM3/12/09
to Prototype & script.aculo.us
> It seems to me the spec on the first behavior is a bit less clear
> (Section 15.3.4.3):  "If argArray is either an array or an arguments
> object, the function is passed the (ToUint32(argArray.length))
> arguments argArray[0], argArray[1], ..., argArray[ToUint32
> (argArray.length)–1]".  That doesn't necessarily completely rule out
> actually using the passed-in array to accomplish that, but I can't
> *imagine* an implementation doing it (talk about dodgy practice).  If
> none of the current implementations do (which is easily checked; mod
> the array in the called function and look for the effect in the
> caller), it doesn't seem like anything to worry about.

That's specified more clearly in the Es 3.1 draft, actually. But it
still does imply that a FormalParameterList is built using the array
items.

Also, if it wasn't the case, you would get extremely weird behaviour
like the following:

var myArray = [1, 2, 3, 4];
function foo(a, b, c, d) {
a = b = c = d = 0;
// or: arguments[0] = arguments[1] ... = 0;
}
foo.apply(null, myArray);
myArray;
>>> [0, 0, 0, 0]

Clearly not an option.

Robert Kieffer

unread,
Mar 12, 2009, 10:04:14 AM3/12/09
to Prototype & script.aculo.us
@Kangax: That's definitely a nice improvement! I've updated my post to
include your method as well as an "Improved" method that adds my trick
for reusing the args array. The "improved" version is a little slower
when you call the bound function w/out args, but is significantly
faster (on most platforms) when you do pass args. Since I'd say it's
a tossup as to how often a bound function will/won't be passed
arguments this seems like a good tradeoff to make.

T.J. Crowder

unread,
Mar 13, 2009, 3:08:50 AM3/13/09
to Prototype & script.aculo.us
> That's specified more clearly in the Es 3.1 draft, actually.

I was quoting the 3.1 draft from November (which had no substantive
changes from the 262 standard); do you have a link for a newer one?

And agreed, it would very weird indeed -- weird and, from an
engineering standpoint, JPW (Just Plain Wrong). But easily tested
(your code is exactly what I had in mind) just to make sure none of
the current implementations is weird and JPW.

-- T.J. :-)

T.J. Crowder

unread,
Mar 13, 2009, 3:12:59 AM3/13/09
to Prototype & script.aculo.us
> ...I'd say it's
> a tossup as to how often a bound function will/won't be passed
> arguments....

Hmmmm. That would be worth checking. I almost never use bind() with
arguments other than the context; in fact, I don't recall the last
time I did.
--
T.J. Crowder
tj / crowder software / com
Independent Software Engineer, consulting services available

Robert Kieffer

unread,
Mar 13, 2009, 5:36:15 PM3/13/09
to Prototype & script.aculo.us
On Mar 13, 12:12 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> > ...I'd say it's
> > a tossup as to how often a bound function will/won't be passed
> > arguments....
>
> Hmmmm.  That would be worth checking.  I almost never use bind() with
> arguments other than the context; in fact, I don't recall the last
> time I did.

But when you call a function returned by bind(), how often are you
passing arguments? That's the case I was referring to. It's that
case where my variant of the "Kangax" technique performs better. In
other words ...

// Yes, it's rare to pass extra arguments to bind, like this:
var someFunc = foo.bind(obj, 1, 2)

// But passing arguments to the bound function, like this, is fairly
common:
someFunc(3,4)

It's this latter case where having the args array cached provides
significant performance benefit, which I would argue is worth doing,
even if it slightly lessens the performance of "someFunc()" with no
arguments. That's the tradeoff we're talking about.

Tobie Langel

unread,
Mar 13, 2009, 7:33:11 PM3/13/09
to Prototype & script.aculo.us
> It's this latter case where having the args array cached provides
> significant performance benefit, which I would argue is worth doing,
> even if it slightly lessens the performance of "someFunc()" with no
> arguments.  That's the tradeoff we're talking about.

I don't think that tradeoff is necessary, actually.

Tobie Langel

unread,
Mar 13, 2009, 7:44:24 PM3/13/09
to Prototype & script.aculo.us


On Mar 13, 8:08 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> I was quoting the 3.1 draft from November (which had no substantive
> changes from the 262 standard); do you have a link for a newer one?

T.J.: You'll find the drafts here:
http://wiki.ecmascript.org/doku.php?id=es3.1:es3.1_proposal_working_draft

T.J. Crowder

unread,
Mar 14, 2009, 3:11:48 AM3/14/09
to Prototype & script.aculo.us
@Tobie: Thanks! And wow, the February language is _much_ more
explicit.

@Richard:

> But when you call a function returned by bind(), how often are you
> passing arguments?

Ah, okay. And the answer is, of course, nearly always.

-- T.J.
> T.J.: You'll find the drafts here:http://wiki.ecmascript.org/doku.php?id=es3.1:es3.1_proposal_working_d...
Message has been deleted

Robert Kieffer

unread,
Mar 14, 2009, 9:04:50 AM3/14/09
to Prototype & script.aculo.us
... and it's probably worth pointing, again, to my updated performance
data on this page:
http://www.broofa.com/blog/2009/03/prototype-bind-performance/

Robert Kieffer

unread,
Mar 14, 2009, 9:15:06 AM3/14/09
to Prototype & script.aculo.us
Are you saying you don't think we should make this tradeoff
(i.e. we should use Kangax's code) or that you believe we can get
the best of both worlds?

If the latter, I'm not sure how you manage that. We're either using
Kangax's code for the inner-most function or the "Improved" code. (As
a reminder to readers, both implementations are available at the
bottom of the source on this page: http://www.broofa.com/Tools/JSLitmus/tests/PrototypeBind.html
). Here are the relevant snippets:

Kangax:
function() {
return arguments.length
? fn.apply(context, args.concat(_slice.call(arguments)))
: fn.apply(context, args);
}

Improved:
function() {
var ll = arguments.length;
args.length = l + ll; while (ll--) args[l+ll] = arguments[ll];
return fn.apply(context, args);
};

Where argumens.length is zero, the Improved code has *slightly* more
overhead. But in the case of arguments > 0, the Improved code reuses
the args array instead of making the _slice.call(), which appears to
be more performant in most cases.

Ergo, it's a tradeoff of some sort - pick your implementation - and I
feel the Improved variant provides more desirable performance
characteristics. It provides the best performance for the slowest case
("the weakest link"). So for most projects I think it will provide
better real-world performance.

kangax

unread,
Mar 14, 2009, 11:24:23 AM3/14/09
to Prototype & script.aculo.us
Why not combine two?

...
function() {
if (arguments.length) {
var ll = arguments.length;
args.length = l + ll;
while (ll--) {
args[l+ll] = arguments[ll];
}
}
return fn.apply(context, args);
}
...

On a side note, I am pretty sure that *most* of the projects do *not*
do partial application. This means that second branch (the one that
uses your optimization) is rarely entered.

To make it clearer, let's mark 4 possible outcomes:

var slice = Array.prototype.slice;

function bind(context) {
var fn = this;
// "plain" version, no partial application
if (arguments.length == 1) {
return function() {
return arguments.length
// 1
? fn.apply(context, arguments)
// 2
: fn.call(context)
}
}
// partial application
var args = _slice.call(arguments, 1);
return function() {
return arguments.length
// 3
? fn.apply(context, args.concat(slice.call(arguments)))
// 4
: fn.apply(context, args)
}
}

When is #1 function used? When there was no partial, but arguments
were passed to bound function:

function foo(){};
var bound = foo.bind({});
bound('blah');

When is #2 function used? When there was no partial and arguments were
not passed to bound function:

function foo(){};
var bound = foo.bind({});
bound();

When is #3 function used? When there was partial and arguments were
passed to bound function. This is your optimization branch.

function foo(){};
var bound = foo.bind({}, 'bar');
bound('baz');

And finally, when is function #4 used? When there was partial and
arguments were not passed to bound function.

function foo(){};
var bound = foo.bind({}, 'bar');
bound();

In my opinion, first and second versions are the ones that are used
more often (but I would want to hear other folks). These 2 versions
never use arrays' concatenation (since there are no "partial"
arguments to "concat" with in the first place).


--
kangax

Tobie Langel

unread,
Mar 14, 2009, 11:43:41 AM3/14/09
to Prototype & script.aculo.us
I'd like to see how we can combine all of this elegantly with
bindAsEventListener and curry.

Event if the cost for this is an extra function call.

kangax

unread,
Mar 14, 2009, 2:10:28 PM3/14/09
to Prototype & script.aculo.us
On Mar 14, 11:43 am, Tobie Langel <tobie.lan...@gmail.com> wrote:
> I'd like to see how we can combine all of this elegantly with
> bindAsEventListener and curry.

I personally don't understand the need for `bindAsEventListener` at
all. It's the most misunderstood method in Prototype. Its scope of use
is so narrow that it makes sense to deprecate it.

To explain:

`bindAsEventListener` guarantees that an event object is being passed
as a first argument to an event handler. The problem is that every
single use of `bindAsEventListener` that I've seen is used with
`observe`. When `observe` uses `attachEvent` (in MSHTML DOM) it
already does pass event object as a first argument to event handler
(which makes `event || window.event` in `bindAsEventListener`
redundant). `bind` suffices most of the time, except when a partial
application is used (or, of course, if you're not using `observe` in
the first place, but an intrinsic event attribute).

Another problem is that `bindAsEventListener` is almost *never* used
with partial application (look at Scriptaculous, for example, or
practically any other snippet on the web).

What this all means is that these 2 expressions are functionally
identical (considering that they are called from within the same
execution context):

myElement.observe('click', onClick.bind(this));
myElement.observe('click', onClick.bindAsEventListener(this));

And there's absolutely no need to use the latter one.

myElement.observe('click', onClick.bindAsEventListener(this, 'foo',
'bar'));

- on the other hand, ensures that an event handler, when called, will
have an `event` object as a first argument (i.e. "foo" and "bar" will
be second and third arguments, rather than first and second). This is
exactly what's misunderstood about this method.

[...]

--
kangax

Tobie Langel

unread,
Mar 14, 2009, 8:00:25 PM3/14/09
to Prototype & script.aculo.us

Right. And that warrants clear(er) documentation and not deprecation,
as there is no other way to handle partial application in event
handlers.

T.J. Crowder

unread,
Mar 15, 2009, 8:26:32 AM3/15/09
to Prototype & script.aculo.us
Here's a thought: bindAsEventListener() is basically bind() where a
certain number of leading args are expected at call-time and the rest
are provided at bind-time. (Unless you use it to create DOM0-style
handlers on IE; Don't Do That, this is 2009.) So perhaps the symbol
bindAsEventListener can be deprecated -- people *really* get the wrong
idea, thinking they have to use it for all event listeners -- and the
functionality replaced with a generalized routine that binds and
partially applies with a leading gap. Just for illustration purposes,
here's a *completely* untested and un-optimized version based on
1.6.0.3's bindAsEventListener:

bindWithGap: function(context, gap) {
var __method = this, args = $A(arguments).slice(2);
return function() {
var newargs = $A(arguments);
newargs.length = gap;
newargs.concat(args);
return __method.apply(context, newargs);
}
},

(Again, that hasn't been improved with any of the goodness discussed
so far in this thread.) So this code:

$('foo').observe('click', this.spiffyHandler.bindAsEventListener(this,
'spiffy', 'args'));

becomes

$('foo').observe('click', this.spiffyHandler.bindWithGap(this, 1,
'spiffy', 'args'));

The deprecation API doc entry for bindAsEventListener() would explain
this stuff and reference the use of bind() or, in highly specific
situations, bindWithGap (and point DOM0 folks at Event#observe /
Element#observe, or show them how to create their own like-for-like
bindDOM() function).

-- T.J. :-)

Robert Kieffer

unread,
Mar 15, 2009, 9:06:59 AM3/15/09
to Prototype & script.aculo.us
On Mar 14, 8:24 am, kangax <kan...@gmail.com> wrote:
> Why not combine two?
>
> ...
> function() {
> if (arguments.length) {
> var ll = arguments.length;
> args.length = l + ll;
> while (ll--) {
> args[l+ll] = arguments[ll];
> }
> }
> return fn.apply(context, args);}
> }

This implementation won't work. "args.length" must be set for each
call. Failure to do so will result in bugs like this (since args is
shared across method calls):

foo = someFunc.bind(1,2)
foo(3) // calls someFunc(1,2,3)
foo() // also calls someFunc(1,2,3)! (args unchanged from before)

Thus, you have to move the "args.length=" assignment above the if
block:

> function() {
> var ll = arguments.length;
> args.length = l + ll;
> if (ll) {
> while (ll--) {
> args[l+ll] = arguments[ll];
> }
> }
> return fn.apply(context, args);}
> }

... and since "if (ll)" is redundant with "while (ll--)", you can get
rid of it:

> function() {
> var ll = arguments.length;
> args.length = l + ll;
> while (ll--) args[l+ll] = arguments[ll];
> return fn.apply(context, args);}
> }

... which, is the implementation I proposed in my previous post.

Regarding your point about the 4 possible outcomes. We're not
disputing the implementation for cases 1 or 2 - it's cases #3 and #4
that are what we care about, both of which end up having to run thru
the above code. Given this, my claim about this being a tradeoff
still stands. Reusing the 'args' array is faster if arguments are
passed, but is slower if they aren't, because you have to take the
time to do "args.length=..." in either case. My performance tests
show this, and I still think it's a good tradeoff to make.

Regarding the viability of "bindAsEventListener", I see that as a
separate discussion. The kind of performance tweaks we're talking
about just don't matter that much where bindAsEventListener is used
(since it's unlikely event listener functions will be called more than
a few dozen times per second.) That said, I do agree that it is a bit
of a wart on the Prototype API.

On the more general topic of binding arguments, Prototype has gone
down a bit of a bad path with all of this. I've never seen a real
need for this support in the bind() method. It's just as easy to bind
values via closure context as it is to do using bind():

var a = 1, b = 2;
var foo = function() {
// reference something with a & b
}.bind(obj);

This code would clean up nicely if Prototype deprecated support for
argument binding. Not that that's gonna happen. I'm just saying. :-)

Tobie Langel

unread,
Mar 15, 2009, 10:35:23 AM3/15/09
to Prototype & script.aculo.us
We've discussed deprecating bindAsEventListener about half a dozen
times already, and it just doesn't make sense, for the reasons
expressed above. So there's no point in discussing this further. I
suggest this be clarified in the documentation.

Regarding partial application in Function.prototype.bind, that's been
part of Prototype for a very long time and is part of the ES 3.1
specs, so that will stay in there too.

Robert, your solution using a single argument array per bound function
and resetting it's length on each bound method call is a huge
improvement over what we had before and has to go in imho.

I'd like to see how we can make the source code cleaner and use the
benefits of that technique for currying, etc.

The difficulty of abstracting your solution comes from the need for
the bound function to keep a reference to the original length of the
array so a to reset it before passing it to the original function.

I suspect that the cleanest solution might be to keep a generic array
updating method and reset the original before calling it.

Thoughts and implementation ideas welcomed.

Best,

Tobie

Ryan Gahl

unread,
Mar 15, 2009, 10:36:03 AM3/15/09
to prototype-s...@googlegroups.com
What this all means is that these 2 expressions are functionally
identical (considering that they are called from within the same
execution context):

myElement.observe('click', onClick.bind(this));
myElement.observe('click', onClick.bindAsEventListener(
this));

FWIW, this was not always true in prototype, which is likely where the confusion comes from. .bindAsEventListener, once upon a time, _was_ required with .observe() - back before elements returned from $() did not get .observe() attached to them (when "Event.observe(el, evtName, func);" was the only way to use .observe);

I agree, of course, that it is hardly ever needed now, but the fact that you identified a case where it _is_ needed (or desirable), kind of points towards keeping it in the API. It's not really doing any harm.

Ryan Gahl

unread,
Mar 15, 2009, 10:52:58 AM3/15/09
to prototype-s...@googlegroups.com
Actually, now that I think about this more, the real reason it was (and still is) needed, was that in IE6, that event object had to be pulled from window.event and then passed into the bound function, otherwise it was (still is for IE6 support) on the developer to do that part (if (!evt) evt = window.event;)

Ryan Gahl

unread,
Mar 15, 2009, 10:54:46 AM3/15/09
to prototype-s...@googlegroups.com
So, (sorry not to capture all this in a single post)...

To re-iterate, Kangax, you _should_ be using .bindAsEventListener in your first case if you want to guarantee backwards X-browser support.

kangax

unread,
Mar 15, 2009, 11:03:22 AM3/15/09
to Prototype & script.aculo.us
Understood.

>
> Regarding your point about the 4 possible outcomes. We're not
> disputing the implementation for cases 1 or 2 - it's cases #3 and #4
> that are what we care about, both of which end up having to run thru
> the above code. Given this, my claim about this being a tradeoff
> still stands. Reusing the 'args' array is faster if arguments are
> passed, but is slower if they aren't, because you have to take the
> time to do "args.length=..." in either case. My performance tests
> show this, and I still think it's a good tradeoff to make.

Yes. As I said before, this is a good optimization of partial
application (cases #3 and #4). I like it and I don't have problems
with it being in a Prototype. All I'm saying is that optimizing cases
#3, 4 is not as important as optimizing cases #1, 2, as these are the
most widely used ones. Of course, it doesn't make your optimization
any less useful for a particular use case.

Tobie, are you OK with these changes?

>
> Regarding the viability of "bindAsEventListener", I see that as a
> separate discussion. The kind of performance tweaks we're talking

Yes, we need a new thread for that.

> about just don't matter that much where bindAsEventListener is used
> (since it's unlikely event listener functions will be called more than
> a few dozen times per second.) That said, I do agree that it is a bit

Actually, frequent event listeners (mousemove, mouseover, mouseout,
scroll, resize, etc.) are called quite often. I never use Prototype's
bind on them, for example, as it slows things down noticeably. It's
just that `bindAsEventListener` is practically useless, since you
rarely want to curry arguments over event listeners.

> of a wart on the Prototype API.
>
> On the more general topic of binding arguments, Prototype has gone
> down a bit of a bad path with all of this. I've never seen a real
> need for this support in the bind() method. It's just as easy to bind
> values via closure context as it is to do using bind():

We already discussed the issues of binding earlier in this thread : )

>
> var a = 1, b = 2;
> var foo = function() {
> // reference something with a & b
> }.bind(obj);

Now imagine `foo` is declared non-locally (in a different execution
context)

>
> This code would clean up nicely if Prototype deprecated support for
> argument binding. Not that that's gonna happen. I'm just saying. :-)

This is very unlikely. ES3.1 `Function.prototype.bind` does partial
application. When browsers implement `bind` natively, it will be easy
to delegate Prototype's bind to native one (or replace it altogether)

--
kangax

kangax

unread,
Mar 15, 2009, 11:09:49 AM3/15/09
to Prototype & script.aculo.us
On Mar 15, 10:35 am, Tobie Langel <tobie.lan...@gmail.com> wrote:
[...]
> The difficulty of abstracting your solution comes from the need for
> the bound function to keep a reference to the original length of the
> array so a to reset it before passing it to the original function.
>
> I suspect that the cleanest solution might be to keep a generic array
> updating method and reset the original before calling it.

Isn't that what your "internal" (used with `Function.prototype`
extensions) `update` does?

function update(array, args) {
var arrayLength = array.length, length = args.length;
while (length--) array[arrayLength + length] = args[length];
return array;
}

--
kangax

kangax

unread,
Mar 15, 2009, 11:18:44 AM3/15/09
to Prototype & script.aculo.us
On Mar 15, 10:54 am, Ryan Gahl <ryan.g...@gmail.com> wrote:
> So, (sorry not to capture all this in a single post)...
>
> To re-iterate, Kangax, you _should_ be using .bindAsEventListener in your
> first case if you want to guarantee backwards X-browser support.

What do you mean by "backwards X-browser support"?

Just like I said, IE (including v. 6) supports `attachEvent` which
*does* pass event object as a first argument to event listener. If you
use `attachEvent` (or, more likely, `observe`) you don't need to touch
`window.event`. It is intrinsic event handler that doesn't pass event
as a first argument.

So, to reiterate, you do not need `bindAsEventListener` when using
`observe` and not partially applying event listener : )

[...]

--
kangax

Tobie Langel

unread,
Mar 15, 2009, 12:23:17 PM3/15/09
to Prototype & script.aculo.us
> Isn't that what your "internal" (used with `Function.prototype`
> extensions) `update` does?

Precisely.

Tobie Langel

unread,
Mar 15, 2009, 12:28:51 PM3/15/09
to Prototype & script.aculo.us
> To re-iterate, Kangax, you _should_ be using .bindAsEventListener in your
> first case if you want to guarantee backwards X-browser support.

No. The only use case for bindAsEventListener other than partial
application is for inline event handlers in IE:

var myObj = {
doSomething: function(event) { /*...*/ }
};
<a href="#" onclick="myObj.doSomething.bindAsEventListener(myObj)"></
a>

Ryan Gahl

unread,
Mar 15, 2009, 12:33:26 PM3/15/09
to prototype-s...@googlegroups.com
What do you mean by "backwards X-browser support"?

Come on dude, really? You didn't get what I was saying there? I meant "cross browser support, including older browsers". Even if I was wrong on the details, I thought that phrase was fairly self explanatory... guess not.


 Just like I said, IE (including v. 6) supports `attachEvent` which
*does* pass event object as a first argument to event listener.

Ok, so I stand corrected there. I must have needed it for methods in which I was passing additional arguments (as stated by you guys already).

Even so, +1 for _not_ deprecating... and sorry to keep this side thread going longer than you wanted, Tobie... I'm done now :)

T.J. Crowder

unread,
Mar 15, 2009, 1:21:04 PM3/15/09
to Prototype & script.aculo.us
Tobie:

> ...it just doesn't make sense

You may think that it doesn't, but that's an opinion, not received
wisdom from on high. If you want to keep supporting DOM0 handlers
with it, fine, say that.

> Regarding partial application in Function.prototype.bind, that's been
> part of Prototype for a very long time

Where, exactly, is the partial application that I suggested? It's not
in bind(), it's not in curry()... It's not a big thing, and I
wouldn't do it barring replacing bindAsEventListener with something
more generalized, but take a sec to at least read a suggestion before
slamming it, eh?

-- T.J.

Tobie Langel

unread,
Mar 15, 2009, 1:37:59 PM3/15/09
to Prototype & script.aculo.us
Sorry T.J. if my last comment came across as agressive.

Just to clarify:

> You may think that it doesn't, but that's an opinion, not received
> wisdom from on high.  If you want to keep supporting DOM0 handlers
> with it, fine, say that.

Deprecating a useful API without replacing it by something at least as
good is not a good practice imho. I'd love to have right-currying, but
as you noticed (hence your bindWithGap suggestion), it leads to
complex and unexpected behaviour in a language that supports passing a
variable number of arguments to a function.

> Where, exactly, is the partial application that I suggested?  It's not
> in bind(), it's not in curry()...  It's not a big thing, and I
> wouldn't do it barring replacing bindAsEventListener with something
> more generalized, but take a sec to at least read a suggestion before
> slamming it, eh?

I was answering Robert's suggestion of removing partial application
from Function#bind, not your bindWithGap proposal. :)



T.J. Crowder

unread,
Mar 15, 2009, 2:02:03 PM3/15/09
to Prototype & script.aculo.us
Gotcha, sorry for the misunderstanding.

> Deprecating a useful API without replacing it by something at least as
> good is not a good practice imho.

We do need that separate thread. (There was more to this paragraph,
but the first sentence kinda rendered the rest of it OT!!)

-- T.J.

Robert Kieffer

unread,
Mar 15, 2009, 8:43:22 PM3/15/09
to Prototype & script.aculo.us
I've put together a new set of tests to explore this avenue of thought
a bit further: http://www.broofa.com/Tools/JSLitmus/tests/PrototypeBind2.html
. Very similar to my previous test page, but I've dropped the
approaches that we're not really interested in at this point, and kept
just the two Kangax variants we've discussed. I've added a new "Less
Improved" test in which I use put the code that marshals the args
array into a separate _update() method. (Note that my _update() is
noticeably different from Kangax's, above, since we I had to pass in
the original length of 'array', and also set the array.length)

The new test is called "Less Improved" because it is noticeably less
performant, especially on Firefox where it is only 1/4 as fast. Here
are the results I get for various platforms:
IE/WinXP - http://tinyurl.com/d442g3
Firefox/MacOSX - http://tinyurl.com/db3lga
Safari/MacOSX - http://tinyurl.com/df4tdx
Opera/MacOSX - http://tinyurl.com/ck4a4o
Chrome/WinXP - http://tinyurl.com/ckt535

Since the overhead of the additional function call is a significant
performance hit on Firefox and IE I'll suggest that we _not_ take this
approach? At least, not where performance is a paramount issue (in
bind()). Instead, the code should be kept inline. I know this is a
bit heretical, but I believe performance trumps DRY in this case.

Tobie Langel

unread,
Mar 15, 2009, 8:49:49 PM3/15/09
to Prototype & script.aculo.us
Thanks.

I'd like the refactoring to include all Function.prototype methods,
not just bind, so we have a clean and coherent src code.

Best,

Tobie

Robert Kieffer

unread,
Mar 17, 2009, 10:58:31 AM3/17/09
to Prototype & script.aculo.us
I've just created a ticket that includes a patch to as much of the
function.js stuff as seemed reasonable. I also took the liberty of
including support for a new unwrap() method.

Details here:
http://prototype.lighthouseapp.com/projects/8886-prototype/tickets/599-patchtest-performance-improvements-to-functionjs-unwrap-support
Reply all
Reply to author
Forward
0 new messages