2016-02-05 2 views
1

Это было дано мне как вопрос для интервью - не получилось, но я все еще хочу понять это.custom querySelectorAll реализация

Цель состоит в том, чтобы написать две querySelectorAll функции: один называется qsa1, который работает для селекторов, состоящих из одного имени тега (например div или span), а другой называется qsa2, которая принимает произвольно вложенные селекторы тегов (такие, как p span или ol 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; 
    } 

Первая проблема с моим кодом, кроме того, что он не работает, то, что он обрабатывает только два селектора (но он должен быть в состоянии очистить первый, второй и четвертый случаи).

Вторая проблема заключается в том, что у меня возникают проблемы с возвратом правильного результата. Я знаю, что, как и в qsa1, я должен возвращать тот же результат, что и я, получив вызов функции getElementsByTagName(), которая «возвращает живые NodeList элементов с указанным именем тега». Создание массива и нажатие или добавление Node s к нему не разрезают его.

Как составить правильный результат возврата?

(для связи, полное тело кода можно найти here)

ответ

0

Вот как я это сделать

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 и не может быть нажата.

+0

Это прекрасно работает! Спасибо, что показали, как успешно выполнить итерацию через поддерево DOM и сформировать возвращаемый массив. Очевидно, я все еще получаю JS, и ваши комментарии очень полезны. – cecil

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

Вместо того, чтобы идти по длине children, вы идете по длине parent.

Как и тот факт, что вы повторно используете имена переменных итераций! Это означает, что i, отображаемый на длину parent, перезаписывается в цикле child!

На боковой ноте цикл 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; 
}