Объектная модель документа (DOM)

Содержание

Вступление
Графы, дерево DOM
Узлы
Свойства узлов: тип, тег и содержимое

Вступление

Объектная Модель Документа (DOM) – это программный интерфейс (API) для HTML и XML документов. DOM предоставляет структурированное представление документа и определяет то, как эта структура может быть доступна из программ, которые могут изменять содержимое, стиль и структуру документа. Представление DOM состоит из структурированной группы узлов и объектов, которые имееют свойства и методы. По существу DOM соединяет веб-страницу с языками описания сценариев либо языками программирования.

Веб-страница – это документ. Документ может быть представлен как в окне браузера, так и в самом HTML-коде. В любом случае, это один и тот же документ. DOM предоставляет другой способ представления, хранения и управления этого документа. DOM полностью поддерживает объектно-ориентированнное представление веб-страницы, делая возможным её изменение при помощи языка описания сценариев наподобие JavaScript.

Стандарты W3C DOM и WHATWG DOM формируют основы DOM, реализованные в большинстве современных браузеров. Многие браузеры предлагают расширения за пределами данного стандарта, поэтому необходимо проверять работоспособность тех или иных возможностей DOM для каждого конкретного браузера.

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

В начале, JavaScript и DOM были тесно связанны, но в последствии они развились в различные сущности. Содержимое страницы хранится в DOM и может быть доступно и изменяться с использованием JavaScript, поэтому мы можем записать это в виде приблизительного равенства:

DOM предполагался быть независимым от любого конкретного языка программирования, обеспечивая структурное представление документа согласно единому и последовательному API. Хотя и все сфокусировано на JavaScript, реализация DOM может быть построена для любого языка.

2. Графы, дерево DOM

Граф — абстрактный математичекий объект, представлющий из себя множество вершин графа и набор рёбер, то есть соединени между парами вершин.

Теория графов — раздел дискретной математики, изучающий свойства графов.

При изображении графов на рисунках чаще всего используется следующая система обозначений: вершины графа изображаются точками или, при конкретизации смысла вершины, прямоугольниками, овалами и др., где внутри фигуры раскрывается смысл вершины (графы блок-схем алгоритмов). Если между вершинами существует ребро, то соответствующие точки (фигуры) соединяются линией или дугой.

Дерево — это связный ациклический граф. Связность означает наличие путей между любой парой вершин, ацикличность — отсутствие циклов и то, что между парами вершин имеется только по одному пути.

DOM - это древовидная структура, где вершины представлены объектами класса Node (узел). Для реализации алгоритмов работы с DOM используют свойства графов.

3. Узлы

Самое главное различие между DOM-узлами – разные узлы являются объектами различных классов.

Поэтому, к примеру, у узла, соответствующего тегу <td> – одни свойства, у <form> – другие, у <a> – третьи.

Есть и кое-что общее, за счёт наследования.

На рисунке выше изображены основные классы:

Узнать класс узла очень просто – достаточно привести его к строке, к примеру, вывести:

'use strict';
alert( document.body ); // [object HTMLBodyElement]

Как видно, DOM-узлы – обычные JavaScript-объекты. Их классы заданы в прототипном стиле. В этом легко убедиться, если вывести в консоли любой элемент через console.dir(elem). Или даже можно напрямую обратиться к методам, которые хранятся в Node.prototype, Element.prototype и так далее.

Далее в этом мы поговорим о самых главных свойствах узлов DOM, которые используются наиболее часто.

4. Свойства узлов: тип, тег и содержимое

4.1 Свойство nodeType

Тип узла содержится в его свойстве nodeType.

  • ELEMENT_NODE = 1;
  • ATTRIBUTE_NODE = 2;
  • TEXT_NODE = 3;
  • CDATA_SECTION_NODE = 4;
  • ENTITY_REFERENCE_NODE = 5;
  • ENTITY_NODE = 6;
  • PROCESSING_INSTRUCTION_NODE = 7;
  • COMMENT_NODE = 8;
  • DOCUMENT_NODE = 9;
  • DOCUMENT_TYPE_NODE = 10;
  • DOCUMENT_FRAGMENT_NODE = 11;
  • NOTATION_NODE = 12;

    В частности, тип «Элемент» ELEMENT_NODE имеет номер 1, а «Текст» TEXT_NODE – номер 3.

    Тип узла можно только читать, изменить его невозможно.

4.2 Свойства nodeName и tagName

