This was given to me as an interview question -- didn't get the job, but I still want to figure it out.
The objective is to write two querySelectorAll
functions: one called qsa1
which works for selectors consisting of a single tag name (e.g. div
or span
) and another called qsa2
which accepts arbitrarily nested tag selectors (such as p span
or ol li code
).
I got the first one easily enough, but the second one is a bit trickier.
I suspect that, in order to handle a variable number of selectors, the proper solution might be recursive, but I figured I'd try to get something working that is iterative first. Here's what I've got so far:
qsa2 = function(node, selector) {
var selectors = selector.split(" ");
var matches;
var children;
var child;
var parents = node.getElementsByTagName(selectors[0]);
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) {
child = children[i];
matches.push(child); // somehow store our result here
}
}
}
}
return matches;
}
The first problem with my code, aside from the fact that it doesn't work, is that it only handles two selectors (but it should be able to clear the first, second, and fourth cases).
The second problem is that I'm having trouble returning the correct result. I know that, just as in qsa1
, I should be returning the same result as I'd get by calling the getElementsByTagName()
function which "returns a live NodeList
of elements with the given tag name". Creating an array and pushing or appending the Node
s to it isn't cutting it.
How do I compose the proper return result?
(For context, the full body of code can be found here)
Here's how I'd do it
function qsa2(selector) {
var next = document;
selector.split(/\s+/g).forEach(function(sel) {
var arr = [];
(Array.isArray(next) ? next : [next]).forEach(function(el) {
arr = arr.concat( [].slice.call(el.getElementsByTagName(sel) ));
});
next = arr;
});
return next;
}
Assume we always start with the document as context, then split the selector on spaces, like you're already doing, and iterate over the tagnames.
On each iteration, just overwrite the outer next
variable, and run the loop again.
I've used an array and concat
to store the results in the loop.
This is somewhat similar to the code in the question, but it should be noted that you never create an array, in fact the matches
variable is undefined
, and can't be pushed to.