What type should .findAll return

Hi All,

So, we've debated a lot the exact syntax for .find/.findAll. However I
intentionally requested that we split out the discussions about return
type for .findAll to a separate thread. So I'm starting that thread
here.

There are a few goals for the return'ed object that I've envisioned
based on discussions so far:

1. It should have at least all of the non-mutating Array methods on
it. Possibly the mutating methods too if we allow the returned object
to be mutable.
2. It should have a object on the prototype chain where we can insert
functions that are specifically useful for lists of nodes. Examples
include .find/.findAll/.matchesSelector/.remove/.addEventListener
3. It would be good if it had the Array prototype object on it's
prototype chain so that if Array.prototype was extended, it would
affect this object too.
4. The object will *not* be live since live results from selector
matching is slow.

Since the returned object won't be live, I don't see a reason to make
it immutable. Hence it seems like we could put Array.prototype on the
prototype chain which would immediately make all non-mutating as well
as mutating functions available on the object.

We should also insert a new prototype object in the prototype chain.
Hence we end up with something like:

object -> [some type].prototype -> Array.prototype -> Object.prototype.

And to ensure that the object acts as much as possible as an array it
should also have it's [[Class]] set to that of an array. This has
subtle effects on a number of functions. For example it affects what
Object.toString() and Array.isArray returns, it affects how
Array.concat behaves, and it affects the JSON serializer.

I'm not sure if setting the [[Class]] to that of an array also gives
the object the magical .length property, but if it doesn't, we need to
also define that the returned object has such a property. Note that
for Arrays, .length doesn't live on the prototype chain, but is rather
a special property on the object itself.

In other words, the returned object is exactly what you'd get if you did:

a = new Array;
a.__proto__ = [some type].prototype;
[some type].prototype.__proto__ = Array.prototype;

and then filled a with the set of nodes which matched the selector
passed to .findAll.


So the remaining question is, what should we use for [some type]. One
option is to use NodeList. However this would result in NodeLists
having Array.prototype on it's prototype chain. Including all mutating
functions. This is iffy in general since NodeLists are returned from
several APIs which return objects which represent a live result of a
query. For example .getElementsByTagName, .getElementsByClassName and
.childNodes.

An additional source of iffiness with this idea is that several of the
mutating methods on Array.prototype don't throw if called on a
immutable objects. For example .pop, .shift, .sort and .reverse all
would not throw if called on an empty immutable list.

Hence I propose that we add a new type. I don't care much for naming
things so I'll just suggest NodeArray for now and let others fight it
out over the name.

For now we can leave NodeArray as empty and just let it be an
extension point for page authors. We can discuss separately if
.findAll/.matchesSelector should be added to NodeArray, and if so how
they should behave.

However, we should probably use NodeArray to "fix" one of the problems
with some of the functions on Array.prototype. For example
Array.prototype.filter always returns a new Array object. This would
mean that:

elem.findAll(...).filter(function(node) { ... });

will return a plain Array and not a NodeArray. However we could make
NodeArray override all such functions and keep their behavior
identical except that they return NodeArrays.

Another way to fix this problem would be to change the definition of
Array.prototype.filter, but I have no idea if that's doable, or how
that would be done.


What do people think?

/ Jonas

Received on Friday, 11 November 2011 09:06:10 UTC