自定义querySelectorAll实现
custom querySelectorAll implemention
这是作为面试问题提供给我的 -- 没有得到这份工作,但我仍然想弄清楚。
objective 是写两个 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;
}
我的代码的第一个问题,除了它不起作用之外,是它只处理两个选择器(但它应该能够清除第一种、第二种和第四种情况)。
第二个问题是我无法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
长度的 i
在 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;
}
这是作为面试问题提供给我的 -- 没有得到这份工作,但我仍然想弄清楚。
objective 是写两个 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;
}
我的代码的第一个问题,除了它不起作用之外,是它只处理两个选择器(但它应该能够清除第一种、第二种和第四种情况)。
第二个问题是我无法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
长度的 i
在 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;
}