使用大型DOM中的document.querySelector更快地搜索元素

时间:2014-09-18 17:59:43

标签: javascript dom css-selectors casperjs selectors-api

在包含数百个元素的巨大DOM中,使用document.querySelector("input[name='foo'][value='bar']")查找元素大约需要3-5秒。有没有办法减少这个时间?可以通过提供元素的完整路径,例如document.querySelector("parent child grandchild and so on然后input[name='foo'][value='Modem']")或任何其他方式?

我正在使用CasperJS来测试一个大型网页,获取每个元素需要很长时间,这使我的测试运行了一个小时..我也尝试了__utils__.findOne()但结果是相同的3每个元素-4秒。由于我的测试主要集中在整个页面的一小部分,我希望有一些方法可以告诉document.querySelector将元素搜索集中在页面的特定部分。

如果有人从大型DOM中获取元素,有人会告诉我最快的方法吗?

更新:这就是我测量时间的方式

var init = (new Date()).getTime();
  var element=this.evaluate(function() {
        return document.querySelector('input[value='somethin'][name='somethin']');
    });
  this.echo('Time Taken :'+((new Date()).getTime() - init));

当我从表单中获取单选按钮时,时间非常高,选择元素和文本框然后在几毫秒内返回(我今天才注意到这一点)。

当我在Chrome这样的现代浏览器控制台中运行document.querySelector('input[value='somethin'][name='somethin']')时,时间不到一秒钟。

我不知道它是否与phantomjs的无头浏览器有什么关系。仅对于该网站中的特定页面,获取元素的速度正在减慢..

是的,页面非常大,有数十万个元素。这是一个十年前的遗留webapp。在IE 8的那个页面上,按F12查看源代码会挂起IE 5分钟,但不是chrome或firefox ..可能是phantomjs的内存过载或其他东西,当我在该特定页面上运行测试时,很少有phantomjs崩溃。我不知道这些信息是否有帮助,但我不确定这些信息是否相关。

1 个答案:

答案 0 :(得分:0)

一般注意事项

最快的选择器将是id选择器,但即使你在树上有更高的id,它们也不会给你带来太多帮助。正如Ian在comments中指出的那样,选择器从右到左被解析/评估。这意味着引擎会查找具有匹配属性的所有输入,即使它只有一个,然后只搜索树以查看之前的元素是否匹配。

我发现如果您可以知道输入的封闭元素,您可以使用JavaScript DOM属性遍历DOM并在树的较小部分上运行querySelector。至少在我的测试中,这会将时间减少一半以上。

内存问题

根据您更新的问题判断,它似乎确实是一个内存问题。当你有数十万个元素时,相对较老的PhantomJS WebKit引擎会尝试分配足够的内存。如果内存占用的内存超过机器所占用的内存,甚至超过机器的内存,则操作系统通过使用硬盘上的交换内存进行补偿。

当您的脚本尝试查询当前仅在swap中的元素时,此查询需要很长时间,因为它必须从高延迟硬盘中获取数据,这与内存相比非常慢。

我的测试运行100k表单,每个查询的每个元素不超过30毫秒。当我增加元素数量时,执行时间呈线性增长,直到我得到(registering to onError

runtime error R6016
- not enough space for thread data

所以我无法在Windows上重现每个查询3-5秒的问题。

可能的解决方案

1。更好的硬件:

尝试在具有更多内存的计算机上运行它,看看它是否运行得更好。

2。通过关闭不必要的应用程序来减少已用内存

3。操作页面以减少内存占用:

  1. 如果您不需要测试页面的某些部分,则可以在运行测试之前将其从DOM中删除。如果您需要测试所有这些,您可以在同一页面上运行多个测试,但每次都删除当前未测试的所有内容。

  2. Don't load images如果这是图像重的网站,请设置casper.options.pageSettings.loadImages = false;

  3. 测试脚本

    var page = require('webpage').create();
    var content = "",
        max = 100000,
        i;
    
    for(i = 0; i < max; i++) {
        content += '<form id="f' + i + '"><input type="hidden" name="in' + i + '" valuate"iv' + i + '"></form>';
    }
    
    page.evaluate(function(content){
        document.body.innerHTML = content;
    }, content);
    
    console.log("FORMS ADDED");
    
    setTimeout(function(){
        var times = page.evaluate(function(max){
            var obj = {
                cssplain: 0,
                cssbyForm: 0,
                cssbyFormChild: 0,
                cssbyFormJsDomChild: 0,
                cssbyFormChildHybridChild: 0,
                cssbyFormHybridChild: 0,
                xpathplain: 0,
                xpathbyForm: 0
            },
                idx, start, el, i,
                repeat = 100;
    
            function runTest(name, obj, test) {
                var idx = Math.floor(Math.random()*max);
                var start = (new Date()).getTime();
                var el = test(idx);
                obj[name] += (new Date()).getTime() - start;
                return el;
            }
    
            for(i = 0; i < repeat; i++){
                runTest('cssplain', obj, function(idx){
                    return document.querySelector('input[name="in'+idx+'"][value="iv'+idx+'"]');
                });
    
                runTest('cssbyForm', obj, function(idx){
                    return document.querySelector('#f'+idx+' input[name="in'+idx+'"][value="iv'+idx+'"]');
                });
    
                runTest('cssbyFormChild', obj, function(idx){
                    return document.querySelector('form:nth-child('+(idx+1)+') input[name="in'+idx+'"][value="iv'+idx+'"]');
                });
    
                runTest('cssbyFormJsDomChild', obj, function(idx){
                    return document.body.children[max-1].querySelector('input[name="in'+idx+'"][value="iv'+idx+'"]');
                });
    
                runTest('cssbyFormChildHybridChild', obj, function(idx){
                    return document.querySelector('form:nth-child('+(idx+1)+')').querySelector('input[name="in'+idx+'"][value="iv'+idx+'"]');
                });
    
                runTest('cssbyFormHybridChild', obj, function(idx){
                    return document.querySelector('#f'+idx).querySelector('input[name="in'+idx+'"][value="iv'+idx+'"]');
                });
    
                runTest('xpathplain', obj, function(idx){
                    return document.evaluate('//input[@name="in'+idx+'" and @value="iv'+idx+'"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
                });
    
                runTest('xpathbyForm', obj, function(idx){
                    return document.evaluate('//form[@id="f'+idx+'"]//input[@name="in'+idx+'" and @value="iv'+idx+'"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
                });
            }
            for(var type in obj) {
                obj[type] /= repeat;
            }
            return obj;
        }, max);
        console.log("TIMES");
        for(var type in times) {
            console.log(type+":\t"+times[type]);
        }
        phantom.exit();
    }, 0); // just in case the content is not yet evaluated
    

    我的机器输出(更好):

    cssbyForm:                  29.55
    cssbyFormChild:             29.97
    cssbyFormChildHybridChild:  11.51
    cssbyFormHybridChild:       10.17
    cssbyFormJsDomChild:        11.73
    cssplain:                   29.39
    xpathbyForm:                206.66
    xpathplain:                 207.05
    

    注意:我直接使用了PhantomJS。当在CasperJS中使用相同的技术时,它不应该有不同的结果。