Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

addEvent - The late entry :)

6 views
Skip to first unread message

Aaron Gray

unread,
Jul 16, 2008, 11:17:14 AM7/16/08
to
I jokingly say this is the late entry :)

Okay I have read all the event entry comments from John's Resig's AddEvent
comepition blog :-

http://ejohn.org/projects/flexible-javascript-events/

and put together the following offering for my LGPL'ed library functions :-

function addEvent( el, type, fn, cascade) {
if ( el.addEventListener) {
cascade = cascade || false;
el.addEventListener( type, fn, cascade)
}
else if ( el.attachEvent) {
el[type+fn] = function() {
fn.call( el, window.event);
}
el.attachEvent( 'on'+type, el[type+fn])
}
else
el[ 'on'+type] = fn
}
function removeEvent( el, type, fn, cascade) {
if ( el.removeEventListener) {
cascade = cascade || false;
el.removeEventListener( type, fn, cascade)
}
else if ( el.detachEvent) {
el.detachEvent( 'on'+type, el[type+fn])
el[type+fn] = null; // clear hash and IE memory leak
}
else
el[ 'on'+type] = null
}


Lessons :-

call W3C first to satisfy Opera and for common sence, then MS as this is
usually easy detectable, then legacy.
Based on 'Weisi Su' entry on
http://ejohn.org/projects/flexible-javascript-events/#comment-276560 and
Michael White' suggestion using W3C first for correct operation on Opera.
plus legacy event handling added by me. Which in the end was all very
simular to some code I wrote the previous day and forgot about :)

Added cascade parameter that defaults to bubble on W3C calls.

The full test case can be found here :-

http://www.aarongray.org/Test/JavaScript/EventTest.html

Okay I have tried it on IE6 with and Drip and it does not seem to produce
and memory leaks AFAICS.

Drip IE memory leak detector :-

http://ejohn.org/projects/flexible-javascript-events/

I have tested it on 32Bit Vista IE7.0.6001, Safari 3.1.2, and Opera 9.51; XP
IE6.

Any browser testing appreciated, particularly on older and less well known
ones.

Well thats about it folks...any comments...holes...or suggesttions are most
welcome.

Regards,

Aaron


Aaron Gray

unread,
Jul 16, 2008, 12:28:50 PM7/16/08
to
> Drip IE memory leak detector :-

Sorry that should be :-

http://www.outofhanwell.com/ieleak/index.php?title=Main_Page

Aaron

Aaron Gray

unread,
Jul 19, 2008, 8:09:15 PM7/19/08
to
"Aaron Gray" <ang.u...@gmail.com> wrote in message
news:6e6hnqF...@mid.individual.net...

Here's some slightly more efficient code :-

if ( window.addEventListener) {
var addEvent = function( el, type, fn, cascade) {


cascade = cascade || false;
el.addEventListener( type, fn, cascade)
}

var removeEvent = function( el, type, fn, cascade) {


cascade = cascade || false;
el.removeEventListener( type, fn, cascade)
}
}

else if ( window.attachEvent) {
var addEvent = function( el, type, fn) {


el[type+fn] = function(){
fn.call( el, window.event);
}
el.attachEvent( 'on'+type, el[type+fn])
}

var removeEvent = function( el, type, fn) {


el.detachEvent( 'on'+type, el[type+fn])
el[type+fn] = null; // clear hash and IE memory leak
}
}
else

{
var addEvent = function( el, type, fn, cascade) {


el[ 'on'+type] = fn
}

var removeEvent = function( el, type, fn) {


el[ 'on'+type] = null
}
}

Test case :-

http://www.aarongray.org/Test/JavaScript/EventTest2.html

Aaron

Aaron Gray

unread,
Jul 19, 2008, 8:32:44 PM7/19/08
to
"Aaron Gray" <ang.u...@gmail.com> wrote in message
news:6efe1iF...@mid.individual.net...

legacy case :-

> {
> var addEvent = function( el, type, fn, cascade) {

cascade parameter should not be here

> el[ 'on'+type] = fn
> }
> var removeEvent = function( el, type, fn) {
> el[ 'on'+type] = null
> }
> }

if ( window.addEventListener) {


var addEvent = function( el, type, fn, cascade) {
cascade = cascade || false;
el.addEventListener( type, fn, cascade)
}
var removeEvent = function( el, type, fn, cascade) {
cascade = cascade || false;
el.removeEventListener( type, fn, cascade)
}
}
else if ( window.attachEvent) {
var addEvent = function( el, type, fn) {
el[type+fn] = function(){
fn.call( el, window.event);
}
el.attachEvent( 'on'+type, el[type+fn])
}
var removeEvent = function( el, type, fn) {
el.detachEvent( 'on'+type, el[type+fn])
el[type+fn] = null; // clear hash and IE memory leak
}
}
else
{

var addEvent = function( el, type, fn) {


el[ 'on'+type] = fn
}
var removeEvent = function( el, type, fn) {
el[ 'on'+type] = null
}
}

Correct test case :-

http://www.aarongray.org/Test/JavaScript/EventTest2.html

Aaron

Aaron Gray

unread,
Jul 19, 2008, 9:49:20 PM7/19/08
to
Here's yet another mod :-

if ( window.addEventListener) {
var addEvent = function( el, type, fn, cascade) {
cascade = cascade || false;
el.addEventListener( type, fn, cascade)
}
var removeEvent = function( el, type, fn, cascade) {
cascade = cascade || false;
el.removeEventListener( type, fn, cascade)
}
}
else if ( window.attachEvent) {
var addEvent = function( el, type, fn) {
el[type+fn] = function(){
fn.call( el, window.event);
}
el.attachEvent( 'on'+type, el[type+fn])
}
var removeEvent = function( el, type, fn) {
el.detachEvent( 'on'+type, el[type+fn])
el[type+fn] = null; // clear hash and IE memory leak
}
}
else
{

if ( isIE)


{
var addEvent = function( el, type, fn) {

el[ 'on'+type] = function(){
fn.call( el, window.event);
}
}
}
else
{
var addEvent = function( el, type, fn) {


el[ 'on'+type] = fn
}
}

var removeEvent = function( el, type, fn) {
el[ 'on'+type] = null
}
}

Needs 'isIE' variable setting.

Aaron

Aaron Gray

unread,
Jul 19, 2008, 9:54:32 PM7/19/08
to
"Aaron Gray" <ang.u...@gmail.com> wrote in message
news:6efjt8F...@mid.individual.net...

Here's a test case :-

http://www.aarongray.org/Test/JavaScript/EventTest3.html

Aaron

dhtml

unread,
Jul 19, 2008, 10:00:46 PM7/19/08
to
On Jul 19, 6:49 pm, "Aaron Gray" <ang.use...@gmail.com> wrote:
> Here's yet another mod :-
>

There are some significant differences in attachEvent and
addEventListener.

