有什么方法可以重构此代码以提高性能?

时间:2020-01-21 03:53:41

标签: javascript html node.js

所以我有一个练习的问题。代码可以工作,但是我想提高代码的性能,这样就不会出现时间复杂性问题(例如,没有嵌套的循环)。但是我似乎找不到办法。

任务是创建一个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

}

1 个答案:

答案 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回调仅在微任务中运行,而不是同步运行。

如果您需要同步缓存集合更新,则可以监视DOMNodeRemovedDOMNodeAdded事件,这些事件是同步的,但已弃用,不建议这样做。

由于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">