4

如果我object.getElementsByTagName(tagName)在 for 语句中使用,

for (index = 0; index < object.getElementsByTagName(tagName).length; index++) {
object.getElementsByTagName(tagName)[index].property = value;
}

浏览器是否在每次循环时都实例化一个新的 nodeList 对象,或者浏览器是否每次都简单地引用一个生成的列表?或者,它实例化一个列表,引用指定的对象并在每次通过循环时卸载列表对象?

我一直想知道将 nodeList 对象存储到变量并在需要时引用它是否更好。

4

5 回答 5

1

这是更好的代码,原因如下:

var elems = object.getElementsByTagName(tagName);
for (var index = 0, len = elems.length; index < len; index++) {
    elems[index].property = value;
}
  1. 它不能getElementsByTagName()多次调用。它只获取一次节点列表。
  2. 它不依赖于任何特定的浏览器实现来提高效率。
  3. 它预加载了 nodeList 的长度,因此即使每次循环都不会重新获取它。
  4. 这只是使您的代码高效的一种更安全的方法。
  5. 更安全的代码使您提出的实现问题变得无关紧要。
于 2013-08-12T08:06:50.143 回答
1

浏览器是否为每次循环实例化一个新的 nodeList 对象,或者浏览器是否每次都简单地引用一个生成的列表?

您可以通过测试两个调用是否getElementsByTagName返回相同的对象来轻松测试:

document.getElementsByTagName('div') === document.getElementsByTagName('div')
// true

似乎用相同的参数调用这个方法确实返回了相同的NodeList对象(至少在 Firefox 和 Chrome 中)。它不会每次都生成一个新列表

所以你可能认为在循环中一遍又一遍地调用它不会有什么不同。但是,正如我所见,将列表存储在变量中的原因有多种:

  • 在每个循环中都有一个不必要的函数调用。

  • 你不知道幕后究竟发生了什么。即使返回相同的对象,也可能会运行确保列表反映文档的当前状态的过程,如果您不调用该函数,则不会发生这种情况。

  • 最重要的是:DOM 规范似乎并不要求返回相同的列表。它在 Firefox 和 Chrome(我测试它的地方)中所做的可能只是一个实现细节,所以我不会依赖这种行为。事实上,规范明确规定要返回一个列表:

    返回值:包含所有匹配元素的新 NodeList 对象。


我一直想知道将 nodeList 对象存储到变量并在需要时引用它是否更好。

是的。您甚至不必getElementsByTagName在添加或删除元素时再次调用,因为返回NodeList的是live,即对文档所做的任何更改都会直接反映在列表中,而无需显式更新。
某些操作,例如访问length列表的 ,也会触发重新评估。因此,除了将列表存储在变量中之外,您可能还希望缓存长度:

var nodes = object.getElementsByTagName(tagName);

for (var index = 0, l = nodes.length; index < l; index++) {
    nodes[index].property = value;
}

这可能非常方便或非常混乱,具体取决于您的需要。如果您只想要调用函数时存在的节点列表,则必须将 NodeList 转换为数组:

var nodes = Array.prototype.slice.call(object.getElementsByTagName(tagName), 0);
于 2013-08-12T07:51:19.620 回答
0

你期望它做什么?读懂你的心?您是否期望 JS 处理器具有object.getElementsByTagName(tagName)每次调用时(可能)返回相同值的语义信息,因此是“循环不变的”,因此可以提升到循环之外(参见http://en .wikipedia.org/wiki/Loop-invariant_code_motion)?

也许您想象浏览器以某种方式“缓存”了元素下具有特定标记名的所有元素的列表,然后在后续调用中返回相同的列表?尽管不看代码很难判断浏览器在内部做什么,但它可能不是,或者一个浏览器可能是而另一个浏览器不是。即使浏览器确实缓存了结果,考虑到任何性能损失在整体方案中可能很小,一次又一次地调用 API 总是比引用已经设置的变量效率低得多。并且您对节点列表的每个元素所做的操作(例如设置属性)可能会导致缓存被重置,这并非不可能。

作为一个小问题,假设列表是一遍又一遍地创建的,它不会被“卸载”;JS中没有这样的概念。用技术术语来说,它的状态将是“就坐在那儿”。它将很快被垃圾收集。

所以是的,无论如何getElementsByTagName只调用一次。实际上,您可以在程序开始时调用它一次(假设只有一个对象和您感兴趣的标记名),因为它返回一个“实时”列表。请参阅https://developer.mozilla.org/en-US/docs/Web/API/element.getElementsByTagName

于 2013-08-12T08:08:58.837 回答
0

您的浏览器每次都会生成一个新列表。但是此代码将像index每次运行一样更改。

首先将节点列表保存在一些是一个好习惯var

var elements = object.getElementsByTagName(tagName);

for (index = 0; index < elements; index++) {
        elements[index].property = value;
}

您可以将您的方法视为:

var index = 0;

while(index<object.getElementsByTagName(tagName))
{
  elements[index].property = value;
  index++;
}
于 2013-08-12T07:48:40.390 回答
0

这个测试表明它每次都先获取数组计算长度!

var testObj = {
    getArray: function () {
        console.log( 'getting array' );
        return this._array;
    },
    _array: ['a','b','c']
};

for ( var index = 0; index < testObj.getArray().length; index++ ){
    document.write( testObj.getArray()[index] );
}

我要么先存储元素,然后每次都引用它的 .length ,要么只将长度存储在一个变量中:)

于 2013-08-12T07:53:13.903 回答