Существует целых два свойства: nodeName и tagName, которые содержат название(тег) элемента узла.

Название HTML-тега всегда находится в верхнем регистре.

'use strict';
alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY

Разница отражена в названиях свойств, но неочевидна:

    • Свойство tagName есть только у элементов Element
    • Свойство nodeName определено для любых узлов Node, для элементов оно равно tagName, а для не-элементов обычно содержит строку с типом узла.

    При работе с элементами, как это обычно бывает, имеет смысл использовать свойство tagName – оно короче.

4.3 Свойство innerHTML

Свойство innerHTML позволяет получить HTML-содержимое элемента в виде строки. В innerHTML можно и читать и писать.

'use strict';
alert( document.body.innerHTML );
alert( document.body.innerHTML = 'Hello World' );

Значение, возвращаемое innerHTML – всегда валидный HTML-код. При записи можно попробовать записать что угодно, но браузер исправит ошибки:

Свойство innerHTML – одно из самых часто используемых.

Свойство outerHTML содержит HTML элемента целиком.

Свойства nodeValue и data

Свойство innerHTML есть только у узлов-элементов.

Содержимое других узлов, например, текстовых или комментариев, доступно на чтение и запись через свойство data.

Свойство nodeValue мы использовать не будем.
Оно работает так же, как data, но на некоторых узлах, где data нет, nodeValue есть и имеет значение null. Как-то использовать это тонкое отличие обычно нет причин.

4.5 Свойство textContent

Свойство textContent содержит только текст внутри элемента, за вычетом всех тегов.

<div>
  <h1>Срочно в номер!</h1>
  <p>Марсиане атакуют людей!</p>
</div>
'use strict';
var news = document.body.children[0];

// \n  Срочно в номер!\n  Марсиане атакуют людей!\n
alert( news.textContent )

Как видно из примера выше, возвращается в точности весь текст, включая переводы строк и пробелы, но без тегов.

Иными словами, elem.textContent возвращает конкатенацию всех текстовых узлов внутри elem.

4.6 Свойство hidden

Как правило, видим или невидим узел, определяется через CSS, свойствами display или visibility.

В стандарте HTML5 предусмотрен специальный атрибут и свойство для этого: hidden.

Технически, атрибут hidden работает так же, как style="display:none". Но его проще поставить через JavaScript (меньше букв), и могут быть преимущества для скринридеров и прочих нестандартных браузеров.

5. Лексическое окружение

Все переменные внутри функции – это свойства специального внутреннего объекта LexicalEnvironment, который создаётся при её запуске.

При запуске функция создает объект LexicalEnvironment, записывает туда аргументы, функции и переменные. Процесс инициализации выполняется в том же порядке, что и для глобального объекта, который, вообще говоря, является частным случаем лексического окружения.

В отличие от window, объект LexicalEnvironment является внутренним, он скрыт от прямого доступа.

'use strict';
var userName = "Alex";

