tmpvar/jsdom — pull request #975 by Joris-van-der-Wel (2014-12-07 23:04:44+00:00)
Title: Use a real array to store child nodes internally

Because of the way NodeList is implemented in jsdom, appendChild is extremely slow.

Here is a benchmark I ran in chrome 39 (using browserify):
```javascript
function iteration(doc) {
var node = doc.createElement('div');
for (var i = 1; i < 500; ++i) {
node.appendChild(doc.createElement('div'));
}
}
```
Calling iteration() `100` times costs roughly:
`30ms` if I use `window.document`
`4500ms` If I use a jsdom 1.4.0 document (mutation events disabled)

After doing some profiling, the culprit seems to be the `_childNodes` `NodeList`.

In these commits instead of `NodeList`, a real array is used. This is now used internally by jsdom as much as possible.

There are some places where I did not touch the use of the `childNodes` `NodeList` because I am too unfamiliar with those parts of the code:
* lib/jsdom/browser/documentAdapter.js
* lib/jsdom/level3/xpath.js

The `childNodes` attribute is now implemented in exactly the same way as the `children` attribute, as a live `NodeList` that is lazily created. Calling, `item()` or `length` on NodeList will update it, retrieving `.childNodes` will update it too.
There is one downside to this implementation. The following code breaks the liveness of `childNodes`:
```javascript
var nodes = something.childNodes;
var len = nodes.length;
for (var i = 0; i < len; ++i) {
// modify "something" here
}
```
This is an existing issue in jsdom _(it applies to every live `NodeList`, such as returned by `.children`, `getElementsByTagName`, etc)_, however `childNodes` in jsdom used to be an exception. This exception is now gone (which I think is good because I do not like exceptions).
I suggest waiting for harmony to be stable in node.js or io.js to solve the live NodeList issue, e.g. by using the Proxy API.

Anyway, some benchmarks results, calling iteration() `100` times costs roughly:
`30ms` if I use `window.document`
`4500ms` If I use a jsdom 1.4.0 document (mutation events disabled)
`90ms` with this patch!

As a side effect, creating elements is now also much faster. This is because Object.defineProperty is now no longer called during element creation (by the NodeList constructor). Calling Object.defineProperty is very slow and should be avoided as much as possible (except when creating your prototypes of course).
Creating `12 000 000` elements of varying tag names costs roughly:
`15s` if I use `window.document`
`57s` If I use a jsdom 1.4.0 document (mutation events disabled)
`14s` with this patch!