By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
438,818 Members | 2,089 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 438,818 IT Pros & Developers. It's quick & easy.

addEvent - The late entry :)

P: n/a
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-j...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
Jul 16 '08 #1
Share this Question
Share on Google+
53 Replies


P: n/a
Drip IE memory leak detector :-

Sorry that should be :-

http://www.outofhanwell.com/ieleak/i...itle=Main_Page

Aaron

Jul 16 '08 #2

P: n/a
"Aaron Gray" <an********@gmail.comwrote in message
news:6e************@mid.individual.net...
>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-j...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.
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/JavaSc...ventTest2.html

Aaron

Jul 20 '08 #3

P: n/a
"Aaron Gray" <an********@gmail.comwrote in message
news:6e************@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/JavaSc...ventTest2.html

Aaron

Jul 20 '08 #4

P: n/a
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

Jul 20 '08 #5

P: n/a
"Aaron Gray" <an********@gmail.comwrote in message
news:6e************@mid.individual.net...
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.
Here's a test case :-

http://www.aarongray.org/Test/JavaSc...ventTest3.html

Aaron

Jul 20 '08 #6

P: n/a
On Jul 19, 6:49*pm, "Aaron Gray" <ang.use...@gmail.comwrote:
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
Jul 20 '08 #7

P: n/a
On Jul 19, 6:49*pm, "Aaron Gray" <ang.use...@gmail.comwrote:
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
Jul 20 '08 #8

P: n/a
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.

Jul 20 '08 #9

P: n/a
"Richard Cornford" <Ri*****@litotes.demon.co.ukwrote in message
news:g5*******************@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
Jul 20 '08 #10

P: n/a

"Richard Cornford" <Ri*****@litotes.demon.co.ukwrote in message
news:g5*******************@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

Jul 20 '08 #11

P: n/a
On Jul 16, 8:17 am, "Aaron Gray" <ang.use...@gmail.comwrote:
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
Jul 20 '08 #12

P: n/a
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.

Jul 20 '08 #13

P: n/a
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.

Jul 20 '08 #14

P: n/a
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_not....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
Jul 20 '08 #15

P: n/a
"Richard Cornford" <Ri*****@litotes.demon.co.ukwrote in message
news:g5*******************@news.demon.co.uk...
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.
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
Jul 20 '08 #16

P: n/a
"Richard Cornford" <Ri*****@litotes.demon.co.ukwrote in message
news:g5*******************@news.demon.co.uk...
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.
So Opera needs a special case calling attachEvent on windows instead of
addEventListener ?

Aaron
Jul 20 '08 #17

P: n/a
Aaron Gray wrote:
Richard Cornford wrote:
>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.

So Opera needs a special case calling attachEvent on windows instead
of addEventListener ?
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.

Jul 20 '08 #18

P: n/a
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_not....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
Jul 21 '08 #19

P: n/a
Peter Michaux wrote:
On Jul 16, 8:17 am, "Aaron Gray" <ang.use...@gmail.comwrote:
>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, <f8*******************@news.demon.co.uk>
Jul 21 '08 #20

P: n/a
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
Jul 21 '08 #21

P: n/a
@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/a...Listener.patch
http://prototype.lighthouseapp.com/a...ethodize.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
Jul 21 '08 #22

P: n/a
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.
Jul 22 '08 #23

P: n/a
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
Jul 22 '08 #24

P: n/a
"Richard Cornford" <Ri*****@litotes.demon.co.ukwrites:
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.'
Jul 22 '08 #25

P: n/a
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
Jul 22 '08 #26

P: n/a
On Jul 22, 2:47*pm, Peter Michaux <petermich...@gmail.comwrote:
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
Jul 22 '08 #27

P: n/a
On Jul 22, 10:58*am, kangax <kan...@gmail.comwrote:
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?
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
Jul 22 '08 #28

P: n/a
Lasse Reichstein Nielsenwrote:
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.

Jul 22 '08 #29

P: n/a
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.

Jul 22 '08 #30

P: n/a
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.

Jul 24 '08 #31

P: n/a
On Jul 23, 8:44 pm, "Richard Cornford" <Rich...@litotes.demon.co.uk>
wrote:
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.
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
Jul 24 '08 #32

P: n/a
On Jul 21, 6:33*pm, Lasse Reichstein Nielsen <l...@hotpop.comwrote:
"Richard Cornford" <Rich...@litotes.demon.co.ukwrites:
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
Jul 25 '08 #33

P: n/a
On Jul 25, 12:46*am, dhtml <dhtmlkitc...@gmail.comwrote:
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.comwrote:
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-Eve...nts-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/libr...12(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>
<titleChange </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
Jul 26 '08 #34

P: n/a
On Jul 25, 6:28*pm, dhtml <dhtmlkitc...@gmail.comwrote:
On Jul 25, 12:46*am, dhtml <dhtmlkitc...@gmail.comwrote:
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

Jul 26 '08 #35

P: n/a
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.

Jul 26 '08 #36

P: n/a
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 += '</codeDuration (milliseconds) = ';
st += '<input type="text" value="X" name="Dur'+c+'"><br>';
st += '<code>'+fncts[c].description;
st += '</codeAverage (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</supargs = ';
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.

Jul 26 '08 #37

P: n/a
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.
Jul 26 '08 #38

P: n/a
On Jul 21, 9:47 pm, Peter Michaux <petermich...@gmail.comwrote:
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
Jul 27 '08 #39

P: n/a
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
Jul 27 '08 #40

P: n/a
On Jul 26, 5:07 pm, Peter Michaux <petermich...@gmail.comwrote:
On Jul 21, 9:47 pm, Peter Michaux <petermich...@gmail.comwrote:
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?
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
Jul 27 '08 #41

P: n/a
On Jul 26, 5:11*pm, dhtml <dhtmlkitc...@gmail.comwrote:
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);
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
Jul 27 '08 #42

P: n/a
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.

Jul 27 '08 #43

P: n/a
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.

Jul 27 '08 #44

P: n/a
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.
>>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.
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´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".
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.

Jul 27 '08 #45

P: n/a
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.

Jul 27 '08 #46

P: n/a
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.
Jul 27 '08 #47

P: n/a
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.
Jul 27 '08 #48

P: n/a
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.
Jul 27 '08 #49

P: n/a
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.

Jul 27 '08 #50

53 Replies

This discussion thread is closed

Replies have been disabled for this discussion.