function sayHi() {
    alert( userName ); // "Вася"

Интерпретатор, при доступе к переменной, сначала пытается найти переменную в текущем LexicalEnvironment, а затем, если её нет – ищет во внешнем LexicalEnvironment (в данном случае им является window), который принадлежит внешней области видимости.

Такой порядок поиска возможен благодаря тому, что ссылка на внешний объект переменных хранится в специальном внутреннем свойстве функции, которое называется [[Scope]]. Это свойство закрыто от прямого доступа, но знание о нём очень важно для понимания того, как работает JavaScript.

При создании функция получает скрытое свойство [[Scope]], которое ссылается на лексическое окружение области видимости (не на всю ОВ, а только на LexicalEnvironment ОВ), в которой она была создана.

Это свойство никогда не меняется. Оно всюду следует за функцией, привязывая её, таким образом, к месту своего рождения.

При запуске функции её объект переменных LexicalEnvironment получает ссылку на «внешнее лексическое окружение» со значением из [[Scope]].

Значение переменной из внешней области берётся всегда текущее. Оно может быть уже не то, что было на момент создания функции.

Это естественно, ведь для доступа к внешней переменной функция по ссылке [[Scope]] обращается во внешний объект переменных и берёт то значение, которое там есть на момент обращения.

6. Глобальная область видимости

Глобальная область видимости является лексической ОВ, она всегда будет на вершине иерархии любой вложенности областей видимости.

Также глобальная область видимости хранит LexicalEnvironment который является конечным звеном в цепочке лексических окружений.

7. Замыкания

Технически все функции в языке JavaScript образуют замыкания: они являются объектами и имеют ассоциированные с ними цепочки областей видимости. Большинство функций вызываются внутри той же цепочки областей видимости, которая действовала на момент определения функции, и в этой ситуации факт образования замыкания не имеет никакого значения. Интересные особенности замыканий начинают проявляться, когда их вызов производится в другой цепочке областей видимости, отличной от той, что действовала на момент определения. Чаще всего это происходит, когда объект вложенной функции возвращается функцией, вмещающей ее определение. Существует множество мощных приемов программирования, вовлекающих такого рода вложенные функции-замыкания, и их использование довольно широко распространено в программировании на языке JavaScript. Замыкания могут выглядеть малопонятными при первом знакомстве, однако вам необходимо хорошо понимать их, чтобы чувствовать себя уверенно при их использовании.

'use strict';
var scope = "global scope"; // Гло­баль­ная пе­ре­мен­ная
function checkscope() {
    var scope = "local scope"; // Ло­каль­ная пе­ре­мен­ная
    function f() { return scope; } // Вер­нет зна­че­ние ло­каль­ной пе­ре­мен­ной scope
        return f;
    }   
checkscope()();
'use strict';
var a = 1;
var foo = (function f1() {
    var b = 2;
    return (function f2() {
        var c = 3;
        return (function f3() {
            var d = 4;
            return function() {
                console.log(a, b, c, d);
            }
        })();
    })();
})();
foo();
'use strict';
var uniqueInteger = (function() {
    var counter = 0; // Ча­ст­ное (private) зна­че­ние для функ­ции ни­же
        return function() {
            return counter++;
        };
}());

Внимательно изучите этот пример, чтобы понять, как он действует. На первый взгляд, первая строка выглядит как инструкция присваивания функции переменной uniqueInteger. Фактически же это определение и вызов функции (как подсказывает открывающая круглая скобка в первой строке), поэтому в действительности переменной uniqueInteger присваивается значение, возвращаемое функцией. Если теперь обратить внимание на тело функции, можно увидеть, что она возвращает другую функцию. Именно этот объект вложенной функции и присваивается переменной uniqueInteger. Вложенная функция имеет доступ к переменным в ее области видимости и может использовать переменную counter, объявленную во внешней функции. После возврата из внешней функции никакой другой программный код не будет иметь доступа к переменной counter: вложенная функция будет обладать исключительным правом доступа к ней.

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

'use strict';
function counter() {
    var n = 0;
    return {
        count: function() {
            return ++n;
        },
        reset: function() {
            n = 0;
        }
    };
}
var c = counter(), d = counter(); // Соз­дать два счет­чи­ка

c.count() // => 1
d.count() // => 1 // они дей­ст­ву­ют не­за­ви­си­мо
c.reset()
c.count()
d.count()

Функция counter() возвращает объект «счетчика». Этот объект имеет два метода: count(), возвращающий следующее целое число, и reset(), сбрасывающий счетчик в начальное состояние. В первую очередь следует запомнить, что два метода совместно используют одну и ту же частную переменную n. Во-вторых, каждый вызов функции counter() создает новую цепочку областей видимости и новую скрытую переменную. То есть, если вызвать функцию counter() дважды, она вернет два объекта-счетчика с различными скрытыми переменными. Вызов методов count() и reset() одного объекта-счетчика не оказывает влияния на другой.

xe re

Как и объекты, замыкания предлагают механизм сохранения состояния. В JavaScript замыкание создается всякий раз, когда функция получает доступ к переменной, определенной вне непосредственной области видимости функции.

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

Замыкание — это когда функция может запомнить и иметь доступ к своей лексической области видимости даже тогда, когда она вызывается вне своей лексической области видимости.

8. Динамическая область видимости

Мы кратко рассмотрим динамическую область видимости, чтобы крепко усвоить разницу.

Динамическая область видимости означает что есть модель, при помощи которой область видимости можно определить динамически во время выполнения, вместо статического определения при написании кода. То, что нам нужно. Давайте отразим это в коде:

'use strict';
function foo() {
    console.log( a ); // 2
}

function bar() {
    var a = 3;
    foo();
}

var a = 2;

bar();

Лексическая область видимости хранит информацию о том, где была создана foo(), что приведет к тому, что будет выведено значение 2.

Динамическая область видимости, не интересуется тем как и где были объявлены функции и области видимости, а скорее интересуется тем откуда они буду вызываться. Здесь цепочка областей видимости основана на стеке вызовов, а не на вложенности областей видимости в коде.

Итак, если бы в JavaScript была динамическая область видимости, то когда выполнилась бы foo(), теоретически вышеприведенный код привел бы к выводу 3.

Чтобы внести ясность, в JavaScript нет, на самом деле, динамической области видимости. В нем есть лексическая область видимости. Проще некуда. Но механизм работы this немного похож на динамическую область видимости.

Ключевое сравнение: лексическая область видимости определяется временем написания кода, тогда как динамическая область видимости (и this!) определяется во время выполнения. Лексическую область видимости интересует где функция была объявлена, а динамическую — откуда была вызвана функция.

И наконец: this интересует как была вызвана функция, что показывает как близко связаны механизм this с идеей динамической области видимости.


9. Паттерн Модуль

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

'use strict';
function foo() {
    var something = "cool";
    var another = [1, 2, 3];

    function doSomething() {
        console.log( something );
    }

    function doAnother() {
        console.log( another.join( " ! " ) );
    }
}

Как видно из этого кода, здесь нет никакого явного замыкания. У нас просто есть некоторые приватные переменные something и another, а также парочка внутренних функций doSomething() и doAnother(), у обеих из которых есть лексическая область видимости (а потому и замыкание!) над внутренней областью foo().

А теперь смотрите:

'use strict';
function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];

    function doSomething() {
        console.log( something );
    }

    function doAnother() {
        console.log( another.join( " ! " ) );
    }

    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
}

