Если я использую object.getElementsByTagName(tagName) в заявлении для
Если я использую object.getElementsByTagName(tagName)
в заявлении,
for (index = 0; index < object.getElementsByTagName(tagName).length; index++) {
object.getElementsByTagName(tagName)[index].property = value;
}
Создает ли браузер новый объект nodeList для каждого прохода цикла или браузер просто обращается к одному сгенерированному списку каждый раз; или, может быть, он создает список, ссылается на указанный объект и выгружает объект списка при каждом прохождении цикла?
Мне было интересно, лучше ли хранить объект nodeList в переменной и ссылаться на него при необходимости.
5 ответов
Создает ли браузер новый объект nodeList для каждого прохода цикла или браузер просто ссылается на один сгенерированный список каждый раз;
Вы можете легко это проверить, проверив два getElementsByTagName
вернуть тот же объект:
document.getElementsByTagName('div') === document.getElementsByTagName('div')
// true
Кажется, что вызов этого метода с тем же аргументом действительно возвращает то же самое NodeList
объект (по крайней мере, в Firefox и Chrome). Он не генерирует новый список каждый раз.
Таким образом, вы можете подумать, что повторный вызов в цикле не будет иметь значения. Однако, на мой взгляд, существует несколько причин, по которым хотелось бы сохранить список в переменной:
У вас есть ненужный вызов функции в каждом цикле.
Вы не знаете, что на самом деле происходит за кулисами. Даже если возвращен тот же объект, могут быть запущенные процедуры, которые гарантируют, что список отражает текущее состояние документа, что не произойдет, если вы не вызовете функцию.
Самое главное: спецификация DOM не требует, чтобы возвращался тот же список. То, что он делает в Firefox и Chrome (там, где я его тестировал), может быть просто деталью реализации, поэтому я бы не стал полагаться на это поведение. Фактически, спецификация явно заявляет, чтобы возвратить новый список:
Возвращаемое значение: новый объект NodeList, содержащий все соответствующие элементы.
Мне было интересно, лучше ли хранить объект nodeList в переменной и ссылаться на него при необходимости.
Да, это. Вам даже не нужно звонить getElementsByTagName
снова, когда элементы добавляются или удаляются, потому что возвращаемый NodeList
в реальном времени, т. е. любые изменения, внесенные в документ, напрямую отражаются в списке без необходимости его явного обновления.
Некоторые операции, такие как доступ к 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);
Это лучший код по причинам, перечисленным ниже:
var elems = object.getElementsByTagName(tagName);
for (var index = 0, len = elems.length; index < len; index++) {
elems[index].property = value;
}
- Это никогда не может вызвать
getElementsByTagName()
больше чем единожды. Выбирает nodeList только один раз. - Это не зависит от какой-либо конкретной реализации браузера, чтобы быть эффективным.
- Он предварительно загружает длину nodeList, так что даже он не перезагружается каждый раз через цикл.
- Это просто более безопасный способ сделать ваш код эффективным.
- Более безопасный код делает вопросы реализации, которые вы задали, неактуальными.
Ваш браузер генерирует новый список каждый раз. Но этот код будет работать как 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++;
}
Что ты ожидал от этого? Читать ваши мысли? Вы ожидали, что процессор 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.
Этот тест показывает, что он сначала получает массив, вычисляющий длину каждый раз!
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] );
}
Я бы либо сначала сохранял элементы, и каждый раз ссылался на его длину, либо просто сохранял длину в переменной:)