我正在尝试实现 getByClassnameHierarchy 但在实现方面遇到问题。 我应该使用 BFS 而不是 DFS 来遍历节点吗?
有人可以向我解释一下使用这些方法的方法以及优点和缺点吗?
// Helper function to make output easier to read
const getIds = (elements=[]) => Array.from(elements).map(x => x.id);
/**
* Return all DOM elements who are _leaf_nodes_ that satisfy the hierarchy.
* Hierarchy is a string of class names separated by `>`, akin to
* CSS CHILD SELECTORS.
*
* ex. getByClassnameHierarchy(#root, 'a>b>c') -> [<div class="c" id="c-1"></div>,<div class="c" id="c-2"></div> ]
* "give me all the elements with class name 'c', who have a strict parent with
* class name 'b', who have a strict parent with class name 'a'"
*
* @param root DOMElement: start the search from this DOM element
* @param hierarchy string: `>`-delimited string of classnames
* @return Array<DOMElement>: all DOM elements that satisfy the target hierarchy
*/
function getByClassnameHierarchy(root, hierarchy) {
// parentNode
const res = [];
const level = hierarchy.split('>');
helper(root, level, 0);
return res;
function helper(root, level, cur) {
if(!root) return
if(root.classList && root.classList.contains(level[cur-1])){ // b
if(root.parentNode.classList.contains(level[cur-2])) { // a
if(root.classList.contains(level[cur-1])) {
res.push(root);
}
}
} //c
root.childNodes.forEach(child => {
helper(child, level, cur + 1);
});
}
}
const root2 = document.getElementById('root2');
// // basic case:
console.log('actual: ', getIds(getByClassnameHierarchy(root2, 'a>b>c')));
console.log(`a>b>c expected:` , `['c-1', 'c-2']`, '\n');
<div id="root2">
<div class="a" id="a-1">
<div class="b" id="b-1">
<div class="c" id="c-1"></div>
<div class="c" id="c-2"></div>
</div>
</div>
</div>
问题:
预期返回:[ 'b-1', 'c-1', 'c-2' ]
而不是 ['c-1', 'c-2' ]
不知道我错在哪里。
最佳答案
2020 年编辑(感谢 @techguy2000 的评论):
此版本应该正确处理根元素本身具有某些类的情况。它还修复了先前版本的一些问题:
const getIds = (elements = []) => Array.from(elements).map(x => x.id);
function getByClassnameHierarchy(element, hierarchy, level = 0) {
const result = [];
const classNames = hierarchy.split('>');
const currentClassName = classNames[0];
const isClassFound = element.classList && element.classList.contains(currentClassName);
const isLastClass = classNames.length === 1;
if (isClassFound) {
if (isLastClass) {
result.push(element);
} else {
element.childNodes.forEach(child => {
result.push(...getByClassnameHierarchy(child, classNames.slice(1).join('>'), level + 1));
});
}
}
if (level === 0) {
element.childNodes.forEach(child => {
result.push(...getByClassnameHierarchy(child, hierarchy, level));
});
}
return result;
}
// Test
const root2 = document.getElementById('root2');
console.log('a>b>c actual:', getIds(getByClassnameHierarchy(root2, 'a>b>c')));
console.log('a>b>c expected:', ['c-1', 'c-2', 'c-3', 'c-4']);
console.log('b>c actual: ', getIds(getByClassnameHierarchy(root2, 'b>c')));
console.log('b>c expected:', ['c-1', 'c-2', 'c-3', 'c-4']);
console.log('b actual: ', getIds(getByClassnameHierarchy(root2, 'b')));
console.log('b expected:', ['b-1', 'b-2']);
console.log('a>c actual: ', getIds(getByClassnameHierarchy(root2, 'a>c')));
console.log('a>c expected:', []);
<div class="a" id="root2">
<div class="a" id="a-1">
<div class="b" id="b-1">
<div class="c" id="c-1"></div>
<div class="c" id="c-2"></div>
</div>
</div>
<div class="b" id="b-2">
<div class="c" id="c-3"></div>
<div class="c" id="c-4"></div>
</div>
</div>
初始答案:
你可以这样做(查看评论以了解详细信息):
const getIds = (elements = []) => Array.from(elements).map(x => x.id);
function getByClassnameHierarchy(root, hierarchy, level = 0) {
let result = [];
// Grab the class names
const classNames = hierarchy.split('>');
// Retrieve the current (first) one
const currentClassName = classNames[0];
// For each child
root.childNodes.forEach(child => {
// If it contains the given class
if (child.classList && child.classList.contains(currentClassName)) {
// Append the result of the following selector on this child,
// or the child itself if we're at the last part of the selector
result = result.concat(classNames.length > 1
? getByClassnameHierarchy(child, classNames.slice(1).join('>'), level + 1)
: child);
}
// Otherwise, if we're still on the first part of the selector,
// append the result of the same selector on this child
else if (level === 0) {
result = result.concat(getByClassnameHierarchy(child, hierarchy, level));
}
});
return result;
}
// Test
const root2 = document.getElementById('root2');
console.log('a>b>c actual:', getIds(getByClassnameHierarchy(root2, 'a>b>c')));
console.log('a>b>c expected:', ['c-1', 'c-2']);
console.log('b>c actual: ', getIds(getByClassnameHierarchy(root2, 'b>c')));
console.log('b>c expected:', ['c-1', 'c-2', 'c-3', 'c-4']);
console.log('b actual: ', getIds(getByClassnameHierarchy(root2, 'b')));
console.log('b expected:', ['b-1', 'b-2']);
console.log('a>c actual: ', getIds(getByClassnameHierarchy(root2, 'a>c')));
console.log('a>c expected:', []);
<div id="root2">
<div class="a" id="a-1">
<div class="b" id="b-1">
<div class="c" id="c-1"></div>
<div class="c" id="c-2"></div>
</div>
</div>
<div class="b" id="b-2">
<div class="c" id="c-3"></div>
<div class="c" id="c-4"></div>
</div>
</div>
关于javascript - getByClassnameHierarchy 实现的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55325681/