[whatwg] Adding ECMAScript 5 array extras to HTMLCollection

On Sun, Apr 25, 2010 at 2:33 AM, David Bruant <bruant at enseirb-matmeca.fr>wrote:

>  Le 24/04/2010 22:50, J Z a ?crit :
>
> On Fri, Apr 23, 2010 at 10:30 PM, David Bruant <bruant at enseirb-matmeca.fr>wrote:
>
>> Hi,
>>
>> In the HTML5 "status of this document" section, one can read : "This
>> specification is intended to replace (be the new version of) what was
>> previously the [...] DOM2 HTML specifications."
>> This spec can be found here : http://www.w3.org/TR/DOM-Level-2-HTML/
>>
>> It defines ECMAScript language Binding (
>> http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html). This
>> document explains how to implement the DOM HTML interfaces in an
>> ECMAScript-compliant environment.
>>
>> Because HTML5 is intended to replace DOM2 HTML, it can "freely" change
>> ECMAScript bindings. My suggestion is the following :
>> Make that HTMLCollection (and all HTML*Collection, as a consequence of
>> inheritence of HTMLCollection) inherit from the ECMAScript Array prototype.
>> This way, it will make available all Array extra methods (forEach, map,
>> filter...) added in ECMAScript5 (and next versions which should go in the
>> same direction).
>>
>> As far as I know, adding this won't break any existing code. The semantics
>> of a Collection and the way it is used is very close from ECMAScript Arrays.
>> I don't think that the notion of "live object" and ECMAScript Array are
>> incompatible either.
>> Once again, I am talking about ECMAScript binding. I have no intention to
>> touch the HTMLCollection interface or other languages bindings.
>>
>> Would the WHATWG have the power to decide something similar regarding
>> NodeList ?
>>
>> Any thoughts ?
>>
>> Thanks,
>>
>> David
>>
>
> As far as I can see, liveness of HTMLCollection actually does matter. When
> iterating over HTMLCollection, it's more or less a rule of thumb to "save"
> length, to avoid any kind of mismatch (in case code within loop modifies
> document and so affects length of collection in question):
>
> for (var i = 0, length = collection.length; i < length; i++)
> // instead of:
> for (var i = 0; i < collection.length; i++)
>
> I think I can take your point as a "pro" more than a "con", because in ES5,
> right before the definition of each array extra method, a paragraph like the
> following can be found :
> "The range of elements processed by forEach is set before the first call to
> callbackfn. Elements which are appended to the array after the call to
> forEach begins will not be visited by callbackfn. If existing elements of
> the array are changed, their value as passed to callback will be the value
> at the time forEach visits them; elements that are deleted after the call to
> forEach begins and before being visited are not visited."
>
> This point is confirmed by every algorithm where the length is "saved" once
> for all before the loop and not got from the .length property each time.
>

Oh, perfect :)


If HTMLCollection was inheriting from Array, and methods like `forEach`,
`map`, etc. were to operate on a live object, there would definitely be
undesired consequences. We can see this in, say, Firefox (which allows to
set [[Prototype]] of `HTMLCollection` to `Array.prototype`):

HTMLCollection.prototype.__proto__ = Array.prototype;

document.getElementsByTagName('div').forEach(function(el) {
  el.parentNode.removeChild(el); // doesn't work as expected
});

This code doesn't work as expected as the following doesn't either :
> var divs = document.getElementsByTagName('div');
> for(var i=0, l = divs.length ; i < l ; i++){
>     var el = divs[i]; // Due to the live-ness, this might not work as
> expected
>     el.parentNode.removeChild(el);
> }
>
> This code written as a for-loop behave exactly the same way (in this case)
> as the .forEach one, so it's as buggy as the forEach one.
>

Sorry, that was a stupid example indeed. It should have been at least
something along the lines of:

var els = document.getElementsByTagName('span');

for (var i = 0; i < els.length; /* can't access length dynamically */ i++) {
  var spanEl = document.createElement('span');
  spanEl.appendChild(document.createTextNode('foo'));
  document.body.appendChild(spanEl);
}


> My point is that forEach doesn't create more bugs than before, which is
> what you seem to imply.
>

If it operates on "static" collection, then I don't see problems either.


> Adding .forEach and other Array extras wouldn't prevent programmers to
> remember that they are dealing with a live object even within a .forEach,
> the same way they are not supposed to forget it with a for-loop.
>

Sure.


>
> I have thought a lot about weirdnesses that people could think about like
> trying to assign a value to the HTMLCollection (divs[14] = myOtherDiv), but
> once again, it wouldn't be more allowed than it currently is (I have no idea
> of what happens today, but if an error is thrown in a for-loop, it should
> throw an error as well in a call within a forEach).
>

How would destructive methods like `push` or `sort` behave? Would
`document.body.childNodes.push(document.createTextNode('foo'))` append text
node to a body element? Or would it be a noop?


>
> // turning live collection into static array fixes this
>
> Array.slice(document.getElementsByTagName('div')).forEach(function(el) {
>   el.parentNode.removeChild(el);
> });
>
>
> I have not found anything about a Array.slice method and the ES5 .splice
> method doesn't seem to by usable in the way you describe. What did you mean
> ?
>

That's array generics in Mozilla (
https://developer.mozilla.org/en/New_in_JavaScript_1.6#Array_and_String_generics).
I used it for brevity, but it could have been standard
`Array.prototype.slice.call(collection, 0)` instead (in environments which
allow such conversion of course).

[...]

-- 
kangax
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.whatwg.org/pipermail/whatwg-whatwg.org/attachments/20100425/db7f0b76/attachment.htm>

Received on Sunday, 25 April 2010 00:39:39 UTC