自定义querySelectorAll实现

custom querySelectorAll implemention

这是作为面试问题提供给我的 -- 没有得到这份工作,但我仍然想弄清楚。

objective 是写两个 querySelectorAll 函数:一个叫做 qsa1 ,它适用于由单个标签名称组成的选择器(例如 divspan) 和另一个名为 qsa2 的标签选择器,它接受任意嵌套的标签选择器(例如 p spanol li code)。

我第一个很容易,但第二个有点棘手。

我怀疑,为了处理可变数量的选择器,正确的解决方案可能是递归的,但我想我会先尝试让迭代的东西工作。到目前为止,这是我得到的:

  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;
  }

我的代码的第一个问题,除了它不起作用之外,是它只处理两个选择器(但它应该能够清除第一种、第二种和第四种情况)。

第二个问题是我无法return获得正确的结果。我知道,就像在 qsa1 中一样,我应该 return 得到与调用 "returns a live NodeList of elements with the given tag name" 函数 getElementsByTagName() 相同的结果。创建一个数组并将 Node 推入或附加到它并没有削减它。

如何编写正确的 return 结果?

(对于上下文,可以找到完整的代码here

这是我的做法

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;
}

假设我们始终以文档作为上下文开始,然后像您已经在做的那样在空格上拆分选择器,并遍历标记名。
在每次迭代中,只需覆盖外部 next 变量,然后再次 运行 循环。
我使用数组和 concat 将结果存储在循环中。

这个有点类似于题中的代码,但是需要注意的是你从来没有创建数组,实际上matches变量是undefined,不能push到.

这里有语法错误:

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++) { // <-----------------------

您不会超过 children 的长度,而是会超过 parent 的长度。

以及您正在重用迭代变量名称这一事实!这意味着映射到 parent 长度的 ichild 循环中被覆盖!

附带说明一下,如果元素为空,for 循环将不会遍历元素,因此您的检查是多余的。

应该是:

for (var i = 0; i < parents.length; i++) {
    children = parents[i].getElementsByTagName(selectors[1]);
        for (var k = 0; k < children.length; i++) { 

我建议使用递归解决方案,而不是使用迭代解决方案,如下所示:

var matches = [];
function recursivelySelectChildren(selectors, nodes){
    if (selectors.length != 0){
        for (var i = 0; i < nodes.length; i++){
            recursivelySelectChildren(nodes[i].getElementsByTagName(selectors[0]), selectors.slice(1))
        }
    } else {
        matches.push(nodes);
    }
}
function qsa(selector, node){
    node = node || document;
    recursivelySelectChildren(selector.split(" "), [node]);
    return matches;
}