Skip to content

Commit

Permalink
Major performance boost.
Browse files Browse the repository at this point in the history
(1) Non-live NodeLists are now implemented using
native Arrays, with an extra `item()` method to
match the NodeList interface.

(2) Using such an array for `Node.childNodes`
is much faster as it doesn't force V8 into
dictionary mode.

(3) The (live) `Node.children` list is now lazily
created, hence the performance penalty is avoided
as long as the property isn't read.
  • Loading branch information
fgnass committed Dec 21, 2011
1 parent 4c6e992 commit c795622
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 44 deletions.
64 changes: 31 additions & 33 deletions lib/jsdom/level1/core.js
Expand Up @@ -12,7 +12,7 @@ var core = {
// Returns Array
mapDOMNodes : function(parent, recursive, callback) {
function visit(parent, result) {
return parent.childNodes.toArray().reduce(reducer, result);
return parent.childNodes.reduce(reducer, result);
}

function reducer(array, child) {
Expand Down Expand Up @@ -139,6 +139,17 @@ core.DOMException.prototype = {
core.DOMException.prototype.__proto__ = Error.prototype;

core.NodeList = function NodeList(element, query) {
if (!query) {
// Non-live NodeList
var list = element || [];
list.item = function(i) {
return this[i];
};
list.toArray = function() {
return this;
};
return list;
}
Object.defineProperties(this, {
_element: {value: element},
_query: {value: query},
Expand All @@ -150,13 +161,14 @@ core.NodeList = function NodeList(element, query) {
};
core.NodeList.prototype = {
update: function() {
var i;
if (this._element && this._version < this._element._version) {
for (var i = 0; i < this._length; i++) {
for (i = 0; i < this._length; i++) {
this[i] = undefined;
}
var nodes = this._snapshot = this._query();
this._length = nodes.length;
for (var i = 0; i < nodes.length; i++) {
for (i = 0; i < nodes.length; i++) {
this[i] = nodes[i];
}
this._version = this._element._version;
Expand Down Expand Up @@ -291,27 +303,17 @@ var attrCopy = function(src, dest, fn) {
};

core.Node = function Node(ownerDocument) {
var self = this;

this._childNodes = [];
this._childNodes = new core.NodeList();
this._ownerDocument = ownerDocument;
this._attributes = new core.AttrNodeMap(ownerDocument, this);

this._childrenList = new core.NodeList(this, function() {
return self._childNodes.filter(function(node) {
return node.tagName;
});
});

this._childNodesList = new core.NodeList(this, function() {
return self._childNodes;
});

this._nodeName = null;
this._childrenList = null;
this._version = 0;
this._nodeValue = null;
this._parentNode = null;
this._nodeName = null;
this._readonly = false;
this.style = null;

This comment has been minimized.

Copy link
@tmpvar

tmpvar Feb 28, 2012

Member

this is causing problems in node 0.4.7.. is it required?

};

core.Node.ELEMENT_NODE = ELEMENT_NODE;
Expand All @@ -328,18 +330,6 @@ core.Node.DOCUMENT_FRAGMENT_NODE = DOCUMENT_FRAGMENT_NODE;
core.Node.NOTATION_NODE = NOTATION_NODE;

core.Node.prototype = {
_attributes: null,
_childNodes: null,
_childNodesList: null,
_childrenList: null,
_version: 0,
_nodeValue: null,
_parentNode: null,
_ownerDocument: null,
_attributes: null,
_nodeName: null,
_readonly: false,
style: null,
ELEMENT_NODE : ELEMENT_NODE,
ATTRIBUTE_NODE : ATTRIBUTE_NODE,
TEXT_NODE : TEXT_NODE,
Expand All @@ -354,6 +344,14 @@ core.Node.prototype = {
NOTATION_NODE : NOTATION_NODE,

get children() {
if (!this._childrenList) {
var self = this;
this._childrenList = new core.NodeList(this, function() {
return self._childNodes.filter(function(node) {
return node.tagName;
});
});
}
return this._childrenList;
},
get nodeValue() {
Expand Down Expand Up @@ -396,7 +394,7 @@ core.Node.prototype = {
set lastChild() { throw new core.DOMException();},

get childNodes() {
return this._childNodesList;
return this._childNodes;
},
set childNodes() { throw new core.DOMException();},

Expand Down Expand Up @@ -520,8 +518,8 @@ core.Node.prototype = {
this._ownerDocument._version++;
}

this._childrenList.update();
this._childNodesList.update();
if (this._childrenList) this._childrenList.update();
//this._childNodesList.update();
},

_attrModified: function(name, value, oldValue) {
Expand Down Expand Up @@ -1508,7 +1506,7 @@ core.Attr.prototype = {
for (var i=0,len=this._childNodes.length;i<len;i++) {
var child = this._childNodes[i];
if (child.nodeType === ENTITY_REFERENCE_NODE) {
val += child.childNodes.toArray().reduce(function(prev, c) {
val += child.childNodes.reduce(function(prev, c) {
return prev += (c.nodeValue || c);
}, '');
} else {
Expand Down
17 changes: 6 additions & 11 deletions lib/jsdom/selectors/index.js
Expand Up @@ -5,24 +5,19 @@ exports.applyQuerySelectorPrototype = function(dom) {
};

dom.Document.prototype.querySelectorAll = function(selector) {
var self = this;
return new dom.NodeList(self, function() {
return Sizzle(selector, self);
});
return new dom.NodeList(Sizzle(selector, this));
};

dom.Element.prototype.querySelector = function(selector) {
return Sizzle(selector, this)[0];
};

dom.Element.prototype.querySelectorAll = function(selector) {
var self = this;
if( !this.parentNode ){
self = this.ownerDocument.createElement("div");
self.appendChild(this);
var el = this;
if (!this.parentNode) {
el = this.ownerDocument.createElement("div");
el.appendChild(this);
}
return new dom.NodeList(self.ownerDocument, function() {
return Sizzle(selector, self.parentNode || self);
});
return new dom.NodeList(Sizzle(selector, el.parentNode || el));
};
};

0 comments on commit c795622

Please sign in to comment.