* w3c DOM Event bugs in Webkit and Safari (and probably a lot in Opera
that I'm not aware of).
* w3c DOM Events bubble as specified, IE events bubble differently,
like legacy events (by "legacy" events, I mean el.onclick = ...)
* different thisArg - attachEvent's this is always window


> Needs 'isIE' variable setting.

Better stick to feature and capability detection.

Garrett

>
> Aaron

dhtml

unread,
Jul 19, 2008, 10:01:18 PM7/19/08
to
On Jul 19, 6:49 pm, "Aaron Gray" <ang.use...@gmail.com> wrote:
> Here's yet another mod :-
>

There are some significant differences in attachEvent and

Thomas 'PointedEars' Lahn

unread,
Jul 20, 2008, 4:37:01 AM7/20/08
to
dhtml wrote:
> On Jul 19, 6:49 pm, "Aaron Gray" <ang.use...@gmail.com> wrote:
>> Here's yet another mod :-
>
> There are some significant differences in attachEvent and
> addEventListener.
>
> * w3c DOM Event bugs in Webkit and Safari (and probably a lot in Opera
> that I'm not aware of).

Could you elaborate on that, please?

> * w3c DOM Events bubble as specified, IE events bubble differently,
> like legacy events (by "legacy" events, I mean el.onclick = ...)

Could you provide a test case that demonstrates the issue, please?

> * different thisArg - attachEvent's this is always window

and

* event listeners *added* with addEventListener() are executed
*in order of addition*;

event listeners *attached* with attachEvent() are executed
*in arbitrary order*

>> Needs 'isIE' variable setting.
>
> Better stick to feature and capability detection.

Full ACK.


PointedEars
--
realism: HTML 4.01 Strict
evangelism: XHTML 1.0 Strict
madness: XHTML 1.1 as application/xhtml+xml
-- Bjoern Hoehrmann

Richard Cornford

unread,
Jul 20, 2008, 8:40:29 AM7/20/08
to
Aaron Gray wrote:
>I jokingly say this is the late entry :)
>
> Okay I have read all the event entry comments from John's Resig's
> AddEvent comepition blog :-
>
> http://ejohn.org/projects/flexible-javascript-events/
>
> and put together the following offering for my LGPL'ed library
> functions :-

Even if you only count posts to this group, there have been so many
variations of this code posted over so many years that the odds are
extremely good that this code is already either someone else's copyright
or public domain. Its only unique feature is the inappropriate use of
"cascade" where most have used 'capturing'.

> function addEvent( el, type, fn, cascade) {
> if ( el.addEventListener) {
> cascade = cascade || false;

Why do this here? You could parenthesise the logical OR expression and
use it as the final argument in the method call, saving the assignment
operation and one scope chain resolution of - cascade -, without making
the result significantly harder to understand.

> el.addEventListener( type, fn, cascade)

But then why provide the - cascade - parameter at all as whatever you do
IE will not process events during a capturing phase because it does not
have one? And the intrinsic event property branch will not capture at
all without explicit calls to - captureEvents - methods (which are not
provided by all (or even most) browsers, and are not available on all
DOM nodes even when provided.

What you have done by providing the - cascade - parameter to the
function is invite programmes using the function to experience radically
different behaviour between browsers, to make themselves aware of those
differences, to test the environment themselves to see which behaviour
is going to apply and to do a great deal of coding to accommodate them.
If they are going to have to do all of that your function, as a method
for attaching the listeners, is almost more trouble than it is worth as
the actual attaching of the listeners is trivial in comparison.

The more sensible alternatives are to provide a full event capturing
emulation for the second and third branches (which had been demonstrated
possible but is a huge bloat for the function) or to specify this method
as 'at target' and 'bubbling' phase only, forget the cascade parameter,
and just use a false boolean literal as the final argument to this
method call.

It has been the norm in cross-browser scripting for event handling to be
'at target' and 'bubbling' phase only precisely because IE has never
provided a capturing phase for events, so most people's experience,
examples and literature will be geared towards that type of event
handling.

> }
> else if ( el.attachEvent) {
> el[type+fn] = function() {

Type-converting the - fn - function into a string and using that as part
of the property name under which a reference to this function is stored
is a staggeringly stupid thing to do.

For a start ECMA 262 specifies the result of the native function -
toString - method as "An implementation dependent representation of the
function", so you have no guarantee about what it may contain at all.

Secondly, there have been bugs in implementation - toString - methods in
the past, which highlights the possibility of bugs in the future. A
specific example was to be found in Opera's javascript prior to version
7. The bug what that all regular expression literals were serialised
as - [object] - by its - toString - method. The result would be that two
functions that differed only in terms of a regular expression literal
would produce identical string representations.

But even if you assume that all function - toString - methods will
return a string representation that is as unique as the function's
source code, and that no bugs exist in the environments in question, the
uniqueness of such a string is nowhere near related to the uniqueness of
the identity of the function object. And it is the identify of the
function object that is important for the - attachEvent - and -
detachEvent - methods.

Consider what happens if the functions being attached represent one of
the many closure based methods of associating a method call with a
particular javascript object instance. A simple example might be:-

function associateObjectWithEvent(obj, methodName){
return (function(ev){
return obj[methodName](ev, this);
});
}

(but any of those clunky 'bind' methods so beloved of library authors
would do just as well as an example). You might very well want to attach
multiple examples of the function returned from that function to the
same event handler of a single element, but they will all serialize as
the same string despite having unique identity. Attach two and then
attempt to remove the first and it will be the second that is removed,
while the first can never then be removed (and that is on IE, the
browser with memory leak issue).

> fn.call( el, window.event);
> }
> el.attachEvent( 'on'+type, el[type+fn])
> }
> else
> el[ 'on'+type] = fn

If this branch is ever good enough it is also always good enough.
Suggesting that the other two branches be removed entirely or that this
branch is not a suitable alternative to them. You invite as much
inconsistency with this as you did with your - cascade - parameter,
except this branch is the one that is least likely to be tested by the
programmer as modern browser will not tend to be entering this branch.
This means that the programmer may get the false impression that what
they are creating is cross-browser when in reality it is very likely to
behave unexpectedly in the event that the code encounters a browser that
will use this branch.

The - addEventListener - and - attachEvent - methods are all about
attaching multiple listeners to a single event on a single element. The
intrinsic event properties only allow the attaching of a single listener
to a single event on a single element. If you want an intrinsic event
property branch it should provide the full emulation of the other
methods and allow multiple factions to be attached. That can be done,
but again bloats the code, and once again brings into question the worth
of having the other two branches as once in place that single approach
would be as viable in all environments (and the absence of branching
would make the outcome much more consistent across browsers).

There is, of course, the question of cancelling default actions and
propagation. Browsers that only support intrinsic event properties may
be expecting default action cancelling to be achieved by returning false
from the function attached to the property, and may not allow the
cancelling of propagation at all. It takes quite a bit of work to
emulate the possibilities offered in the other branches for those
environments, and may mean imposing system-wide restrictions (like
forbidding propagation cancelling) in order to guarantee consistent
handling across browsers.

> }
> function removeEvent( el, type, fn, cascade) {
> if ( el.removeEventListener) {
> cascade = cascade || false;
> el.removeEventListener( type, fn, cascade)
> }
> else if ( el.detachEvent) {
> el.detachEvent( 'on'+type, el[type+fn])
> el[type+fn] = null; // clear hash and IE memory leak
> }
> else
> el[ 'on'+type] = null
> }
>
>
> Lessons :-
>
> call W3C first to satisfy Opera and for common sence,
> then MS as this is usually easy detectable, then legacy.
> Based on 'Weisi Su' entry on
> http://ejohn.org/projects/flexible-javascript-events/#comment-276560
> and Michael White' suggestion using W3C first for correct
> operation on Opera. plus legacy event handling added by me.
> Which in the end was all very simular to some code I wrote
> the previous day and forgot about :)
>
> Added cascade parameter that defaults to bubble on W3C calls.

That was a mistake if you are not going to facilitate (emulate) a
capturing phase on browsers that don't use the W3C events DOM path.

<snip>


> Okay I have tried it on IE6 with and Drip and it does not seem
> to produce and memory leaks AFAICS.

<snip>

Has drip been improved so that it can now detect closure based circular
references? It could not do that last time I looked at it. The
structures you create in the IE branch of this code will always produce
circular chains of references between DOM nodes and javascript objects,
and so you will only not provoke the issue if all listeners attached
with this method are later removed. Arranging that symmetrical handling
of listeners is another burden placed on the programmers using this
method. That is OK so long as they are told up-front that they will have
to address that issue themselves.

<snip>


> Well thats about it folks...any comments...holes...or suggesttions
> are most welcome.

We will see. The biggest fault in your code is the inconsistent
interface it provides. Having the - cascade - parameter when it can only
influence the behaviour of some browsers is a bad idea. The fault of
trying to use the serialised function objects in the - addEvent - branch
is also significant. And the implication of the total, that the desired
outcome is a single general method, is also a mistake.

Recently I have been thinking about how to express what it is about the
attempt to be general that tends to results in code that bloated and
inefficient. Initially it occurred to me that being attempting to be
'general' in browser scripting has at least two dimensions; there are
the number of application contexts that are covered by the outcome, and
there are the number of browsers in which those application contexts are
accommodated.

These two dimensions can be confused so I should illustrate. In the
context here, and application context might be regarded as 'what you
intend doing', e.g.:-

1. You want to trigger some global code as the result of the user
activating (clicking or keyboard triggering) a button. You don't need to
know anything about the element that was activated, just what to do as a
result of its being activated.

2. You want the activation of a link to use methods of a series of
previously instantiated javascript objects to progressively create and
construct a query string that is added to the link's HREF before it is
used for navigation.

The first is very simple and does not require any preservation of -
this - references in attached handlers (as they are irrelevant) and does
not involve any 'binging' of javascript objects to functions. If it is
the most complex requirement of the system being built it can be
accommodated with extremely simple (and so by implication (in javascript
terms) fast) javascript code.

The second implies a need to reference the element activated and so
suggests that normalising the - this - references would be a good idea
(even if not essential (you could extract the element from the event
object's properties), it implies an ability to attach multiple listeners
to a single event and it requires a means of associating the listeners
with distinct javascript object instances.

Any method that accommodates the second will accommodate the first, but
at the cost of being bigger and slower (particularly if it is not going
to constrain the browser support dimension in the process). If a system
being created only needs the first to be accommodated then the code that
could accommodate the latter is over the top for that system.

You code cannot accommodate the latter because the need to bind
functions to javascript object instances would trip up the function
serialisation approach to function object identification. Thus you code
is more general that is needed for the first and not general enough for
the second, in the 'application contexts' dimension. The attempt to be
'general' usually ends up falling short of being truly general in that
dimension because the number of possible permutations can become so
great that they all cannot be accommodated behind a single API without
its becoming so ludicrously and obviously bloated that nobody would
consider using it. (As illustrated by the theoretical truly general
elopement position/dimension reporting method. Nobody has ever coded the
algorithm because the few people who appreciate what is involved realise
that executing 2000+ statements of code just to find out the page
relative x/y coordinates and width/height of an element is a
non-starter).

If the problem of attempting to be general is expressed as having two
dimension it is easy to why trying to extend the range of one of those
dimensions can have a disproportional impact on internal complexity,
bulk and performance. However, I have since realised that the attempt to
be 'general' has at least one more dimension.

There is a dimension of relevant knowledge, understanding and experience
(skills) of the people attempting to use any code that attempts to be
general. You will notice that one of arguments used to promote the
notion of general-purpose javascript libraries is that they allow
individual with relatively few relevant skills to achieve things that
would otherwise be beyond their abilities. And this is a direct
continuation of the copy-and-paste collections that have existed for
many years that (apparently, or superficially) allowed single
functionalities to be achieved by individuals through no more work than
pasting a block of code into an HTML document.

So where your code provides that - cascade - parameter, if properly
documented, a knowledgeable and experienced javascript programmer could
appreciate that if they wanted to use it the onus was on them to
accommodate the outcome. But to make the same API accommodate a wider
range possible 'programmers' that work would have to be moved inside
functions (so that the API's users did not have to face it). But that
would result in bloat, and it would result in a performance fall-off
(less so for W3C events DOM supporting browsers but pretty big for some
of the others).

With the ranges of at least three dimensions being expanded in the
pursuit of being 'general' the impact on internal complexity, bulk and
performance can be expected to be proportional to the cube of measure of
any signal dimension that is to be extended.

Richard.

Richard Cornford

unread,
Jul 20, 2008, 8:57:48 AM7/20/08
to
Aaron Gray wrote:
> Here's yet another mod :-
>
> if ( window.addEventListener) {

The W3C - EventTarget - interface is specified as being implemented by
objects implementing the core DOM Node interface (and in reality are
only reliable on objects implementing Element). The window object is not
a Node and so it is not valid to infer that if a window object has an -
EventTarget - method then all Nodes/Elements will, or vice versa. Opera
7, for example, had an - attachEvent - method on its window objects but
no - addEventListener -, while all of its nodes had - addEventListener -
methods. Thus you are forcing Opera 7 to use the (less efficient)
attachEvent branch having previously asserted that Opera browsers did
not work particularly well with that branch.

> var addEvent = function( el, type, fn, cascade) {
> cascade = cascade || false;
> el.addEventListener( type, fn, cascade)

<snip>


> else
> {
>
> if ( isIE)
> {
> var addEvent = function( el, type, fn) {
> el[ 'on'+type] = function(){
> fn.call( el, window.event);

Are you suggesting here that IE 4 (which is below your ECMAScript 3
minimum for script support) did not call functions assigned to its
intrinsic event properties with the - this - value - being a referee to
the element to which the listener was attached? Or is this an attempt to
normalise the event object for the - fn - functions, and if so why not
do so in the next branch as non-IE browsers that are not DOM standard
may have chosen to only emulate IE in their event handling?

> }
> }
> }
> else
> {
> var addEvent = function( el, type, fn) {
> el[ 'on'+type] = fn
> }
> }

Richard.

Aaron Gray

unread,
Jul 20, 2008, 1:23:17 PM7/20/08
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message
news:g5vcod$65e$1$8300...@news.demon.co.uk...

> Aaron Gray wrote:
>> Here's yet another mod :-
>>
>> if ( window.addEventListener) {
>
> The W3C - EventTarget - interface is specified as being implemented by
> objects implementing the core DOM Node interface (and in reality are only
> reliable on objects implementing Element). The window object is not a Node
> and so it is not valid to infer that if a window object has an -
> EventTarget - method then all Nodes/Elements will, or vice versa. Opera 7,
> for example, had an - attachEvent - method on its window objects but no -
> addEventListener -, while all of its nodes had - addEventListener -
> methods. Thus you are forcing Opera 7 to use the (less efficient)
> attachEvent branch having previously asserted that Opera browsers did not
> work particularly well with that branch.

Okay I have made a mod here then :-

if ( typeof addEventListener != "undefined")
...

This appears to function correctly on IE, FF, Safari, and Opera.

Thanks,

Aaron


Aaron Gray

unread,
Jul 20, 2008, 1:25:01 PM7/20/08
to

"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message
news:g5vcod$65e$1$8300...@news.demon.co.uk...
> Aaron Gray wrote:
>> Here's yet another mod :-
>> var addEvent = function( el, type, fn, cascade) {
>> cascade = cascade || false;
>> el.addEventListener( type, fn, cascade)
> <snip>
>> else
>> {
>>
>> if ( isIE)
>> {
>> var addEvent = function( el, type, fn) {
>> el[ 'on'+type] = function(){
>> fn.call( el, window.event);
>
> Are you suggesting here that IE 4 (which is below your ECMAScript 3
> minimum for script support) did not call functions assigned to its
> intrinsic event properties with the - this - value - being a referee to
> the element to which the listener was attached? Or is this an attempt to
> normalise the event object for the - fn - functions, and if so why not do
> so in the next branch as non-IE browsers that are not DOM standard may
> have chosen to only emulate IE in their event handling?

So I can just drop that behaviour, and revert back to the previous version
for legacy support.

Aaron

Peter Michaux

unread,
Jul 20, 2008, 1:37:06 PM7/20/08
to
On Jul 16, 8:17 am, "Aaron Gray" <ang.use...@gmail.com> wrote:
> I jokingly say this is the late entry :)
>
> Okay I have read all the event entry comments from John's Resig's AddEvent
> comepition blog :-
>
> http://ejohn.org/projects/flexible-javascript-events/


I'd recommend not taking anyone's word as gospel in the JavaScript
world. There are certain programmers like Richard Cornford, Martin
Honnen and Douglas Crockford with track records for being correct that
I default to thinking they are correct and seriously wonder why if I
seem to think they are wrong. Otherwise I consider just about everyone
else wrong by default.


> and put together the following offering for my LGPL'ed library functions :-

Why LGPL? There are JavaScript libraries under the MIT and BSD
licenses which are more liberal than LGPL.

Offering your library under the LGPL means, most importantly, you are
offering your library to the world. I think it is great you are taking
the project of writing a library seriously but if you are going to
release your library publicly you may want to give it a very low
version number like 0.0 or 0.1 so people know you are just starting to
learn the issues of both JavaScript and cross browser coding.


> function addEvent( el, type, fn, cascade) {
> if ( el.addEventListener) {
> cascade = cascade || false;
> el.addEventListener( type, fn, cascade)
> }
> else if ( el.attachEvent) {
> el[type+fn] = function() {
> fn.call( el, window.event);
> }
> el.attachEvent( 'on'+type, el[type+fn])
> }
> else
> el[ 'on'+type] = fn}

Richard Cornford has pointed out some major problems in the code
above, the asymmetrical use of "cascade", the implicit toString of fn,
the non-equivalent fallback use of element properties like "onclick",
the creation of circular memory leaks in IE6 with no way to clean up.

What about the preventDefault problem in Safari versions something
like 2.0.2 and less? If an event listener for click or double click
events is added to addEventListener then preventDefault doesn't work?

What if the browser does support JavaScript but doesn't support any of
these types of event attachment APIs?

What if Internet Explorer 9 changes attachEvent to be an ActiveX
object so that the type converting test for el.attachEvent throws an
error even though a call to el.attachEvent() will work?

Why test which event model is the appropriate one for the browser each
time your addEvent is called when testing just the first time is
sufficient?

There are seemingly zillions of very fine details related to both
JavaScript and the browser environment that make writing your addEvent
function alone worthy of a very long investigation before declaring it
production ready for use on the general web and better than the
alternative libraries available for download today.


> function removeEvent( el, type, fn, cascade) {
> if ( el.removeEventListener) {
> cascade = cascade || false;
> el.removeEventListener( type, fn, cascade)
> }
> else if ( el.detachEvent) {
> el.detachEvent( 'on'+type, el[type+fn])
> el[type+fn] = null; // clear hash and IE memory leak
> }
> else
> el[ 'on'+type] = null
>
> }
>
> Lessons :-
>
> call W3C first to satisfy Opera and for common sence, then MS as this is
> usually easy detectable, then legacy.

I think it would be a good idea to detect support for legacy before
using it. Better yet, follow Richard's advice that if legacy is good
enough it is always good enough or don't include legacy at all.

> Based on 'Weisi Su' entry onhttp://ejohn.org/projects/flexible-javascript-events/#comment-276560and
> Michael White' suggestion using W3C first for correct operation on Opera.
> plus legacy event handling added by me. Which in the end was all very
> simular to some code I wrote the previous day and forgot about :)
>
> Added cascade parameter that defaults to bubble on W3C calls.
>
> The full test case can be found here :-
>
> http://www.aarongray.org/Test/JavaScript/EventTest.html
>
> Okay I have tried it on IE6 with and Drip and it does not seem to produce
> and memory leaks AFAICS.
>
> Drip IE memory leak detector :-
>
> http://ejohn.org/projects/flexible-javascript-events/
>
> I have tested it on 32Bit Vista IE7.0.6001, Safari 3.1.2, and Opera 9.51; XP
> IE6.

That is an extremely small set of OS/Browser combinations. I tested my
first try at a general purpose library on many

http://forkjavascript.org/welcome/browser_support

I plan on testing on quite a few more for v0.2 which I am writing now.


> Any browser testing appreciated, particularly on older and less well known
> ones.

Based on my experience, you will need to do all the testing yourself
which means having many operating systems available (e.g. Windows XP,
Mac OS X, Linux) and downloading many versions of old browsers. There
are many old versions of IE, O, S, NN, FF available on the web.


> Well thats about it folks...any comments...holes...or suggesttions are most
> welcome.

Specify the exact requirements for your event code. Post them to the
group for criticism.

Slow down. Read the YUI event library, as an example, until you think
you have found all the things that are wrong with it. Code a better
version. Read another event library and repeat the cycle. It is a long
road ahead.

Peter

Richard Cornford

unread,
Jul 20, 2008, 1:50:39 PM7/20/08
to
Aaron Gray wrote:

> Richard Cornford wrote:
>> Aaron Gray wrote:
>>> Here's yet another mod :-
>>> var addEvent = function( el, type, fn, cascade) {
>>> cascade = cascade || false;
>>> el.addEventListener( type, fn, cascade)
>> <snip>
>>> else
>>> {
>>>
>>> if ( isIE)
>>> {
>>> var addEvent = function( el, type, fn) {
>>> el[ 'on'+type] = function(){
>>> fn.call( el, window.event);
>>
>> Are you suggesting here that IE 4 (which is below your ECMAScript
>> 3 minimum for script support) did not call functions assigned to
>> its intrinsic event properties with the - this - value - being a
>> referee to the element to which the listener was attached? Or is
>> this an attempt to normalise the event object for the - fn -
>> functions, and if so why not do so in the next branch as non-IE
>> browsers that are not DOM standard may have chosen to only
>> emulate IE in their event handling?
>
> So I can just drop that behaviour, and revert back to the previous
> version for legacy support.

Again you miss the point. This is a question of providing a consistent
API. Either you normalise the event object and assert that the functions
passed in as listeners will receive a normalised event as their first
argument or you specify that the listeners are themselves responsible
for that aspect of event processing. It does not matter which you
choose, it is just that you should choose one or the other, document it
and implement it consistently.

Incidentally, if you are asked questions here you will get a lot
further, a lot faster, by answering them. Evasion is pointless.

Richard.

Richard Cornford

unread,
Jul 20, 2008, 1:50:43 PM7/20/08
to
Aaron Gray wrote:
> Richard Cornford wrote:
>> Aaron Gray wrote:
>>> Here's yet another mod :-
>>>
>>> if ( window.addEventListener) {
>>
>> The W3C - EventTarget - interface is specified as being implemented
>> by objects implementing the core DOM Node interface (and in reality
>> are only reliable on objects implementing Element). The window
>> object is not a Node and so it is not valid to infer that if a
>> window object has an - EventTarget - method then all Nodes/Elements
>> will, or vice versa. Opera 7, for example, had an - attachEvent -
>> method on its window objects but no - addEventListener -, while
>> all of its nodes had - addEventListener - methods. Thus you are
>> forcing Opera 7 to use the (less efficient) attachEvent branch
>> having previously asserted that Opera browsers did not work
>> particularly well with that branch.
>
> Okay I have made a mod here then :-
>
> if ( typeof addEventListener != "undefined")
> ...
>
> This appears to function correctly on IE, FF, Safari, and Opera.

You have completely missed the point. The test was fine (well, the test
is actually debatable but I would have no problem with it), it is the
inference made from the test that is unfounded.

Richard.

Peter Michaux

unread,
Jul 20, 2008, 1:59:25 PM7/20/08
to
On Jul 20, 1:37 am, Thomas 'PointedEars' Lahn <PointedE...@web.de>
wrote:

[snip]

> * event listeners *added* with addEventListener() are executed
> *in order of addition*;

The spec seems to disagree

"Although all EventListeners on the EventTarget are guaranteed to be
triggered by any event which is received by that EventTarget, no
specification is made as to the order in which they will receive the
event with regards to the other EventListeners on the EventTarget."

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow-basic

[snip]

> Full ACK.

"ACK"?

Emacs, Vim and various other text editors have autocompletion (a.k.a.
skeletons, snippets, etc) where you can type an abbreviation and the
editor expands the abbreviation to that for which it stands. Using
this sort of technology would add no extra burden to you when writing
a post but would make your posts much easier to read for those
unfamiliar with your uncommon English abbreviation. "ACK" does not
appear in my English dictionary, for example.

Peter

Peter Michaux

unread,
Jul 20, 2008, 2:36:48 PM7/20/08
to
On Jul 20, 5:40 am, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

[snip]

> (but any of those clunky 'bind' methods so beloved of library authors
> would do just as well as an example).

Partial application is a common technique in lambda languages and not
looked down upon when used appropriately. The implementation in
JavaScript is not as aesthetic as in some other languages somewhat due
to the "this" issue in JavaScript; however, conceptually the binding
of some parameters to one functions and producing another function
taking no or less parameters is the same. I don't understand why you
would apparently balk at this concept. The use of a "bind" function is
not clunky, in my opinion.

[snip]

> > fn.call( el, window.event);
> > }
> > el.attachEvent( 'on'+type, el[type+fn])
> > }
> > else
> > el[ 'on'+type] = fn
>
> If this branch is ever good enough it is also always good enough.

Functionally yes. I think, in this case, the third branch is
unnecessary even if implemented so all three branches have the same
behavior. If someone did think the third branch was necessary then the
first two branches (using addEventListener or attachEvent) could be
justified as performance boosts. A large portion of the remainder of
your message below is related to keeping code small which is really
just for a performance boost.

> There is, of course, the question of cancelling default actions and
> propagation.

Do you happen to know of a way to detect the Safari versions which do
not honour calls to preventDefault for click or double click events
when the listener was attached using addEventListener? There is a
"legacy" workaround using onclick and ondblclick properties of
elements but these are a bit ugly and have some drawbacks which need
documentation. At this point, since those versions of Safari have been
automatically upgraded, I'd rather put those versions of Safari down
the degradation path as though they didn't have event models at all. I
just don't know how to detect these versions of Safari.

[snip]

> Recently I have been thinking about how to express what it is about the
> attempt to be general that tends to results in code that bloated and
> inefficient.

[snip interesting thoughts]

You an Matt Kruse have faced off many times about this whole "general"
library business. I don't quite see the fuss.

Matt is being a bit extreme by suggesting that the general position
reporting function should be written even though you have stated it
would be 2000+ statements and far too slow to be practical. Your
multiple implementations approach seems more appropriate in this
situation.

In messages like this one, you on the other hand seem to eschew things
"general" (though I don't think you do so 100%). Take, for example,
the scroll reporting code you wrote in the FAQ notes

http://www.jibbering.com/faq/faq_notes/not_browser_detect.html#bdScroll

I consider that level of "multi-browserness" sufficiently "general".
That is, I would be comfortable using this type of code on the
unrestricted web.

When you write that an "attempt to be general that tends to results in
code that bloated and inefficient" I think it is worth defining where
you draw the line between bloated and non-bloated code and efficient
and inefficient code.

If a "general" event library could be written in 10 lines would that
be acceptable to use in cases where it is more general than necessary?
What if it was 20 lines? 50 lines? 200 lines? 10000 lines? The
absolute size of the general code does matter to some extent.

Imagine the 200 line version's only drawback was download time and
initial interpretation, with no other runtime penalties. If that code
was already written, would it be worth using in situations where code
only 50% size could be used given the smaller code is not already
written. Writing 100 lines of event library code is probably not
trivial and require heavy testing. I would use the 200 line version as
it is ready, tested, cacheable, not really a download burden for the
majority of today's network connections (even most mobile networks).

I think that the extreme positions for and against "general" are both
faulty. When general code has acceptable performance, then creating
and maintaining one version is the winner. I think an event library
falls into this category. When the general code is unacceptably slow
then more optimized versions must be written. The position reporting
problem falls into this category.

"Premature optimization is the root of all evil" seems to apply and
suggests the strategy to use general code until there is push back
from some human (possibly the programmer, testers, customers) that the
code really is too slow. Then, and only then, fallback to the multiple
implementations strategy which is, in many regards, an optimization
strategy.

Peter

Aaron Gray

unread,
Jul 20, 2008, 3:30:25 PM7/20/08
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message
news:g5vtth$nsp$1$8300...@news.demon.co.uk...

Sorry I find it hard to follow your arguments.

For the called event handler I was intending/trying to give a constsant
interface.

For the calling API I was trying to give support for setting single events
per element. But also hoping to offer extended functionality of individual
browsers. This may be a bad thing.

Aaron


Aaron Gray

unread,
Jul 20, 2008, 5:30:43 PM7/20/08
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message
news:g5vttk$nss$1$8300...@news.demon.co.uk...

So Opera needs a special case calling attachEvent on windows instead of
addEventListener ?

Aaron


Richard Cornford

unread,
Jul 20, 2008, 6:02:34 PM7/20/08
to
Peter Michaux wrote:

> On Jul 20, 5:40 am, Richard Cornford wrote:
>
> [snip]
>
>> (but any of those clunky 'bind' methods so beloved of library
>> authors would do just as well as an example).
>
> Partial application is a common technique in lambda languages
> and not looked down upon when used appropriately.

True, and it is a very useful facility to have. But it is only very
occasionally a useful facility to have.

> The implementation in JavaScript is not as aesthetic as in some
> other languages somewhat due to the "this" issue in JavaScript;
> however, conceptually the binding of some parameters to one
> functions and producing another function taking no or less
> parameters is the same. I don't understand why you would
> apparently balk at this concept. The use of a "bind" function
> is not clunky, in my opinion.

Take the last version of Prototype.js that I looked at (1.6.0.2).
Internally there are 28 calls to its - bind - method and precisely zero
of those pass more than one argument to the method. The method's code
is:-

| 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 each of those calls to - bind - test the length of arguments and
finds that it is 1 and so calls Object.isUndefined. If the first
argument is not undefined (and in 9 of those calls the argument passed
is - this -, which can never be undefined by detention) it goes on to
call - $A - in order to make an array of the arguments object, it then
renders that array empty by - shift-ing the one argument out of it, and
later it concatenates that empty array onto the array resulting form
another call to - $A -. The availability of a more efficient alternative
seems like a good idea, even if that alternative were only made
available internally. That alternative could be as simple as:-

simpleBind: function(obj) {
var fnc = this;
return (function() {
return fnc.apply(obj, arguments);
});
}

- and still fully satisfy all of the internal uses of bind that appear
in Prototype.js.

But it is the very fact that Prototype.js uses - bind - so much and yet
in using it never exploits its full capabilities that demonstrates that
the need for all of those capabilities is not that great. Even in code
as general as Prototype.js the ratio of common cases that don't need
'outer' arguments to cases that do is at least 28 to 1. It might be
argued that the real need is so infrequent that it should be handled
externally by the code that needs it. Or that a two stage process where
any 'outer' set of arguments is associated with one function object and
then it is that function that is passed into a much simplified - bind -
method, would provide a better interface. Something like:-

simpleBind: function(obj) {
var fnc = this;
return (function() {
return fnc.apply(obj, arguments);
});
}
complexBind:function(obj){
var fnc = this;
var args = $A(arguments);
args.shift();
return (function(){
return fnc.apply(this. args.concat($A(arguments)))
}.simpleBind(obj));
}

(untested) - where the common case enters at one point and the much less
common case enters at another. That uncommon case suffers for its
increased demands but usually, and certainly in Prototype.js's internal
use, the benefit outweighs the losses. And if a particular application
can be seen to employ the (normally) uncommon case extensively it can
employ the original process. Except that it cannot because it is in the
nature of these large-scale internally interdependent general-purpose
libraries that people using them cannot play with the APR safely
(maintenance and upgrade headaches follow form the attempt) and the
libraries themselves cannot change their external API without breaking
existing code that uses them.

So, yes 'clunky'. Certainly far short of being well-thought-out or
elegant. Indeed so far so that their only way out is to petition for a
new - bind - method in the new language versions so that faster native
code can rescue their authors from the consequences of original designs.

> [snip]
>
>> > fn.call( el, window.event);
>> > }
>> > el.attachEvent( 'on'+type, el[type+fn])
>> > }
>> > else
>> > el[ 'on'+type] = fn
>>
>> If this branch is ever good enough it is also always good
>> enough.
>
> Functionally yes. I think, in this case, the third branch is
> unnecessary even if implemented so all three branches have
> the same behavior. If someone did think the third branch was
> necessary then the first two branches (using addEventListener
> or attachEvent) could be justified as performance boosts.

Which assumes that there would be a performance boost. Probably the
calling of event listeners is so fast that measuring a difference would
be difficult. But the extra function call overhead in the -
attachEvent - branch is unlikely to help in that regard.

> A large portion of the remainder of your message below is
> related to keeping code small which is really just for a
> performance boost.

Not just, size and the internal complexity that manifests itself in size
have consequences for understandably and so maintainability.

>> There is, of course, the question of cancelling default
>> actions and propagation.
>
> Do you happen to know of a way to detect the Safari versions
> which do not honour calls to preventDefault for click or
> double click events when the listener was attached using
> addEventListener?

No, it has not yet been an issue for me. But Safari browsers expose many
'odd' properties in their DOMs so I would bet that an object inference
test could be devised even if a direct feature test could not, and the
result would still be fare superior to the UA sniffing that seems to go
on at present.

> There is a "legacy" workaround using onclick and ondblclick
> properties of elements but these are a bit ugly and have some
> drawbacks which need documentation. At this point, since those
> versions of Safari have been automatically upgraded, I'd rather
> put those versions of Safari down the degradation path as though
> they didn't have event models at all. I just don't know how to
> detect these versions of Safari.
>
> [snip]
>
>> Recently I have been thinking about how to express what it is about
>> the
>> attempt to be general that tends to results in code that bloated and
>> inefficient.
>
> [snip interesting thoughts]
>
> You an Matt Kruse have faced off many times about this whole
> "general" library business. I don't quite see the fuss.

Talking (even arguing) about them is better way of testing the veracity
of ideas than not.

> Matt is being a bit extreme by suggesting that the general
> position reporting function should be written

Matt's position has tended to be that someone else (someone other than
him) should write it.

> even though you have stated it would be 2000+ statements and
> far too slow to be practical. Your multiple implementations
> approach seems more appropriate in this situation.
>
> In messages like this one, you on the other hand seem to
> eschew things "general" (though I don't think you do so 100%).
> Take, for example, the scroll reporting code you wrote in the
> FAQ notes
>
> http://www.jibbering.com/faq/faq_notes/not_browser_detect.html#bdScroll
>
> I consider that level of "multi-browserness" sufficiently "general".
> That is, I would be comfortable using this type of code on the
> unrestricted web.

But it is not general. The only dimension in which it is general is the
browser support dimension (though it should not be too bad in the
programmer understanding dimension). It does not even cover the general
case of wanting to find the degree to which a document has been scrolled
because it has no facility for reporting on any document other than the
one containing the SCRIPT element that contained/loaded its code. Add
support for multi-frame object models and you have something else again.

> When you write that an "attempt to be general that tends to results
> in code that bloated and inefficient" I think it is worth defining
> where you draw the line between bloated and non-bloated code and
> efficient and inefficient code.

I am not drawing a line. I am expression the direction of movement that
results form a cause and effect relationship. Understanding the
relationship allows people to draw their own lines at the points which
suite their situations/contexts/applications.

> If a "general" event library could be written in 10 lines would
> that be acceptable to use in cases where it is more general than
> necessary?

Certainly if performance followed size.

> What if it was 20 lines? 50 lines? 200 lines? 10000 lines? The
> absolute size of the general code does matter to some extent.
>
> Imagine the 200 line version's only drawback was download time
> and initial interpretation, with no other runtime penalties.
> If that code was already written, would it be worth using in
> situations where code only 50% size could be used given the
> smaller code is not already written. Writing 100 lines of event
> library code is probably not trivial and require heavy testing.
> I would use the 200 line version as it is ready, tested,
> cacheable, not really a download burden for the majority of
> today's network connections (even most mobile networks).

In that hypothetical situation, I probably would use the code as well.

> I think that the extreme positions for and against "general"
> are both faulty.

You are not seeing the question of how 'general' is "general". An event
library, no matter how large/capable is not 'general' in every sense. It
should have no element retrieve methods, no retrieval using CSS
selectors, no built-in GUI widgets, etc. It is (potentially) a task
specific component, even if it comprehensively addresses its task.

> When general code has acceptable performance, then creating
> and maintaining one version is the winner.

Given the number of dimension to 'general' it has not yet been
demonstrated that truly general code can be created so any "when general
code ..." statement is irrelevant. Certainly if you can get away with a
single implementation of a task-specific component then there is no need
for an alternative.

> I think an event library falls into this category.

Of a component that only needs one good implementation? I doubt that, in
Aaron's code to date we have not even discussed the possibilities opened
up by having multiple frames (where normalising the event to -
window.event - is not necessarily the right thing to do). I would not
like the idea of importing a large-ish single document component into
every individual frame when a slightly larger multi frame system could
do the whole job, or of using that larger multiple frame version in a
single page application.

> When the general code is unacceptably slow then more optimized
> versions must be written. The position reporting
> problem falls into this category.

But the position reporting problem also demonstrates that the fact that
actual applications of javascript (web sites/applications, etc) are
specific can be exploited to facilitate problem solving. That is,
because at least some of the possible permutations of applications will
not be present in any specific application those excluded permutations
do not need to be handled by code that will never see them. It is not
just speed, there is also the possibility of getting to cut the Gordian
knot rather then untying it. There is also an advantage in the speed
with which re-useable code can be created, because you do not have to
spend time writing code for situations that you know will not apply. And
there is also a testing advantage, because excluded permutations do not
need to be tested.

> "Premature optimization is the root of all evil" seems to apply
> and suggests the strategy to use general code until there is push
> back from some human (possibly the programmer, testers, customers)
> that the code really is too slow. Then, and only then, fallback
> to the multiple implementations strategy which is, in many regards,
> an optimization strategy.

It is not just optimisation. It is not solving problems that are
currently not your problems, it is not spending time writing code that
is not needed, and it is not spending time testing code that will not be
executed in the context of its use.

Richard.

Richard Cornford

unread,
Jul 20, 2008, 6:22:25 PM7/20/08
to
Aaron Gray wrote:
> Richard Cornford wrote:
>> Aaron Gray wrote:
>>> Richard Cornford wrote:
<snip>

> For the called event handler I was intending/trying to give a
> constsant interface.

This is the sort of information that should come with code when it is
first posted. It is necessary in order to make an informed judgement
bout whether the code achieves its specification or not.

> For the calling API I was trying to give support for setting single
> events per element. But also hoping to offer extended functionality
> of individual browsers. This may be a bad thing.

It is a bad thing. There is no point putting a layer over the APIs
provided by the browser if the result is precisely as complex to use as
the existing API. Someone wanting to use that extended functionality is
going to want to know whether it is available or not, and once they have
tested for that they have carried out 70-odd% of the work your functions
do internally, and everything they would have needed to do in order to
decide which listener attaching method to use for themselves.

Richard.

Richard Cornford

unread,
Jul 20, 2008, 6:22:31 PM7/20/08
to

Event handing on objects outside of the DOM (which pretty much comes
down to window/frame objects) needs to be handled completely
independently of event handling on elements within the DOM. Any
parallels between the two should be regarded as fortunate coincidences
rather than as being related (or implying a relationship). (And then
being cautious about load and unload events)

Richard.

Thomas 'PointedEars' Lahn

unread,
Jul 20, 2008, 8:07:40 PM7/20/08
to
Peter Michaux wrote:

> Thomas 'PointedEars' Lahn wrote:
>> * event listeners *added* with addEventListener() are executed
>> *in order of addition*;
>
> The spec seems to disagree
>
> "Although all EventListeners on the EventTarget are guaranteed to be
> triggered by any event which is received by that EventTarget, no
> specification is made as to the order in which they will receive the
> event with regards to the other EventListeners on the EventTarget."
>
> http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow-basic

Thanks, I was not aware of that paragraph.

However, implementations so far appear to implement what W3C DOM Level 3
says (currently a Working Draft):

,-<http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-flow>
|
| [...]
|
| Firstly, the implementation must determine the current target. [...]
|
| Secondly, the implementation must determine the current target's candidate
| event listeners. This is the list of all event listeners that have been
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| registered on the current target in their order of registration. Once
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| determined, the candidate event listeners cannot be changed, adding or
| removing listeners does not affect the current target's candidate event
| listeners.
|
| Finally, the implementation must process all candidate event listeners in
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| order and trigger each listener if all the following conditions are met.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| A listener is triggered by invoking the EventListener.handleEvent() method
| or an equivalent binding-specific mechanism.
|
| * The event object's immediate propagation has not been stopped.
| * The listener has been registered for this event phase.
| * The listener has been registered for this event type.

This behavior makes sense to me as `add' would indicate an order, whereas
`attach' does not.

>> Full ACK.
>
> "ACK"?

ACK.

> [...] would make your posts much easier to read for those


> unfamiliar with your uncommon English abbreviation. "ACK" does not
> appear in my English dictionary, for example.

It appears in the Jargon File, FOLDOC (which took it from the former), and
Wikipedia, for example. The word can be easily found in these and millions
of other locations with Google, which you are using, many of them explaining
it as it was meant here:

| Results 1 - 10 of about 15,500,000 for ACK. (0.21 seconds)

You are only *playing* stupid, I hope.


PointedEars
--
Anyone who slaps a 'this page is best viewed with Browser X' label on
a Web page appears to be yearning for the bad old days, before the Web,
when you had very little chance of reading a document written on another
computer, another word processor, or another network. -- Tim Berners-Lee

Peter Michaux

unread,
Jul 20, 2008, 8:09:03 PM7/20/08
to
On Jul 20, 3:02 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> Peter Michaux wrote:
> > On Jul 20, 5:40 am, Richard Cornford wrote:

[snip regarding Prototype.js's implementation and use of "bind"]

I'm a little surprised you know so much about the internals of
Prototype.js. I haven't looked inside that library for more than a few
minutes in the last year. All that $, $A, $H, and my personal
"favorite" $$ are successful repellent. I don't even know what these
functions do exactly and in some case not even partially.

The last time I did look in the library it seemed that the
Prototype.js typists don't seem to care about any sort of modularity
in their code. It is one big lump that cannot be untangled. I am not
surprised that their heavy-duty "bind" is used internally. RobG
pointed out sometime that many functions in Prototype.js start by
calling $ for one of the arguments. Even functions that aren't API
functions do this which is completely pointless.

This example of "bind" does make a good point that the function is
more "general" than needed in the particular case. But there would
only be a need to write a more trimmed down version if the performance
penalty of the current function is causing a problem. I don't want to
make it seem like writing especially inefficient code is ok until
there are customer complaints but there is a balance which needs to be
struck in the interest of saving development dollars.

I happen to use "bind" for partial application in the opposite way.
I'm not concerned with setting "this" but rather with the partial
application of the explicit arguments to a function. I create a
"thunk" in Lisp terminology (a function that takes no arguments.) I'm
probably a little too keen on Lisp-style of programming right now but
it is a pleasure compared to the bothers of "this".

[snip]

> > A large portion of the remainder of your message below is
> > related to keeping code small which is really just for a
> > performance boost.
>
> Not just, size and the internal complexity that manifests itself in size
> have consequences for understandably and so maintainability.

I've thought about the saying "premature optimization is the root of
all evil" in several different ways recently. I believe the original
use was with respect to reducing CPU use and so speeding up the
program. Now this saying can apply to size, speed, maintainability,
generality. None of these should be optimized prematurely as the
others usually suffer. That is the reason I pressed the issues I did
below. Sometimes it seems you are advocating optimizing size, speed at
the cost of maintainability (i.e. multiple versions) and generality.


> >> There is, of course, the question of cancelling default
> >> actions and propagation.
>
> > Do you happen to know of a way to detect the Safari versions
> > which do not honour calls to preventDefault for click or
> > double click events when the listener was attached using
> > addEventListener?
>
> No, it has not yet been an issue for me. But Safari browsers expose many
> 'odd' properties in their DOMs so I would bet that an object inference
> test could be devised even if a direct feature test could not, and the
> result would still be fare superior to the UA sniffing that seems to go
> on at present.

I'd be surprised if there is an object change between versions 2.0.2
and 2.0.3 (if those are the correct numbers, I have to check) where
the bug was fixed. If that one bug was the only thing changed in the
release or if no new objects or properties were added, that would be a
problem for my goal. I suppose I could throw away versions 2.0.3 and
2.0.4 if something noticeable changed in version 2.0.5, for example.
All these versions were automatic upgrades and so none are really in
use now.

[snip]

> > You an Matt Kruse have faced off many times about this whole
> > "general" library business. I don't quite see the fuss.
>
> Talking (even arguing) about them is better way of testing the veracity
> of ideas than not.

Indeed.


> > Matt is being a bit extreme by suggesting that the general
> > position reporting function should be written
>
> Matt's position has tended to be that someone else (someone other than
> him) should write it.

I think there is a bit of that but also I think he wants to read an
acknowledgement that using code that is slightly too general for a
situation is tolerable.


> > even though you have stated it would be 2000+ statements and
> > far too slow to be practical. Your multiple implementations
> > approach seems more appropriate in this situation.
>
> > In messages like this one, you on the other hand seem to
> > eschew things "general" (though I don't think you do so 100%).
> > Take, for example, the scroll reporting code you wrote in the
> > FAQ notes
>
> >http://www.jibbering.com/faq/faq_notes/not_browser_detect.html#bdScroll
>
> > I consider that level of "multi-browserness" sufficiently "general".
> > That is, I would be comfortable using this type of code on the
> > unrestricted web.
>
> But it is not general. The only dimension in which it is general is the
> browser support dimension (though it should not be too bad in the
> programmer understanding dimension). It does not even cover the general
> case of wanting to find the degree to which a document has been scrolled
> because it has no facility for reporting on any document other than the
> one containing the SCRIPT element that contained/loaded its code. Add
> support for multi-frame object models and you have something else again.

Fair enough. I really don't think about multiple frames/windows as I
almost never use them (at not least where this would matter.)


[snip]

> > What if it was 20 lines? 50 lines? 200 lines? 10000 lines? The
> > absolute size of the general code does matter to some extent.
>
> > Imagine the 200 line version's only drawback was download time
> > and initial interpretation, with no other runtime penalties.
> > If that code was already written, would it be worth using in
> > situations where code only 50% size could be used given the
> > smaller code is not already written. Writing 100 lines of event
> > library code is probably not trivial and require heavy testing.
> > I would use the 200 line version as it is ready, tested,
> > cacheable, not really a download burden for the majority of
> > today's network connections (even most mobile networks).
>
> In that hypothetical situation, I probably would use the code as well.

[point A] I will refer to this below.

> > I think that the extreme positions for and against "general"
> > are both faulty.
>
> You are not seeing the question of how 'general' is "general". An event
> library, no matter how large/capable is not 'general' in every sense. It
> should have no element retrieve methods, no retrieval using CSS
> selectors, no built-in GUI widgets, etc.

I don't know how CSS selectors or built in widgets have anything to do
with an event library.


> It is (potentially) a task
> specific component, even if it comprehensively addresses its task.

If it could truly comprehensively do its job then I think that would
mean "general".


> > When general code has acceptable performance, then creating
> > and maintaining one version is the winner.
>
> Given the number of dimension to 'general' it has not yet been
> demonstrated that truly general code can be created so any "when general
> code ..." statement is irrelevant. Certainly if you can get away with a
> single implementation of a task-specific component then there is no need
> for an alternative.

That is a valuable acknowledgement by including "get away with".


> > I think an event library falls into this category.
>
> Of a component that only needs one good implementation? I doubt that, in
> Aaron's code to date we have not even discussed the possibilities opened
> up by having multiple frames (where normalising the event to -
> window.event - is not necessarily the right thing to do). I would not
> like the idea of importing a large-ish single document component into
> every individual frame when a slightly larger multi frame system could
> do the whole job, or of using that larger multiple frame version in a
> single page application.

Given the your acknowledgement at "point a" above, it would seem the
size of "slightly" might play a role.

If the slightly larger multi-frame system was written and there was a
tight deadline, I would use it.

If the single page version was already written and could do the job by
being included in every individual frame then I would use it on a
tight deadline. Caching could be set up with some no-check, far-future
expiration date header so there is no cost to including it in every
page.

This is now going into the "need to know the requirements", which is
your point anyway.

[snip]

> > "Premature optimization is the root of all evil" seems to apply
> > and suggests the strategy to use general code until there is push
> > back from some human (possibly the programmer, testers, customers)
> > that the code really is too slow. Then, and only then, fallback
> > to the multiple implementations strategy which is, in many regards,
> > an optimization strategy.
>
> It is not just optimisation. It is not solving problems that are
> currently not your problems, it is not spending time writing code that
> is not needed, and it is not spending time testing code that will not be
> executed in the context of its use.

Nicely written; however, if the code is already written, tested, and
available for download from the web, but solves a problem more general
then problem at hand, where does one draw the line and say it is *too*
general? There must be some observable that indicates this situation.
For example "the client is complaining downloads are too long" or "the
client is complaining the application is not responsive enough" or
"the other programmers are spending too much time on maintenance" or
the genuine expectation that one of these sorts of problems will
arise.

The majority of JavaScirpt programmers (almost all less "uptight" than
us) seem to agree that there is a problem that can be specified and
solved with a library that can be shared on the web to the benefits of
others. Perhaps each project you work on is so radically different,
and perhaps quite advanced, that your given problems are not solved
well by the feature sets of these prepackaged libraries (leaving the
quality of these libraries aside for a moment.)

For my own use, I developed a library and slapped the label "Fork"
onto it. I think it solves roughly the same problem as the base
utilities of YUI!, Prototype.js, jQuery, Dojo, etc. This vaguely
specified problem is what people call the "general" problem and the
use of "general" in this case is incorrect. Your use of "general" is
better. The same problem occurs with the distinction between multi-
browser and cross-browser. Libraries claiming to be cross-browser are
usually just multi-browser for some very small value of "multi". I
will endeavor to be more careful about my use of the word "general".

What would be great is if there was a word for this vaguely specified
problem that so many libraries roughly solve because these libraries,
though more general than necessary in many cases, are acceptable
solutions for what I dare to say are "most" browser scripting
problems. Many of the regulars past and present of this group have
maintained their own solutions to roughly the same problem. Matt
Kruse, Thomas Lahn (I'll probably get crucified for including his name
in this list), David Mark, and I have all shown code we have written
that we can cart around to various projects. The code may be too
general in some cases but the cost of extra download time is
acceptable (perhaps unnoticeable) and the paying customer would rather
we just get on with development than trimming the code down to a
minimal implementation.

The vaguely specified problem is *something* *approximately* like the
following. This is probably closer to the problem that the c.l.js
regulars solve than the mainstream libraries solve but they aren't far
off.

---------------

Full support in this generation of browsers
IE6+, FF1+, O8.5+, S2+
No errors (syntax or runtime) in
IE5+, NN6+, O7+, S1+
Possibly syntax errors but no runtime errors in
IE4+, NN4+
Who cares
IE3-, NN3-

A single page implementation of an event library. (Not worried about
frames/windows as you discussed above.)

Probably many, many other restrictions/requirements/assumptions.

---------------

The above specific problem (or one quite close) seems to be the one
that needs solving most frequently and so is the one for which the
most prefabricated is available for download on the web. This has been
incorrectly referred to as the "general" problem and the available
solutions have been labeled "cross-browser" even though they are not
even close and don't even attempt to use feature testing well.

Since this problem arises so frequently it is good that programmers
share code to solve this problem (or problems very similar). The fact
that this problem is more general than necessary in many cases is
clearly not a problem. If it were then customers would have complained
enough that some other problem would be solved (perhaps your multiple
implementations system would be popular.)

I have tried solving only the problem at hand for a given web page. As
more requirements arrive from the customer, I find I always end up,
once again, solving this same vaguely specified problem. Perhaps this
vaguely specified problem is exactly at the perceived level of
functionality the web can provide without being too expensive to
develop.

There is something to this vaguely specified problem, don't you agree?

Peter

Peter Michaux

unread,
Jul 20, 2008, 8:33:45 PM7/20/08
to
On Jul 20, 5:07 pm, Thomas 'PointedEars' Lahn <PointedE...@web.de>
wrote:

It doesn't make sense to me and I wish they wouldn't include this in
the specification.

If two unrelated pieces of code are registering listeners on an
element, the order of execution of those listeners would not matter.

If two related listeners are being registered and the order does
matter then they should be combined and added as one listener so the
order is explicit in the code. Anything else and debugging is a
nightmare.

Also, I don't think "add" means "append at the end". For example, the
Mathematical concept of addition for numbers is commutative.


> >> Full ACK.
>
> > "ACK"?
>
> ACK.
>
> > [...] would make your posts much easier to read for those
> > unfamiliar with your uncommon English abbreviation. "ACK" does not
> > appear in my English dictionary, for example.
>
> It appears in the Jargon File, FOLDOC (which took it from the former), and
> Wikipedia, for example. The word can be easily found in these and millions
> of other locations with Google, which you are using, many of them explaining
> it as it was meant here:
>
> | Results 1 - 10 of about 15,500,000 for ACK. (0.21 seconds)
>
> You are only *playing* stupid, I hope.

Sometimes but I genuinely don't know about 95% of the abbreviations
you use. I didn't know "ACK", for example. Have a look in the group
archives for "ack". There are not many uses and many are the
interjection use of "ACK!!" It seems you studied all the obscure
abbreviations you could and wield them to bewilder your opponents and
gain some sort of sense of superiority. Given how many messages you
type here per month, the extra nine characters you saved with "ACK"
are insignificant.

Peter

P.S. I didn't snip anything because I deemed it all relevant.

P.P.S "P.S." is a well known abbreviation for "post script" and
"P.P.S." is for "post, post script". I'm sure you could see a way of
extending this system for any number of notes included after the main
body of text.

Thomas 'PointedEars' Lahn

unread,
Jul 20, 2008, 8:57:59 PM7/20/08
to
Peter Michaux wrote:
> On Jul 16, 8:17 am, "Aaron Gray" <ang.use...@gmail.com> wrote:
>> and put together the following offering for my LGPL'ed library
>> functions :-
>
> Why LGPL? There are JavaScript libraries under the MIT and BSD licenses
> which are more liberal than LGPL.
> Offering your library under the LGPL means, most importantly, you are
> offering your library to the world.

In a nutshell:

LGPL: You should use GPL v2 instead.

<http://www.gnu.org/licenses/why-not-lgpl.html>

GPL v2: You should use GPL v3 instead.

<http://www.gnu.org/licenses/rms-why-gplv3.html>

GPL v3: Copyright notices must be included and left in when contained.
Source code must be provided on request, free of charge. Users
may modify and redistribute the program under these conditions.

<http://www.gnu.org/licenses/gpl-3.0.html>

MITL: Copyright notices must be included and left in when contained.
Source code does not need to be provided. Users may modify and
redistribute the program under these conditions.

<http://www.opensource.org/licenses/bsd-license.php>

BSDL: See MITL plus the "This product includes software developed by
the University of California, Berkeley and its contributors."
acknowledgement. Advertising with the name of the university
or its contributors is forbidden.

<http://www.opensource.org/licenses/mit-license.php>

A common misconception is that one had to provide the source code along with
the program and was not allowed to make a profit from GPL'd software. This
is wrong.

Furthermore, distributing a library written in an ECMAScript implementation
under the BSD or MIT licenses is questionable. If the library is to be used
client-side (as here), you will have to provide the source code anyway. And
as for the BSD License, unless you really use University products in it, you
could as well distribute under the MIT or GPL with little difference in your
freedom or that of your users.

> I think it is great you are taking the project of writing a library
> seriously but if you are going to release your library publicly you may
> want to give it a very low version number like 0.0 or 0.1 so people know
> you are just starting to learn the issues of both JavaScript and cross
> browser coding.

Full ACK.


PointedEars
--
Prototype.js was written by people who don't know javascript for people
who don't know javascript. People who don't know javascript are not
the best source of advice on designing systems that use javascript.
-- Richard Cornford, cljs, <f806at$ail$1$8300...@news.demon.co.uk>

Thomas 'PointedEars' Lahn

unread,
Jul 20, 2008, 9:28:43 PM7/20/08
to
Peter Michaux wrote:
> Thomas 'PointedEars' Lahn wrote:
>> Peter Michaux wrote:
>>> Thomas 'PointedEars' Lahn wrote:
>>>> * event listeners *added* with addEventListener() are executed *in
>>>> order of addition*;
>>> The spec seems to disagree [...]

>>> http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow-basic
>>
>> Thanks, I was not aware of that paragraph.
>>
>> However, implementations so far appear to implement what W3C DOM Level
>> 3 says (currently a Working Draft):
>>
>> [Event listeners for an event target are to be triggered in order of
>> registration]

>>
>> This behavior makes sense to me as `add' would indicate an order,
>> [W3C DOM Level 3 (WD): Event listeners are to be triggered in order

>
> It doesn't make sense to me and I wish they wouldn't include this in the
> specification.
>
> If two unrelated pieces of code are registering listeners on an element,
> the order of execution of those listeners would not matter.

And so their being triggered in order of registration would not be a drawback.

> If two related listeners are being registered and the order does matter
> then they should be combined and added as one listener so the order is
> explicit in the code. Anything else and debugging is a nightmare.

It may not be possible to know the previously registered event listener,
insofar this solution is not generally applicable. However, I'd rather they
included an interface for a native event registry in the Specification that
could be used to determine all event listeners previously registered by the
same user, at least, so that that one would have to write less
less-efficient (sic!) non-native code for that.

> Also, I don't think "add" means "append at the end". For example, the
> Mathematical concept of addition for numbers is commutative.

YMMV.

>>>> Full ACK.
>>> "ACK"?
>> ACK.
>>
>>> [...] would make your posts much easier to read for those unfamiliar
>>> with your uncommon English abbreviation. "ACK" does not appear in my
>>> English dictionary, for example.
>> It appears in the Jargon File, FOLDOC (which took it from the former),
>> and Wikipedia, for example. The word can be easily found in these and
>> millions of other locations with Google, which you are using, many of
>> them explaining it as it was meant here:
>>
>> | Results 1 - 10 of about 15,500,000 for ACK. (0.21 seconds)
>>
>> You are only *playing* stupid, I hope.
>

> [...] I genuinely don't know about 95% of the abbreviations you use.

That is your problem alone. There is none among them that is unfamiliar on
Usenet. (Compared to our dear Doctor, for example, I am being quite liberal
also in that regard.) Of course a googlodyte like you now-apparently are
does not need to know, but then also he better did not go on whining about them.

> I didn't know "ACK", for example. Have a look in the group archives for
> "ack". There are not many uses and many are the interjection use of
> "ACK!!" It seems you studied all the obscure abbreviations you could and
> wield them to bewilder your opponents and gain some sort of sense of
> superiority. Given how many messages you type here per month, the extra
> nine characters you saved with "ACK" are insignificant.

So you are not playing stupid?

> [...] P.S. I didn't snip anything because I deemed it all relevant.


>
> P.P.S "P.S." is a well known abbreviation for "post script" and "P.P.S."
> is for "post, post script". I'm sure you could see a way of extending
> this system for any number of notes included after the main body of text.

OK, you are not playing. You *are* stupid, and you are wasting my time.


Score adjusted

kangax

unread,
Jul 21, 2008, 12:20:28 AM7/21/08
to
On Jul 20, 6:02 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> [snip]

> Take the last version of Prototype.js that I looked at (1.6.0.2).


> Internally there are 28 calls to its - bind - method and precisely zero
> of those pass more than one argument to the method. The method's code
> is:-
>
> | 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)));
> | }
> | },
>

The 1.6.0.2 release is almost 6 months old. Function.prototype.bind
from the latest revision (http://github.com/sstephenson/prototype/tree/
master/src/base.js#L168) "forks" returning function based on a number
of arguments given.

Similar changes are about to be made to other Function.prototype.*
methods. Using:

__method.apply(null, [this].concat($A(arguments)));

where:

__method.call(null, this);

would suffice, is no doubt reckless.

--
kangax

jdalton

unread,
Jul 21, 2008, 1:46:59 AM7/21/08
to
@Richard Cornford

> Indeed so far so that their only way out is to petition for a
> new - bind - method in the new language versions so that faster native
> code can rescue their authors from the consequences of original designs.
As far as I know the Prototype core members have not petition for a
native bind method.
I believe other developers have seen its worth and have requested its
addition.
I dislike the tone of your comment. The authors are perfectly capable
and do not need “rescuing”.

You make some very good points about Prototype’s bind method.
There are been some performance patches submitted that deal with bind
and other methods:
http://prototype.lighthouseapp.com/attachments/31739/0467-Optimize-bind-bindAsEventListener.patch
http://prototype.lighthouseapp.com/attachments/33717/0473-faster-curry-wrap-and-methodize.patch

@ Peter Michaux


> Prototype.js typists don't seem to care about any sort of modularity

As far as I know the core has avoided modularity because it is harder
to maintain.
If file size is an issue you can gzip and minify it to around 20kb.

> RobG pointed out sometime that many functions in Prototype.js start by
> calling $ for one of the arguments. Even functions that aren't API
> functions do this which is completely pointless.

$() resolves the element(s) from an ID(s) and extend the element(s) if
needed.
Prototype should have a reason for using $() in the places it is used.
You can read the documentation: http://www.prototypejs.org/api/utility

- JDD

Richard Cornford

unread,
Jul 21, 2008, 5:37:35 PM7/21/08
to
jdalton wrote:
>@Richard Cornford
>> Indeed so far so that their only way out is to petition for a
>> new - bind - method in the new language versions so that faster
>> native code can rescue their authors from the consequences of
>> original designs.

> As far as I know the Prototype core members have not petition
> for a native bind method.

Maybe, but that has not stopped the authors of other similar libraries
making such petitions, and citing Prototype.js methods in order to
bolster their position.

> I believe other developers have seen its worth and have
> requested its addition.
>
> I dislike the tone of your comment.

Should I take it that the contempt is coming across?

> The authors are perfectly
> capable and do not need “rescuing”.

The evidence (past and present) suggests otherwise.

> You make some very good points about Prototype’s bind method.

You don't say?

> There are been some performance patches submitted that deal
> with bind and other methods:
> http://prototype.lighthouseapp.com/attachments/31739/0467-Optimize-bind-bindAsEventListener.patch

<snip>

That ends up with a method that looks like:-

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

| if (args.length) {


| return function() {
| return __method.apply(object, args.concat($A(arguments)));
| }
| }

| return function() {
| return __method.apply(object, arguments);
| }
| },

- which is a bit of a half-arse effort at improving performance, and
faulty in terms of the logic it uses. The branch that just returns this
is only acted upon if the value of the first argument is undefined, but
the apply and call method both use the global object as the - this -
value if their first argument is null or undefined. Javascript offers a
very simple test that discriminated null and undefined form all other
values, so that could be used in place of the comparatively heavyweight
and insufficient - Object.isUndefined(arguments[0]) - test.

As the - args - array is only to be used when it has a non-zero length
it would be better to only create that array in the branch that uses it.
It remains where it is because the - object - variable needs to be
initialised for both of the following branches. Except in reality it
does not need to be initialised at all. If instead of using a
variable, - object - had been declared as a single formal parameter for
the method that parameter would have automatically been assigned the
same value a arguments[0]. Having done that the - args - array creation
can be moved into the branch that uses it, and so not executed at all
when only one argument was used with the - bind - method.

Once the - object - parameter is being used it is no longer necessary
to - shift - its value out of the front of the array, and that opens up
the possibility of using an alternative approach to create the array.
Specifically applying the - Array.prototype.slice - to the arguments
object, as in - args = Array.prototype.slice.call(arguments, 1); -,
where fast native code creates the desired array and skips the first
argument in the process.

And finally, using the - concat - method to append an array created from
the arguments object is very convoluted and relatively inefficient when
the arguments object can be used as the second argument to the - apply -
method and the - apply - method could be called on - push -, which will
take any number of arguments and append them to an array. That is:-
__method.apply(object, args.concat($A(arguments))); - can be replaced
with - __method.apply(obj, args.push.apply(args, arguments)); - and
should result in superior performance.

The result might resemble:-

Function.prototype.bind = function(obj){
var args, fnc;
if(arguments.length > 1){
fnc = this;
args = Array.prototype.slice.call(arguments, 1);
return (function(){
return fnc.apply(obj, args.push.apply(args, arguments));
});
}else if(obj == null){
return this;
}else{


fnc = this;
return (function(){
return fnc.apply(obj, arguments);
});
}
};

- which has actually become very different form the authors of
Prototype.js's best efforts to date. And it has lost all of its internal
dependencies on Prototype.js in the process. (Maybe there is a lesson in
that.)

Your faith in the authors or Prototype.js is touching, but no
justification for it is evident in the code they write. An I bet that
when they become aware of some of the possibilities suggested above they
will happily take all the credit for any resulting performance gains
that would be the consequences of their applying them (and some can be
applied all over the place in Prototype.js methods).

Richard.

Richard Cornford

unread,
Jul 21, 2008, 8:36:52 PM7/21/08
to
Richard Cornford wrote:
<snip>

> The result might resemble:-
>
> Function.prototype.bind = function(obj){
> var args, fnc;
> if(arguments.length > 1){
> fnc = this;
> args = Array.prototype.slice.call(arguments, 1);
> return (function(){
> return fnc.apply(obj, args.push.apply(args, arguments));

That line will work better as:-

return fnc.apply(obj, (args.push.apply(args, arguments)&&args));

or the returned function as:-

return (function(){
args.push.apply(args, arguments);
return fnc.apply(obj, args);
});

> });
> }else if(obj == null){
> return this;
> }else{
> fnc = this;
> return (function(){
> return fnc.apply(obj, arguments);
> });
> }
> };

<snip>

Richard.

kangax

unread,
Jul 21, 2008, 8:58:43 PM7/21/08
to
On Jul 21, 5:37 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> [snip]

> And finally, using the - concat - method to append an array created from
> the arguments object is very convoluted and relatively inefficient when
> the arguments object can be used as the second argument to the - apply -
> method and the - apply - method could be called on - push -, which will
> take any number of arguments and append them to an array. That is:-
> __method.apply(object, args.concat($A(arguments))); - can be replaced
> with - __method.apply(obj, args.push.apply(args, arguments)); - and
> should result in superior performance.

I didn't know about Array.prototype.push being faster than
Array.prototype.concat in this case. Thanks for the tip.

>
> The result might resemble:-
>
> Function.prototype.bind = function(obj){
> var args, fnc;
> if(arguments.length > 1){
> fnc = this;
> args = Array.prototype.slice.call(arguments, 1);
> return (function(){
> return fnc.apply(obj, args.push.apply(args, arguments));

Wouldn't it be better to invoke "push" from the Array.prototype
directly, rather than resolving the reference through the args'
prototype chain?

--
kangax

Lasse Reichstein Nielsen

unread,
Jul 21, 2008, 9:33:51 PM7/21/08
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> writes:

> Function.prototype.bind = function(obj){
> var args, fnc;
> if(arguments.length > 1){
> fnc = this;
> args = Array.prototype.slice.call(arguments, 1);

This copies the arguments ...

> return (function(){
> return fnc.apply(obj, args.push.apply(args, arguments));

... and this push call changes the copy. I.e., every time
the function is called, the args array is made larger.
Also, the push function doesn't return the updated array.

In this case, the concat function would probably be better, i.e.:


The change could be rolled back, e.g.:
return function() {
return fnc.apply(obj, args.concat(arguments));
}

/L
--
Lasse Reichstein Nielsen
DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleDOM.html>
'Faith without judgement merely degrades the spirit divine.'

Peter Michaux

unread,
Jul 22, 2008, 12:47:30 AM7/22/08
to
On Jul 21, 2:37 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

[snip]

> Function.prototype.bind = function(obj){
> var args, fnc;
> if(arguments.length > 1){
> fnc = this;
> args = Array.prototype.slice.call(arguments, 1);

There is no guarantee that I know of stating the above line will work.
Array prototype properties like "slice" are not generic in ECMAScript
3 like the generics in ECMAScript 4 are planned to be. That is, in
ECMAScript 3, Array.prototype.slice doesn't necessarily work with an
array-like "this" object. The "this" object should be an actually
Array object. If I remember correctly, David Mark suggested he new of
an implementation where the above code would error. I use a loop to
accomplish the goal in the above line.

[snip]

Peter

RobG

unread,
Jul 22, 2008, 2:34:03 AM7/22/08
to
On Jul 22, 2:47 pm, Peter Michaux <petermich...@gmail.com> wrote:
> On Jul 21, 2:37 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
> wrote:
>
> [snip]
>
> > Function.prototype.bind = function(obj){
> >   var args, fnc;
> >   if(arguments.length > 1){
> >     fnc = this;
> >     args = Array.prototype.slice.call(arguments, 1);
>
> There is no guarantee that I know of stating the above line will work.
> Array prototype properties like "slice" are not generic in ECMAScript
> 3 like the generics in ECMAScript 4 are planned to be.

In regard to what ECMA-262 Ed. 3 says about Array.prototype.slice,
there is a note at the bottom of Section 15.4.4.10 that says:

NOTE The slice function is intentionally generic; it does
not require that its this value be an Array object. Therefore
it can be transferred to other kinds of objects for use as a
method. Whether the slice function can be applied successfully
to a host object is implementation-dependent.


> That is, in
> ECMAScript 3, Array.prototype.slice doesn't necessarily work with an
> array-like "this" object. The "this" object should be an actually
> Array object. If I remember correctly, David Mark suggested he new of
> an implementation where the above code would error.

Was that specifically in regard to the arguments object, or in
general?


--
Rob

RobG

unread,
Jul 22, 2008, 2:40:09 AM7/22/08
to

For some value of "better". It seems to me that:

variableObj->args->args[[Prototype]]->push


is fewer lookups than:

variableObj->[[Scope]]window->Array->prototype->push


and less to type - you may have other criteria. :-)


--
Rob

Richard Cornford

unread,
Jul 22, 2008, 3:46:35 AM7/22/08
to
Lasse Reichstein Nielsen> wrote:

> Richard Cornford writes:
>
>> Function.prototype.bind = function(obj){
>> var args, fnc;
>> if(arguments.length > 1){
>> fnc = this;
>> args = Array.prototype.slice.call(arguments, 1);
>
> This copies the arguments ...
>
>> return (function(){
>> return fnc.apply(obj, args.push.apply(args, arguments));
>
> ... and this push call changes the copy. I.e., every time
> the function is called, the args array is made larger.
> Also, the push function doesn't return the updated array.

You are right. That will work for the first call and then be a mess on
all subsequent calls.

> In this case, the concat function would probably be better, i.e.:
>
>
> The change could be rolled back, e.g.:
> return function() {
> return fnc.apply(obj, args.concat(arguments));
> }

Yes it would, but not like that because step 4 in the concert algorithm
checks whether the argument is an array and branches if it is not. And
that would leave the whole arguments object being append to the array.

It will be necessary to turn the arguments object into an array again,
so:-

return function() {
return fnc.apply(obj, args.concat(args.slice.call(arguments, 0)));
}

Richard.

Richard Cornford

unread,
Jul 22, 2008, 3:46:40 AM7/22/08
to
Peter Michaux wroteL:

> On Jul 21, 2:37 pm, Richard Cornford wrote:
>
> [snip]
>
>> Function.prototype.bind = function(obj){
>> var args, fnc;
>> if(arguments.length > 1){
>> fnc = this;
>> args = Array.prototype.slice.call(arguments, 1);
>
> There is no guarantee that I know of stating the above
> line will work.

There is ECMA 262 saying that it will work, at least so long as nobody
has re-assigned to the - Array.prototype - or its - slice - method.

> Array prototype properties like "slice" are not generic
> in ECMAScript 3 like the generics in ECMAScript 4 are planned
> to be.

The spec says they are generic, so if they are not in some
implementations that would be an implementation bug.

> That is, in ECMAScript 3, Array.prototype.slice doesn't necessarily
> work with an array-like "this" object. The "this" object should be
> an actually Array object.

The algorithm is only interested in the - this - object having a -
length - property and 'array index;' properties. Though not having
either will not break the - slice - algorithm.

> If I remember correctly, David Mark suggested he new of
> an implementation where the above code would error. I
> use a loop to accomplish the goal in the above line.

As Prototoype.js has never even come close to being cross-browser such
rumours are not problematic. The approach can be tested in the 3 or 4
supported browsers and shown to work there.

Richard.

Richard Cornford

unread,
Jul 23, 2008, 8:44:03 PM7/23/08
to
kangax wrote:

> On Jul 21, 5:37 pm, Richard Cornford wrote:
>
>> [snip]
>
>> And finally, using the - concat - method to append an array created
>> from
>> the arguments object is very convoluted and relatively inefficient
>> when
>> the arguments object can be used as the second argument to the -
>> apply -
>> method and the - apply - method could be called on - push -, which
>> will
>> take any number of arguments and append them to an array. That is:-
>> __method.apply(object, args.concat($A(arguments))); - can be replaced
>> with - __method.apply(obj, args.push.apply(args, arguments)); - and
>> should result in superior performance.
>
> I didn't know about Array.prototype.push being faster than
> Array.prototype.concat in this case. Thanks for the tip.

Faster, but not usefully so.

However, it seems that the fastest method of turning an arguments object
into an array is:-

((arguments.length == 1)?[arguments[0]]:Array.apply(this, arguments))

- where - this - is the global object in my tests, but should not be
altered by the process so could be any object. I did try null as first
argument, which is fine on everything but Firefox, where it makes the
process considerably slower than using an object reference.

Unfortunately when the Array constructor, called as a function (so not
with the - new - keyword), is only given one argument and that argument
turns out to be a numeric value that is a positive integer smaller than
2 to the power of 32 then you get different behaviour. So the expression
has to include that special handling for (arguments.length == 1). My
test still show that whole expression outperforming all of the
alternatives that I could think of.

The precise benefit depends on the number of arguments. With zero
arguments the different can be very small on some browsers (especially
firefox and IE). I did my comparisons against Prototype.js's - $A -
function , which is considered to be 100% in the following numbers. At
20 arguments Windows Safari 3 executes that expression in 48% of the
time, and at zero arguments 47%. Mac Safari 2 was better, ranging from
16% with 20 arguments down to 34% with zero.

Opera 9.2 showed the next greatest performance change. 23% at 20
arguments and 57% at zero.
IE 7 came next with 62% at 20 arguments and 81% at zero.
IE 6: 65% to 89%.

Firefox 2.0.4 was the worst I have tried to date. At 20 arguments it
only manages 79% and was down to 85% by zero arguments, but the actual
execution time for the expression (and all of the alternatives) was the
longest of the group tested on the same hardware (by at least a factor
of 2) so the actual gain in time saved was still greater than for some
of the better performing JS engines.

Array.prototype.slice.call(arguments, X); - has still got to be the
fastest method if not all of the arguments are wanted, and (strangely) -
Array.prototype.splice.call(arguments, 0, arguments.length); - is
another alternative, but there, with Windows Safari 3, the benefit
dropped off with more arguments and at 8 arguments it was worse than -
$A -.

These were tests on a mixture of Pentium 4, Core 2 Duo and Core 2 Quad
processors and Windows and Mac OSs so the results may not yet be
sufficiently reprehensive for the comparisons to hold in general. I will
probably post the test code for these tomorrow (or soonish) in case
anyone wants to see if other hardware permutations contradict those
results (or try it with other browsers/OSs).

Richard.

Jorge

unread,
Jul 23, 2008, 9:32:54 PM7/23/08
to
On 24 jul, 02:44, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> At
> 20 arguments Windows Safari 3 executes that expression in 48% of the
> time, and at zero arguments 47%. Mac Safari 2 was better, ranging from
> 16% with 20 arguments down to 34% with zero.
>
> Opera 9.2 showed the next greatest performance change. 23% at 20
> arguments and 57% at zero.
> IE 7 came next with 62% at 20 arguments and 81% at zero.
> IE 6: 65% to 89%.
>
> Firefox 2.0.4 was the worst I have tried to date. At 20 arguments it
> only manages 79% and was down to 85% by zero arguments, but the actual
> execution time for the expression (and all of the alternatives) was the
> longest of the group tested on the same hardware (by at least a factor
> of 2) so the actual gain in time saved was still greater than for some
> of the better performing JS engines.
>

Now let's see who's got the balls to argue with Richard for having
posted a benchmark... a *benchmark* !

Duh. And Safari wins one more time, again, as ever. LOL.

Benchmark (computing): the result of running a computer program, or a
set of programs, in order to assess the relative performance of an
object by running a number of standard tests and trials against it.

--Jorge.

kangax

unread,
Jul 23, 2008, 9:46:05 PM7/23/08
to
On Jul 23, 8:44 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

Thanks for an exhaustive comparison.

Unfortunately, prototype.js's $A is a general-purpose function. That's
one of the reasons why it's slower than other alternatives. Besides
converting arguments object into an array, it's often used to convert
array-like objects (namely NodeList's) into actual arrays (by
exploiting length property that NodeList's expose).

$A is also used for delegating behavior to a passed object's toArray
method if an object has one. Delegating allows to decouple concerns,
as we know, so this pattern allows other data structures to define
"toArray" containing its own logic.

As an example, String.prototype.toArray is defined as:

...
function() {
return this.split('');
}
...

It would probably be wiser to use a separate function when converting
arguments to an array, rather than relying on a "heavier" $A. We might
consider this in later revisions.


Best,

--
kangax

dhtml

unread,
Jul 25, 2008, 3:46:00 AM7/25/08
to
On Jul 20, 1:37 am, Thomas 'PointedEars' Lahn <PointedE...@web.de>
wrote:
> dhtml wrote:

> > On Jul 19, 6:49 pm, "Aaron Gray" <ang.use...@gmail.com> wrote:
> >> Here's yet another mod :-
>
> > There are some significant differences in attachEvent and
> > addEventListener.
>
> > * w3c DOM Event bugs in Webkit and Safari (and probably a lot in Opera
> > that I'm not aware of).
>
> Could you elaborate on that, please?
>

In Webkit and Firefox (I meant to say), events fire on bubble when
useCapture has been set to true. This is a bug and in direct violation
of the Events spec, which states:

| useCapture of type boolean
| If true, useCapture indicates that the user wishes to
| initiate capture. After initiating capture, all events of the
| specified type will be dispatched to the registered
| EventListener before being dispatched to any EventTargets
| beneath them in the tree. Events which are bubbling upward
| through the tree will not trigger an EventListener designated
| to use capture.

"Events which are bubbling upward through the tree will not trigger an
EventListener designated to use capture."

https://bugzilla.mozilla.org/show_bug.cgi?id=235441
https://bugs.webkit.org/show_bug.cgi?id=9127

The spec statment was strengthened in D3E:
http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-EventTarget-addEventListener

| useCapture of type boolean
| If true, useCapture indicates that the user wishes to add
| the event listener for the capture phase only, i.e. this
| event listener will not be triggered during the target and
| bubbling phases. If false, the event listener will only be
| triggered during the target and bubbling phases.


> > * w3c DOM Events bubble as specified, IE events bubble differently,
> > like legacy events (by "legacy" events, I mean el.onclick = ...)
>
> Could you provide a test case that demonstrates the issue, please?
>

I was wrong about the legacy events bubbling differently, but not
about IE DOM Events. They don't bubble the same.

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-htmlevents

| change
| The change event occurs when a control loses the input focus and its
| value has been modified since gaining focus. This event is valid for
| INPUT, SELECT, and TEXTAREA. element.
| Bubbles: Yes
| Cancelable: No
| Context Info: None
|
| submit
| The submit event occurs when a form is submitted. This event only
| applies to the FORM element.
| Bubbles: Yes
| Cancelable: Yes
| Context Info: None

Both of these events should bubble.
Moz and Safari: these events bubble when registered with
addEventListener.

IE: these events don't bubble

MSDN docs
onchange: http://msdn.microsoft.com/en-us/library/ms536912(VS.85).aspx
onsubmit: http://msdn.microsoft.com/en-us/library/ms536972.aspx

I'll post up an example tomorrow.


> > * different thisArg - attachEvent's this is always window
>
> and


>
> * event listeners *added* with addEventListener() are executed
>   *in order of addition*;
>

>   event listeners *attached* with attachEvent() are executed
>   *in arbitrary order*
>

Good to know, but I can't think of a good reason for relying on the
order.


> PointedEars

dhtml

unread,
Jul 25, 2008, 4:45:13 AM7/25/08
to
On Jul 21, 6:33 pm, Lasse Reichstein Nielsen <l...@hotpop.com> wrote:

> "Richard Cornford" <Rich...@litotes.demon.co.uk> writes:
> > Function.prototype.bind = function(obj){
> >   var args, fnc;
> >   if(arguments.length > 1){
> >     fnc = this;
> >     args = Array.prototype.slice.call(arguments, 1);
>
> This copies the arguments ...
>
> >     return (function(){
> >       return fnc.apply(obj, args.push.apply(args, arguments));
>
> ... and this push call changes the copy. I.e., every time
> the function is called, the args array is made larger.
> Also, the push function doesn't return the updated array.
>


Good point. push() will return a number. Passing a number as the
second argument to Function.prototype.apply would result in a
TypeError -- Definitely not good for performance.

concat returns a new Array. So do slice and splice.


> In this case, the concat function would probably be better, i.e.:
>
> The change could be rolled back, e.g.:
>       return function() {
>         return fnc.apply(obj, args.concat(arguments));
>       }
>

But that would not work in the way that you want it o with an
arguments object because arguments is not an array, so it would be
added next in the list. Now if arguments were an array, it would work
as desired, adding up all the items to the array. So it would be
necessary to slice() it.

Garrett

> /L

dhtml

unread,
Jul 25, 2008, 9:28:06 PM7/25/08
to
On Jul 25, 12:46 am, dhtml <dhtmlkitc...@gmail.com> wrote:
> On Jul 20, 1:37 am, Thomas 'PointedEars' Lahn <PointedE...@web.de>
> wrote:
>
> > dhtml wrote:
> > > On Jul 19, 6:49 pm, "Aaron Gray" <ang.use...@gmail.com> wrote:


> > Could you provide a test case that demonstrates the issue, please?
>

I've added a test case below that adds callbacks for change, submit,
and select events. The callbacks are added to an ancestor node. The
example shows that the events DO NOT bubble in IE, just as the MSDN
spec states.

> We can see that the events don't bubble in IE.


> I was wrong about the legacy events bubbling differently, but not
> about IE DOM Events. They don't bubble the same.
>

> http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroup...


>
> | change
> | The change event occurs when a control loses the input focus and its
> | value has been modified since gaining focus. This event is valid for
> | INPUT, SELECT, and TEXTAREA. element.
> | Bubbles: Yes
> | Cancelable: No
> | Context Info: None
> |
> | submit
> | The submit event occurs when a form is submitted. This event only
> | applies to the FORM element.
> | Bubbles: Yes
> | Cancelable: Yes
> | Context Info: None
>
> Both of these events should bubble.
> Moz and Safari: these events bubble when registered with
> addEventListener.
>
> IE: these events don't bubble
>
> MSDN docs
>  onchange:http://msdn.microsoft.com/en-us/library/ms536912(VS.85).aspx
>  onsubmit:http://msdn.microsoft.com/en-us/library/ms536972.aspx
>
> I'll post up an example tomorrow.

Example:
==================================================================

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> Change </title>
<style type="text/css">
label { cursor: default; }
</style>
</head>

<body>

<h1>Form Dirty?</h1>

<form action="">

<fieldset id="ff"><legend>legend</legend>
<select name='big'>
<option disabled="disabled">disabled option</option>
<option>one</option>
<option>two</option>
</select>

<textarea name="a" cols="12" rows="1">foo</textarea>
<label>
<input type="radio" name="df" value="R1"/>radio
</label>
<label>
<input type="radio" name="df" value="R2"/>radio
</label>
<input type="checkbox"/>
<input type='text'/>
<input type="password"/>
<input type="submit" value="go"/>
</fieldset>

</form>

<strong>attachEvent/addEventListener</strong>
<pre id='monitor'>

</pre>
<strong>legacy event</strong>
<pre id='monitor2'>

</pre>
<script type="text/javascript">(function(){
var ff = document.getElementById('ff'),
monitor = document.getElementById('monitor'),
monitor2 = document.getElementById('monitor2');

if(ff.addEventListener) {
ff.addEventListener("change", getTimeStamp, false);
document.addEventListener("submit", getTimeStamp, false);
document.addEventListener("select", getTimeStamp, false);
} else if(ff.attachEvent) {
ff.attachEvent("onchange", getTimeStamp);
document.attachEvent("onsubmit", getTimeStamp);
document.attachEvent("onselect", getTimeStamp);
}

ff.onchange = document.onsubmit = document.onselect = legacy;

function getTimeStamp(e){
e = e || event;
var tag = (e.target || e.srcElement);
monitor.innerHTML = "this = " + this.nodeName
+ "\ntarget: " + (tag.type || tag.nodeName)
+ "\ntype: " +(e.type + "\ntimestamp: " + e.timeStamp);
}
function legacy(e) {
e = e || event;
var tag = (e.target || e.srcElement);
monitor2.innerHTML = "this = " + this.nodeName + "\n"
+ "target: " + (tag.type || tag.nodeName)
+ "\ntype: legacy on" + (e.type + "\ntimestamp: " + e.timeStamp);
return false;
}
})();</script>
</body>
</html>

==================================================================

By interacting with the example, it can be seen that the registered
events do fire callbacks in MSIE. This is because these events don't
bubble, and this is clearly stated on MSDN documentation links
(provided).

It can also be observed that the events will fire callbacks
registered
with useCapture=true, by changing the code to:

ff.addEventListener("change", getTimeStamp, true);
document.addEventListener("submit", getTimeStamp, true);
document.addEventListener("select", getTimeStamp, true);

Callbacks fire on bubbled event in: Opera9, Safari3, Firefox3, a bug
in all 3 browsers.

Garrett

>
> > PointedEars

dhtml

unread,
Jul 25, 2008, 9:30:42 PM7/25/08
to
On Jul 25, 6:28 pm, dhtml <dhtmlkitc...@gmail.com> wrote:
> On Jul 25, 12:46 am, dhtml <dhtmlkitc...@gmail.com> wrote:
> > On Jul 20, 1:37 am, Thomas 'PointedEars' Lahn <PointedE...@web.de>
> > wrote:
> > > dhtml wrote:
> By interacting with the example, it can be seen that the registered
> events do fire callbacks in MSIE.
^
correction: do NOT fire callbacks in MSIE.

Garrett

>
> Garrett
>
>
>
> > > PointedEars
>
>

Richard Cornford

unread,
Jul 26, 2008, 11:18:58 AM7/26/08
to
Jorge wrote:

> On 24 jul, 02:44, Richard Cornford wrote:
>> At 20 arguments Windows Safari 3 executes that expression
>> in 48% of the time, and at zero arguments 47%. Mac Safari
>> 2 was better, ranging from 16% with 20 arguments down to
>> 34% with zero.
>>
>> Opera 9.2 showed the next greatest performance change. 23%
>> at 20 arguments and 57% at zero.
>> IE 7 came next with 62% at 20 arguments and 81% at zero.
>> IE 6: 65% to 89%.
>>
>> Firefox 2.0.4 was the worst I have tried to date. At 20
>> arguments it only manages 79% and was down to 85% by zero
>> arguments, but the actual execution time for the expression
>> (and all of the alternatives) was the longest of the group
>> tested on the same hardware (by at least a factor of 2) so
>> the actual gain in time saved was still greater than for some
>> of the better performing JS engines.
>>
>
> Now let's see who's got the balls to argue with Richard for
> having posted a benchmark... a *benchmark* !

Your point being?

> Duh. And Safari wins one more time, again, as ever. LOL.

<snip>

That is not implied by anything that I posted. Even if one process runs
in 16% of the time taken by another on some Safari browsers that does
not mean that the time taken by either process was shorter then those
exhibited by all other browsers.

And where Mac Safari is concerned direct comparisons become extremely
difficult due to the impossibility of running tests against IE on
equivalent hardware. We can expect Mac Safari to be in a position to
take advantage of low-level OS details unavailable to other browser
manufacturers, in the same way as we can expect Windows IE to be in a
position to take advantage of a similar understanding of Window OS
details.

Richard.

Richard Cornford

unread,
Jul 26, 2008, 11:18:53 AM7/26/08
to
On Jul 24, 2:46 am, kangax wrote:
> On Jul 23, 8:44 pm, "Richard Cornford wrote:
<snip>
>> ... . I did my comparisons against Prototype.js's - $A -
>> function , ...
<snip>

>> These were tests on a mixture of Pentium 4, Core 2 Duo and
>> Core 2 Quad processors and Windows and Mac OSs so the results
>> may not yet be sufficiently reprehensive for the comparisons
>> to hold in general. ...
<snip>

> Thanks for an exhaustive comparison.

Didn't I make the point that they were not exhaustive clearly enough?

> Unfortunately, prototype.js's $A is a general-purpose function.
> That's one of the reasons why it's slower than other alternatives.
> Besides converting arguments object into an array, it's often
> used to convert array-like objects (namely NodeList's) into
> actual arrays (by exploiting length property that NodeList's
> expose).

What - $A - may or may not be is irrelevant to the question. The
approach used in converting an - arguments - object into an array can be
chosen at the point of needing to do it. There is no necessity to choose
to use a general function to do it, and no reason for the changing of
the approach used for converting - arguments - objects to arrays to
impact on the - $A - function at all.

> $A is also used for delegating behavior to a passed object's
> toArray method if an object has one. Delegating allows to
> decouple concerns, as we know, so this pattern allows other
> data structures to define "toArray" containing its own logic.
>
> As an example, String.prototype.toArray is defined as:
>
> ...
> function() {
> return this.split('');}
>
> ...

But we can be certain that - arguments - objects will not have -
toArray - methods (unless they are explicitly assigned them).

> It would probably be wiser to use a separate function when
> converting arguments to an array, rather than relying on a
> "heavier" $A. We might consider this in later revisions.

I did include a dedicated function in my tests (one that did little more
than create a new array and loop over the 'array index' properties of
its argument object copying values to corresponding properties of the
new array). Inevitably it was a little faster than - $A - by virtue of
doing less, but it was such a minimal difference that I would not regard
it as worth consideration in comparison to the differences that some of
the alternatives offer.

At this point it is probably worth posting my test code. The following
is a simple scripted HTML page. At the top there is a filed for entering
the number of iterations to perform (which may need adjusting for
particular browser/OS/hardware combinations). Generally, the number of
iterations should be the most that any particular system will allow
without putting up the 'A script on this page ... " dialog, and
certainly large enough to make the total duration of _all_ the results
greater than 300 milliseconds at (absolute) minimum (as the 10
millisecond resolution of most browser timers would make shorter
durations close to meaningless).

There is a table at the bottom of the page into which the results are
written, and a number of buttons ladled 'Test' to start to test process
(which are disabled while the test runs). On of these buttons is under
the output table at the bottom of the page. Individual loop runs are
separated with - setTimeout - calls with long-ish intervals (to
guarantee that the browser has time to catch up with any work it needs
to do (including re-laying out the results table) and reduce the
likelihood of seeing the "A Script on this page ..." dialog.

The individual test functions are stored in an array of objects with the
global identifier - fncts -. These object have - description - and -
testFnc - properties, where - testFnc - is the function that executes
the expression being tested. With the exception of the first item in
this array (which is used to gauge the overheads involved in the test
loop) items in this array may be removed, or alternatives added. Layout
and overall test looping is driven from the length of this array. The
last item in the array is the one against which comparisons are made and
should be the one expected to be slowest.

And array of argument sets with the global identifier - args - is used
to demonstrate the impact of changing numbers of arguments on the
process. The number of entries in this array can also be changed as the
array length is used to drive that aspect of the test process. Also,
currently all of the arguments are numeric literals. Other types of
arguments may be tried to see whether that has an impact on performance
(unlikely but not impossible).

<html>
<head>
<title>arguemnts to Array tests</title>
<style type="text/css">
TD.alRight {
text-align:right;
}
</style>
<script type="text/javascript">


function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
function makeArray(iterable) {
var c, ar = [];
if((c = iterable.length)){
do{
ar[--c] = iterable[c];
}while(c)
}
return ar;
}

var args = [
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9],
[1,2,3,4,5,6,7,8],
[1,2,3,4,5,6,7],
[1,2,3,4,5,6],
[1,2,3,4,5],
[1,2,3,4],
[1,2,3],
[1,2],
[1],
[]
];

var argsIndex = 0;

var frm = null;
var fncts = [
/* Timing of an "empty" loop to estimate the overheads of the
test itself. */
{
testFnc:function(){
return arguments;
},
description:'Only overheads.'
},
{
testFnc:function(){
var ar = [];
ar.push.apply(ar, arguments);
return ar;
},
description:'push.apply'
},
{
testFnc:function(){
var ar = [];
ar.unshift.apply(ar, arguments);
return ar;
},
description:'unshift.apply'
},
{
testFnc:function(){
return (
arguments.length == 1)?
[arguments[0]]:
Array.apply(null, arguments
);
},
description:'Array.apply(null'
},
{
testFnc:function(){
return (


arguments.length == 1)?
[arguments[0]]:
Array.apply(this, arguments

);
},
description:'Array.apply(this'
},
{
testFnc:function(){
return Array.prototype.slice.call(arguments, 0);
},
description:'slice.call'
},
{
testFnc:function(){
return Array.prototype.splice.call(
arguments, 0, arguments.length
);
},
description:'splice.call'
},
{
testFnc:function(){
switch(arguments.length){
case 0:
return [];
case 1:
return [arguments[0]];
default:
return Array.apply(this, arguments);
}
},
description:'switch'
},
{
testFnc:function(){
return makeArray(arguments);
},
description:'makeArray'
},
{
testFnc:function(){
return $A(arguments);
},
description:'$A'
}
];

function runTest(p){
var lim = +frm['loopLimit'].value;
var N, totTime, stTime;
var obj = {};
stTime = new Date().getTime();
for(var c = 0;c < lim;c++){
N = fncts[p].testFnc.apply(this, args[argsIndex]);
}
totTime = (new Date().getTime() - stTime);
frm["Dur"+p].value = totTime;
frm["Avr"+p].value = (totTime/lim);
frm["Res"+p].value = N;
act(p+1);
}

var f;
var running = false;
function setButtons(bl){
frm['loopLimit'].disabled = bl;
var sw = frm['bt'];
if(typeof sw.length == 'undefined'){
sw = [sw];
}
for(var c = 0;c < sw.length;c++){
sw[c].disabled = bl;
}
}
function startTests(){
if(!running){
frm = document.forms['f'].elements;
setButtons(true);
frm["Dur0"].value = '';frm["Avr0"].value = '';
for(var c = 1;c < fncts.length;c++){
frm["Dur"+c].value = '';
frm["Avr"+c].value = '';
frm["Res"+c].value = '';
}
running = true;
act(0);
}
}
function act(p){
/* setTimeout is used to minimise the occurrences
of 'a script on this page is running slow' dialogs. */
if(p >= fncts.length){
++argsIndex;
if(argsIndex < args.length){
setTimeout('report();startTests()',1000);
}else{
setTimeout('report();argsIndex = 0;',1000);
}
}else{
setTimeout(('(f = runTest('+p+'));'),2000);
}
}
function report(){
var co = ((argsIndex - 1)<<1)+1
var emDur, unaC, diff1, diff2, c, evaC;
var lim = +frm['loopLimit'].value;
var row, tBody = document.getElementById('outBody');
emDur = +frm["Dur0"].value;
c = (fncts.length-1);
row = tBody.rows[1+c];
diff1 = (frm["Dur"+c].value - emDur); //if this is negative then
//the whole test is invalid.
if(diff1 < 0){
// "Empty" loop longer than base test loop
row.cells[co].innerHTML = '+++++++'
return;
}
unaC = diff1 / lim;
if(!unaC){
row.cells[co].innerHTML = 'Loop too short';
return;
}
row.cells[co].innerHTML = formatVal(100);
row.cells[co+1].innerHTML = formatVal(unaC);
for(c = 1;c < (fncts.length-1);c++){
row = tBody.rows[1+c];
diff2 = (frm["Dur"+c].value - emDur);
if(diff2 < 0){
//"Empty" loop longer than this test';
row.cells[co].innerHTML = '*******'
}else{
evaC = diff2 / lim;
row.cells[co].innerHTML = formatVal(((evaC/unaC)*100));
row.cells[co+1].innerHTML = formatVal(evaC);
}
}
tBody.rows[0].cells[0].innerHTML = (navigator.userAgent);
tBody.rows[1].cells[0].innerHTML = ('Iterations = '+lim);
setButtons(false);
running = false;
}
function roundToR(X, R) { return Math.round(X*R)/R }
function formatVal(n){
if(isNaN(n)){
return '------'
}
var s = String(roundToR(n, 10000));
var ind;
if((ind = s.indexOf('.')) < 0){
s += '.';
ind = s.length - 1;
}
s += '000'
return s.substring(0, (ind+4));
}
</script>
</head>
<body>
<div>
<form name="f" action="#">
Loop Length = <input type="text" value="80000"
name="loopLimit"><br><br>


<input type="button" value="Test" name="bt" onclick="startTests();">
Repeat tests to reduce/expose the influence of background tasks.
<br><br>
Empty Loop Duration (milliseconds) = <input type="text" value="X"
name="Dur0"><br>
Empty Loop Average (milliseconds) = <input type="text" value="X"
name="Avr0" size="22"><br>
(result = <input type="text" value="X" name="Res0" size="42">)<br>
<br>

<script type="text/javascript">
var c, len = fncts.length;
var st = ''
for(c = 1;c < len;++c){
st += '<br><code>'+fncts[c].description;
st += '</code> Duration (milliseconds) = ';
st += '<input type="text" value="X" name="Dur'+c+'"><br>';
st += '<code>'+fncts[c].description;
st += '</code> Average (milliseconds) = ';
st += '<input type="text" value="X" name="Avr'+c;
st += '" size="22"><br>';
st += '(result = <input type="text" value="X" name="Res'+c;
st += '" size="42">)<br>';
}
document.write(st);
</script>

<input type="button" value="Test" name="bt" onclick="startTests();">
Repeat tests to reduce/expose the influence of background tasks.
<br><br>

Average: (duration of test - duration of &quot;empty&quot; loop)
/ loop length (Milliseconds)<br>

<script type="text/javascript">
var tdsOut = [''];
tdsOut.toString = function(){
return ('<td>'+this.join('<\/td><td class="alRight">')+'<\/td>');
}
var rowsOut = [];
rowsOut.toString = function(){
return ('<tr>'+this.join('<\/tr><tr>')+'<\/tr>');
}
var tableOut = [
'<table border="2"><tbody id="outBody">',
rowsOut,
'<\/tbody><\/table>'
]

//fncts.length // vertical
//args.length){ //horezontal
var c, d, len = fncts.length, dLen = args.length;
var st = '<th><\/th>'
for(d = 0;d < dLen;++d){
st += '<th colspan="2">N<sup>o</sup> args = ';
st += args[d].length+'<\/th>'
}
rowsOut.push(st);
for(d = 0;d < dLen;++d){
tdsOut.push('%');
tdsOut.push('Average');
}
rowsOut.push(String(tdsOut));
for(c = 1;c < len;++c){
tdsOut.length = 0;
tdsOut.push(c+':'+fncts[c].description);
for(d = 0;d < dLen;++d){
tdsOut.push('');
tdsOut.push('');
}
rowsOut.push(String(tdsOut));
}
document.write(tableOut.join(''));
</script>
<br><br>
<input type="button" value="Test" name="bt" onclick="startTests();">
Repeat tests to reduce/expose the influence of background tasks.
<br><br>
</form>
</div>
</body>
</html>

It may be observed that the process with the description - switch - is
actually the fasted across the board. Suggesting that short-circuiting
the process in the (probably common) case of having zero arguments is
beneficial . However, I have not proposed this as the fasted approach
because I was interested in a single expression that could replace the -
$A(arguments) - expression, and - switch - is a statement not an
expression. Moving the switch statement into a function and calling that
in place of - $A - would add the function call overheads and so may
negate its advantages (indeed a quick test of that in IE6 shows the
function call overheads have that approach drop to 120% of - $A -
performance at two arguments and it does not overtake - $A - again until
the number of arguments gets up to 5).

If the zero arguments case really is expected to be common then the
expression:-

(
(!arguments.length)?
[]:
(


(arguments.length == 1)?
[arguments[0]]:
Array.apply(this, arguments)
)

)

- might prove advantageous (performance-wise, otherwise it is getting
too big/complex). Though it would still be possible to recognise the
common case at the point of calling and so call a faster alternative
instead and so avoid any need to be processing arguments object for that
case.

Richard.

Jorge

unread,
Jul 26, 2008, 1:15:22 PM7/26/08
to
On Jul 26, 5:18 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> Jorge wrote:
> > On 24 jul, 02:44, Richard Cornford wrote:
> >> At 20 arguments Windows Safari 3 executes that expression
> >> in 48% of the time, and at zero arguments 47%. Mac Safari
> >> 2 was better, ranging from 16% with 20 arguments down to
> >> 34% with zero.
>
> >> Opera 9.2 showed the next greatest performance change. 23%
> >> at 20 arguments and 57% at zero.
> >> IE 7 came next with 62% at 20 arguments and 81% at zero.
> >> IE 6: 65% to 89%.
>
> >> Firefox 2.0.4 was the worst I have tried to date. At 20
> >> arguments it only manages 79% and was down to 85% by zero
> >> arguments, but the actual execution time for the expression
> >> (and all of the alternatives) was the longest of the group
> >> tested on the same hardware (by at least a factor of 2) so
> >> the actual gain in time saved was still greater than for some
> >> of the better performing JS engines.
>
> > Now let's see who's got the balls to argue with Richard for
> > having posted a benchmark... a *benchmark* !
>
> Your point being?

That whenever I've posted here a benchmark showing that a certain code
runs x times faster in browser a than in browser b (in my machine)
they all jump on me saying that JS benchmarks are meaningless.

> > Duh. And Safari wins one more time, again, as ever. LOL.
>
> <snip>
>
> That is not implied by anything that I posted. Even if one process runs
> in 16% of the time taken by another on some Safari browsers that does
> not mean that the time taken by either process was shorter then those
> exhibited by all other browsers.
>

Now I'm lost. Unless the trick here is in the word *all* other
browsers.
Certainly it was faster than *all* other browsers in which you've run
the test. Or not (?), and that info is useful.

> And where Mac Safari is concerned direct comparisons become extremely
> difficult due to the impossibility of running tests against IE on
> equivalent hardware.

Boot the Mac (intel) in Windows.

> We can expect Mac Safari to be in a position to
> take advantage of low-level OS details unavailable to other browser
> manufacturers,

Safari´s source code unlike IE's is open sourced. Darwin unlike
Windows is open sourced. So unless the code we're talking about was a
call to a Cocoa framework, that's impossible as there aren't any
"unavailable details".

And "expecting" that is being quite paranoic unless you discover
(studying the source code) the (highly unlikely) event that whenever
something runs faster in Safari it happens to be a call to propietary
code, as a Cocoa framework for example.

> in the same way as we can expect Windows IE to be in a
> position to take advantage of a similar understanding of Window OS
> details.

That's not only possible, but very likely, as history has shown us
already.

--Jorge.

Peter Michaux

unread,
Jul 26, 2008, 8:07:57 PM7/26/08
to David Mark
On Jul 21, 9:47 pm, Peter Michaux <petermich...@gmail.com> wrote:
> On Jul 21, 2:37 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
> wrote:
>
> [snip]
>
> > Function.prototype.bind = function(obj){
> > var args, fnc;
> > if(arguments.length > 1){
> > fnc = this;
> > args = Array.prototype.slice.call(arguments, 1);
>

[snip]

> If I remember correctly, David Mark suggested he new of
> an implementation where the above code would error. I use a loop to
> accomplish the goal in the above line.

I'm cc'ing David about this.

David, am I remembering incorrectly?

Thanks,
Peter

dhtml

unread,
Jul 26, 2008, 8:11:38 PM7/26/08
to
On Jul 26, 8:18 am, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> On Jul 24, 2:46 am, kangax wrote:
>
> > On Jul 23, 8:44 pm, "Richard Cornford wrote:
> <snip>
> >> ... . I did my comparisons against Prototype.js's - $A -
> >> function , ...
> <snip>
> >> These were tests on a mixture of Pentium 4, Core 2 Duo and
> >> Core 2 Quad processors and Windows and Mac OSs so the results
> >> may not yet be sufficiently reprehensive for the comparisons
> >> to hold in general. ...
> <snip>
> > Thanks for an exhaustive comparison.
>
> Didn't I make the point that they were not exhaustive clearly enough?
>
> > Unfortunately, prototype.js's $A is a general-purpose function.
> > That's one of the reasons why it's slower than other alternatives.
> > Besides converting arguments object into an array, it's often
> > used to convert array-like objects (namely NodeList's) into
> > actual arrays (by exploiting length property that NodeList's
> > expose).
>

And the fact that NodeList has been given numeric-named properties,
e.g
document.childNodes["0"]

> What - $A - may or may not be is irrelevant to the question. The
> approach used in converting an - arguments - object into an array can be
> chosen at the point of needing to do it.

By choosing this approach, the overhead of an extra function call can
be avoided.

function getReversedChildNodes(el) {
var kids = Array.prototype.slice.call(el.childNodes);
return kids.reverse();
}


>
> But we can be certain that - arguments - objects will not have -
> toArray - methods (unless they are explicitly assigned them).
>

For an arguments object, Array.prototype.slice.call(arguments) inline
would be faster.

> > It would probably be wiser to use a separate function when
> > converting arguments to an array, rather than relying on a
> > "heavier" $A. We might consider this in later revisions.
>

I think it would be faster and clearer as to what the code is doing by
using an inline call.

Inline code would be fastest:

// Get the arguments as a real Array.
[].slice.call(arguments).

It would be concise, simple, and easy to understand.

If the Array were needed from:

A string:
s.split('');

An arguments object:
Array.prototype.push.apply(arguments);

An Array-like object:
Array.prototype.slice.call(nodeList);

A native ecmascript object:
var r = [];
for(var p in o) r.push(o[p]);

The requirements of Prototype.js is that the $A function convert an
array-like object into an Array. (array-like - has sequential numeric
properties and a length property).

Function.prototype.apply requires the second argument to be an Array
or arguments object. Using push.apply() or unshift.apply as a
replacement in $A would impose a new restriction that will probably
break existing code in or using Prototype.js.

A generalized approach would not be able to differentiate an arguments
object from a NodeList, so that would reduce the switch to: A string,
an Array-like object.

The potential value of creating an abstraction for an object that has
a specific toArray seems to be only that there are different types of
objects that can be converted to an array in different ways.

Enumerable has a toArray:-

toArray -> map -> each, with n calls to iterator/Prototype.K, which
returns the item. It is a long string of function calls. The "Write a
Loop Once" Pattern is powerful, when needed. It can also be used in a
template pattern, and a Template pattern can also use other types of
iteration, such as setInterval.

map is just an alias to collect:

collect: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator(value, index));
});
return results;
}

If there is no iterator function, and if the each function is simply a
Mapper that calls the iterator function, then it would be much more
efficient to simply return the object as an array.

However, for other needs, if a unique mapper function is desired, then
the Array extras can be used on the object after converting it to an
Array (which can also be sorted, et c). This has the benefit of using
the standard built-in Array.

Why is toArray called in $A? The only benefit would seem to be with:

A native ecmascript object:
var r = [];
for(var p in o) r.push(o[p]);

Which is something that is seen in Hash._each:-

_each: function(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
},

Where the iterator takes a "pair" type object that is an array with
key and value properties. The Hash object's prototype chain is also
enumerated over, which seems undesirable and unexpected, as far as a
HashMap type of functionality goes.

Iteration abstractions are powerful but not always needed. The costs
are complexity (especially in PrototypeJS) and performance.

I would not mind a native Array.fromArrayLike(obj) in a newer version
of ES, but it doesn't seem necessary to me. Mountains out of
molehills.

It's nice out. That will be all for today!


Garrett


Peter Michaux

unread,
Jul 26, 2008, 8:19:52 PM7/26/08
to

David replied to me

"Yes. DOM node collections throw exceptions on slice, not arguments."

ECMA-262 3rd 15.4.4.10

"The slice function is intentionally generic; it does not require that
its this value be an Array object. Therefore it can be transferred to
other kinds of objects for use as a method. Whether the slice function
can be applied successfully to a host object is implementation-
dependent."

Peter

dhtml

unread,
Jul 27, 2008, 3:04:12 AM7/27/08
to

Would have to be:-
var a = [];
a.push.apply(a, arguments);


Example:-
var argsIsArray = (function(){

var a = [];
a.push.apply(a, arguments);
return a;

})(1,2,3).constructor === Array;

alert(argsIsArray);

"true"

> Garrett

Richard Cornford

unread,
Jul 27, 2008, 5:53:53 PM7/27/08
to
Peter Michaux wrote:
> On Jul 26, 5:07 pm, Peter Michaux wrote:
>> On Jul 21, 9:47 pm, Peter Michaux wrote:

>>> On Jul 21, 2:37 pm, Richard Cornford wrote:
<snip>
>>>> Function.prototype.bind = function(obj){
>>>> var args, fnc;
>>>> if(arguments.length > 1){
>>>> fnc = this;
>>>> args = Array.prototype.slice.call(arguments, 1);
>>
>> [snip]
>>
>>> If I remember correctly, David Mark suggested he new of
>>> an implementation where the above code would error.
>>> I use a loop to accomplish the goal in the above line.
>>
>> I'm cc'ing David about this.
>>
>> David, am I remembering incorrectly?
>
> David replied to me
>
> "Yes. DOM node collections throw exceptions on slice,
> not arguments."
>
> ECMA-262 3rd 15.4.4.10
<snip>
> ... . Whether the slice function can be applied successfully

> to a host object is implementation-dependent."

I suspected that if there had been an example of an implementation with
an issue handling arguments object then that would have come to my
attention by now.

(Incidentally, I do intend responding to your last substantial response
to me in this thread. I have started writing that response but I don't
think I will have time to finish it tonight.)

Richard.

Richard Cornford

unread,
Jul 27, 2008, 5:53:43 PM7/27/08
to
dhtml wrote:
>On Jul 26, 8:18 am, Richard Cornford wrote:
<snip>

>> What - $A - may or may not be is irrelevant to the question.
>> The approach used in converting an - arguments - object into
>> an array can be chosen at the point of needing to do it.
>
> By choosing this approach, the overhead of an extra function
> call can be avoided.
>
> function getReversedChildNodes(el) {
> var kids = Array.prototype.slice.call(el.childNodes);
> return kids.reverse();
> }

Why don't you see that it is superfluous to respond to me asserting the
very thing that I not only have already suggested but gong to some
effort to ascertain the truth of?

<snip - more pointless noise>

It's nice out. That will be all for today!

Ah, doing something smart for a change.

Richard.

Richard Cornford

unread,
Jul 27, 2008, 5:53:48 PM7/27/08
to
> Jorge wrote:

On the 3GHz 4 core CPU I used for some of the tests differences in the
durations of single operations was tenths of a nanosecond, and on an OS
that does not necessarily report the time with a precision of better
than +/- 10 milliseconds. It is trivially easy to undertake such a test
and come up with results that are meaningless (and/or misleading).

It is meaningless to post numbers in isolation. It is important to say
how those numbers were obtained, what they are supposed to mean, how
they show that meaning and that the numbers are reported in reality. To
which end it is a very good idea to post a demonstration page that will
demonstrate the method and facilitate third party verification of the
results.

>>> Duh. And Safari wins one more time, again, as ever. LOL.
>>
>> <snip>
>>
>> That is not implied by anything that I posted. Even if
>> one process runs in 16% of the time taken by another on
>> some Safari browsers that does not mean that the time
>> taken by either process was shorter then those exhibited
>> by all other browsers.
>>
>
> Now I'm lost. Unless the trick here is in the word *all*
> other browsers.

The word 'all' is their following consideration. I did state that
Firefox was slower in these tests than any other browser tested on the
same hardware/OS combination, and so implied it was slower than Safari
(which it was). But being faster than Firefox is not the same as being
fastest.

> Certainly it was faster than *all* other browsers in which
> you've run the test. Or not (?), and that info is useful.

No. In reality Opera 9.20 and Windows Safari 3 tested on the same box
running the same OS reported vary similar durations for the various
expressions tested. For some Safari was fractionally ahead and for
others Opera was ahead. Safari, for some reason, singled out -
Array.prototype.splice.call(arguments, 0, arguments.length) - for
particularly bad performance (worse than - $A(arguments) - with more
than about 6 arguments and getting worse as the number of arguments
increased, while Opera showed that expression to be between ~50% and
~35% better than - $A(arguments) - at zero and 20 arguments
respectively.

>> And where Mac Safari is concerned direct comparisons become
>> extremely difficult due to the impossibility of running tests
>> against IE on equivalent hardware.
>
> Boot the Mac (intel) in Windows.

And would that be the same hardware? The box may contain the same
hardware but what was being used in what way by each operating system
would be difficult to know. Imagine, for example, on OS using a generic
hardware drives while the other fully exploited the capabilities of a
dedicated driver.

>> We can expect Mac Safari to be in a position to take
>> advantage of low-level OS details unavailable to other browser
>> manufacturers,
>

> Safari愀 source code unlike IE's is open sourced. Darwin


> unlike Windows is open sourced. So unless the code we're
> talking about was a call to a Cocoa framework, that's
> impossible as there aren't any "unavailable details".

OK, not unavailable, but unlikely to as obvious to the readers of open
source code than they are to its authors. Details hide well among very
large collections of similar details.

> And "expecting" that is being quite paranoic

The word is 'cynical', and I am.

> unless you discover (studying the source code) the (highly
> unlikely) event that whenever something runs faster in Safari
> it happens to be a call to propietary code, as a Cocoa
> framework for example.

Ah, so heaven forbid a corporation might ever use inside information to
help negotiate itself into a monopoly position.

>> in the same way as we can expect Windows IE to be in a
>> position to take advantage of a similar understanding of
>> Window OS details.
>
> That's not only possible, but very likely, as history has
> shown us already.

So you are saying that Microsoft have already done this thing but Apple
never would even if they could? You have not understood capitalism.

Richard.

Jorge

unread,
Jul 27, 2008, 6:21:49 PM7/27/08
to
On Jul 27, 11:53 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:
>

> > And "expecting" that is being quite paranoic
>
> The word is 'cynical', and I am.
>

I read many of your posts and you don't look like a cynical. Unlike
you, instead, some others in cljs do.

I really meant paranoic :

"A tendency on the part of an individual or group toward *excessive or
irrational suspiciousness and distrustfulness* of others."

Regards,
--Jorge.

Jorge

unread,
Jul 27, 2008, 6:30:52 PM7/27/08
to
On Jul 27, 11:53 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:
>

> So you are saying that Microsoft have already done this thing but Apple
> never would even if they could? You have not understood capitalism.
>

I understand capitalism. Up until now it has proved to be the less bad
of the solutions.

And Apple could move right now as much of the Safari code as they wish
into private, propietary OSX frameworks.
If they don't it's not because they can't.

--Jorge.

Jorge

unread,
Jul 27, 2008, 6:40:27 PM7/27/08
to
On Jul 27, 11:53 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:
>

> > Boot the Mac (intel) in Windows.
>
> And would that be the same hardware? The box may contain the same
> hardware but what was being used in what way by each operating system
> would be difficult to know. Imagine, for example, on OS using a generic
> hardware drives while the other fully exploited the capabilities of a
> dedicated driver.
>

That would not be very smart. The drivers are included in the OSX
install DVD. Whats prevents you to install the appropiate drivers (?).
Or are you "irrationaly suspicious" about the bundled Windows drivers
as well ?

:-)

--Jorge.

Jorge

unread,
Jul 27, 2008, 6:49:42 PM7/27/08
to
On Jul 27, 11:53 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> > Jorge wrote:
> > On Jul 26, 5:18 pm, Richard Cornford wrote:
> >> Jorge wrote:
> >>> On 24 jul, 02:44, Richard Cornford wrote:
> >>>> At 20 arguments Windows Safari 3 executes that
> >>>> expression in 48% of the time, and at zero arguments
> >>>> 47%. Mac Safari 2 was better, ranging from 16% with
> >>>> 20 arguments down to 34% with zero.
>
> >>>> Opera 9.2 showed the next greatest performance change.
> >>>> 23% at 20 arguments and 57% at zero. IE 7 came next
> >>>> with 62% at 20 arguments and 81% at zero. IE 6: 65%
> >>>> to 89%.
>
> >>>> Firefox 2.0.4 was the worst I have tried to date. At
> >>>> 20 arguments it only manages 79% and was down to 85%
> >>>> by zero arguments, but the actual execution time for
> >>>> the expression (and all of the alternatives) was the
> >>>> longest of the group tested on the same hardware (by
> >>>> at least a factor of 2) so the actual gain in time
> >>>> saved was still greater than for some of the better
> >>>> performing JS engines.
>

Sooner or later you're going to have to admit that Apple is doing it
very well with their Safari browser, in contrast, the multimillion$
software company's browser is a real pity, even in its latest (still
beta after 6(?) years) incarnation. They ought to give for free some
Red-Bulls for breakfast at Redmond.

--Jorge.

Richard Cornford

unread,
Jul 27, 2008, 6:48:51 PM7/27/08
to
Jorge wrote:

> On Jul 27, 11:53 pm, Richard Cornford wrote:
>>
>>> And "expecting" that is being quite paranoic
>>
>> The word is 'cynical', and I am.
>
> I read many of your posts and you don't look like a cynical.

A person who is cynical is a cynic.

> Unlike you, instead, some others in cljs do.
>
> I really meant paranoic :
>
> "A tendency on the part of an individual or group
> toward *excessive or irrational suspiciousness and
> distrustfulness* of others."

So you are disputing the degree to which the suspicion and
distrustfulness is excessive and/or irrational? Personally I see taking
both as a starting position as infinitely preferable to blind faith,
unconditional trust and taking everything at face value.

Richard.

Jorge

unread,
Jul 27, 2008, 7:07:34 PM7/27/08
to
On Jul 28, 12:48 am, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:
>

> So you are disputing the degree to which the suspicion and
> distrustfulness is excessive and/or irrational?

Yes, because Safari's source code is open, of course.

<kidding>
And as they're using it at Redmond trying to learn how to write
browsers, they would have told us everybody already if they had found
anything weird.
</kidding>

--Jorge.

dhtml

unread,
Jul 28, 2008, 12:32:03 AM7/28/08
to
On Jul 27, 2:53 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:

> dhtml wrote:
> >On Jul 26, 8:18 am, Richard Cornford wrote:
> <snip>
> >> What - $A - may or may not be is irrelevant to the question.
> >> The approach used in converting an - arguments - object into
> >> an array can be chosen at the point of needing to do it.
>
> > By choosing this approach, the overhead of an extra function
> > call can be avoided.
>
> > function getReversedChildNodes(el) {
> >   var kids = Array.prototype.slice.call(el.childNodes);
> >   return kids.reverse();
> > }
>
> Why don't you see that it is superfluous to respond to me asserting the
> very thing that I not only have already suggested but gong to some
> effort to ascertain the truth of?
>

I actually did agree with the part about using inline code.

How many times have you seen some insanely clever scheme to 'subclass
array' when the author should have been just using slice()? Well then,
this is the simple answer. Hey - at least the code works, right?

> <snip - more pointless noise>
>

If you'd asked, it probably would have been a lot better of a way to
learn. But then again, nah, that probably wouldn't be possible. You
seem to prefer to impart, rather than receive knowledge (publicly). A
large portion of the things I wrote are not the type of thing that
would seem interesting to the way that someone like you thinks (based
on my observations of what you write).

You go and write tests for how many milliseconds faster it is for
calling Array.push, and post examples. The PrototypeJS guys appreciate
these and call them 'useful nuggets', within the 'piles of dung' you
keep slinging.

> It's nice out. That will be all for today!
>
> Ah, doing something smart for a change.
>

What is that supposed to mean?

Garrett

> Richard.

Richard Cornford

unread,
Aug 30, 2008, 10:27:52 AM8/30/08
to
On Jul 21, 1:09 am, Peter Michaux wrote:

> On Jul 20, 3:02 pm, Richard Cornford wrote:
>> Peter Michaux wrote:
>>> On Jul 20, 5:40 am, Richard Cornford wrote:
<snip>
> I'm a little surprised you know so much about the
> internals of Prototype.js.

We do get asked about it form time to time, so some familiarity with the
code seems like a good idea if those questions are to be answered
(responsibly).

> I haven't looked inside that library for more than
> a few minutes in the last year. All that $, $A, $H,
> and my personal "favorite" $$ are successful repellent.

That particular mistake in Prototype.js does contribute to some pretty
opaque code, both within it and using it.

> I don't even know what these functions do exactly
> and in some case not even partially.

No, but you can guess that whatever it is they will probably do it
badly. The - $A - in the 1.6.0.2 version, for example, would loop for
ever if the argument passed to it was an object with a negative -
length - property (and that is a fault that could be fixed with zero
impact on its performance).

> The last time I did look in the library it seemed
> that the Prototype.js typists don't seem to care about
> any sort of modularity in their code. It is one big lump
> that cannot be untangled.

That is another factor that makes understanding it much harder than it
needs to be.

> I am not surprised that their heavy-duty "bind" is used
> internally. RobG pointed out sometime that many functions
> in Prototype.js start by calling $ for one of the arguments.
> Even functions that aren't API functions do this which is
> completely pointless.

Yes, superficially it looks like an effort to make the code tolerate
mistakes made by its programmers (internal and external), but if that
was the intention you have to wonder why functions like $A are so
vulnerable to incorrect argument types.

> This example of "bind" does make a good point that the
> function is more "general" than needed in the particular
> case. But there would only be a need to write a more
> trimmed down version if the performance penalty of the
> current function is causing a problem.

But the performance is bad. I have followed attempts to speed JQuery up
more than Prototype.js, and JQuery is really bad in that regard. Over
the years there have been numerous posts to this group from people
complaining, for example, that sorting a 4,000 row table was
unacceptably slow. To which there is pretty much no answer but to say
that if you create a table that large manipulating it through the DOM
with javascript is going to be slow no matter how much optimisation is
done in the code. But I am reading people complaining about JQuery
performance at table sorting with 200-400 rows.

> I don't want to make it seem like writing especially
> inefficient code is ok until there are customer complaints
> but there is a balance which needs to be struck in the
> interest of saving development dollars.

Generally is does seem like a good idea to aim to get something working
well before worrying about performance. On the other hand, there are
aspects of performance that can be considered at the design stage and
would be expensive to sort out (re-design) later.

<snip>
>>> A large portion of the remainder of your message below is
>>> related to keeping code small which is really just for a
>>> performance boost.
>
>> Not just, size and the internal complexity that manifests
>> itself in size have consequences for understandably and
>> so maintainability.
>
> I've thought about the saying "premature optimization is
> the root of all evil" in several different ways recently.
> I believe the original use was with respect to reducing
> CPU use and so speeding up the program. Now this saying
> can apply to size, speed, maintainability, generality.

How would premature maintainability work?

> None of these should be optimized prematurely as the
> others usually suffer.

For which a good picture of what (or rather when) qualifies as
"premature". Which is likely to end up being answered on a per-context
basis, as you know there will be contexts where performance is paramount
and others were it would be a near irrelevance.

> That is the reason I pressed the issues I did below.
> Sometimes it seems you are advocating optimizing size,
> speed at the cost of maintainability (i.e. multiple versions)
> and generality.

The code authoring strategy that I advocate is primarily aimed at
maximum code re-use and fast development. On their own, those two
aspects are not enough to justify one choice over another, so it is
useful to point out their impacts in other areas. But I don't see what
maintainability has got to do with this as using pre-written, well
tested and reliable implementations of task specific interfaces in the
form of discrete modules does not harm maintainability. Indeed my
experiences suggests it aids it considerably; even finding bugs in an
interface implementation allows for fixing those and testing the result
in isolation form the rest of the system, and the results do not tend to
have any (non-positive) impact outside of the module in question.

>>>> There is, of course, the question of cancelling default
>>>> actions and propagation.
>
>>> Do you happen to know of a way to detect the Safari versions
>>> which do not honour calls to preventDefault for click or
>>> double click events when the listener was attached using
>>> addEventListener?
>
>> No, it has not yet been an issue for me. But Safari browsers
>> expose many 'odd' properties in their DOMs so I would bet that
>> an object inference test could be devised even if a direct
>> feature test could not, and the result would still be fare
>> superior to the UA sniffing that seems to go on at present.
>
> I'd be surprised if there is an object change between versions
> 2.0.2 and 2.0.3 (if those are the correct numbers, I have to
> check) where the bug was fixed.

I think you would be surprised by how many strange, non-documented and
non-standard features Safari's object model does expose (or has
exposed).

<snip>
>> Matt's position has tended to be that someone else (someone
>> other than him) should write it.
>
> I think there is a bit of that but also I think he wants to
> read an acknowledgement that using code that is slightly too
> general for a situation is tolerable.

If so that will be because he want to use such a statement as a
justification for using code that is way too general for a situation.

<snip>
>>> In messages like this one, you on the other hand seem to
>>> eschew things "general" (though I don't think you do so 100%).
>>> Take, for example, the scroll reporting code you wrote in the
>>> FAQ notes
<snip>
>>> I consider that level of "multi-browserness" sufficiently
>>> "general". That is, I would be comfortable using this type
>>> of code on the unrestricted web.
>
>> But it is not general. The only dimension in which it is
>> general is the browser support dimension (though it should
>> not be too bad in the programmer understanding dimension).
>> It does not even cover the general case of wanting to find the
>> degree to which a document has been scrolled because it has no
>> facility for reporting on any document other than the one
>> containing the SCRIPT element that contained/loaded its code.
>> Add support for multi-frame object models and you have something
>> else again.
>
> Fair enough. I really don't think about multiple frames/windows
> as I almost never use them (at not least where this would matter.)

But code supporting multiple frames is a more general code than code
that only supports the frame it is loaded into.

>>> What if it was 20 lines? 50 lines? 200 lines? 10000 lines? The
>>> absolute size of the general code does matter to some extent.
>
>>> Imagine the 200 line version's only drawback was download time
>>> and initial interpretation, with no other runtime penalties.
>>> If that code was already written, would it be worth using in
>>> situations where code only 50% size could be used given the
>>> smaller code is not already written. Writing 100 lines of event
>>> library code is probably not trivial and require heavy testing.
>>> I would use the 200 line version as it is ready, tested,
>>> cacheable, not really a download burden for the majority of
>>> today's network connections (even most mobile networks).
>
>> In that hypothetical situation, I probably would use the code
>> as well.
>
> [point A] I will refer to this below.
>
>>> I think that the extreme positions for and against "general"
>>> are both faulty.
>
>> You are not seeing the question of how 'general' is "general".
>> An event library, no matter how large/capable is not 'general'
>> in every sense. It should have no element retrieve methods,
>> no retrieval using CSS selectors, no built-in GUI widgets, etc.
>
> I don't know how CSS selectors or built in widgets have anything
> to do with an event library.

They shouldn't have anything to do with an event library. Separating
separate responsibilities into discrete units/modules is a good design
strategy (leaving the extent to which that is done (the sizes/scopes of
those units/modules) the subject of debate).

However, the general-purpose librates do not follow this strategy (or
where they do they do not follow through with this strategy). JQuery,
for example, has recently grown a (predictably dodgy) position reporting
API as part of its core. JQuery's plug-in philosophy is one of the few
better ideas in its design, but runs against pressures for the library
to be more general and in the end loses to the louder popular demand.

>> It is (potentially) a task specific component, even if it
>> comprehensively addresses its task.
>
> If it could truly comprehensively do its job then I think that
> would mean "general".

In one sense. It would not be generally applicable as the overheads in
handling the multi-frame (with sub-frames reloading at intervals) would
be considerable and pointless in a single page context.

>>> When general code has acceptable performance, then creating
>>> and maintaining one version is the winner.
>
>> Given the number of dimension to 'general' it has not yet been
>> demonstrated that truly general code can be created so any "when
>> general code ..." statement is irrelevant. Certainly if you can
>> get away with a single implementation of a task-specific component
>> then there is no need for an alternative.
>
> That is a valuable acknowledgement by including "get away with".

There is always a place for pragmatism, and I have always advocated
designing for the context, which necessitates appreciating the full
context.

>>> I think an event library falls into this category.
>
>> Of a component that only needs one good implementation? I doubt
>> that, in Aaron's code to date we have not even discussed the
>> possibilities opened up by having multiple frames (where
>> normalising the event to - window.event - is not necessarily
>> the right thing to do). I would not like the idea of importing
>> a large-ish single document component into every individual
>> frame when a slightly larger multi frame system could do the
>> whole job, or of using that larger multiple frame version in a
>> single page application.
>
> Given the your acknowledgement at "point a" above, it would
> seem the size of "slightly" might play a role.

Yes.

> If the slightly larger multi-frame system was written and there
> was a tight deadline, I would use it.

How tight is the deadline and what does not written mean in context? If
an overall strategy of building form re-usable low level components,
through intermediate components up to the final top level has been
employed then the odds are good that at least some (and likely much) of
the existing code can be re-used in the 'writing from scratch', and then
some other components may be available in addition. If the deadline is
really tight none of that will matter, but otherwise ...

> If the single page version was already written and could do
> the job by being included in every individual frame then I
> would use it on a tight deadline. Caching could be set up with
> some no-check, far-future expiration date header so there is
> no cost to including it in every page.

That is not "no cost", it is a minimal download cost but the code must
still be compiled whenever a page loads, and complied before any
page-specific set-up can be performed.

<snip>
>> It is not just optimisation. It is not solving problems that
>> are currently not your problems, it is not spending time
>> writing code that is not needed, and it is not spending time
>> testing code that will not be executed in the context of its
>> use.
>
> Nicely written; however, if the code is already written, tested,
> and available for download from the web, but solves a problem
> more general then problem at hand, where does one draw the line
> and say it is *too* general? There must be some observable that
> indicates this situation. For example "the client is complaining
> downloads are too long" or "the client is complaining the
> application is not responsive enough" or "the other programmers
> are spending too much time on maintenance" or the genuine
> expectation that one of these sorts of problems will arise.

And where a less, but still sufficiently, general alternative is
available ("already written, tested, and available for download from the
web")?


===========================================================
>
> The majority of JavaScirpt programmers (almost all less "uptight"
> than us) seem to agree that there is a problem that can be
> specified and solved with a library that can be shared on the
> web to the benefits of others.

For the majority of "JavaScript Programmers" the "problem" is how to do
their job while having a near negligible technical understanding of the
technologies involved. They are right in seeing anything that lets them
get away with that as a solution to their problem, and it is
unsurprising that they would then apply pressure to authors of these
libraries to cover more of the circumstances they fact but could not
cope with for themselves.

It is the solving of a problem, but it is not necessarily resulting in
the best solution for the problems that should be getting the best
solutions (the problems belonging to the clients/employers and projects
being worked on).

> Perhaps each project you work on is so radically different,
> and perhaps quite advanced, that your given problems are not
> solved well by the feature sets of these prepackaged libraries
> (leaving the quality of these libraries aside for a moment.)

I am employed as a specialist in a context where a specialist is
necessary. The 'popular' general-purpose libraries really are an
irrelevance in my context; they just cannot cut it there.

But knowing how trivially easy it is to achieve many of the things that
I see these libraries being used for on the public Internet I sill see
much of what they do get used for as totally inappropriate (even
disregarding all the browser support inevitably sacrificed whenever one
is used). It is clear that much of what is happening in this regard
follows from people employing other people's examples in the same
unconsidered 'copy-n-past' style that has been the norm in web
development for its entire existence.

> For my own use, I developed a library and slapped the label
> "Fork" onto it. I think it solves roughly the same problem
> as the base utilities of YUI!, Prototype.js, jQuery, Dojo,
> etc. This vaguely specified problem is what people call the
> "general" problem and the use of "general" in this case is
> incorrect. Your use of "general" is better. The same problem
> occurs with the distinction between multi-browser and
> cross-browser. Libraries claiming to be cross-browser are
> usually just multi-browser for some very small value of
> "multi". I will endeavor to be more careful about my use
> of the word "general".
>
>
> What would be great is if there was a word for this vaguely
> specified problem that so many libraries roughly solve
> because these libraries, though more general than necessary
> in many cases, are acceptable solutions for what I dare to
> say are "most" browser scripting problems.

I don't think that they are acceptable solution for most browser
scripting problems. Their shortcomings probably go unnoticed by the
clients that pay for these solutions (because they will tend to use
default configurations of 'common' web browsers and will never have seen
the potential of the optimum solutions to their problems). I don't see
not observing issues as being the same as there not being issues
(consider your own blind-spot for multi-frame scripts because you don't
see that problem in your work).

> Many of the regulars past and present of this group have
> maintained their own solutions to roughly the same problem.
> Matt Kruse, Thomas Lahn (I'll probably get crucified for
> including his name in this list), David Mark, and I have
> all shown code we have written that we can cart around to
> various projects. The code may be too general in some cases
> but the cost of extra download time is acceptable (perhaps
> unnoticeable) and the paying customer would rather we just
> get on with development than trimming the code down to a
> minimal implementation.

You don't think that I have code that I can "cart around"?

> The vaguely specified problem is *something* *approximately*
> like the following. This is probably closer to the problem
> that the c.l.js regulars solve than the mainstream libraries
> solve but they aren't far off.
>
>
> ---------------
>
>
> Full support in this generation of browsers
> IE6+, FF1+, O8.5+, S2+
> No errors (syntax or runtime) in
> IE5+, NN6+, O7+, S1+
> Possibly syntax errors but no runtime errors in
> IE4+, NN4+
> Who cares
> IE3-, NN3-

What about all the other browsers? And what about the configurable
aspects of the browsers you have listed? With ActiveX disable IE 6 won't
do AJAX despite its being IE 6.

The problem to be solved is to identify an exploit the capabilities of
any browser that provides the necessary capabilities and to provide
planed and controlled outcomes on all that do not. The "who cares" list
is only acceptable because there does come a point when browsers are too
old to be worth considering, but apart form that thinking in terms of
lists of browsers should be restricted to the practical necessities of
testing and not a significant part of the design problem (except in
known environments were a list may be part of the context).

> A single page implementation of an event library. (Not worried about
> frames/windows as you discussed above.)
>
> Probably many, many other restrictions/requirements/assumptions.
>
> ---------------
>
> The above specific problem (or one quite close) seems to be
> the one that needs solving most frequently and so is the one
> for which the most prefabricated is available for download on
> the web.

What you are describing as a problem here is not a problem at all; it is
one possible (even if likely/common) aspect of the solutions to other,
more specific problems.

> This has been
> incorrectly referred to as the "general" problem and the
> available solutions have been labeled "cross-browser" even
> though they are not even close and don't even attempt to use
> feature testing well.
>
> Since this problem arises so frequently it is good that
> programmers share code to solve this problem (or problems
> very similar).

Good and bad. Bad in the sense that people who appreciate the
consequences adopt code that becomes the limiting factor in the projects
where they use it without knowing that they are doing that, let alone
considering whether that is appropriate for their context.

> The fact that this problem is more general than
> necessary in many cases is clearly not a problem. If it were
> then customers would have complained enough that some other
> problem would be solved (perhaps your multiple implementations
> system would be popular.)

Customers don't complain; they go somewhere else. Search engines are
used to provide a list of alternatives so it doesn't often matter if
some of those alternatives don't work so long as there are more in the
list. And if you have ever tried complaining about
defective/non-functional/error throwing web sites you would quickly
learn why anyone who attempted to complain would rapidly be strongly
discouraged from carrying on.

> I have tried solving only the problem at hand for a given
> web page. As more requirements arrive from the customer,
> I find I always end up, once again, solving this same
> vaguely specified problem.

Yes, so what you are describing as the problem is in reality just an
aspect of the solutions to other problems. If you were writing systems
for intranets you would probably find those other problems still
occurring, but your "vaguely specified problem" might never appear at
all.

> Perhaps this vaguely specified
> problem is exactly at the perceived level of functionality
> the web can provide without being too expensive to develop.

Very unlikely.

> There is something to this vaguely specified problem, don't
> you agree?

No, it is just symptom of something else.

Richard.

dhtml

unread,
Aug 30, 2008, 11:33:34 PM8/30/08
to
Richard Cornford wrote:
> On Jul 21, 1:09 am, Peter Michaux wrote:
>> On Jul 20, 3:02 pm, Richard Cornford wrote:
>>> Peter Michaux wrote:
>>>> On Jul 20, 5:40 am, Richard Cornford wrote:


> No, but you can guess that whatever it is they will probably do it
> badly. The - $A - in the 1.6.0.2 version, for example, would loop for
> ever if the argument passed to it was an object with a negative - length
> - property (and that is a fault that could be fixed with zero impact on
> its performance).

The same type of bug exists in the Array extras in Firefox.

Array.forEach({length:-1}, funciton(i){document.title=i;});

Firefox 3:
javascript:void(Array.forEach({length:-1}, funciton(i){document.title=i;}));

Result:
"Slow Script" dialog.

Garrett

0 new messages