在大型DOM中使用document.querySelector可以更快地搜索元素
Searching element faster using document.querySelector in a large DOM
在一个包含数百个元素的庞大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的无头浏览器有关。只有对于该网站中的特定页面,获取元素的速度才会减慢。。
是的,这个页面很大,有成千上万的元素。这是一个已有十年历史的遗留网络应用程序。在使用IE8的页面上,按F12查看源代码会挂起IE 5分钟,但不会挂起chrome或firefox。。也许是phantomjs的内存过载之类的,当我在那个特定的页面上运行测试时,phantomjs很少崩溃。我不知道这些信息是否有帮助,但我不确定什么是相关的。
一般注意事项
最快的选择器是id选择器,但即使你有更高级别的id,它们也不会给你带来太多好处。正如Ian在评论中指出的,选择器是从右到左解析/求值的。这意味着,即使只有一个属性匹配,引擎也会查找所有具有匹配属性的输入,然后在树上搜索,查看之前的元素是否匹配。
我发现,如果您能够知道输入在哪个封闭元素中,那么您可以使用JavaScriptDOM属性遍历DOM,并在树的较小部分上运行querySelector
。至少在我的测试中,这减少了一半以上的时间。
内存问题
从你更新的问题来看,这似乎真的是一个记忆问题。当您拥有数十万个元素时,相对较旧的PhantomJS WebKit引擎将尝试分配足够的内存。当它占用的内存超过可用内存,甚至超过机器的内存时,操作系统会使用硬盘上的交换内存进行补偿。
当脚本试图查询当前仅处于交换状态的元素时,此查询需要很长时间,因为它必须从高延迟硬盘中获取数据,与内存相比,高延迟硬盘非常慢。
我的测试运行了100k个表单,每个表单有一个元素,每个查询不到30毫秒。当我增加元素的数量时,执行时间线性增长,直到在某个时刻我(通过注册到onError
)获得
运行时错误R6016-线程数据空间不足
因此,我无法在windows上重现您每次查询3-5秒的问题。
可能的解决方案
1.更好的硬件:
试着在内存更多的机器上运行它,看看它是否运行得更好。
2.通过关闭不必要的应用程序来减少已用内存
3.操作页面以减少内存占用:
-
如果页面中有一些部分不需要测试,那么只需在运行测试之前将它们从DOM中删除即可。如果需要测试所有测试,可以在同一页面上运行多个测试,但每次都会删除当前未测试的所有内容。
-
如果这是一个图像密集的网站,请通过设置
casper.options.pageSettings.loadImages = false;
来加载图像。
测试脚本
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
在我的机器上输出(更好):
cssby表格:29.55cssbyFormChild:29.97cssbyFormChildHybridChild:11.51cssbyFormHybridChild:10.17cssbyFormJsDomChild:11.73cssplain:29.39xpathbyForm:206.66xpathplain:207.05
注意:我直接使用了PhantomJS。当在CasperJS中使用相同的技术时,它不应该有不同的结果。
- AngularJS/HTML/Bootstrap元素用于动态搜索结果
- 在元素列表中搜索给定字符串
- jQuery-在数组中搜索类似的元素
- AJAX搜索2个元素
- jQuery递归搜索类的父元素
- 如何重置搜索表单中的特定表单元素
- 使用数据表,如何在要搜索的
中指定元素 - 自动完成搜索无法正常工作,请不要迭代$中的所有元素.每个ul-li都会及时显示响应
- Angular JS根据搜索结果和点击事件更新DOM元素
- 如何在不使用任何 html 输入/搜索元素的情况下读取本地客户端文本文件
- 在元素中搜索字符串,然后将其放入条件语句中
- 当我单击chrome的输入类型=搜索元素上的X时,是否触发了任何事件
- 如何通过搜索innerHTML的内容来获取HTML元素
- 增加在选择元素HTML中搜索的时间
- 使用DOM树中的某种节点号进行元素搜索
- 隐藏Google自定义搜索元素API 2.0的搜索引擎ID
- JQuery 在渲染后搜索 dom 元素,并用其相应的值替换键
- 如何获得下一个兄弟的值,如果元素搜索特定的字符串
- 如何在鼠标悬停时将焦点设置为输入元素(搜索框)
- 我怎样才能得到这个link元素?总是显示“无法找到带有链接文本的元素”==“搜索”