所以我有一个练习的问题。代码可以工作,但是我想提高代码的性能,这样就不会出现时间复杂性问题(例如,没有嵌套的循环)。但是我似乎找不到办法。
任务是创建一个JavaScript选择引擎,该函数将在给定CSS选择器的情况下返回DOM元素。我不能使用任何外部库或document.querySelector / queryselectorAll。
HTML主体是这个
import React, { Component } from 'react';
import { TextInput, Button, ScrollView, View } from 'react-native';
export default class CoolComponent extends Component {
state = {
isShowingText: false
}
nameAdd = () => {
this.setState({
isShowingText: true
})
}
render() {
return (
<View style={{ alignItems: 'center', top: 50 }}>
{this.state.isShowingText ? <TextInput style={{ width: '50', height: '50', borderWidth: 1 }}></TextInput> : null}
<ScrollView></ScrollView>
<Button
title="Press me"
onPress={this.nameAdd}
/>
</View>
);
}
}
我当前的解决方案是
<body>
<div></div>
<div id="some_id" class="some_class some_other_class"></div>
<img id="some_other_id" class="some_class some_other_class"></img>
<input type="text">
</body>
示例的最终结果应为
var $ = function (selector) {
if (typeof selector !== "string"){
throw TypeError("Please enter a selector in a string format")
}
let results = [];
let classes = getClasses();
let hasClass, hasID, str, a
if (document.getElementsByTagName(selector)){
hasClass = selector.includes(".") ? true : false
hadID = selector.includes("#") ? true : false
}
if (hasClass && hasID) {
let i = 0;
while (i < classes.length){
if (selector.includes(classes[i])) {
str = classes[i];
a = document.getElementsByClassName(str);
if (selector.includes(a[i].id)) results.push(a[i]);
}
i++
}
} else if (hasClass) {
let list1, list2;
for (i = 0; i < classes.length; i++) {
if (selector.includes(classes[i])) {
str = classes[i];
a = document.getElementsByClassName(str);
for (j = 0; j < a.length; j++) {
if (selector.charAt(0) === ".") {
results.push(a[j]);
} else if (selector.includes(a[j].tagName.toLowerCase())) {
results.push(a[j]);
}
}
}
}
} else if (hasID) {
if (selector.charAt(0) === "#") results.push(window[selector.substring(selector.indexOf("#") + 1, selector.length)]);
} else {
for (var i = 0; i < t.length; i++) results.push(t[i]);
}
return results;
}
function getClasses(){
let nodesArray = document.body.childNodes
let results = [];
i = 0;
while (i < nodesArray.length){
let element = nodesArray[i].nodeName;
if (!element.includes("#")){
for (j = 0; j < nodesArray[i].classList.length; j++) results.push(nodesArray[i].classList[j]);
}
i++
}
let x = Array.from(new Set(results))
console.log(x)
return x
}
答案 0 :(得分:1)
元素具有内置的方法,可让您检查它们是否与特定的选择器匹配:.matches
。不必为已经解决的问题重新发明轮子就容易得多。获取文档中的所有元素,然后在每次调用$
时,返回一个按元素是否匹配选择器进行过滤的元素数组:
const getAllChildren = (parent, results = []) => {
Array.prototype.forEach.call(
parent.children,
(child) => {
results.push(child);
getAllChildren(child, results);
}
);
return results;
};
var $ = function (selector) {
return getAllChildren(document.body)
.filter(elm => elm.matches(selector));
}
console.log($("div")) // Should return 2 DIVs
console.log($("img.some_class")) // Should return 1 IMG
console.log($("#some_id")) // Should return 1 DIV
console.log($(".some_class")) // Should return 1 DIV and 1 IMG
console.log($("input#some_id")) // Should return an empty array
console.log($("div#some_id.some_class")) // Should return 1 DIV
console.log($("div.some_class#some_id")) // Should return 1 DIV
<div></div>
<div id="some_id" class="some_class some_other_class"></div>
<img id="some_other_id" class="some_class some_other_class"></img>
<input type="text">
如果您事先知道在特定期间内不会添加或删除任何新节点,则可以缓存子节点,而不用重新检索它们:
const getAllChildren = (parent, results = []) => {
Array.prototype.forEach.call(
parent.children,
(child) => {
results.push(child);
getAllChildren(child, results);
}
);
return results;
};
const allElements = getAllChildren(document.body);
var $ = function (selector) {
return allElements.filter(elm => elm.matches(selector));
}
console.log($("div")) // Should return 2 DIVs
console.log($("img.some_class")) // Should return 1 IMG
console.log($("#some_id")) // Should return 1 DIV
console.log($(".some_class")) // Should return 1 DIV and 1 IMG
console.log($("input#some_id")) // Should return an empty array
console.log($("div#some_id.some_class")) // Should return 1 DIV
console.log($("div.some_class#some_id")) // Should return 1 DIV
<div></div>
<div id="some_id" class="some_class some_other_class"></div>
<img id="some_other_id" class="some_class some_other_class"></img>
<input type="text">
另一种避免在调用$
时必须重新获取所有元素的方法是在文档上设置一个深层的MutationObserver,它将监听DOM中任何位置的添加和删除的节点,然后添加/从集合中删除那些。但是,这种方法对于频繁更改的大页面来说很昂贵,并且MutationObserver回调仅在微任务中运行,而不是同步运行。
如果您需要同步缓存集合和更新,则可以监视DOMNodeRemoved
和DOMNodeAdded
事件,这些事件是同步的,但已弃用,不建议这样做。
由于Kaiido的建议,您还可以使用TreeWalker获取某个父对象的所有后代元素:
var $ = function (selector) {
const results = [];
const treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
// Works, but less efficient than checking inside the while loop below:
// { acceptNode: node => node.matches(selector) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }
null
);
while(treeWalker.nextNode()) {
const node = treeWalker.currentNode;
if (node.matches(selector)) {
results.push(node);
}
}
return results;
}
console.log($("div")) // Should return 2 DIVs
console.log($("img.some_class")) // Should return 1 IMG
console.log($("#some_id")) // Should return 1 DIV
console.log($(".some_class")) // Should return 1 DIV and 1 IMG
console.log($("input#some_id")) // Should return an empty array
console.log($("div#some_id.some_class")) // Should return 1 DIV
console.log($("div.some_class#some_id")) // Should return 1 DIV
<div></div>
<div id="some_id" class="some_class some_other_class"></div>
<img id="some_other_id" class="some_class some_other_class"></img>
<input type="text">