var foo = CoolModule();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

Этот шаблон в JavaScript мы называем модуль. Самый распространенный путь реализации шаблона модуля часто называют "Действенный модуль" и это тот вариант, который мы тут и представили.

Вышеприведенный код показывает автономный конструктор модуля с названием CoolModule(), который можно вызвать любое количество раз, каждый раз создавая новый экземпляр модуля. Небольшая вариация этого шаблона — это когда вы заботитесь о том, чтобы был только один экземпляр, что-то вроде "синглтона":

'use strict';
var foo = (function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];

    function doSomething() {
        console.log( something );
    }

    function doAnother() {
        console.log( another.join( " ! " ) );
    }

    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
})();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

Модули — это всего лишь функции, поэтому они могут принимать параметры:

'use strict';
function CoolModule(id) {
    function identify() {
        console.log( id );
    }

    return {
        identify: identify
    };
}

var foo1 = CoolModule( "foo 1" );
var foo2 = CoolModule( "foo 2" );

foo1.identify(); // "foo 1"
foo2.identify(); // "foo 2"

Еще одна небольшая, но полнофункциональная вариация модульного шаблона — дать имя объекту, который вы возвращаете как публичное API:

'use strict';
var foo = (function CoolModule(id) {
    function change() {
        // modifying the public API
        publicAPI.identify = identify2;
    }

    function identify1() {
        console.log( id );
    }

    function identify2() {
        console.log( id.toUpperCase() );
    }

    var publicAPI = {
        change: change,
        identify: identify1
    };

    return publicAPI;
})( "foo module" );

foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE

Сохраняя внутреннюю ссылку на объект публичного API внутри экземпляра модуля вы можете менять эту ссылку на модуль изнутри, включая добавление и удаление методов, свойств и изменение их значений.

Модули требуют две ключевых характеристики: 1) внешнюю функцию-обертку, которую будут вызывать, чтобы создать закрытую область видимости 2) возвращаемое значение функции-обертки должно включать в себя ссылку на не менее чем одну внутреннюю функцию, у которой потом будет замыкание на внутреннюю область видимости обертки.

Теперь вы сможете заметить замыкания повсюду в своем существующем коде и у нас теперь есть возможность обнаруживать и использовать все их преимущества!

10. Объект arguments

Если число аргументов в вызове функции превышает число имен параметров, функция лишается возможности напрямую обращаться к неименованным значениям. Решение этой проблемы предоставляет объект Arguments. В теле функции идентификатор arguments ссылается на объект Arguments, присутствующий в вызове. Объект Arguments – это объект, подобный массиву, позволяющий извлекать переданные функции значения по их номерам, а не по именам.

