Выразительный JavaScript (Хавербеке) - страница 131

Получается неудобно. Хотя стандарты – и весьма полезная штука, в нашем случае преимущество независимости от языка не такое уж и полезное. Лучше иметь интерфейс, хорошо приспособленный к языку, который вы используете, чем интерфейс, который будет знаком при использовании разных языков.

Чтобы показать неудобную интеграцию с языком, рассмотрим свойство >childNodes, которое есть у узлов DOM. В нём содержится объект, похожий на массив, со свойством >length, и пронумерованные свойства для доступа к дочерним узлам. Но это – экземпляр типа >NodeList, не настоящий массив, поэтому у него нет методов вроде >forEach.

Есть также проблемы, связанные с плохой продуманностью системы. К примеру, нельзя создать новый узел и сразу добавить к нему свойства или дочерние узлы. Сначала нужно его создать, затем добавить дочерние по одному, и в конце назначить свойства по одному, с использованием побочных эффектов. Код, плотно работающий с DOM, получается длинным, некрасивым и со множеством повторов.

Но эти проблемы не фатальные. JavaScript позволяет создавать абстракции. Легко написать вспомогательные функции, позволяющие выражать операции более понятно и коротко. Вообще, такого рода инструменты предоставляют много библиотек, направленных на программирование для браузера.

Обход дерева

Узлы DOM содержат много ссылок на соседние. Это показано на диаграмме:



Хотя тут показано только по одной ссылке каждого типа, у каждого узла есть свойство >parentNode, указывающего на его родительский узел. Также у каждого узла-элемента (тип 1) есть свойство >childNodes, указывающее на массивоподобный объект, содержащий его дочерние узлы.

В теории можно пройти в любую часть дерева, используя только эти ссылки. Но JavaScript предоставляет нам много дополнительных вспомогательных ссылок. Свойства >firstChild и >lastChild показывают на первый и последний дочерний элементы, или содержат >null у тех узлов, у которых нет дочерних. >previousSibling и >nextSibling указывают на соседние узлы – узлы того же родителя, что и текущего узла, но находящиеся в списке сразу до или после текущей. У первого узла свойство >previousSibling будет >null, а у последнего >nextSibling будет >null.

При работе с такими вложенными структурами пригождаются рекурсивные функции. Следующая ищет в документе текстовые узлы, содержащие заданную строку, и возвращает >true, когда находит:

>function talksAbout(node, string) {

>  if (node.nodeType == document.ELEMENT_NODE) {

>    for (var i = 0; i < node.childNodes.length; i++) {

>      if (talksAbout(node.childNodes[i], string))