Предположим, что была определена функция f, которая требует один аргумент, x. Если вызвать эту функцию с двумя аргументами, то первый будет доступен внутри функции по имени параметра x или как arguments[0]. Второй аргумент будет доступен только как arguments[1]. Кроме того, подобно настоящим массивам, arguments имеет свойство length, определяющее количество содержащихся элементов. То есть в теле функции f, вызываемой с двумя аргументами, arguments.length имеет значение 2.

Объект Arguments иллюстрирует важную возможность JavaScript-функций: они могут быть написаны таким образом, чтобы работать с любым количеством аргументов. Следующая функция принимает любое число аргументов и возвращает значение самого большого из них (аналогично ведет себя встроенная функция Math.max()).

Функции, подобные этой и способные принимать произвольное число аргументов, называются функциями с переменным числом аргументов (variadic functions, variable arity functions или varargs functions). Этот термин возник вместе с появлением языка программирования C.

Не следует забывать, что arguments фактически не является массивом – это объект Arguments. В каждом объекте Arguments имеются пронумерованные элементы массива и свойство length, но с технической точки зрения это не массив. Лучше рассматривать его как объект, имеющий некоторые пронумерованные свойства.

11. Устаревшая конструкция "with"

Конструкция with позволяет использовать в качестве области видимости для переменных произвольный объект.

В современном JavaScript от этой конструкции отказались. С use strict она не работает, но её ещё можно найти в старом коде, так что стоит познакомиться с ней, чтобы если что – понимать, о чём речь.

Синтаксис:

'use strict';
with(obj) {
  ...код...
}

Любое обращение к переменной внутри with сначала ищет её среди свойств obj, а только потом – вне with.

'use strict';
var a = 5;

var obj = {
    a: 10
};

with(obj) {
    alert( a ); // 10, из obj
}

12. Упражнения

1. Векторный тип

Напишите конструктор Vector, представляющий вектор в двумерном пространстве. Он принимает параметры x и y (числа), которые хранятся в одноимённых свойствах.

Дайте Vector два метода, plus и minus, которые принимают другой вектор в качестве параметра и возвращают новый вектор, который хранит в x и y сумму или разность двух векторов (один this, второй – аргумент).

'use strict';
console.log(new Vector(1, 2).plus(new Vector(2, 3)));
// → Vector{x: 3, y: 5}
console.log(new Vector(1, 2).minus(new Vector(2, 3)));
// → Vector{x: -1, y: -1}
console.log(new Vector(3, 4).length); // Длина вектора

После, реализуйте модуль Vector. Вынесите интерфейс.

2. Названия месяцев

Напишите простой модуль month, преобразующий номера месяцев (начиная с нуля) в названия и обратно. Выделите ему собственное пространство имён, т.к. ему потребуется внутренний массив с названиями месяцев.

'use strict';
console.log(month.name(2));
// → March
console.log(month.number("November"));
// → 10

3. Counter

Реализуйте конструктор счет­чи­ка Counter(id), id уникальный идентификатор счетчика. Counter.prototype.count - поле счет­чи­ка. Counter.prototype.reset() - обнуляет счетчик. set Counter.prototype.count(value) - позволяет установить счетчик. get Counter.prototype.id - возвращает идентификатор счетчика. Counter.prototype.autoCounting(timeout) - позволяет установить автоматическое увеличение счетчика через timeout. Counter.prototype.startCounting(timeout) - запускает автоматическое увеличение счетчика. Counter.prototype.stopCounting(timeout) - отменяет автоматическое увеличение счетчика.

После, реализуйте модуль Counter. Вынесите интерфейс.

4. allArguments

Напишите функцию allArguments, что выводит в консоль все аргументы переданые функции ( количество аргументов зарание неизвесно).

5. allArgumentsAdd

Напишите функцию allArgumentsAdd, что возвращает суму всех аргументов переданных функции (количество аргументов зарание неизвесно).

6. Car

Создайте конструктор Car, детально описывающиющий свойство автомобиля (минимум 15 полей).

'use strict';
var chevy = new Car("Chevy", "Bel Air", 1957, "red", 2, false, 1021);
var cadi = new Car("GM", "Cadillac", 1955, "tan", 5, false, 12892);
var taxi = new Car("Webville Motors", "Taxi", 1955, "yellow", 4, false, 281341);
var fiat = new Car("Fiat", "500", 1957, "Medium Blue", 2, false, 88000);

Создайте массив марок автомобилей и на основе создайте массив из экземпляров Car(brand) (минимум 10 елементов).

6. Cars brand

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

//

///

////

results matching ""

    No results matching ""