renovations

This commit is contained in:
Ilya Kantor 2015-01-24 23:53:50 +03:00
parent a6431c3f97
commit dce565963b
115 changed files with 1433 additions and 1563 deletions

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="148px" height="128px" viewBox="0 0 148 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>noun_1211_cc + Message</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="combined-brown" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="variable" sketch:type="MSArtboardGroup" transform="translate(-161.000000, -71.000000)">
<g id="noun_1211_cc-+-Message" sketch:type="MSLayerGroup" transform="translate(161.000000, 71.000000)">
<g id="noun_1211_cc">
<path d="M17,37.1960457 L129.557785,37.1960457 L129.557785,80.1456641 C129.557785,80.5185728 129.479295,81.0083855 129.279367,81.4404861 C129.07944,81.8725868 112.704625,117 112.704625,117 L112.704625,62.6396695 L129.559266,37.1960457 L148,9 L35.4407339,9 L17,37.1960457 L17,37.1960457 Z" id="Shape" fill="#D1C4B1" sketch:type="MSShapeGroup"></path>
<path d="M17,66 L17,38 L2,66" id="Shape" fill="#D1C4B1" sketch:type="MSShapeGroup"></path>
<g id="Rectangle-5-+-&quot;World!&quot;" transform="translate(15.000000, 0.000000)">
<path d="M18.9106925,0.395208397 L73.4146415,58.8435379 L55.0893075,75.9321883 L0.585358502,17.4838588 L18.9106925,0.395208397 L18.9106925,0.395208397 Z" id="Rectangle-5" stroke="#8A704D" stroke-width="2" fill="#FFF9EB" sketch:type="MSShapeGroup"></path>
<text id="&quot;Hello!&quot;" sketch:type="MSTextLayer" transform="translate(39.885486, 40.782373) rotate(47.000000) translate(-39.885486, -40.782373) " font-family="Open Sans" font-size="14" font-weight="526" fill="#8A704D">
<tspan x="10.392998" y="46.2823729">"Hello!"</tspan>
</text>
</g>
<path d="M0,68 L0,122.729971 C0,126.150501 1.4836395,128 4.38712201,128 L104.473443,128 C107.59502,128 110,125.4521 110,124.524372 L110,68 L0,68 L0,68 Z" id="Shape" fill="#D1C4B1" sketch:type="MSShapeGroup"></path>
</g>
<text id="Message" sketch:type="MSTextLayer" font-family="Open Sans" font-size="18" font-weight="526" fill="#FFFFFF">
<tspan x="17" y="105">Message</tspan>
</text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="148px" height="128px" viewBox="0 0 148 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>noun_1211_cc + Message</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="combined-grey" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="variable" sketch:type="MSArtboardGroup" transform="translate(-161.000000, -71.000000)">
<g id="noun_1211_cc-+-Message" sketch:type="MSLayerGroup" transform="translate(161.000000, 71.000000)">
<g id="noun_1211_cc">
<path d="M17,37.1960457 L129.557785,37.1960457 L129.557785,80.1456641 C129.557785,80.5185728 129.479295,81.0083855 129.279367,81.4404861 C129.07944,81.8725868 112.704625,117 112.704625,117 L112.704625,62.6396695 L129.559266,37.1960457 L148,9 L35.4407339,9 L17,37.1960457 L17,37.1960457 Z" id="Shape" fill="#C7C7C7" sketch:type="MSShapeGroup"></path>
<path d="M17,66 L17,38 L2,66" id="Shape" fill="#C7C7C7" sketch:type="MSShapeGroup"></path>
<g id="Rectangle-5-+-&quot;World!&quot;" transform="translate(15.000000, 0.000000)">
<path d="M18.9106925,0.395208397 L73.4146415,58.8435379 L55.0893075,75.9321883 L0.585358502,17.4838588 L18.9106925,0.395208397 L18.9106925,0.395208397 Z" id="Rectangle-5" stroke="#8A704D" stroke-width="2" fill="#FFF9EB" sketch:type="MSShapeGroup"></path>
<text id="&quot;Hello!&quot;" sketch:type="MSTextLayer" transform="translate(39.885486, 40.782373) rotate(47.000000) translate(-39.885486, -40.782373) " font-family="Open Sans" font-size="14" font-weight="526" fill="#8A704D">
<tspan x="10.392998" y="46.2823729">"Hello!"</tspan>
</text>
</g>
<path d="M0,68 L0,122.729971 C0,126.150501 1.4836395,128 4.38712201,128 L104.473443,128 C107.59502,128 110,125.4521 110,124.524372 L110,68 L0,68 L0,68 Z" id="Shape" fill="#C7C7C7" sketch:type="MSShapeGroup"></path>
</g>
<text id="Message" sketch:type="MSTextLayer" font-family="Open Sans" font-size="18" font-weight="526" fill="#FFFFFF">
<tspan x="17" y="105">Message</tspan>
</text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="148px" height="128px" viewBox="0 0 148 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>noun_1211_cc + Message</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="combined-green" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="variable" sketch:type="MSArtboardGroup" transform="translate(-161.000000, -71.000000)">
<g id="noun_1211_cc-+-Message" sketch:type="MSLayerGroup" transform="translate(161.000000, 71.000000)">
<g id="noun_1211_cc">
<path d="M17,37.1960457 L129.557785,37.1960457 L129.557785,80.1456641 C129.557785,80.5185728 129.479295,81.0083855 129.279367,81.4404861 C129.07944,81.8725868 112.704625,117 112.704625,117 L112.704625,62.6396695 L129.559266,37.1960457 L148,9 L35.4407339,9 L17,37.1960457 L17,37.1960457 Z" id="Shape" fill="#CFCE95" sketch:type="MSShapeGroup"></path>
<path d="M17,66 L17,38 L2,66" id="Shape" fill="#CFCE95" sketch:type="MSShapeGroup"></path>
<g id="Rectangle-5-+-&quot;World!&quot;" transform="translate(15.000000, 0.000000)">
<path d="M18.9106925,0.395208397 L73.4146415,58.8435379 L55.0893075,75.9321883 L0.585358502,17.4838588 L18.9106925,0.395208397 L18.9106925,0.395208397 Z" id="Rectangle-5" stroke="#8A704D" stroke-width="2" fill="#FFF9EB" sketch:type="MSShapeGroup"></path>
<text id="&quot;Hello!&quot;" sketch:type="MSTextLayer" transform="translate(39.885486, 40.782373) rotate(47.000000) translate(-39.885486, -40.782373) " font-family="Open Sans" font-size="14" font-weight="526" fill="#8A704D">
<tspan x="10.392998" y="46.2823729">"Hello!"</tspan>
</text>
</g>
<path d="M0,68 L0,122.729971 C0,126.150501 1.4836395,128 4.38712201,128 L104.473443,128 C107.59502,128 110,125.4521 110,124.524372 L110,68 L0,68 L0,68 Z" id="Shape" fill="#CFCE95" sketch:type="MSShapeGroup"></path>
</g>
<text id="Message" sketch:type="MSTextLayer" font-family="Open Sans" font-size="18" font-weight="526" fill="#FFFFFF">
<tspan x="17" y="105">Message</tspan>
</text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,22 +1,39 @@
# Окружение: DOM, BOM и JS # Окружение: DOM, BOM и JS
Сам по себе язык JavaScript не предусматривает работы с браузером, он вообще не знает про HTML. Сам по себе язык JavaScript не предусматривает работы с браузером.
Он вообще не знает про HTML. Но позволяет легко расширять себя новыми функциями и объектами.
Но в браузере есть ряд специальных объектов, которые образуют Document Object Model (DOM) и дают доступ к документу, а также объекты Browser Object Model (BOM), которые позволяют использовать различные возможности браузера, такие как коммуникация с сервером, открытие новых окон и т.п.
[cut] [cut]
На рисунке ниже схематически отображена структура, которая получается если посмотреть на совокупность браузерных объектов с "высоты птичьего полёта". На рисунке ниже схематически отображена структура, которая получается если посмотреть на совокупность браузерных объектов с "высоты птичьего полёта".
<img src="windowObjects.png"> <img src="windowObjects.png">
Как видно из рисунка, на вершине стоит `window`, который играет роль *глобального объекта*, но вместе с этим даёт доступ к функционалу по управлению окном браузера, у него есть методы `window.focus()`, `window.open()` и другие. Как видно из рисунка, на вершине стоит `window`.
Все остальные объекты делятся на 3 группы.
У этого объекта двоякая позиция -- он с одной стороны является глобальным объектом в JavaScript, с другой -- содержит свойства и методы для управления окном браузера, открытия новых окон, например:
```js
//+ run
// открыть новое окно/вкладку с URL http://ya.ru
window.open('http://ya.ru');
```
## Объектная модель документа (DOM) ## Объектная модель документа (DOM)
Глобальный объект `document` даёт возможность взаимодействовать с содержимым страницы. Глобальный объект `document` даёт возможность взаимодействовать с содержимым страницы.
Он и громадное количество его свойств, методов и связанных с ним интерфейсов описаны в [стандарте W3C DOM](http://www.w3.org/DOM/DOMTR). Пример использования:
```js
//+ run
document.body.style.background = 'red';
alert('Элемент BODY стал красным, а сейчас обратно вернётся');
document.body.style.background = '';
```
Он и громадное количество его свойств и методов описаны в [стандарте W3C DOM](http://www.w3.org/DOM/DOMTR).
По историческим причинам когда-то появилась первая версия стандарта DOM Level 1, затем придумали ещё свойства и методы, и появился DOM Level 2, на текущий момент поверх них добавили ещё DOM Level 3 и готовится DOM 4. По историческим причинам когда-то появилась первая версия стандарта DOM Level 1, затем придумали ещё свойства и методы, и появился DOM Level 2, на текущий момент поверх них добавили ещё DOM Level 3 и готовится DOM 4.
@ -24,6 +41,8 @@
Также информацию по работе с элементами страницы можно найти в стандарте [HTML 5](http://www.w3.org/TR/html5/Overview.html). Также информацию по работе с элементами страницы можно найти в стандарте [HTML 5](http://www.w3.org/TR/html5/Overview.html).
Мы подробно ознакомимся с DOM далее в этой части учебника.
## Объектная модель браузера (BOM) ## Объектная модель браузера (BOM)
BOM -- это объекты для работы с чем угодно, кроме документа. BOM -- это объекты для работы с чем угодно, кроме документа.
@ -32,14 +51,19 @@ BOM -- это объекты для работы с чем угодно, кро
<ul> <ul>
<li>Объект [navigator](https://developer.mozilla.org/en/DOM/window.navigator) содержит общую информацию о браузере и операционной системе. Особенно примечательны два свойства: `navigator.userAgent` -- содержит информацию о браузере и `navigator.platform` -- содержит информацию о платформе, позволяет различать Windows/Linux/Mac и т.п.</li> <li>Объект [navigator](https://developer.mozilla.org/en/DOM/window.navigator) содержит общую информацию о браузере и операционной системе. Особенно примечательны два свойства: `navigator.userAgent` -- содержит информацию о браузере и `navigator.platform` -- содержит информацию о платформе, позволяет различать Windows/Linux/Mac и т.п.</li>
<li>Объект [location](https://developer.mozilla.org/en-US/docs/Web/API/Window.location) содержит информацию о текущем URL страницы и позволяет перенаправить посетителя на новый URL.</li> <li>Объект [location](https://developer.mozilla.org/en-US/docs/Web/API/Window.location) содержит информацию о текущем URL страницы и позволяет перенаправить посетителя на новый URL.</li>
<li>Функции `alert/confirm/prompt` -- тоже входят в BOM. <li>Функции `alert/confirm/prompt` -- тоже входят в BOM.</li>
</li>
</ul> </ul>
Большинство возможностей BOM стандартизированы в [HTML 5](http://www.w3.org/TR/html5/Overview.html), но браузеры любят изобрести что-нибудь своё, особенное. Пример использования:
```js
//+ run
alert(location.href); // выведет текущий адрес
```
## Объекты и функции JavaScript Большинство возможностей BOM стандартизированы в [HTML 5](http://www.w3.org/TR/html5/Overview.html), хотя различные браузеры и предоставляют зачастую что-то своё, в дополнение к стандарту.
JavaScript -- объекты и методы языка JavaScript, который даёт возможность управлять всем этим. Именно их описывает стандарт EcmaScript. ## Итого
Далее в этом курсе мы будем, преимущественно, изучать DOM, поскольку именно документ занимает центральную роль в организации интерфейса, и работа с ним -- сложнее всего. Итак, у нас есть DOM, BOM и, собственно, язык JavaScript, который даёт возможность управлять всем этим.
Далее мы приступим к изучению DOM, поскольку именно документ занимает центральную роль в организации интерфейса, и работа с ним -- сложнее всего.

View file

@ -1,6 +1,6 @@
# Методы contains и compareDocumentPosition # Методы contains и compareDocumentPosition
Если есть два элемента, то иногда бывает нужно понять, находится ли один в другом, и произвести обработку в зависимости от результата. Если есть два элемента, то иногда бывает нужно понять, лежит ли один из них выше другого, то есть является ли его предком.
Обычные поисковые методы здесь не дают ответа, но есть два специальных. Они используются редко, но когда подобная задача встаёт, то знание метода может сэкономить много строк кода. Обычные поисковые методы здесь не дают ответа, но есть два специальных. Они используются редко, но когда подобная задача встаёт, то знание метода может сэкономить много строк кода.
@ -20,7 +20,7 @@ var result = parent.contains(child);
Бывает, что у нас есть два элемента, к примеру, `<li>` в списке, и нужно понять, какой из них выше другого. Бывает, что у нас есть два элемента, к примеру, `<li>` в списке, и нужно понять, какой из них выше другого.
Это поможет сделать другой метод. Метод `compareDocumentPosition` -- более мощный, чем `contains`, он предоставляет одновременно информацию и о содержании и об относительном порядке элементов.
Синтаксис: Синтаксис:
@ -55,32 +55,33 @@ var result = nodeA.compareDocumentPosition(nodeB);
<ul> <ul>
<li>1.1</li> <li>1.1</li>
</ul> </ul>
<p>...</p>
<script> <script>
var ul = document.getElementsByTagName('ul')[0]; var p = document.body.children[0];
var ul = document.body.children[1];
// 1. соседи
alert( ul.compareDocumentPosition( ul.previousSibling ) ); // 2 = 10
alert( ul.compareDocumentPosition( ul.nextSibling ) ); // 4 = 100
// 2. родитель/потомок
alert( ul.compareDocumentPosition( ul.firstChild ) ); // 20 = 10100
alert( ul.compareDocumentPosition( ul.parentNode ) ); // 10 = 1010
// 3. вообще разные узлы
var li = ul.children[0]; var li = ul.children[0];
alert( li.compareDocumentPosition( document.body.lastChild ) ); // 4 = 100
// 1. <ul> находится после <p>
alert( ul.compareDocumentPosition( p ) ); // 2 = 10
// 2. <p> находится до <ul>
alert( p.compareDocumentPosition( ul ) ); // 4 = 100
// 3. <ul> родитель <li>
alert( ul.compareDocumentPosition( li ) ); // 20 = 10100
// 4. <ul> потомок <body>
alert( ul.compareDocumentPosition( document.body ) ); // 10 = 1010
</script> </script>
``` ```
Комментарии: Более подробно:
<ol> <ol>
<li>Узлы являются соседями, поэтому стоит только бит "предшествования": какой перед каким.</li> <li>Узлы не вложены один в другой, поэтому стоит только бит "предшествования", отсюда `10`.</li>
<li>Здесь стоят сразу два бита: `10100` означает, что `ul` одновременно содержит `ul.firstChild` и является его предшественником, то есть при прямом обходе дерева документа сначала встречается `nodeA`, а потом `nodeB`. <li>То же самое, но обратный порядок узлов, поэтому `100`.</li>
Аналогично, `1010` означает, что `ul.parentNode` содержит `ul` и предшествует ему.</li> <li>Здесь стоят сразу два бита: `10100` означает, что `ul` одновременно содержит `li` и является его предшественником, то есть при прямом обходе дерева документа сначала встречается `ul`, а потом `li`.</li>
<li>Так как ни один из узлов не является предком друг друга, то стоит только бит предшествования: `li` предшествует последнему узлу документа, никакого сюрприза здесь нет.</li> <li>Аналогично предыдущему, `1010` означает, что `document.body` содержит `ul` и предшествует ему.</li>
</ol> </ol>
[smart header="Перевод в двоичную систему"] [smart header="Перевод в двоичную систему"]
Самый простой способ самостоятельно посмотреть, как число выглядит в 2-ной системе -- вызвать для него `toString(2)`, например: Самый простой способ самостоятельно посмотреть, как число выглядит в 2-ной системе -- вызвать для него `toString(2)`, например:
@ -101,7 +102,7 @@ alert( 20..toString(2) );
Здесь после `20` две точки, так как если одна, то JS подумает, что после неё десятичная часть -- будет ошибка. Здесь после `20` две точки, так как если одна, то JS подумает, что после неё десятичная часть -- будет ошибка.
[/smart] [/smart]
Проверка условия "`nodeA` содержит `nodeB`" с использованием битовых операций: `nodeA.compareDocumentPosition(nodeB) & 16`, например: Проверить конкретное условие, например, "`nodeA` содержит `nodeB`", можно при помощи битовых операций, в данном случае: `nodeA.compareDocumentPosition(nodeB) & 16`, например:
```html ```html
<!--+ run --> <!--+ run -->
@ -110,12 +111,12 @@ alert( 20..toString(2) );
</ul> </ul>
<script> <script>
var nodeA = document.body; var body = document.body;
var nodeB = document.body.children[0].children[0]; var li = document.body.children[0].children[0];
*!* *!*
if( nodeA.compareDocumentPosition(nodeB) & 16 ) { if( body.compareDocumentPosition(li) & 16 ) {
alert( nodeA +' содержит ' + nodeB ); alert( body +' содержит ' + li );
} }
*/!* */!*
</script> </script>
@ -134,76 +135,33 @@ if( nodeA.compareDocumentPosition(nodeB) & 16 ) {
<dd>Номер элемента `node` в порядке прямого обхода дерева. Только для узлов-элементов.</dd> <dd>Номер элемента `node` в порядке прямого обхода дерева. Только для узлов-элементов.</dd>
</dl> </dl>
На их основе можно написать кросс-браузерную реализацию `compareDocumentPosition`: На их основе можно написать полифилл для `compareDocumentPosition`:
```js ```js
// Адаптировано с http://ejohn.org/blog/comparing-document-position/ // код с http://compatibility.shwups-cms.ch/en/polyfills/?&id=82
function compareDocumentPosition(a, b) { (function(){
return a.compareDocumentPosition ? var el = document.documentElement;
a.compareDocumentPosition(b) : if( !el.compareDocumentPosition && el.sourceIndex !== undefined ){
(a != b && a.contains(b) && 16) +
(a != b && b.contains(a) && 8) + Element.prototype.compareDocumentPosition = function(other){
(a.sourceIndex >= 0 && b.sourceIndex >= 0 ? return (this != other && this.contains(other) && 16) +
(a.sourceIndex < b.sourceIndex && 4) + (this != other && other.contains(this) && 8) +
(a.sourceIndex > b.sourceIndex && 2) : (this.sourceIndex >= 0 && other.sourceIndex >= 0 ?
1); (this.sourceIndex < other.sourceIndex && 4) +
} (this.sourceIndex > other.sourceIndex && 2)
: 1
) + 0;
}
}
}());
``` ```
Эта функция будет работать для узлов-элементов во всех браузерах. С этим полифиллом метод доступен для элементов во всех браузерах.
## Итого ## Итого
Для проверки, лежит ли один узел внутри другого, достаточно метода `nodeA.contains(nodeB)`.
Для расширенной проверки на предшествование есть метод `compareDocumentPosition`, кросс-браузерный вариант которого приведён выше.
Пример использования:
```html
<!--+ run -->
<ul> <ul>
<li id="li1">1</li> <li>Для проверки, является ли один узел предком другого, достаточно метода `nodeA.contains(nodeB)`.</li>
<li id="li2">2</li> <li>Для расширенной проверки на предшествование есть метод `compareDocumentPosition`.</li>
<li>Для IE8 нужен полифилл для `compareDocumentPosition`.</li>
</ul> </ul>
<script>
var body = document.body;
*!*
if( compareDocumentPosition(body, li1) & 16 ) {
alert( 'BODY содержит LI-1' );
}
if( compareDocumentPosition(li1, li2) & 4 ) {
alert( 'LI-1 предшествует LI-2' );
}
*/!*
function compareDocumentPosition(a, b) {
return a.compareDocumentPosition ?
a.compareDocumentPosition(b) :
(a != b && a.contains(b) && 16) +
(a != b && b.contains(a) && 8) +
(a.sourceIndex >= 0 && b.sourceIndex >= 0 ?
(a.sourceIndex < b.sourceIndex && 4) +
(a.sourceIndex > b.sourceIndex && 2) :
1);
}
</script>
```
Список битовых масок для проверки:
<table>
<tr>
<th>Биты</th>
<th>Число</th>
<th>Значение</th>
</tr>
<tr><td>000000</td><td>0</td><td>`nodeA` и `nodeB` -- один и тот же узел</td></tr>
<tr><td>000001</td><td>1</td><td>Узлы в разных документах (или один из них не в документе)</td></tr>
<tr><td>000010</td><td>2</td><td>`nodeB` предшествует `nodeA` (в порядке обхода документа)</td></tr>
<tr><td>000100</td><td>4</td><td>`nodeA` предшествует `nodeB`</td></tr>
<tr><td>001000</td><td>8</td><td>`nodeB` содержит `nodeA`</td></tr>
<tr><td>010000</td><td>16</td><td>`nodeA` содержит `nodeB`</td></tr>
</table>

View file

@ -10,34 +10,16 @@
Каждый `SPAN` раскрашивается при помощи CSS. Каждый `SPAN` раскрашивается при помощи CSS.
Жизнь часам будет обеспечивать функция `update`, вызываемая каждую секунду: `setInterval(update, 1000)`. Жизнь часам будет обеспечивать функция `update`, вызываемая каждую секунду:
```js
var timerId; // таймер, если часы запущены
function clockStart() { // запустить часы
if (timerId) return;
timerId = setInterval(update, 1000);
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null;
}
```
Обратите внимание, что вызов `update` не только запланирован, но и производится тут же в строке `(*)`. Иначе посетителю пришлось бы ждать до первого выполнения `setInterval`.
Функция обновления часов:
```js ```js
function update() { function update() {
var clock = document.getElementById('clock'); var clock = document.getElementById('clock');
*!* *!*
var date = new Date(); // (*) var date = new Date(); // (*)
*/!* */!*
var hours = date.getHours(); var hours = date.getHours();
if (hours < 10) hours = '0'+hours; if (hours < 10) hours = '0'+hours;
clock.children[0].innerHTML = hours; clock.children[0].innerHTML = hours;
@ -56,4 +38,19 @@ function update() {
На самом деле мы не можем опираться на счетчик для вычисления даты, т.к. `setInterval` не гарантирует точную задержку. Если в другом участке кода будет вызван `alert`, то часы остановятся, как и любые счетчики. На самом деле мы не можем опираться на счетчик для вычисления даты, т.к. `setInterval` не гарантирует точную задержку. Если в другом участке кода будет вызван `alert`, то часы остановятся, как и любые счетчики.
[edit src="solution"]Полный код решения[/edit] Функция `clockStart` для запуска часов:
```js
function clockStart() { // запустить часы
setInterval(update, 1000);
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
timerId = null;
}
```
Обратите внимание, что вызов `update` не только запланирован, но и тут же производится тут же в строке `(*)`. Иначе посетителю пришлось бы ждать до первого выполнения `setInterval`, то есть целую секунду.

View file

@ -2,9 +2,9 @@
<html> <html>
<head><meta charset="utf-8"> <head><meta charset="utf-8">
<style> <style>
.hour { color: red } .hour { color: red }
.min { color: green } .min { color: green }
.sec { color: blue } .sec { color: blue }
</style> </style>
</head> </head>
<body> <body>
@ -35,25 +35,14 @@ function update() {
} }
function clockStart() { function clockStart() {
if (timerId) return; setInterval(update, 1000);
timerId = setInterval(update, 1000);
update(); // <-- начать тут же, не ждать 1 секунду пока setInterval сработает update(); // <-- начать тут же, не ждать 1 секунду пока setInterval сработает
} }
function clockStop() { clockStart();
clearInterval(timerId);
timerId = null;
}
</script> </script>
<input type="button" onclick="clockStart()" value="Старт">
<input type="button" onclick="clockStop()" value="Стоп">
</body> </body>
</html> </html>

View file

@ -2,7 +2,7 @@
[importance 4] [importance 4]
Создайте цветные часики как в примере ниже, **используя `setInterval`**: Создайте цветные часики как в примере ниже:
[iframe src="solution"] [iframe src="solution" height=100]

View file

@ -1,7 +0,0 @@
Общее решение описано в [аналогичной задаче с setInterval](/task/clock-setinterval).
Способ через `setTimeout` -- по сути, такой же, только функция `update` каждый раз ставит себя в очередь заново.
Заметим, что в данном случае целесообразнее использовать `setInterval`, т.к. нужна не задержка между запусками, а просто запуск каждую секунду.
[edit src="solution"]Открыть решение в песочнице[/edit]

View file

@ -1,58 +0,0 @@
<!DOCTYPE HTML>
<html>
<head><meta charset="utf-8">
<style>
.hour { color: red }
.min { color: green }
.sec { color: blue }
</style>
</head>
<body>
<div id="clock">
<span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>
<script>
var timerId;
function update() {
var clock = document.getElementById('clock');
var date = new Date();
var hours = date.getHours()
if (hours < 10) hours = '0'+hours;
clock.children[0].innerHTML = hours;
var minutes = date.getMinutes();
if (minutes < 10) minutes = '0'+minutes;
clock.children[1].innerHTML = minutes;
var seconds = date.getSeconds();
if (seconds < 10) seconds = '0'+seconds;
clock.children[2].innerHTML = seconds;
timerId = setTimeout(update, 1000);
}
function clockStart() {
if (timerId) return;
update();
}
function clockStop() {
clearTimeout(timerId);
timerId = null;
}
</script>
<input type="button" onclick="clockStart()" value="Старт">
<input type="button" onclick="clockStop()" value="Стоп">
</body>
</html>

View file

@ -1,10 +0,0 @@
<!DOCTYPE HTML>
<html>
<body>
<input type="button" onclick="clockStart()" value="Start">
<input type="button" onclick="clockStop()" value="Stop">
</body>
</html>

View file

@ -1,10 +0,0 @@
# Часики при помощи "setTimeout"
[importance 3]
Создайте цветные часы, **используя `setTimeout` вместо `setInterval`**:
[iframe src="solution"]

View file

@ -2,7 +2,7 @@
Нужно учесть два момента. Нужно учесть два момента.
<ol> <ol>
<li>Родителя может не быть (элемент уже удален или еще не вставлен).</li> <li>Родителя может не быть (элемент уже удален или еще не вставлен). В этом случае мы не будем ничего делать.</li>
<li>Для совместимости со стандартным методом нужно вернуть удаленный элемент.</li> <li>Для совместимости со стандартным методом нужно вернуть удаленный элемент.</li>
</ol> </ol>
@ -10,7 +10,9 @@
```js ```js
function remove(elem) { function remove(elem) {
return elem.parentNode ? elem.parentNode.removeChild(elem) : elem; var parent = elem.parentNode;
if (parent) elem.parentNode.removeChild(elem);
return elem;
} }
``` ```

View file

@ -4,7 +4,7 @@
Напишите функцию, которая удаляет элемент из DOM. Напишите функцию, которая удаляет элемент из DOM.
Синтаксис должен быть таким: `remove(elem)`, то есть, в отличие от `parentNode.removeChild(elem)` -- без родительского элемента. Синтаксис должен быть таким: `remove(elem)`, то есть, более короткий вариант, чем `parentNode.removeChild(elem)`.
```html ```html
<div>Это</div> <div>Это</div>

View file

@ -1,4 +1,4 @@
Для того, чтобы добавить элемент *после* `refElem`, мы можем вставить его *перед* `refElem.nextSibling`. Для того, чтобы добавить элемент *после* `refElem`, мы можем, используя `insertBefore`, вставить его *перед* `refElem.nextSibling`.
Но что если `nextSibling` нет? Это означает, что `refElem` является последним потомком своего родителя и можем использовать `appendChild`. Но что если `nextSibling` нет? Это означает, что `refElem` является последним потомком своего родителя и можем использовать `appendChild`.
@ -6,17 +6,17 @@
```js ```js
function insertAfter(elem, refElem) { function insertAfter(elem, refElem) {
var parent = refElem.parentNode; var parent = refElem.parentNode;
var next = refElem.nextSibling; var next = refElem.nextSibling;
if (next) { if (next) {
return parent.insertBefore(elem, next); return parent.insertBefore(elem, next);
} else { } else {
return parent.appendChild(elem); return parent.appendChild(elem);
} }
} }
``` ```
Но код может быть гораздо короче, если использовать фишку со вторым аргументом null метода `insertBefore`: Но код может быть гораздо короче, если вспомнить, что `insertBefore` со вторым аргументом null работает как `appendChild`:
```js ```js
function insertAfter(elem, refElem) { function insertAfter(elem, refElem) {
@ -24,6 +24,6 @@ function insertAfter(elem, refElem) {
} }
``` ```
Если нет `nextSibling`, то второй аргумент `insertBefore` становится `null` и тогда `insertBefore(elem,null)` работает как `appendChild`. Если нет `nextSibling`, то второй аргумент `insertBefore` становится `null` и тогда `insertBefore(elem, null)` осуществит вставку в конец, как и требуется.
В решении нет проверки на существование `refElem.parentNode`, поскольку вставка после элемента без родителя -- уже ошибка, пусть она возникнет в функции, это нормально. В решении нет проверки на существование `refElem.parentNode`, поскольку вставка после элемента без родителя -- уже ошибка, пусть она возникнет в функции, это нормально.

View file

@ -12,7 +12,7 @@ function removeChildren(elem) {
Если вы попробуете это на практике, то увидите, то это не сработает. Если вы попробуете это на практике, то увидите, то это не сработает.
Не сработает потому, что `childNodes` всегда начинается 0 и автоматически смещается, когда первый потомок удален(т.е. тот, что был вторым, станет первым), поэтому такой цикл по `k` пропустит половину узлов. Не сработает потому, что коллекция `childNodes` всегда начинается с индекса 0 и автоматически обновляется, когда первый потомок удален(т.е. тот, что был вторым, станет первым). А переменная `k` в цикле всё время увеличивается, поэтому такой цикл пропустит половину узлов.
# Решение через DOM # Решение через DOM
@ -26,9 +26,9 @@ function removeChildren(elem) {
} }
``` ```
# Неправильное решение (innerHTML) # Альтернатива через innerHTML
Прямая попытка использовать `innerHTML` была бы неправильной: Можно и просто обнулить содержимое через `innerHTML`:
```js ```js
function removeChildren(elem) { function removeChildren(elem) {
@ -36,11 +36,9 @@ function removeChildren(elem) {
} }
``` ```
Дело в том, что в IE8- свойство `innerHTML` на большинстве табличных элементов (кроме ячеек `TH/TD`) не работает. Будет ошибка. Это не будет работать в IE8- для таблиц, так как на большинстве табличных элементов (кроме ячеек `TH/TD`) в старых IE запрещено менять `innerHTML`.
# Верное решение (innerHTML) Впрочем, можно завернуть `innerHTML` в `try/catch`:
Можно завернуть `innerHTML` в `try/catch`:
```js ```js
function removeChildren(elem) { function removeChildren(elem) {

View file

@ -25,5 +25,3 @@
removeChildren(ol); // очищает список removeChildren(ol); // очищает список
</script> </script>
``` ```
P.S. Проверьте ваше решение в IE8.

View file

@ -1,6 +1,4 @@
Делайте проверку на `null` в цикле. `prompt` возвращает это значение только если был нажат ESC. Делаем цикл, пока посетитель что-то вводит -- добавляет `<li>`.
Контент в `LI` добавляйте с помощью `document.createTextNode`, чтобы правильно работали &lt;, &gt; и т.д. Содержимое в `<li>` присваиваем через `document.createTextNode`, чтобы правильно работали &lt;, &gt; и т.д.
[edit src="solution"]Решение в песочнице[/edit]

View file

@ -13,7 +13,7 @@
while (true) { while (true) {
var data = prompt("Введите текст для пункта списка", ""); var data = prompt("Введите текст для пункта списка", "");
if (data === null) { if (!data) {
break; break;
} }
@ -22,6 +22,6 @@
ul.appendChild(li); ul.appendChild(li);
} }
</script> </script>
</body> </body>
</html> </html>

View file

@ -8,13 +8,11 @@
<ol> <ol>
<li>Запрашивайте содержимое пункта у пользователя с помощью `prompt`.</li> <li>Запрашивайте содержимое пункта у пользователя с помощью `prompt`.</li>
<li>Создавайте пункт и добавляйте его к `UL`.</li> <li>Создавайте пункт и добавляйте его к `UL`.</li>
<li>Процесс прерывается, когда пользователь нажимает ESC.</li> <li>Процесс прерывается, когда пользователь нажимает ESC или вводит пустую строку.</li>
</ol> </ol>
**Все элементы должны создаваться динамически.** **Все элементы должны создаваться динамически.**
Если посетитель вводит теги -- в списке они показываются как обычный текст. Если посетитель вводит теги -- пусть в списке они показываются как обычный текст.
[demo src="solution"] [demo src="solution"]
P.S. `prompt` возвращает `null`, если пользователь нажал ESC.

View file

@ -46,4 +46,4 @@
createTree(container, data); createTree(container, data);
</script> </script>
</body> </body>
</html> </html>

View file

@ -47,6 +47,4 @@ createTree(container, data); // создаёт
Если получится -- сделайте оба. Если получится -- сделайте оба.
P.S. Желательно, чтобы в дереве не было лишних элементов, в частности -- пустых `<ul></ul>` на нижнем уровне. P.S. Желательно, чтобы в дереве не было лишних элементов, в частности -- пустых `<ul></ul>` на нижнем уровне.

View file

@ -0,0 +1,8 @@
# Подсказки
<ol>
<li>Получить количество вложенных узлов можно через `elem.getElementsByTagName('*').length`.</li>
<li>Текст в начале `<li>` доступен как `li.firstChild`, его содержимое -- `li.firstChild.data`.</li>
</ol>
# Решение

View file

@ -42,7 +42,7 @@
</ul> </ul>
<script> <script>
var lis = document.getElementsByTagName('li') var lis = document.getElementsByTagName('li');
for(i=0; i<lis.length; i++) { for(i=0; i<lis.length; i++) {
// получить количество детей // получить количество детей
@ -50,7 +50,7 @@ for(i=0; i<lis.length; i++) {
if (!childCount) continue; if (!childCount) continue;
// добавить кол-во детей к текстовому узлу // добавить кол-во детей к текстовому узлу
lis[i].firstChild.nodeValue += ' ['+childCount+']'; lis[i].firstChild.data += ' ['+childCount+']';
} }
</script> </script>

View file

@ -0,0 +1,10 @@
# Дерево
[importance 5]
Есть дерево [edit src="source"]в песочнице[/edit].
Напишите код, который добавит каждому элементу списка `<li>` количество вложенных в него элементов. Узлы нижнего уровня, без детей -- пропускайте.
Результат:
[iframe border=1 src="solution"]

View file

@ -111,19 +111,17 @@ div.innerHTML = "<strong>Ура!</strong> Вы прочитали это важ
</script> </script>
``` ```
**Этот же метод используется для вставки в начало элемента.** Для вставки элемента в начало достаточно указать, что вставлять будем перед первым потомком:
Достаточно указать, что вставлять будем перед первым потомком:
```js ```js
list.insertBefore(newLi, list.firstChild); list.insertBefore(newLi, list.firstChild);
``` ```
У человека, который посмотрит на этот код внимательно, наверняка возникнет вопрос: "А что, если `list` вообще пустой, в этом случае ведь `list.firstChild = null`, произойдёт ли вставка?" У читателя, который посмотрит на этот код внимательно, наверняка возникнет вопрос: "А что, если `list` вообще пустой, в этом случае ведь `list.firstChild = null`, произойдёт ли вставка?"
Ответ -- да, произойдёт. Ответ -- да, произойдёт.
**Дело в том, что если в качестве `nextSibling` указать `null`, то `insertBefore` сработает как `appendChild`:** **Дело в том, что если вторым аргументом указать `null`, то `insertBefore` сработает как `appendChild`:**
```js ```js
parentElem.insertBefore(elem, null); parentElem.insertBefore(elem, null);
@ -131,12 +129,18 @@ parentElem.insertBefore(elem, null);
parentElem.appendChild(elem) parentElem.appendChild(elem)
``` ```
Так что `insertBefore` универсален.
</dd> </dd>
</dl> </dl>
Все методы вставки возвращают вставленный узел, например `parentElem.appendChild(elem)` возвращает `elem`. [smart]
Все методы вставки возвращают вставленный узел.
### Добавление сообщения Например, `parentElem.appendChild(elem)` возвращает `elem`.
[/smart]
### Пример использования
Добавим сообщение в конец `<body>`: Добавим сообщение в конец `<body>`:
@ -180,9 +184,9 @@ parentElem.appendChild(elem)
## Клонирование узлов: cloneNode ## Клонирование узлов: cloneNode
А как бы мне вставить второе такое же сообщение? А как бы вставить второе похожее сообщение?
Конечно, можно сделать функцию для генерации сообщений и поместить туда этот код, но в ряде случаев гораздо эффективнее -- *клонировать* существующий `div`. В частности, если элемент большой, то клонировать его будет гораздо быстрее, чем пересоздавать. Конечно, можно сделать функцию для генерации сообщений и поместить туда этот код, но в ряде случаев гораздо эффективнее -- *клонировать* существующий `div`, а потом изменить текст внутри. В частности, если элемент большой, то клонировать его будет гораздо быстрее, чем пересоздавать.
Вызов `elem.cloneNode(true)` создаст "глубокую" копию элемента -- вместе с атрибутами, включая подэлементы. Если же вызвать с аргумнтом `false`, то он копия будет без подэлементов, но это нужно гораздо реже. Вызов `elem.cloneNode(true)` создаст "глубокую" копию элемента -- вместе с атрибутами, включая подэлементы. Если же вызвать с аргумнтом `false`, то он копия будет без подэлементов, но это нужно гораздо реже.
@ -223,8 +227,8 @@ div.parentNode.insertBefore( div2, div.nextSibling );
``` ```
<ol> <ol>
<li>Для вставки нам нужен будущий родитель. Мы, возможно, не знаем, где точно находится `div` (или не хотим зависеть от того, где он), но если нужно вставить рядом с ним, то родителем определённо будет `div.parentNode`.</li> <li>Для вставки нам нужен будущий родитель. Мы, возможно, не знаем, где точно находится `div` (или не хотим зависеть от того, где он), но если нужно вставить рядом с `div`, то родителем определённо будет `div.parentNode`.</li>
<li>Вставляем в родителя. Мы хотели бы вставить после `div`, но метода `insertAfter` нет, есть только `insertBefore`, поэтому вставляем *перед* его правым соседом `div.nextSibling`.</li> <li>Мы хотели бы вставить *после* `div`, но метода `insertAfter` нет, есть только `insertBefore`, поэтому вставляем *перед* его правым соседом `div.nextSibling`.</li>
</ol> </ol>
@ -292,22 +296,35 @@ div.parentNode.insertBefore( div2, div.nextSibling );
</script> </script>
``` ```
## Пример использования текстовых узлов ## Текстовые узлы для вставки текста
При работе с сообщением мы использовали только узлы-элементы и `innerHTML`. При работе с сообщением мы использовали только узлы-элементы и `innerHTML`.
Но и текстовые узлы тоже имеют интересную область применения! Было бы несправедливо их обойти. Но и текстовые узлы тоже имеют интересную область применения!
У них есть две особенности. Начнем с небольшого вопроса. Если текст для сообщения нужно показать именно как текст, а не как HTML, то можно обернуть его в текстовый узел.
Например:
Ответили на вопрос выше? Даже если нет, то, поглядев в решение, вы легко увидите разницу. ```html
<!--+ run -->
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.css">
Итак, отличий два: <script>
<ol> var div = document.createElement('div');
<li>При создании текстового узла `createTextNode('<b>...</b>')` любые специальные символы и теги в строке будут интерпретированы *как текст*. А `innerHTML` обработал бы их *как HTML*.</li> div.className = "alert alert-success";
<li>Во всех современных браузерах создание и вставка текстового узла работает гораздо быстрее, чем присвоение HTML.</li> document.body.appendChild(div);
</ol>
*!*
var text = prompt("Введите текст для сообщения", "Жили были <a> и <b>!");
// вставится именно как текст, без HTML-обработки
div.appendChild( document.createTextNode(text) );
*/!*
</script>
```
В современных браузерах (кроме IE8-) в качестве альтернативы можно использовать присвоение `textContent`.
## Итого ## Итого
@ -329,7 +346,7 @@ div.parentNode.insertBefore( div2, div.nextSibling );
Все эти методы возвращают `elem`. Все эти методы возвращают `elem`.
Запомнить порядок аргументов очень просто: **новый(вставляемый) элемент -- всегда первый.** **Запомнить порядок аргументов очень просто: новый(вставляемый) элемент -- всегда первый.**
Методы для изменения DOM также описаны в спецификации <a href="http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html">DOM Level 1</a>. Методы для изменения DOM также описаны в спецификации <a href="http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html">DOM Level 1</a>.

View file

@ -1,49 +0,0 @@
# Подсказки
<ul>
<li>Проверить поддержку `insertAdjacentHTML` можно так:
```js
if (elem.insertAdjacentHTML) { ... }
```
</li>
<li>Если этот метод не поддерживается, то сделайте временный элемент, через `innerHTML` поставьте туда `html`, а затем переместите содержимое в `DocumentFragment`. Последнее действие -- вставка в документ.</li>
</ul>
# Решение
```html
<!--+ run -->
<ul>
<li>1</li>
<li>2</li>
<li>5</li>
</ul>
<script>
var ul = document.body.children[0];
var li5 = ul.children[2];
function insertBefore(elem, html) {
if (elem.insertAdjacentHTML) {
elem.insertAdjacentHTML("beforeBegin", html);
} else {
var fragment = document.createDocumentFragment();
var tmp = document.createElement('DIV');
tmp.innerHTML = html;
while(tmp.firstChild) {
// перенести все узлы во fragment
fragment.appendChild(tmp.firstChild);
}
elem.parentNode.insertBefore(fragment, elem);
}
}
insertBefore(li5, "<li>3</li><li>4</li>")
</script>
```

View file

@ -1,34 +0,0 @@
# Вставка insertAdjacentHTML/DocumentFragment
[importance 4]
Напишите кроссбраузерную функцию `insertBefore(elem, html)`, которая:
<ul>
<li>Вставляет HTML-строку `html` перед элементом `elem`, используя `insertAdjacentHTML`,</li>
<li>Если он не поддерживается (старый Firefox) -- то через `DocumentFragment`.</li>
</ul>
В обоих случаях должна быть лишь одна операция с DOM документа.
Следующий код должен вставить два пропущенных элемента списка `<li>3</li><li>4</li>`:
```html
<ul>
<li>1</li>
<li>2</li>
<li>5</li>
</ul>
<script>
var ul = document.body.children[0];
var li5 = ul.children[2];
function insertBefore(elem, html) {
/* ваш код */
}
insertBefore(li5, "<li>3</li><li>4</li>")
</script>
```

View file

@ -8,4 +8,4 @@
P.S. Создавать `DocumentFragment` здесь ни к чему. Можно вытащить из документа `TBODY` и иметь дело с ним в отрыве от DOM (алгоритм 4). P.S. Создавать `DocumentFragment` здесь ни к чему. Можно вытащить из документа `TBODY` и иметь дело с ним в отрыве от DOM (алгоритм 4).
P.P.S. Если нужно сделать много узлов, то обычно `innerHTML` работает быстрее, чем генерация элементов через DOM-вызовы. Но в данном случае мы не создаём элементы, а сортируем и перевставляем готовые, так что результаты могут отличаться. P.P.S. Если нужно сделать много узлов, то обычно `innerHTML` работает быстрее, чем удаление и вставка элементов через DOM-вызовы. То есть, сгенерировать таблицу заново эффективнее.

View file

@ -44,4 +44,5 @@
Как сделать, чтобы сортировка работала как можно быстрее? А если в таблице 10000 строк (бывает и такое)? Как сделать, чтобы сортировка работала как можно быстрее? А если в таблице 10000 строк (бывает и такое)?
P.S. Может ли здесь помочь `DocumentFragment`? P.S. Может ли здесь помочь `DocumentFragment`?
P.P.S. Если предположить, что у нас заранее есть массив данных для таблицы в JavaScript -- что быстрее: отсортировать эту таблицу или сгенерировать новую? P.P.S. Если предположить, что у нас заранее есть массив данных для таблицы в JavaScript -- что быстрее: отсортировать эту таблицу или сгенерировать новую?

View file

@ -31,7 +31,7 @@ document.body.appendChild(ul); // затем в документ
Как ни странно, между этими последовательностями есть разница. В большинстве браузеров, второй вариант -- быстрее. Как ни странно, между этими последовательностями есть разница. В большинстве браузеров, второй вариант -- быстрее.
Почему же? Иногда говорят: "потому что браузер перерисовывает каждый раз при добавлении элемента". Это не так. **Дело вовсе не в перерисовке**. Почему же? Иногда говорят: "потому что браузер перерисовывает каждый раз при добавлении элемента". Это не так. Дело вовсе не в перерисовке.
Браузер достаточно "умён", чтобы ничего не перерисовывать понапрасну. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта, и на тот момент уже совершенно без разницы, в какой последовательности были изменены узлы. Браузер достаточно "умён", чтобы ничего не перерисовывать понапрасну. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта, и на тот момент уже совершенно без разницы, в какой последовательности были изменены узлы.
@ -39,6 +39,7 @@ document.body.appendChild(ul); // затем в документ
Что именно происходит -- зависит от конкретной, внутренней браузерной реализации DOM, но это отнимает время. Конечно, браузеры развиваются и стараются свести лишние действия к минимуму. Что именно происходит -- зависит от конкретной, внутренней браузерной реализации DOM, но это отнимает время. Конечно, браузеры развиваются и стараются свести лишние действия к минимуму.
[online]
### Бенчмарк [#insert-bench-tbody] ### Бенчмарк [#insert-bench-tbody]
Чтобы легко проверить текущее состояние дел -- вот два бенчмарка. Чтобы легко проверить текущее состояние дел -- вот два бенчмарка.
@ -53,10 +54,9 @@ document.body.appendChild(ul); // затем в документ
<table id="bench-table"></table> <table id="bench-table"></table>
```js Код для тестов находится в файле [](insert-bench.js).
//+ hide="открыть код" src="insert-bench.js"
```
[/online]
## Добавление множества узлов ## Добавление множества узлов
Продолжим работать со вставкой узлов. Продолжим работать со вставкой узлов.
@ -71,7 +71,7 @@ document.body.appendChild(ul); // затем в документ
ul.innerHTML += "<li>1</li><li>2</li>..."; ul.innerHTML += "<li>1</li><li>2</li>...";
``` ```
Но операция `+=` с `innerHTML` не работает с DOM. Она не прибавляет, а заменяет всё содержимое списка на дополненную строку. Это не только медленно, но все внешние ресурсы (картинки) будут загружены заново! Так лучше не делать. Но операция `+=` с `innerHTML` не работает с DOM. Она не прибавляет, а заменяет всё содержимое списка на дополненную строку. Это не только медленно, но все внешние ресурсы (картинки) будут загружены заново. Так лучше не делать.
А если нужно вставить в середину списка? Здесь `innerHTML` вообще не поможет. А если нужно вставить в середину списка? Здесь `innerHTML` вообще не поможет.
@ -122,9 +122,9 @@ li5.insertAdjacentHTML("beforeBegin", "<li>3</li><li>4</li>");
</script> </script>
``` ```
Единственный недостаток этого метода -- он не работает в Firefox до версии 8. Но его можно легко добавить, используя следующий JavaScript: [insertAdjacentFF.js](/files/tutorial/browser/dom/insertAdjacentFF.js). Единственный недостаток этого метода -- он не работает в Firefox до версии 8. Но его можно легко добавить, используя [полифилл insertAdjacentHTML для Firefox](insertAdjacentHTML.js).
У этого метода есть "близнецы-братья", которые поддерживаются везде, кроме FF, но в него они добавляются этим же скриптом: У этого метода есть "близнецы-братья", которые поддерживаются везде, кроме Firefox, но в него они добавляются тем же полифиллом:
<ul> <ul>
<li>[elem.insertAdjacentElement(where, newElem)](http://help.dottoro.com/ljbreokf.php) -- вставляет в произвольное место не строку HTML, а элемент `newElem`.</li> <li>[elem.insertAdjacentElement(where, newElem)](http://help.dottoro.com/ljbreokf.php) -- вставляет в произвольное место не строку HTML, а элемент `newElem`.</li>
@ -136,7 +136,7 @@ li5.insertAdjacentHTML("beforeBegin", "<li>3</li><li>4</li>");
## DocumentFragment ## DocumentFragment
[warn header="Важно для старых браузеров"] [warn header="Важно для старых браузеров"]
Оптимизация, о которой здесь идёт речь, важна в первую очередь для старых браузеров, включая IE9-. В современных браузерах эффект от нее, как правило, не превышает 20%, а иногда может быть и отрицательным. Оптимизация, о которой здесь идёт речь, важна в первую очередь для старых браузеров, включая IE9-. В современных браузерах эффект от нее, как правило, небольшой, а иногда может быть и отрицательным.
[/warn] [/warn]
До этого мы говорили о вставке строки в DOM. А что делать в случае, когда надо в существующий `UL` вставить много *DOM-элементов*? До этого мы говорили о вставке строки в DOM. А что делать в случае, когда надо в существующий `UL` вставить много *DOM-элементов*?
@ -165,9 +165,9 @@ fragment.cloneNode(true); // клонирование с подэлемента
**У `DocumentFragment` нет обычных свойств DOM-узлов, таких как `innerHTML`, `tagName` и т.п. Это не узел.** **У `DocumentFragment` нет обычных свойств DOM-узлов, таких как `innerHTML`, `tagName` и т.п. Это не узел.**
**"Фишка" заключается в том, что когда `DocumentFragment` вставляется в DOM -- то он исчезает, а вместо него вставляются его дети. Это свойство является уникальной особенностью `DocumentFragment`.** Его "Фишка" заключается в том, что когда `DocumentFragment` вставляется в DOM -- то он исчезает, а вместо него вставляются его дети. Это свойство является уникальной особенностью `DocumentFragment`.
Например, если добавить в него много `LI`, и потом `appendChild` к `UL`, то фрагмент растворится, и в DOM вставятся именно `LI`, причём в том же порядке, в котором были во фрагменте. Например, если добавить в него много `LI`, и потом вызвать `ul.appendChild(fragment)`, то фрагмент растворится, и в DOM вставятся именно `LI`, причём в том же порядке, в котором были во фрагменте.
Псевдокод: Псевдокод:
@ -184,35 +184,22 @@ for (цикл по li) {
ul.appendChild(fragment); // вместо фрагмента вставятся элементы списка ul.appendChild(fragment); // вместо фрагмента вставятся элементы списка
``` ```
В современных браузерах эффект от такой оптимизации может быть различным. Чтобы понять текущее положение вещей, попробуйте в различных браузерах следующий небольшой бенчмарк. В современных браузерах эффект от такой оптимизации может быть различным, а на небольших документах иногда и отрицательным.
При нажатии на кнопки ниже в список добавляются `100` элементов. Понять текущее положение вещей вы можете, запустив следующий [edit src="benchmark"]небольшой бенчмарк[/edit].
[pre]
<div>
<input type="button" onclick="alert(bench(DocumentFragmentTest.insertPlain,200))" value="Обычная вставка"/>
<input type="button" onclick="alert(bench(DocumentFragmentTest.insertDocumentFragment,200))" value="Вставка через DocumentFragment">
</div>
[/pre]
<ul id="bench-list"></ul>
<script src="/files/tutorial/browser/dom/documentfragment-bench.js"></script>
```js
//+ hide="открыть код" src="documentfragment-bench.js"
```
## Итого ## Итого
<ul> <ul>
<li>**Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе.** <li>Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе.
Конкретная разница зависит от внутренней реализации DOM в браузере.</li> Конкретная разница зависит от внутренней реализации DOM в браузере.</li>
<li>**Семейство методов `elem.insertAdjacentHTML(where, html)`, `insertAdjacentElement`, `insertAdjacentText` позволяет вставлять HTML/элемент/текст в произвольное место документа.** <li>Семейство методов `elem.insertAdjacentHTML(where, html)`, `insertAdjacentElement`, `insertAdjacentText` позволяет вставлять HTML/элемент/текст в произвольное место документа.
Метод `insertAdjacentHTML` не поддерживается в Firefox до версии 8, остальные два метода не поддерживаются в Firefox, на момент написания текста, вообще, но есть небольшой скрипт [insertAdjacentFF.js](/files/tutorial/browser/dom/insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox. Метод `insertAdjacentHTML` не поддерживается в Firefox до версии 8, остальные два метода не поддерживаются в Firefox, на момент написания текста, вообще, но есть небольшой скрипт [insertAdjacentFF.js](insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox.
</li> </li>
<li>**`DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше.** <li>`DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше или наоборот отрицательный.
Элементы сначала вставляются в него, а потом -- он вставляется в DOM. При вставке `DocumentFragment` "растворяется", и вместо него вставляются содержащиеся в нём узлы. Элементы сначала вставляются в него, а потом -- он вставляется в DOM. При вставке `DocumentFragment` "растворяется", и вместо него вставляются содержащиеся в нём узлы.
@ -296,11 +283,11 @@ var appendLast = new function() {
</script> </script>
<style> <style>
##bench-table td { #bench-table td {
padding: 0; padding: 0;
} }
##bench-list li { #bench-list li {
display: inline-block; display: inline-block;
margin: 0; margin: 0;
padding: 2px; padding: 2px;

View file

@ -0,0 +1,12 @@
function bench(test, times) {
var sum = 0;
for(var i=0; i<times; i++) {
if(test.setup) test.setup();
var t = new Date();
test.work();
sum += (new Date() - t);
if(test.tearDown) test.tearDown();
}
return sum;
}

View file

@ -0,0 +1,45 @@
var DocumentFragmentTest = new function() {
var benchList = document.getElementById('bench-list');
var items = [];
for(var i=0; i<100; i++) {
var li = document.createElement('li');
li.innerHTML = i;
items.push(li);
}
this.insertPlain = new function() {
this.setup = function() {
while(benchList.firstChild) {
benchList.removeChild(benchList.firstChild);
}
}
this.work = function() {
for(var i=0; i<items.length; i++) {
benchList.appendChild(items[i]);
}
}
};
this.insertDocumentFragment = new function() {
this.setup = function() {
// очистить всё
while(benchList.firstChild) {
benchList.removeChild(benchList.firstChild);
}
}
this.work = function() {
var docFrag = document.createDocumentFragment();
for(var i=0; i<items.length; i++) {
docFrag.appendChild(items[i]);
}
benchList.appendChild(docFrag);
}
};
}

View file

@ -0,0 +1,20 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
Вставляются 100 элементов LI в пустой UL.
<input type="button" onclick="alert(bench(DocumentFragmentTest.insertPlain,200))" value="Замерить время на обычную вставку"/>
<input type="button" onclick="alert(bench(DocumentFragmentTest.insertDocumentFragment,200))" value="Замерить время на вставку через DocumentFragment">
<ul id="bench-list"></ul>
<script src="bench.js"></script>
<script src="documentfragment-bench.js"></script>
</body>
</html>

View file

@ -0,0 +1,35 @@
// http://learn.javascript.ru/files/tutorial/browser/dom/insertAdjacentFF.js
// Добавляет поддержку insertAdjacent* в Firefox
if (typeof HTMLElement != "undefined" && !HTMLElement.prototype.insertAdjacentElement) {
HTMLElement.prototype.insertAdjacentElement = function(where, parsedNode) {
switch(where) {
case 'beforeBegin':
this.parentNode.insertBefore(parsedNode, this)
break;
case 'afterBegin':
this.insertBefore(parsedNode, this.firstChild);
break;
case 'beforeEnd':
this.appendChild(parsedNode);
break;
case 'afterEnd':
if(this.nextSibling) this.parentNode.insertBefore(parsedNode, this.nextSibling);
else this.parentNode.appendChild(parsedNode);
break;
}
}
HTMLElement.prototype.insertAdjacentHTML = function(where, htmlStr) {
var r = this.ownerDocument.createRange();
r.setStartBefore(this);
var parsedHTML = r.createContextualFragment(htmlStr);
this.insertAdjacentElement(where, parsedHTML)
}
HTMLElement.prototype.insertAdjacentText = function(where, txtStr) {
var parsedText = document.createTextNode(txtStr)
this.insertAdjacentElement(where, parsedText)
}
}

View file

@ -49,20 +49,22 @@ HTML-документ ниже будет содержать `1 2 3`.
**Методы `document.write` и `document.writeln` пишут напрямую в текст документа, до того как браузер построит из него DOM, поэтому они могут записать в документ все, что угодно, любые стили и незакрытые теги.** **Методы `document.write` и `document.writeln` пишут напрямую в текст документа, до того как браузер построит из него DOM, поэтому они могут записать в документ все, что угодно, любые стили и незакрытые теги.**
Браузер учтет их при построении DOM, точно так же, как учитывает очередную порцию HTML-текста. Браузер учтёт их при построении DOM, точно так же, как учитывает очередную порцию HTML-текста.
Технически, вызвать `document.write` можно в любое время, однако, когда HTML загрузился, и браузер полностью построил DOM, документ становится *"закрытым"*. Попытка дописать что-то в закрытый документ открывает его заново. При этом все текущее содержимое удаляется. Технически, вызвать `document.write` можно в любое время, однако, когда HTML загрузился, и браузер полностью построил DOM, документ становится *"закрытым"*. Попытка дописать что-то в закрытый документ открывает его заново. При этом все текущее содержимое удаляется.
Текущая страница, скорее всего, уже загрузилась, поэтому если вы нажмёте на эту кнопку -- её содержимое удалится: Текущая страница, скорее всего, уже загрузилась, поэтому если вы нажмёте на эту кнопку -- её содержимое удалится:
<input type="button" onclick='document.write("Пустая страница!");' value="Запустить document.write('Пустая страница!')"> [pre no-typography]
<input type="button" onclick='document.write("Пустая страница!");' value="Запустить document.write('Пустая страница!')">
[/pre]
Из-за этой особенности `document.write` для загруженных документов не используют. Из-за этой особенности `document.write` для загруженных документов не используют.
[warn header="XHTML и `document.write`"] [warn header="XHTML и `document.write`"]
В некоторых современных браузерах при получении страницы с заголовком `Content-Type: text/xml` или `Content-Type: text/xhtml+xml` включается "XML-режим" чтения документа. Метод `document.write` при этом не работает. В некоторых современных браузерах при получении страницы с заголовком `Content-Type: text/xml` или `Content-Type: text/xhtml+xml` включается "XML-режим" чтения документа. Метод `document.write` при этом не работает.
Это одна из причин, по которой XML-режим обычно не используют. Это лишь одна из причин, по которой XML-режим обычно не используют.
[/warn] [/warn]
@ -71,10 +73,10 @@ HTML-документ ниже будет содержать `1 2 3`.
Метод `document.write` -- динозавр, он существовал десятки <strike>миллионов</strike> лет назад. С тех пор, как появился и стал стандартным метод `innerHTML`, нужда в нём возникает редко, но некоторые преимущества, всё же, есть. Метод `document.write` -- динозавр, он существовал десятки <strike>миллионов</strike> лет назад. С тех пор, как появился и стал стандартным метод `innerHTML`, нужда в нём возникает редко, но некоторые преимущества, всё же, есть.
<ul> <ul>
<li>**Метод `document.write` работает быстрее, фактически это самый быстрый способ добавить на страницу текст, сгенерированный скриптом.** <li>Метод `document.write` работает быстрее, фактически это самый быстрый способ добавить на страницу текст, сгенерированный скриптом.
Это естественно, ведь он не модифицирует существующий DOM, а пишет в текст страницы до его генерации.</li> Это естественно, ведь он не модифицирует существующий DOM, а пишет в текст страницы до его генерации.</li>
<li>**Метод `document.write` вставляет любой текст на страницу "как есть", в то время как `innerHTML` может вписать лишь валидный HTML.** (при попытке подсунуть невалидный -- браузер скорректирует его).</li> <li>Метод `document.write` вставляет любой текст на страницу "как есть", в то время как `innerHTML` может вписать лишь валидный HTML (при попытке подсунуть невалидный -- браузер скорректирует его).</li>
</ul> </ul>
Эти преимущества являются скорее средством оптимизации, которое нужно использовать именно там, где подобная оптимизация нужна или уместна. Эти преимущества являются скорее средством оптимизации, которое нужно использовать именно там, где подобная оптимизация нужна или уместна.
@ -104,7 +106,7 @@ HTML-документ ниже будет содержать `1 2 3`.
document.write('<script src="'+url+'"><\/script>'); document.write('<script src="'+url+'"><\/script>');
``` ```
Здесь `<\/script>` вместо `</script>`: обратный слеш `\` обычно используется для вставки спецсимволов типа `\n`, а если такго спецсимвола нет, в данном случае `\/` не является спецсимволом, то он просто исчезает. Так что получается такой альтернативный способ безопасно вставить строку `</script>`. Здесь `<\/script>` вместо `</script>`: обратный слеш `\` обычно используется для вставки спецсимволов типа `\n`, а если такого спецсимвола нет, в данном случае `\/` не является спецсимволом, то будет проигнорирован. Так что получается такой альтернативный способ безопасно вставить строку `</script>`.
[/smart] [/smart]
Сервер, получив запрос с такими параметрами, обрабатывает его и, исходя учитывая переданную информацию, генерирует текст скрипта, в котором обычно есть какой-то другой `document.write`, рисующий на этом месте баннер. Сервер, получив запрос с такими параметрами, обрабатывает его и, исходя учитывая переданную информацию, генерирует текст скрипта, в котором обычно есть какой-то другой `document.write`, рисующий на этом месте баннер.
@ -128,16 +130,16 @@ document.write('<script src="'+url+'"><\/script>');
Метод `document.write` (или `writeln`) пишет текст прямо в HTML, как будто он там всегда был. Метод `document.write` (или `writeln`) пишет текст прямо в HTML, как будто он там всегда был.
<ul> <ul>
<li>**Этот метод редко используется, так как работает только из скриптов, выполняемых в процессе загрузки страницы.** <li>Этот метод редко используется, так как работает только из скриптов, выполняемых в процессе загрузки страницы.
Запуск после загрузки приведёт к очистке документа.</li> Запуск после загрузки приведёт к очистке документа.</li>
<li>**Метод `document.write` очень быстр.** <li>Метод `document.write` очень быстр.
В отличие от установки `innerHTML` и DOM-методов, он не изменяет существующий документ, а работает на стадии текста, до того как DOM-структура сформирована. </li> В отличие от установки `innerHTML` и DOM-методов, он не изменяет существующий документ, а работает на стадии текста, до того как DOM-структура сформирована. </li>
<li>**Иногда `document.write` используют для добавления скриптов с динамическим URL.** <li>Иногда `document.write` используют для добавления скриптов с динамическим URL.
Рекомендуется избегать этого, так как браузер остановится на месте добавления скрипта и будет ждать его загрузки. Если скрипт будет тормозить, то и страница -- тоже. Рекомендуется избегать этого, так как браузер остановится на месте добавления скрипта и будет ждать его загрузки. Если скрипт будет тормозить, то и страница -- тоже.
Поэтому желательно подключать внешние скрипты, используя вставку скрипта через DOM. Поэтому желательно подключать внешние скрипты, используя вставку скрипта через DOM или `async/defer`. Современные системы рекламы и статистики так и делают.
</li> </li>
</ul> </ul>

View file

@ -1,232 +0,0 @@
# Современный DOM: полифиллы
В старых IE, особенно в IE8 и ниже, ряд стандартных DOM-свойств не поддерживаются или поддерживаются плохо.
Но это не значит, что, поддерживая IE8-, мы должны о них забыть!
Их можно использовать, необходимо только поставить нужный полифилл.
[cut]
## Полифиллы
"Полифилл" (англ. polyfill) -- это библиотека, которая добавляет в старые браузеры поддержку возможностей, которые в современных браузерах являются встроенными.
Один полифилл мы уже видели, когда изучали собственно JavaScript -- это библиотека [ES5 shim](https://github.com/es-shims/es5-shim). Если её подключить, то в IE8- начинают работать многие возможности ES5. Работает она через модификацию стандартных объектов и их прототипов. Это типично для полифиллов.
В работе с DOM несовместимостей гораздо больше, как и способов их обхода.
Как правило, полифиллы организованы в виде коллекции, из которой можно как выбрать отдельные свойства и функции, так и подключить всё вместе, пачкой.
Примеры полифиллов:
<ul>
<li>[](https://github.com/jonathantneal/polyfill) -- ES5 вместе с DOM</li>
<li>[](https://github.com/termi/ES5-DOM-SHIM) -- ES5 вместе с DOM</li>
<li>[](https://github.com/inexorabletash/polyfill) -- ES5+ вместе с DOM</li>
</ul>
Есть и более мелкие библиотеки, а также коллекции ссылок на них:
<ul>
<li>[](http://compatibility.shwups-cms.ch/en/polyfills/)</li>
<li>[](http://html5please.com/#polyfill)</li>
<li>[](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills)</li>
</ul>
Например, мы хотим в браузере IE8 использовать свойство `firstElementChild`, которое по умолчанию отсутствует.
Для этого мы либо смотрим свою любимую коллекцию полифиллов на предмет его поддержки, либо набираем в Google: ["\"polyfill firstElementChild\""](https://www.google.ru/search?q=polyfill+firstElementChild). Одна из ссылок укажет на [](http://compatibility.shwups-cms.ch/en/polyfills/)...
И вот, пожалуйста, код, который нужно подключить к IE8, чтобы получить `firstElementChild` (и не только):
<ul>
<li>[](http://compatibility.shwups-cms.ch/en/polyfills?&id=81)</li>
</ul>
## Что делает полифилл?
Как работает полифилл для `firstElementChild`? Посмотрим внимательнее на его JS:
```js
//+ run
*!*
if( document.createElement('div').firstElementChild === undefined ) { // (1)
*/!*
*!*
Object.defineProperty(Element.prototype, 'firstElementChild', { // (2)
*/!*
get: function () {
var el = this.firstChild;
do {
if( el.nodeType === 1 ) {
return el;
}
el = el.nextSibling;
} while(el);
return null;
}
});
}
```
Если этот код запустить, то `firstElementChild` появится у всех элементов в IE8.
Общий вид этого полифилла довольно типичен. Обычно полифилл состоит из двух частей:
<ol>
<li>Проверка, есть ли встроенная возможность.</li>
<li>Эмуляция, если её нет.</li>
</ol>
### Проверка встроенного свойства
Для проверки встроенной поддержки `firstElementChild` создаём элемент и смотрим, есть ли у него это свойство.
**"Фишка" заключается в том, что если бы DOM-свойство поддерживалось, то его значение никогда не было бы `undefined`. Если детей нет -- свойство было бы `null`.**
Сравните:
```js
//+ run
var div = document.createElement('div');
alert( div.firstChild ); // null, поддержка есть
alert( div.blabla ); // undefined, поддержки нет
```
**Важная тонкость -- элемент, который мы тестируем, должен *по стандарту* поддерживать такое свойство.**
Попытаемся, к примеру, проверить "поддержку" свойства `value`. У `input` оно есть, у `div` такого свойства нет:
```js
//+ run
var div = document.createElement('div');
var input = document.createElement('input');
alert( input.value ); // пустая строка, поддержка есть
alert( div.value ); // undefined, поддержки нет
```
[smart header="Поддержка значений свойств"]
**Если мы хотим проверить поддержку не свойства целиком, а некоторых его значений, то ситуация сложнее.**
Например, нам интересно, поддерживает ли браузер `<input type="range">`. То есть, понятно, что свойство `type` у `input`, в целом, поддерживается, а вот конкретный тип `<input>`?
**Для этого можно присвоить такой атрибут и посмотреть, подействовал ли он.**
Например, сохранился ли он в свойстве:
```js
//+ run
var input = document.createElement("input");
*!*
input.setAttribute("type", "range");
*/!*
var support = (input.type == "range");
alert('Поддержка: ' + support);
```
Эта проверка работает, так как хоть в атрибут `type` и можно присвоить любую строку, но в DOM-свойство `type` [по стандарту](http://www.w3.org/TR/html-markup/input.html) хранит реальный тип `input'а`. Если присвоить неподдерживаемый тип, то свойство `type` не изменится.
Ниже вы можете увидеть, верно ли сработал код определения поддержки. Если он вывел `true`, то HTML ниже выведет красивый "слайдер", иначе -- текстовое поле.
```html
<!--+ autorun height=40 -->
<input type="range">
```
[/smart]
### Ещё проверки: Modernizr
Существует целый фреймворк [Modernizr](http://modernizr.com/), посвящённый разнообразным проверкам. Использовать его очень просто.
Можно либо скачать сборку, которая проверяет поддержку именно того, что интересно, со страницы [](http://modernizr.com/download/), либо подключить вообще все проверки файлом [](http://modernizr.com/downloads/modernizr.js).
Пример использования:
```html
<!--+ run height="10" -->
<script src="http://modernizr.com/downloads/modernizr.js"></script>
<script>
alert( Modernizr.inputtypes.range ); // есть поддержка input type="range"
</script>
```
### Добавляем поддержку
Если мы осуществили проверку и видим, что встроенной поддержки нет -- полифилл должен её добавить.
Для этого вспомним, что DOM элементы описываются соответствующими JS-классами.
Например:
<ul>
<li>`<li>` -- [HTMLLiElement](http://www.w3.org/TR/html5/grouping-content.html#the-li-element) (см. секцию DOM Interface)</li>
<li>`<a>` -- [HTMLAnchorElement](http://www.w3.org/TR/html5/text-level-semantics.html#the-a-element)</li>
<li>`<body>` -- [HTMLBodyElement](http://www.w3.org/TR/html5/sections.html#the-body-element)</li>
</ul>
Они наследуют, как указано в секции DOM Interface по ссылкам выше, от [HTMLElement](http://www.w3.org/TR/html5/dom.html#htmlelement), который является общим родительским классом для HTML-элементов.
А `HTMLElement`, в свою очередь, наследует от [Element](http://www.w3.org/TR/dom/#interface-element), который является общим родителем не только для HTML, но и для других DOM-структур, например для XML и SVG.
**Для добавления нужной возможности берётся правильный класс и модифицируется его `prototype`.**
Самое простое -- это добавить всем элементам в прототип функцию, например:
```js
//+ run
Element.prototype.sayHi = function() {
alert("Привет от " + this);
}
document.body.sayHi(); // Привет от [object HTMLBodyElement]
```
**Сложнее -- добавить свойство, но это тоже возможно, через `Object.defineProperty`:**
```js
//+ run
Object.defineProperty(Element.prototype, 'lowerTag', {
get: function() {
return this.tagName.toLowerCase();
}
});
alert( document.body.lowerTag ); // body
```
[warn header="Геттер-сеттер и IE8"]
В IE8 современные методы для работы со свойствами, такие как [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) и другие не поддерживаются для произвольных объектов, но отлично работают для DOM-элементов.
Чем полифиллы и пользуются.
[/warn]
В итоге получается, что если в браузере нет нужного свойства -- оно появляется.
Эта техника почти не работает в совсем старых IE6,7. Когда-то для них использовалась особая "IE-магия" при помощи `.htc`-файлов, которые [более не поддерживаются](http://msdn.microsoft.com/en-us/library/ie/hh801216.aspx).
Если нужно поддерживать и эти версии, то рекомендуется воспользоваться фреймворками. К счастью, для большинства проектов эти браузеры уже стали историей.
## Итого
<ul>
<li>Для того, чтобы спокойно и без оглядки на старые браузеры использовать современные возможности DOM, используются особые библиотеки, которые называют "полифиллами" (polyfill).</li>
<li>Для поиска полифилла обычно достаточно ввести в поисковике `"polyfill"`, и нужное свойство либо метод. Как правило, полифиллы идут в виде коллекций скриптов.</li>
<li>Полифиллы хороши тем, что мы просто подключаем их и используем везде современный DOM/JS, а когда старые браузеры окончательно отомрут -- просто выкинем полифилл, без изменения кода.</li>
</ul>
Для создания полифилла DOM-свойства или метода:
<ul>
<li>Создаётся элемент, который его, в теории, должен поддерживать.</li>
<li>Соответствующее свойство сравнивается с `undefined`.</li>
<li>Если его нет -- модифицируется прототип, обычно это `Element.prototype` -- в него дописываются новые геттеры и функции.</li>
</ul>
Другие полифиллы сделать сложнее. Например, для добавления поддержки `<input type="range">` полифилл должен искать все такие элементы на странице и обрабатывать их. Возможности такого полифилла ограничены -- если уже существующему `<input>` поменять `type` на `range` -- полифилл не "подхватит" его.
И это нормальная ситуация, что полифилл не обеспечивает 100% совместимости. Скорее всего, мы не собираемся так делать, а значит -- полифилл вполне подойдёт.

View file

@ -6,7 +6,7 @@
[cut] [cut]
Согласно DOM-модели, документ является иерархией, деревом. Каждый HTML-тег образует узел дерева с типом "элемент". Вложенные в него теги становятся дочерними узлами. Для представления текста создаются узлы с типом "текст". Согласно DOM-модели, документ является иерархией, деревом. Каждый HTML-тег образует узел дерева с типом "элемент". Вложенные в него теги становятся дочерними узлами. Для представления текста создаются узлы с типом "текст".
**DOM -- это представление документа в виде дерева объектов, доступное для изменения через JavaScript.** DOM -- это представление документа в виде дерева объектов, доступное для изменения через JavaScript.
## Пример DOM ## Пример DOM
@ -25,23 +25,25 @@
</html> </html>
``` ```
Его вид:
<div class="domtree"></div> <div class="domtree"></div>
<script> <script>
var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"TITLE","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"О лосях"}]},{"name":"#text","nodeType":3,"content":"\n "}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n Правда о лосях\n \n\n"}]}]} var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"TITLE","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"О лосях"}]},{"name":"#text","nodeType":3,"content":"\n "}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n Правда о лосях\n \n\n"}]}]}
drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690, 350); drawHtmlTree(node, 'div.domtree', 690, 350);
</script> </script>
В этом дереве выделено два типа узлов. В этом дереве выделено два типа узлов.
<ol> <ol>
<li><span style="color:#5e8aae">Теги образуют синие узлы-элементы (element node).</span> Естественным образом одни узлы вложены в другие. Структура дерева образована за счет синих элементов-узлов -- тегов HTML.</li> <li>Теги образуют *узлы-элементы* (element node). Естественным образом одни узлы вложены в другие. Структура дерева образована исключительно за счет них.</li>
<li><span style="color:#ab9347">Текст внутри элементов образует **текстовые узлы** (text node),</span> обозначенные как `#text`. Текстовый узел содержит исключительно строку текста и не может иметь потомков.</li> <li>Текст внутри элементов образует *текстовые узлы* (text node), обозначенные как `#text`. Текстовый узел содержит исключительно строку текста и не может иметь потомков, то есть он всегда на самом нижнем уровне.</li>
</ol> </ol>
Например, у корневого элемента `<html>` есть три узла-потомка: `<head>`, текстовый узел и `<body>` [online]
**На рисунке выше синие узлы-элементы можно кликать, при этом их дети будут скрываться-раскрываться.** **На рисунке выше синие узлы-элементы можно кликать, при этом их дети будут скрываться-раскрываться.**
[/online]
Обратите внимание на специальные символы в текстовых узлах: Обратите внимание на специальные символы в текстовых узлах:
<ul> <ul>
@ -49,11 +51,11 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690, 3
<li>пробел: `␣`</li> <li>пробел: `␣`</li>
</ul> </ul>
**Пробелы и переводы строки -- это тоже текст, полноправные символы, которые учитываются в DOM!** **Пробелы и переводы строки -- это тоже текст, полноправные символы, которые учитываются в DOM.**
В частности, в примере выше тег `<head>` содержит не только узел-элемент `<title>`, но и два текстовых узла до и после него, состоящие из пробелов и переводов строк. Они не видны, но они существуют! В частности, в примере выше тег `<html>` содержит не только узлы-элементы `<head>` и `<body>`, но и `#text` (пробелы, переводы строки) между ними.
Исключения из этого правила только на самом верхнем уровне -- пробелы до `<head>` по стандарту игнорируются, а любое содержимое после `</body>` не создаёт узла, браузер переносит его в конец `body`. Впрочем, как раз на самом верхнем уровне из этого правила есть исключения: пробелы до `<head>` по стандарту игнорируются, а любое содержимое после `</body>` не создаёт узла, браузер переносит его внутрь, в конец `body`.
В остальных случаях всё честно -- если пробелы есть в документе, то они есть и в DOM, а если их убрать, то и в DOM их не будет, получится так: В остальных случаях всё честно -- если пробелы есть в документе, то они есть и в DOM, а если их убрать, то и в DOM их не будет, получится так:
@ -66,7 +68,7 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690, 3
<script> <script>
var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[{"name":"TITLE","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"О лосях"}]}]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Правда о лосях\n"}]}]} var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[{"name":"TITLE","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"О лосях"}]}]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Правда о лосях\n"}]}]}
drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690, 300); drawHtmlTree(node, 'div.domtree', 690, 300);
</script> </script>
## Автоисправление ## Автоисправление
@ -100,9 +102,7 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690,
</script> </script>
[warn header="Таблицы всегда содержат `<tbody>`"] [warn header="Таблицы всегда содержат `<tbody>`"]
Важный "особый случай" при работе с DOM -- таблицы. По стандарту DOM они обязаны иметь `<tbody>`, однако по стандарту HTML их можно написать без него. Важный "особый случай" при работе с DOM -- таблицы. По стандарту DOM они обязаны иметь `<tbody>`, однако в HTML их можно написать без него. В этом случае браузер добавляет `<tbody>` самостоятельно.
В этом случае браузер добавляет `<tbody>` самостоятельно.
Например, для такого HTML: Например, для такого HTML:
@ -117,7 +117,7 @@ DOM-структура будет такой:
<script> <script>
var node = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType":1,"children":[{"name":"TR","nodeType":1,"children":[{"name":"TD","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"1"}]},{"name":"#text","nodeType":3,"content":"\n"}]}]}]}; var node = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType":1,"children":[{"name":"TR","nodeType":1,"children":[{"name":"TD","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"1"}]},{"name":"#text","nodeType":3,"content":"\n"}]}]}]};
drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 600, 200); drawHtmlTree(node, 'div.domtree', 600, 200);
</script> </script>
Вы видите? Появился `<tbody>`, как будто документ был таким: Вы видите? Появился `<tbody>`, как будто документ был таким:
@ -152,7 +152,7 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 600,
*!* *!*
<!-- комментарий --> <!-- комментарий -->
*/!* */!*
<li>..и коварное!</li> <li>...и коварное!</li>
</ol> </ol>
</body> </body>
</html> </html>
@ -160,9 +160,9 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 600,
<div class="domtree"></div> <div class="domtree"></div>
<script> <script>
var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n Правда о лосях\n "},{"name":"OL","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Лось — животное хитрое"}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"#comment","nodeType":8,"content":" комментарий "},{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"..И коварное!"}]},{"name":"#text","nodeType":3,"content":"\n "}]},{"name":"#text","nodeType":3,"content":"\n \n\n"}]}]}; var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n Правда о лосях\n "},{"name":"OL","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Лось — животное хитрое"}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"#comment","nodeType":8,"content":" комментарий "},{"name":"#text","nodeType":3,"content":"\n "},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":".. коварное!"}]},{"name":"#text","nodeType":3,"content":"\n "}]},{"name":"#text","nodeType":3,"content":"\n \n\n"}]}]};
drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690, 550); drawHtmlTree(node, 'div.domtree', 690, 550);
</script> </script>
**В этом примере тегов уже больше, и даже появился узел нового типа -- *комментарий*.** **В этом примере тегов уже больше, и даже появился узел нового типа -- *комментарий*.**
@ -171,11 +171,17 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690,
**Всё, что есть в HTML, находится и в DOM.** **Всё, что есть в HTML, находится и в DOM.**
Вообще-то это секрет, но и директива `<!DOCTYPE...>` тоже является DOM-узлом, и находится в дереве DOM непосредственно перед HTML. На иллюстрациях выше этот факт скрыт, поскольку здесь и далее мы с этим узлом работать не будем. Даже директива `<!DOCTYPE...>`, которую мы ставим в начале HTML, тоже является DOM-узлом, и находится в дереве DOM непосредственно перед `<html>`. На иллюстрациях выше этот факт скрыт, поскольку мы с этим узлом работать не будем, он никогда не нужен.
Даже сам объект `document`, формально, является DOM-узлом, самым-самым корневым. Даже сам объект `document`, формально, является DOM-узлом, самым-самым корневым.
Всего различают 12 типов узлов, но на практике мы работаем с тремя из них: элемент, текст и, редко, комментарии (если они есть в HTML). Всего различают 12 типов узлов, но на практике мы работаем с четырьмя из них:
<ol>
<li>Документ -- точка входа в DOM.</li>
<li>Элементы -- основные строительные блоки.</li>
<li>Текстовые узлы -- содержат, собственно, текст.</li>
<li>Комментарии -- иногда в них можно включить информацию, которая не будет показана, но доступна из JS.</li>
</ol>
## Возможности, которые дает DOM ## Возможности, которые дает DOM
@ -183,7 +189,9 @@ drawHtmlTree(node, [].pop.call(document.querySelectorAll('div.domtree')), 690,
**DOM нужен для того, чтобы манипулировать страницей -- читать информацию из HTML, создавать и изменять элементы.** **DOM нужен для того, чтобы манипулировать страницей -- читать информацию из HTML, создавать и изменять элементы.**
Узел `HTML` можно получить как `document.documentElement`, а `BODY` -- как `document.body`. Получив узел, мы можем что-то сделать с ним. Узел `HTML` можно получить как `document.documentElement`, а `BODY` -- как `document.body`.
Получив узел, мы можем что-то сделать с ним.
Например, можно поменять цвет `BODY` и вернуть обратно: Например, можно поменять цвет `BODY` и вернуть обратно:
@ -196,7 +204,10 @@ document.body.style.backgroundColor = '';
alert('Сбросили цвет BODY'); alert('Сбросили цвет BODY');
``` ```
Фактически, DOM предоставляет возможность делать со страницей всё, что угодно, и далее мы этому научимся. DOM предоставляет возможность делать со страницей всё, что угодно.
Позже мы более подробно рассмотрим различные свойства и методы DOM-узлов.
## Особенности IE8- ## Особенности IE8-
@ -225,11 +236,9 @@ IE8- не генерирует текстовые узлы, если они со
Эта, с позволения сказать, "оптимизация" не соответствует стандарту и IE9+ уже работает как нужно, то есть как описано ранее. Эта, с позволения сказать, "оптимизация" не соответствует стандарту и IE9+ уже работает как нужно, то есть как описано ранее.
Но, по большому счёту, для нас должно быть без разницы, старый или новый IE. Но, по большому счёту, для нас это отличие должно быть без разницы, ведь при работе с DOM/HTML мы в любом случае не должны быть завязаны на то, есть пробел между тегами или его нет. Мало ли, сегодня он есть, а завтра решили переформатировать HTML и его не стало.
**При работе с DOM/HTML мы в любом случае не должны быть завязаны на то, есть пробел между тегами или его нет. Мало ли, сегодня он есть, а завтра решили переформатировать HTML и его не стало.** К счастью, свойства и методы DOM, которые мы пройдём далее, вполне позволяют писать код, который будет работать корректно во всех версиях браузеров. Так что знать об этом отличии надо, если вы хотите поддерживать старые IE, но проблем оно нам создавать не будет.
К счастью, свойства и методы DOM, которые мы пройдём далее, вполне позволяют писать код, который будет работать корректно во всех версиях браузеров.
## Итого ## Итого
@ -242,7 +251,7 @@ IE8- не генерирует текстовые узлы, если они со
Кстати, DOM-модель используется не только в JavaScript, это известный способ представления XML-документов. Кстати, DOM-модель используется не только в JavaScript, это известный способ представления XML-документов.
В следующих главах мы лучше познакомимся с DOM и увидим, какие свойства и методы нужны, чтобы творить красивые штуки со страницей. В следующих главах мы познакомимся с DOM более плотно.
[libs] [libs]
d3 d3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

View file

@ -5,7 +5,7 @@
## Доступ к элементу ## Доступ к элементу
Откройте документ [losi.html](/files/tutorial/browser/dom/dom-tree/losi.html) и, в инструментах разработчика, перейдите во вкладку Elements. Откройте документ [losi.html](losi.html) и, в инструментах разработчика, перейдите во вкладку Elements.
Чтобы проанализировать любой элемент: Чтобы проанализировать любой элемент:
<ul> <ul>
@ -16,8 +16,6 @@
<img src="1.png"> <img src="1.png">
**В более новой версии Chrome расположение элементов управления может отличаться, но происходящее всё равно должно быть понятно.**
Справа будет различная информация об элементе: Справа будет различная информация об элементе:
<dl> <dl>
<dt>Computed Style</dt> <dt>Computed Style</dt>
@ -31,13 +29,13 @@
</dl> </dl>
[warn header="DOM не полностью отображается в инструментах!"] [warn header="DOM не полностью отображается в инструментах!"]
Во вкладке Elements не отображаются пробельные узлы. Это сделано для удобства просмотра. На самом-то деле они есть. Отображение DOM во вкладке Elements не совсем соответствует реальному. В частности, там не отображаются пробельные узлы. Это сделано для удобства просмотра. На мы-то знаем, что они есть.
Зато в Elements могут быть видны псевдоэлементы, такие как `::before`, `::after`. Это также сделано для удобства, в DOM их нет. В Elements можно увидеть CSS-псевдоэлементы, такие как `::before`, `::after`. Это также сделано для удобства, в DOM их не существует.
[/warn] [/warn]
## Ссылки $0 $1... ## Выбранные элементы $0 $1...
Зачастую бывает нужно выбрать элемент DOM и сделать с ним что-то на JavaScript. Зачастую бывает нужно выбрать элемент DOM и сделать с ним что-то на JavaScript.
@ -51,33 +49,32 @@
$0.style.backgroundColor = 'red'; $0.style.backgroundColor = 'red';
``` ```
Пример, как это может выглядеть в браузере: В браузере это может выглядеть примерно так:
<img src="2.png"> <img src="2.png">
**Итак, можно выделить элемент и редактировать его в консоли. Есть и обратный путь: любой элемент из консоли можно открыть во вкладке Elements.** Мы выделили элемент, применили к нему JavaScript в консоли, тут же увидели изменения в браузере.
Есть и обратная дорожка: любой элемент из JS-переменной можно посмотреть во вкладке Elements, для этого:
Чтобы показать элемент из JS-переменной во вкладке Elements:
<ol> <ol>
<li>Выведите эту переменную в консоли, например при помощи `console.log`.</li> <li>Выведите эту переменную в консоли, например при помощи `console.log`.</li>
<li>Кликните на нём правой кнопкой мыши.</li> <li>Кликните на выводе в консоли правой кнопкой мыши.</li>
<li>Выберите соответствующий пункт меню.</li> <li>Выберите соответствующий пункт меню.</li>
</ol> </ol>
<img src="3.png"> <img src="3.png">
**Таким образом можно легко перемещаться из Elements в консоль и обратно.** Таким образом, можно легко перемещаться из Elements в консоль и обратно.
Выбрал элемент -- попробовал на нём JavaScript -- посмотрел что получилось в DOM.
## Ещё методы консоли ## Ещё методы консоли
Для поиска элементов в консоли есть два специальных метода: Для поиска элементов в консоли есть два специальных метода:
<ul> <ul>
<li>`$$("div.my")` -- возвращает все элементы по CSS-селектору.</li> <li>`$$("div.my")` -- ищет все элементы в DOM по данному CSS-селектору.</li>
<li>`$("div.my")` -- возвращает один элемент по CSS-селектору, если их много, то только первый.</li> <li>`$("div.my")` -- ищет первый элемент в DOM по данному CSS-селектору.</li>
</ul> </ul>
Более полная документация по методам консоли доступна здесь: [Console API Reference для Chrome](https://developers.google.com/chrome-developer-tools/docs/console-api) и здесь: [Command Line API для Firebug](https://getfirebug.com/wiki/index.php/Command_Line_API), а также на [firebug.ru](http://firebug.ru). Более полная документация по методам консоли доступна на страницах [Console API Reference для Chrome](https://developers.google.com/chrome-developer-tools/docs/console-api) и [Command Line API для Firebug](https://getfirebug.com/wiki/index.php/Command_Line_API), а также на [firebug.ru](http://firebug.ru).
Другие браузеры реализуют почти такой же функционал, как и Chrome/Firebug. Другие браузеры реализуют похожий функционал, освоив Chrome/Firebug, вы легко с ними разберётесь.

View file

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html>
<body>
Правда о лосях
<ol>
<li>Лось — животное хитрое</li>
<!-- комментарий -->
<li>..И коварное!</li>
</ol>
</body>
</html>

View file

@ -1,18 +0,0 @@
<!DOCTYPE HTML>
<html>
<head><meta charset="utf-8"></head>
<body>
<div>Пользователи:</div>
<ul id="user-list">
<li>Маша</li>
<li>Вовочка</li>
</ul>
<!-- комментарий -->
<script>
// ... ваш код
</script>
</body>
</html>

View file

@ -9,26 +9,24 @@ document.documentElement.firstChild
Второй способ работает, так как пробелы перед `<head>` игнорируются. Второй способ работает, так как пробелы перед `<head>` игнорируются.
Также в современных браузерах доступен `document.head`.
# UL # UL
Два варианта: Например, так:
```js ```js
document.getElementById('user-list')
document.body.children[1] document.body.children[1]
``` ```
Предпочтителен второй вариант, так как он не зависит от положении узла в документе. Мало ли, вдруг мы решим вставить какую-то ещё информацию перед ним.
# LI # LI
Два варианта: Можно так:
```js ```js
document.getElementById('user-list').children[1];
document.body.children[1].children[1]; // LI document.body.children[1].children[1]; // LI
``` ```
Если комментарий переместить между элементами списка, то в IE8- он станет одним из `children`, в результате последний код станет работать некорректно. Может возникнуть проблема с комментарием в IE8-, так как он станет одним из `children`, в результате последний код станет работать некорректно.
Чтобы это обойти, нужно либо не ставить комментарии в те места HTML, где планируются такие выборки, либо использовать другие методы поиска в HTML, которые мы рассмотрим [далее](/searching-elements-dom). В последующих разделах учебника мы рассмотрим другие методы поиска по DOM, которые позволят эту проблему обойти.

View file

@ -1,18 +0,0 @@
<!DOCTYPE HTML>
<html>
<head><meta charset="utf-8"></head>
<body>
<div>Пользователи:</div>
<ul id="user-list">
<li>Маша</li>
<li>Вовочка</li>
</ul>
<!-- комментарий -->
<script>
// ... ваш код
</script>
</body>
</html>

View file

@ -5,7 +5,24 @@
Для страницы: Для страницы:
```html ```html
<!--+ src="index.html" --> <!DOCTYPE HTML>
<html>
<head><meta charset="utf-8"></head>
<body>
<div>Пользователи:</div>
<ul>
<li>Маша</li>
<li>Вовочка</li>
</ul>
<!-- комментарий -->
<script>
// ... ваш код
</script>
</body>
</html>
``` ```
<ul> <ul>

View file

@ -22,6 +22,4 @@ if (!elem.firstChild) { ... }
if (!elem.lastChild) { ... } if (!elem.lastChild) { ... }
``` ```
Последний - самый короткий. Также существует метод [hasChildNodes](https://developer.mozilla.org/en-US/docs/Web/API/Node.hasChildNodes), который позволяет вызовом `elem.hasChildNodes()` определить наличие детей. Он работает так же, как проверка `elem.childNodes.length != 0`.
P.S. Также есть метод [hasChildNodes](https://developer.mozilla.org/en-US/docs/Web/API/Node.hasChildNodes), который позволяет вызовом `elem.hasChildNodes()` определить наличие детей. Он работает так же, как проверка `elem.childNodes.length != 0`.

View file

@ -0,0 +1 @@
Для удобства работы с таблицей используем специальные свойства `rows` и `cells`.

View file

@ -7,10 +7,11 @@
Вам нужно будет получить из таблицы `table` все диагональные `td` и выделить их, используя код: Вам нужно будет получить из таблицы `table` все диагональные `td` и выделить их, используя код:
```js ```js
// в переменной td DOM-элемент для тега <td>
td.style.backgroundColor = 'red'; td.style.backgroundColor = 'red';
``` ```
Должно получиться так: Должно получиться так:
[iframe src="solution"] [iframe src="solution" height=180]

View file

@ -1,33 +1,36 @@
# Ссылки между DOM-элементами # Навигация по DOM-элементам
Для того, чтобы изменить узел DOM или его содержимое, нужно сначала его получить. DOM позволяет делать что угодно с HTML-элементом и его содержимым, но для этого нужно сначала нужный элемент получить.
Доступ к DOM начинается с объекта `document`. Из него можно добраться до любых узлов. Доступ к DOM начинается с объекта `document`. Из него можно добраться до любых узлов.
[cut] [cut]
Вот картина, которая, с небольшими дополниниями, будет обсуждаться в этой главе: Так выглядят основные ссылки, по которым можно переходить между узлами DOM:
<img src="navigation.png"> <img src="dom-links.svg">
## Корень: documentElement и body Посмотрим на них повнимательнее.
Войти в "корень" дерева можно двумя путями. ## Сверху documentElement и body
Самые верхние элементы дерева доступны напрямую из `document`.
<dl> <dl>
<dt>`<HTML>` = `document.documentElement`</dt> <dt>`<HTML>` = `document.documentElement`</dt>
<dd>Первая точка входа -- `document.documentElement`. Это свойство ссылается на DOM-объект для тега `HTML`.</dd> <dd>Первая точка входа -- `document.documentElement`. Это свойство ссылается на DOM-объект для тега `<html>`.</dd>
<dt>`<BODY>` = `document.body`</dt> <dt>`<BODY>` = `document.body`</dt>
<dd>Вторая точка входа -- `document.body`, который соответствует тегу `BODY`.</dd> <dd>Вторая точка входа -- `document.body`, который соответствует тегу `<body>`.</dd>
</dl> </dl>
Оба варианта отлично работают. Но есть одна тонкость: **`document.body` может быть равен `null`**. В современных браузерах (кроме старых IE) также действует `document.head` -- прямой доступ к `<head>`
Например, при доступе к `document.body` в момент обработки тега `HEAD`, то `document.body = null`. Это вполне логично, потому что `BODY` еще не существует. [warn header="Есть одна тонкость: `document.body` может быть равен `null`"]
Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта.
**Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта.** В частности, если скрипт находится в `<head>`, то в нём недоступен `document.body`.
В следующем примере, первый `alert` выведет `null`: Поэтому в следующем примере первый `alert` выведет `null`:
```html ```html
<!--+ run --> <!--+ run -->
@ -35,133 +38,88 @@
<html> <html>
<head> <head>
<script> <script>
alert("Из HEAD: " + document.body); // null *!*
alert("Из HEAD: " + document.body); // null, body ещё нет
*/!*
</script> </script>
</head> </head>
<body> <body>
<script> <script>
alert("Из BODY: " + document.body); alert("Из BODY: " + document.body); // body есть
</script> </script>
</body> </body>
</html> </html>
``` ```
[/warn]
[smart header="В DOM активно используется `null`"] [smart header="В DOM активно используется `null`"]
В мире DOM в качестве значения, обозначающего "нет такого элемента" или "узел не найден", используется не `undefined`, а `null`.
В мире DOM в качестве значения "нет такого элемента" или "узел не найден" используется не `undefined`, а `null`.
[/smart] [/smart]
## document.getElementById или просто id
Если элементу назначен специальный атрибут `id`, то можно получить его прямо по переменной с именем из значения `id`. ## Дети: childNodes, firstChild, lastChild
Например:
```html
<!--+ run -->
<div id="*!*content-holder*/!*">
<div id="*!*content*/!*">Элемент</div>
</div>
<script>
*!*
alert( content ); // DOM-элемент
alert( window['content-holder'] ); // в имени дефис, поэтому через [...]
*/!*
</script>
```
По стандарту, значение `id` должно быть уникально, то есть в документе может быть только один элемент с данным `id`.
Это поведение соответствует [стандарту](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem). Оно существует, в первую очередь, для совместимости, как осколок далёкого прошлого и не очень приветствуется, поскольку использует глобальные переменные. Браузер пытается помочь нам, смешивая пространства имён JS и DOM, но при этом возможны конфликты.
**Более правильной и общепринятой практикой является доступ к элементу вызовом `document.getElementById("идентификатор")`.**
Например:
```html
<!--+ run -->
<div id="*!*content*/!*">Выделим этот элемент</div>
<script>
*!*
var elem = document.getElementById('content');
elem.style.background = 'red';
alert( elem == content ); // true
content.style.background = ""; // один и тот же элемент
*/!*
</script>
```
**Далее я изредка буду использовать прямое обращение через переменную в примерах, чтобы было меньше букв и проще было понять происходящее. Но предпочтительным методом является `document.getElementById`.**
## Дочерние элементы
Здесь и далее мы будем использовать два принципиально разных термина. Здесь и далее мы будем использовать два принципиально разных термина.
<ul> <ul>
<li>**Дочерние элементы (или дети)** -- элементы, которые лежат *непосредственно* внутри данного. Например, внутри `<HTML>` обычно лежат `<HEAD>` и `<BODY>`.</li> <li>**Дочерние элементы (или дети)** -- элементы, которые лежат *непосредственно* внутри данного. Например, внутри `<HTML>` обычно лежат `<HEAD>` и `<BODY>`.</li>
<li>**Потомки** -- все элементы, которые лежат внутри данного, вместе с их детьми, детьми их детей и так далее. То есть, всё поддерево.</li> <li>**Потомки** -- все элементы, которые лежат внутри данного, вместе с их детьми, детьми их детей и так далее. То есть, всё поддерево DOM.</li>
</ul> </ul>
### childNodes
Псевдо-массив `childNodes` хранит все дочерние элементы, включая текстовые. Псевдо-массив `childNodes` хранит все дочерние элементы, включая текстовые.
Пример ниже последовательно выведет дочерние элементы `document.body`: Пример ниже последовательно выведет дочерние элементы `document.body`:
```html ```html
<!--+ run src="index.html" --> <!--+ run -->
<!DOCTYPE HTML>
<html>
<body>
<div>Начало</div>
<ul>
<li>Информация</li>
</ul>
<div>Конец</div>
<script>
*!*
for(var i=0; i<document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
*/!*
</script>
...
</body>
</html>
``` ```
Во всех браузерах, кроме старых IE, `document.body.childNodes[0]` это текстовый узел из пробелов, а `DIV` -- второй потомок: `document.body.childNodes[1]`. Обратим внимание на маленькую деталь. Если запустить пример выше, то последним будет выведен элемент `<script>`. На самом-то деле в документе есть ещё текст (обозначенный троеточием), но на момент выполнения скрипта браузер ещё до него не дошёл.
В IE8- не создаются пустые текстовые узлы, поэтому там дети начнутся с `DIV`. Пробельный узел будет в *итоговом документе*, но его еще нет на момент выполнения скрипта.
Почему же перечисление узлов в примере выше заканчивается на `SCRIPT`? Неужели под скриптом нет пробельного узла? [warn header="Список детей -- только для чтения!"]
Скажем больше -- все навигационные свойства, которые перечислены в этой главе -- только для чтения. Нельзя просто заменить элемент присвоением `childNodes[i] = ...`.
Да просто потому, что пробельный узел будет в *итоговом документе*, но его еще нет на момент выполнения скрипта. Изменение DOM осуществляется другими методами, которые мы рассмотрим далее, все навигационные ссылки при этом обновляются автоматически.
[warn header="Коллекция только для чтения!"]
Все навигационные свойства, которые перечислены в этой главе -- только для чтения. Нельзя просто заменить элемент присвоением `childNodes[i] = ...`. В частности, методы массива для `childNodes` тоже не поддерживаются, поэтому это свойство и называют "коллекцией".
Изменения DOM осуществляется другими методами, которые мы рассмотрим далее, все навигационные ссылки при этом обновляются автоматически.
[/warn] [/warn]
Свойства `firstChild` и `lastChild` обеспечивают быстрый доступ к первому и последнему элементу.
Всегда верно:
### children ```js
elem.childNodes[0] === elem.firstChild
А что если текстовые узлы нам не интересны? elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
**Свойство `children`, перечисляет только дочерние узлы-элементы, соответствующие тегам.**
Модифицируем предыдущий пример, применив `children` вместо `childNodes`.
Теперь он будет выводить не все узлы, а только узлы-элементы:
```html
<!--+ src="index.html" run link -->
``` ```
[warn header="В IE8- в `children` присутствуют узлы-комментарии"] ## Коллекции -- не массивы
С точки зрения стандарта это ошибка, но IE8- также включает в `children` узлы, соответствующие HTML-комментариям.
Это может привести к сюрпризам при использовании свойства `children`, поэтому HTML-комментарии либо убирают либо используют функцию (или фреймворк, к примеру, jQuery), который автоматически офильтрует их. DOM-коллекции, такие как `childNodes` и другие, которые мы увидим далее, не являются JavaScript-массивами.
[/warn]
### Коллекции -- не массивы В них нет методов массивов, таких как `forEach`, `map`, `push`, `pop` и других.
Коллекции, которые возвращают методы поиска, не являются массивами.
У них нет методов массива, таких как `join`, `pop`, `forEach` и т.п.
Например, этот пример выполнится с ошибкой:
```js ```js
//+ run //+ run
@ -174,11 +132,12 @@ elems.forEach(function(elem) { // нет такого метода!
}); });
``` ```
Можно для перебора коллекции использовать обычный цикл `for(var i=0; i<elems.length; i++) ...` Именно поэтому `childNodes` и называют "коллекция" или "псевдомассив".
Но что делать, если уж очень хочется воспользоваться методами массива? Можно для перебора коллекции использовать обычный цикл `for(var i=0; i<elems.length; i++) ...` Но что делать, если уж очень хочется воспользоваться методами массива?
Это возможно, основных варианта два:
Варианта два:
<ol> <ol>
<li>Применить метод массива через `call/apply`: <li>Применить метод массива через `call/apply`:
@ -215,12 +174,15 @@ elems.forEach(function(elem) {
</li> </li>
</ol> </ol>
[warn header="Нельзя перебирать коллекцию через `for..in`"] [warn header="Нельзя перебирать коллекцию через `for..in`"]
Ранее мы говорили, что не рекомендуется использовать для перебора массива цикл `for..in`. Ранее мы говорили, что не рекомендуется использовать для перебора массива цикл `for..in`.
**Коллекции -- наглядный пример, почему нельзя. Они похожи на массивы, но у них есть свои свойства и методы, которых в массивах нет.** **Коллекции -- наглядный пример, почему нельзя. Они похожи на массивы, но у них есть свои свойства и методы, которых в массивах нет.**
При запуске этого кода вы увидите, что `alert` сработает не 3, а целых 5 раз! К примеру, код ниже должен перебрать все дочерние элементы `<html>`. Их, естественно, два: `<head>` и `<body>`. Максимум, три, если взять ещё и текст между ними.
Но в примере ниже `alert` сработает не три, а целых 5 раз!
```js ```js
//+ run //+ run
@ -231,140 +193,169 @@ for(var key in elems) {
} }
``` ```
Цикл `for..in` вывел не только ожидаемые индексы `0`, `1`, `2`, по которым лежат узлы в коллекции, но и свойства `length` (в коллекции оно enumerable), а также функцию `item(n)` -- она никогда не используется, возвращает `n-й` элемент коллекции, старый аналог обращения по индексу `[n]`. Цикл `for..in` выведет не только ожидаемые индексы `0`, `1`, `2`, по которым лежат узлы в коллекции, но и свойство `length` (в коллекции оно enumerable), а также функцию `item(n)` -- она никогда не используется, возвращает `n-й` элемент коллекции, проще обратиться по индексу `[n]`.
В реальном коде мы хотим перебирать только элементы, поэтому желательно использовать `for(var i=0; i<elems.length; i++)`, либо методы массивов, как описано выше. В реальном коде нам нужны только элементы, мы же будем работать с ними, а служебные свойства -- не нужны. Поэтому желательно использовать `for(var i=0; i<elems.length; i++)`.
[/warn] [/warn]
## firstChild и lastChild
Свойства `firstChild` и `lastChild` обеспечивают быстрый доступ к первому и последнему дочернему элементу. ## Соседи и родитель
Доступ к элементам слева и справа данного можно получить по ссылкам `previousSibling` / `nextSibling`.
Родитель доступен через `parentNode`. Если долго идти от одного элемента к другому, то рано или поздно дойдёшь до корня DOM, то есть до `document.documentElement`, а затем и `document`.
## Навигация только по элементам
Навигационные ссылки, описанные выше, равно касаются всех узлов в документе. В частности, в `childNodes` сосуществуют и текстовые узлы и узлы-элементы и узлы-комментарии, если есть.
Но для большинства задач текстовые узлы нам не интересны.
Поэтому посмотрим на дополнительный набор ссылок, которые их не учитывают:
<img src="dom-links-elements.svg">
Эти ссылки похожи на те, что раньше, только в ряде мест стоит слово `Element`:
<ul>
<li>`children` -- только дочерние узлы-элементы, то есть соответствующие тегам.</li>
<li>`firstElementChild`, `lastElementChild` -- соответственно, первый и последний дети-элементы.</li>
<li>`previousElementSibling`, `nextElementSibling` -- соседи-элементы.</li>
<li>`parentElement` -- родитель-элемент.</li>
</ul>
[smart header="Родитель-элемент? А что, бывают родители не-элементы?"]
Почти всегда родитель и так элемент, ведь текстовые узлы не могут иметь потомков. Поэтому это свойство равно `parentNode`, кроме одного исключения.
В корне DOM находится `document`, и он-то как раз не элемент. То есть, если идти по цепочке `parentElement` вверх, то мы, остановимся мы не на `document` как в случае с `parentNode`, а на `document.documentElement`.
Иногда это имеет значение, если хочется перебрать всех предков и вызвать какой-то метод, а на документе его нет.
[/smart]
Модифицируем предыдущий пример, применив `children` вместо `childNodes`.
Теперь он будет выводить не все узлы, а только узлы-элементы:
Например, для документа:
```html ```html
<!--+ run -->
<!DOCTYPE HTML>
<html>
<body> <body>
<div>...</div> <div>Начало</div>
<ul>...</ul>
<div>...</div> <ul>
<li>Информация</li>
</ul>
<div>Конец</div>
<script>
*!*
for(var i=0; i<document.body.children.length; i++) {
alert( document.body.children[i] ); // DIV, UL, DIV, SCRIPT
}
*/!*
</script>
...
</body> </body>
</html>
``` ```
DOM-дерево будет таким (внутренности `div` и `ul` скрыты):
<div class="domtree" id="traversing-dom-domtree"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"\n "},{"name":"DIV","nodeType":1,"_children":[{"name":"#text","nodeType":3,"content":"..."}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"UL","nodeType":1,"_children":[{"name":"#text","nodeType":3,"content":"..."}]},{"name":"#text","nodeType":3,"content":"\n "},{"name":"DIV","nodeType":1,"_children":[{"name":"#text","nodeType":3,"content":"..."}]},{"name":"#text","nodeType":3,"content":"\n"}]}]}
drawHtmlTree(node, document.getElementById('traversing-dom-domtree'), 690, 400);
});
</script>
Если бы пробельных узлов не было, например, в IE8, то была бы такая картина ссылок:
<img src="children.png">
С другой стороны, так как пробельные узлы, всё же, есть, то `body.firstChild` и `body.lastChild` будут указывать как раз на них, то есть на первый и последний `#text`.
Всегда верны равенства: Всегда верны равенства:
```js ```js
body.firstChild === body.childNodes[0] elem.firstElementChild === elem.children[0]
body.lastChild === body.childNodes[body.childNodes.length-1] elem.lastElementChild === body.children[body.children.length-1]
``` ```
## parentNode, previousSibling и nextSibling
Ранее мы смотрели свойства для доступа к детям. Теперь рассмотрим ссылки для доступа вверх и в стороны от узла. [warn header="В IE8- поддерживается только `children`"]
Других навигационных свойств в этих браузерах нет. Впрочем, как мы увидим далее, можно легко сделать полифилл, и они, всё же, будут.
<ul> [/warn]
<li>Свойство `parentNode` ссылается на родительский узел.</li>
<li>Свойства `previousSibling` и `nextSibling` дают доступ к левому и правому соседу.</li>
</ul>
Ниже изображены ссылки между `BODY` и его потомками для документа:
```html
<!--+ src="index.html" run link -->
```
Ссылки (пробельные узлы обозначены решеткой `#`):
<img src="siblings2.png">
## Ссылки для элементов (IE9+)
Все современные браузеры, включая IE9+, поддерживают дополнительные ссылки:
<ul>
<li>`firstElementChild` -- первый потомок-элемент (`=children[0]`)</li>
<li>`lastElementChild` -- последний потомок-элемент (`=children[children.length-1]`)</li>
<li>`nextElementSibling` -- правый брат-элемент</li>
<li>`previousElementSibling` -- левый брат-элемент</li>
<li>`parentElement` -- родительский узел-элемент, по факту отличается от `parentNode` только тем, что `document.documentElement.parentElement = null`, то есть `document` он элементом не считает.</li>
</ul>
Любые другие узлы, кроме элементов, просто игнорируются.
Например:
```html
<!--+ run -->
<body>
firstElementChild: <div>...</div>
<!-- комментарий -->
lastElementChild: <span>...</span>
<script>
alert(document.body.firstElementChild.nextElementSibling); // SPAN
</script>
</body>
```
Современные браузеры также поддерживают дополнительные интерфейсы для обхода DOM c фильтром по узлам: `NodeIterator`, `TreeFilter` и `TreeWalker`. Они были утверждены аж в 2000-м году, однако на практике оказались неудобными, и потому практически не применяются. Вы можете почитать о них в стандарте [DOM 2 Traversal](http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-Filters).
## Итого [warn header="В IE8- в `children` присутствуют узлы-комментарии"]
С точки зрения стандарта это ошибка, но IE8- также включает в `children` узлы, соответствующие HTML-комментариям.
Сверху в DOM можно войти либо через `document.documentElement` (тег `HTML`), либо через `document.body` (тег `BODY`). Это может привести к сюрпризам при использовании свойства `children`, поэтому HTML-комментарии либо убирают либо используют фреймворк, к примеру, jQuery, который даёт свои методы перебора и отфильтрует их.
[/warn]
По элементу DOM можно получить всех соседей через ссылки: ## Особые ссылки для таблиц
У конкретных элементов DOM могут быть свои дополнительные ссылки для большего удобства навигации.
Здесь мы рассмотрим таблицу, так как это важный частный случай и просто для примера.
В списке ниже выделены наиболее полезные:
<dl> <dl>
<dt>`childNodes`, `children`</dt> <dt>`TABLE`</dt>
<dd>Список дочерних узлов.</dd> <dd>
<dt>`firstChild`, `lastChild`</dt> <ul>
<dd>Первый и последний потомки</dd> <li>**`table.rows`** -- список строк `TR` таблицы.</li>
<dt>`parentNode`</dt> <li>`table.caption/tHead/tFoot` -- ссылки на элементы таблицы `CAPTION`, `THEAD`, `TFOOT`.</li>
<dd>Родительский узел</dd> <li>`table.tBodies` -- список элементов таблицы `TBODY`, по спецификации их может быть несколько.</li>
<dt>`previousSibling`, `nextSibling`</dt> </ul></dd>
<dd>Соседи влево-вправо</dd> <dt>`THEAD/TFOOT/TBODY`</dt>
<dd>
<ul>
<li>`tbody.rows` -- список строк `TR` секции.</li>
</ul></dd>
<dt>`TR`</dt>
<dd>
<ul>
<li>**`tr.cells`** -- список ячеек `TD/TH`</li>
<li>**`tr.sectionRowIndex`** -- номер строки в текущей секции `THEAD/TBODY`</li>
<li>`tr.rowIndex` -- номер строки в таблице</li>
</ul>
</dd>
<dt>`TD/TH`</dt>
<dd>
<ul>
<li>**`td.cellIndex`** -- номер ячейки в строке</li>
</ul>
</dd>
</dl> </dl>
Все навигационные ссылки доступны только для чтения и поддерживаются автоматически. Пример использования:
Свойства-коллекции, хотя и имеют индексы, а также `length`, не являются массивами. Поэтому их и называют "псевдомассивами", "коллекциями" или "списками". Далее мы встретимся с кучей других полезных коллекций, которые тоже не будут массивами. ```html
<!--+ run height=100 -->
<table>
<tr>
<td>один</td> <td>два</td>
</tr>
<tr>
<td>три</td> <td>четыре</td>
</tr>
</table>
В современных браузерах, включая IE9+, реализованы дополнительные свойства, работающие только для элементов: <script>
var table = document.body.children[0];
alert( table.*!*rows[0].cells[0]*/!*.innerHTML ) // "один"
</script>
```
Спецификация: [HTML5: tabular data](http://www.w3.org/TR/html5/tabular-data.html).
Даже если эти свойства не нужны вам прямо сейчас, имейте их в виду на будущее, когда понадобится пройтись по таблице.
Конечно же, таблицы -- не исключение.
Аналогичные полезные свойства есть у HTML-форм, они позволяют из формы получить все её элементы, а из них -- в свою очередь, форму. Мы рассмотрим их позже.
# Итого
В DOM доступна навигация по соседним узлам через ссылки:
<ul> <ul>
<li>`firstElementChild` -- первый потомок-элемент</li> <li>По любым узлам.</li>
<li>`lastElementChild` -- последний потомок-элемент </li> <li>Только по элементам.</li>
<li>`nextElementSibling` -- правый брат-элемент</li>
<li>`previousElementSibling` -- левый брат-элемент</li>
</ul> </ul>
Картинка только со ссылками для элементов: Также некоторые виды элементов предоставляют дополнительные ссылки для большего удобства, например у таблиц есть свойства для доступа к строкам/ячейкам.
<img src="navigation-elements.png">
<ul>
<li>`children*` -- единственное свойство из списка, поддерживаемое IE8-.</li>
</ul>
[libs] [libs]
d3 d3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="425px" height="296px" viewBox="0 0 425 296" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>dom-links-elements</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="dom-links-elements" sketch:type="MSArtboardGroup" transform="translate(-126.000000, -68.000000)">
<text id="document.documentEle" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="257.692383" y="85">document.documentElement &lt;HTML&gt;</tspan>
</text>
<text id="document.body-(если-" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="292.84375" y="153">document.body (если внутри body)</tspan>
</text>
<path d="M341.5,130.5 L341.5,100.5" id="Line-4" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-4-decoration-1" d="M341.5,100.5 C340.45,104.28 339.55,107.52 338.5,111.3 C340.6,111.3 342.4,111.3 344.5,111.3 C343.45,107.52 342.55,104.28 341.5,100.5 C341.5,100.5 341.5,100.5 341.5,100.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<path d="M353.2715,195.2195 C353.9275,195.2195 354.4565,195.7755 354.4565,196.4575 C354.4565,197.1385 353.9275,197.6935 353.2455,197.6935 C352.5895,197.6935 352.0595,197.1385 352.0595,196.4575 C352.0595,195.7755 352.5895,195.2195 353.2715,195.2195 L353.2715,195.2195 Z M351.1755,200.6715 C350.4935,200.6715 349.9645,200.1165 349.9645,199.4345 C349.9645,198.7535 350.4935,198.1985 351.1515,198.1985 C351.8325,198.1985 352.3625,198.7535 352.3625,199.4345 C352.3625,200.1165 351.8325,200.6715 351.1755,200.6715 L351.1755,200.6715 Z M351.1755,192.2415 C351.8325,192.2415 352.3625,192.7975 352.3625,193.4785 C352.3625,194.1605 351.8325,194.7155 351.1515,194.7155 C350.4935,194.7155 349.9645,194.1605 349.9645,193.4785 C349.9645,192.7975 350.4935,192.2415 351.1755,192.2415 L351.1755,192.2415 Z M349.6615,195.2955 C350.3185,195.2955 350.8475,195.8505 350.8475,196.5325 C350.8475,197.1885 350.3185,197.7435 349.6365,197.7435 C348.9805,197.7435 348.4495,197.1885 348.4495,196.5075 C348.4495,195.8505 348.9805,195.2955 349.6615,195.2955 L349.6615,195.2955 Z M349.0815,203.6495 C348.4245,203.6495 347.8695,203.0945 347.8695,202.4135 C347.8695,201.7315 348.4005,201.2015 349.0565,201.2015 C349.7375,201.2015 350.2675,201.7315 350.2675,202.4135 C350.2675,203.0945 349.7375,203.6495 349.0815,203.6495 L349.0815,203.6495 Z M349.0815,189.2635 C349.7375,189.2635 350.2675,189.8195 350.2675,190.5005 C350.2675,191.1815 349.7375,191.7375 349.0565,191.7375 C348.4005,191.7375 347.8695,191.1815 347.8695,190.5005 C347.8695,189.8195 348.4245,189.2635 349.0815,189.2635 L349.0815,189.2635 Z M346.9875,206.6275 C346.3305,206.6275 345.7755,206.0725 345.7755,205.3905 C345.7755,204.7105 346.3045,204.1795 346.9605,204.1795 C347.6425,204.1795 348.1725,204.7105 348.1725,205.3905 C348.1725,206.0725 347.6425,206.6275 346.9875,206.6275 L346.9875,206.6275 Z M346.9875,186.2855 C347.6425,186.2855 348.1725,186.8415 348.1725,187.5225 C348.1725,188.2035 347.6425,188.7595 346.9605,188.7595 C346.3045,188.7595 345.7755,188.2035 345.7755,187.5225 C345.7755,186.8415 346.3305,186.2855 346.9875,186.2855 L346.9875,186.2855 Z M346.0015,195.2955 C346.6575,195.2955 347.1885,195.8505 347.1885,196.5325 C347.1885,197.1885 346.6575,197.7435 345.9775,197.7435 C345.3215,197.7435 344.7905,197.1885 344.7905,196.5075 C344.7905,195.8505 345.3465,195.2955 346.0015,195.2955 L346.0015,195.2955 Z M342.3675,195.2955 C343.0245,195.2955 343.5545,195.8505 343.5545,196.5325 C343.5545,197.1885 342.9985,197.7435 342.3425,197.7435 C341.6865,197.7435 341.1305,197.1885 341.1305,196.5075 C341.1305,195.8505 341.6865,195.2955 342.3675,195.2955 L342.3675,195.2955 Z M338.7085,195.2955 C339.3645,195.2955 339.8945,195.8505 339.8945,196.5325 C339.8945,197.1885 339.3645,197.7435 338.6835,197.7435 C338.0275,197.7435 337.4965,197.1885 337.4965,196.5075 C337.4965,195.8505 338.0275,195.2955 338.7085,195.2955 L338.7085,195.2955 Z M335.0485,195.2955 C335.7045,195.2955 336.2355,195.8505 336.2355,196.5325 C336.2355,197.1885 335.7045,197.7435 335.0235,197.7435 C334.3665,197.7435 333.8365,197.1885 333.8365,196.5075 C333.8365,195.8505 334.3925,195.2955 335.0485,195.2955 L335.0485,195.2955 Z M331.4145,195.2955 C332.0705,195.2955 332.6005,195.8505 332.6005,196.5325 C332.6005,197.1885 332.0455,197.7435 331.3885,197.7435 C330.7335,197.7435 330.1775,197.1885 330.1775,196.5075 C330.1775,195.8505 330.7335,195.2955 331.4145,195.2955 L331.4145,195.2955 Z M327.7545,195.2955 C328.4105,195.2955 328.9415,195.8505 328.9415,196.5325 C328.9415,197.1885 328.4105,197.7435 327.7295,197.7435 C327.0735,197.7435 326.5435,197.1885 326.5435,196.5075 C326.5435,195.8505 327.0735,195.2955 327.7545,195.2955 L327.7545,195.2955 Z" id="Fill-7" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(340.500000, 196.456500) rotate(-90.000000) translate(-340.500000, -196.456500) "></path>
<path d="M134.5,171 L547,171" id="Line-5" stroke="#8A704D" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<text id="parentElement" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold">
<tspan x="295" y="228" fill="#8A704D">parent</tspan>
<tspan x="341.183594" y="228" fill="#EE6B47">Element</tspan>
</text>
<rect id="Rectangle-6" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="303" y="271" width="75" height="24"></rect>
<rect id="Rectangle-7" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="285" y="136" width="113" height="24"></rect>
<rect id="Rectangle-8" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="251" y="68" width="194" height="24"></rect>
<text id="&lt;DIV&gt;" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="321.051859" y="288">&lt;DIV&gt;</tspan>
</text>
<path d="M340.5,263.5 L340.5,237.5" id="Line-6" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-6-decoration-1" d="M340.5,237.5 C339.45,241.28 338.55,244.52 337.5,248.3 C339.6,248.3 341.4,248.3 343.5,248.3 C342.45,244.52 341.55,241.28 340.5,237.5 C340.5,237.5 340.5,237.5 340.5,237.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<path d="M387.5,282.5 L547,282.5" id="Line-7" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-7-decoration-1" d="M546.5,282.5 C542.72,281.45 539.48,280.55 535.7,279.5 C535.7,281.6 535.7,283.4 535.7,285.5 C539.48,284.45 542.72,283.55 546.5,282.5 C546.5,282.5 546.5,282.5 546.5,282.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<text id="nextElementSibling" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold">
<tspan x="396" y="268" fill="#8A704D">next</tspan>
<tspan x="426.789062" y="268" fill="#EE6B47">Element</tspan>
<tspan x="480.669922" y="268" fill="#8A704D">Sibling</tspan>
</text>
<path d="M288,282.5 L132,282.5" id="Line-8" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-8-decoration-1" d="M132,282.5 C135.78,283.55 139.02,284.45 142.8,285.5 C142.8,283.4 142.8,281.6 142.8,279.5 C139.02,280.55 135.78,281.45 132,282.5 C132,282.5 132,282.5 132,282.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<text id="previousElementSibli" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold">
<tspan x="126" y="268" fill="#8A704D">previous</tspan>
<tspan x="187.578125" y="268" fill="#EE6B47">Element</tspan>
<tspan x="241.458984" y="268" fill="#8A704D">Sibling</tspan>
</text>
<path d="M236.5,341.5 L294.5,305.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M445,342 L389.5,305.5" id="Line-3" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<text id="children" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#EE6B47">
<tspan x="308" y="329">children</tspan>
</text>
<text id="firstElementChild--" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold">
<tspan x="163" y="363" fill="#8A704D">first</tspan>
<tspan x="201.486328" y="363" fill="#EE6B47">Element</tspan>
<tspan x="255.367188" y="363" fill="#8A704D">Child </tspan>
</text>
<text id="lastElementChild" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold">
<tspan x="393" y="363" fill="#8A704D">last</tspan>
<tspan x="423.789062" y="363" fill="#EE6B47">Element</tspan>
<tspan x="477.669922" y="363" fill="#8A704D">Child</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="415px" height="346px" viewBox="0 0 415 346" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>dom-links</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="dom-links" sketch:type="MSArtboardGroup" transform="translate(-133.000000, -2.000000)">
<text id="document" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="309.710938" y="18">document</tspan>
</text>
<text id="document.documentEle" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="257.692383" y="85">document.documentElement &lt;HTML&gt;</tspan>
</text>
<text id="document.body-(если-" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="292.84375" y="153">document.body (если внутри body)</tspan>
</text>
<path d="M341.5,130.5 L341.5,100.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-decoration-1" d="M341.5,100.5 C340.45,104.28 339.55,107.52 338.5,111.3 C340.6,111.3 342.4,111.3 344.5,111.3 C343.45,107.52 342.55,104.28 341.5,100.5 C341.5,100.5 341.5,100.5 341.5,100.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<path d="M353.2715,195.2195 C353.9275,195.2195 354.4565,195.7755 354.4565,196.4575 C354.4565,197.1385 353.9275,197.6935 353.2455,197.6935 C352.5895,197.6935 352.0595,197.1385 352.0595,196.4575 C352.0595,195.7755 352.5895,195.2195 353.2715,195.2195 L353.2715,195.2195 Z M351.1755,200.6715 C350.4935,200.6715 349.9645,200.1165 349.9645,199.4345 C349.9645,198.7535 350.4935,198.1985 351.1515,198.1985 C351.8325,198.1985 352.3625,198.7535 352.3625,199.4345 C352.3625,200.1165 351.8325,200.6715 351.1755,200.6715 L351.1755,200.6715 Z M351.1755,192.2415 C351.8325,192.2415 352.3625,192.7975 352.3625,193.4785 C352.3625,194.1605 351.8325,194.7155 351.1515,194.7155 C350.4935,194.7155 349.9645,194.1605 349.9645,193.4785 C349.9645,192.7975 350.4935,192.2415 351.1755,192.2415 L351.1755,192.2415 Z M349.6615,195.2955 C350.3185,195.2955 350.8475,195.8505 350.8475,196.5325 C350.8475,197.1885 350.3185,197.7435 349.6365,197.7435 C348.9805,197.7435 348.4495,197.1885 348.4495,196.5075 C348.4495,195.8505 348.9805,195.2955 349.6615,195.2955 L349.6615,195.2955 Z M349.0815,203.6495 C348.4245,203.6495 347.8695,203.0945 347.8695,202.4135 C347.8695,201.7315 348.4005,201.2015 349.0565,201.2015 C349.7375,201.2015 350.2675,201.7315 350.2675,202.4135 C350.2675,203.0945 349.7375,203.6495 349.0815,203.6495 L349.0815,203.6495 Z M349.0815,189.2635 C349.7375,189.2635 350.2675,189.8195 350.2675,190.5005 C350.2675,191.1815 349.7375,191.7375 349.0565,191.7375 C348.4005,191.7375 347.8695,191.1815 347.8695,190.5005 C347.8695,189.8195 348.4245,189.2635 349.0815,189.2635 L349.0815,189.2635 Z M346.9875,206.6275 C346.3305,206.6275 345.7755,206.0725 345.7755,205.3905 C345.7755,204.7105 346.3045,204.1795 346.9605,204.1795 C347.6425,204.1795 348.1725,204.7105 348.1725,205.3905 C348.1725,206.0725 347.6425,206.6275 346.9875,206.6275 L346.9875,206.6275 Z M346.9875,186.2855 C347.6425,186.2855 348.1725,186.8415 348.1725,187.5225 C348.1725,188.2035 347.6425,188.7595 346.9605,188.7595 C346.3045,188.7595 345.7755,188.2035 345.7755,187.5225 C345.7755,186.8415 346.3305,186.2855 346.9875,186.2855 L346.9875,186.2855 Z M346.0015,195.2955 C346.6575,195.2955 347.1885,195.8505 347.1885,196.5325 C347.1885,197.1885 346.6575,197.7435 345.9775,197.7435 C345.3215,197.7435 344.7905,197.1885 344.7905,196.5075 C344.7905,195.8505 345.3465,195.2955 346.0015,195.2955 L346.0015,195.2955 Z M342.3675,195.2955 C343.0245,195.2955 343.5545,195.8505 343.5545,196.5325 C343.5545,197.1885 342.9985,197.7435 342.3425,197.7435 C341.6865,197.7435 341.1305,197.1885 341.1305,196.5075 C341.1305,195.8505 341.6865,195.2955 342.3675,195.2955 L342.3675,195.2955 Z M338.7085,195.2955 C339.3645,195.2955 339.8945,195.8505 339.8945,196.5325 C339.8945,197.1885 339.3645,197.7435 338.6835,197.7435 C338.0275,197.7435 337.4965,197.1885 337.4965,196.5075 C337.4965,195.8505 338.0275,195.2955 338.7085,195.2955 L338.7085,195.2955 Z M335.0485,195.2955 C335.7045,195.2955 336.2355,195.8505 336.2355,196.5325 C336.2355,197.1885 335.7045,197.7435 335.0235,197.7435 C334.3665,197.7435 333.8365,197.1885 333.8365,196.5075 C333.8365,195.8505 334.3925,195.2955 335.0485,195.2955 L335.0485,195.2955 Z M331.4145,195.2955 C332.0705,195.2955 332.6005,195.8505 332.6005,196.5325 C332.6005,197.1885 332.0455,197.7435 331.3885,197.7435 C330.7335,197.7435 330.1775,197.1885 330.1775,196.5075 C330.1775,195.8505 330.7335,195.2955 331.4145,195.2955 L331.4145,195.2955 Z M327.7545,195.2955 C328.4105,195.2955 328.9415,195.8505 328.9415,196.5325 C328.9415,197.1885 328.4105,197.7435 327.7295,197.7435 C327.0735,197.7435 326.5435,197.1885 326.5435,196.5075 C326.5435,195.8505 327.0735,195.2955 327.7545,195.2955 L327.7545,195.2955 Z" id="Fill-7" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(340.500000, 196.456500) rotate(-90.000000) translate(-340.500000, -196.456500) "></path>
<path d="M341.5,62.5 L341.5,32.5" id="Line-2" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-2-decoration-1" d="M341.5,32.5 C340.45,36.28 339.55,39.52 338.5,43.3 C340.6,43.3 342.4,43.3 344.5,43.3 C343.45,39.52 342.55,36.28 341.5,32.5 C341.5,32.5 341.5,32.5 341.5,32.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<path d="M134.5,171 L547,171" id="Line" stroke="#8A704D" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<text id="parentNode" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="304" y="228">parentNode</tspan>
</text>
<rect id="Rectangle-6" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="303" y="271" width="75" height="24"></rect>
<rect id="Rectangle-7" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="285" y="136" width="113" height="24"></rect>
<rect id="Rectangle-9" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="285" y="2" width="113" height="24"></rect>
<rect id="Rectangle-8" stroke="#BCA68E" stroke-width="2" sketch:type="MSShapeGroup" x="251" y="68" width="194" height="24"></rect>
<text id="&lt;DIV&gt;" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="321.051859" y="288">&lt;DIV&gt;</tspan>
</text>
<path d="M340.5,263.5 L340.5,237.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-decoration-1" d="M340.5,237.5 C339.45,241.28 338.55,244.52 337.5,248.3 C339.6,248.3 341.4,248.3 343.5,248.3 C342.45,244.52 341.55,241.28 340.5,237.5 C340.5,237.5 340.5,237.5 340.5,237.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<path d="M387.5,282.5 L500,282.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-decoration-1" d="M499.5,282.5 C495.72,281.45 492.48,280.55 488.7,279.5 C488.7,281.6 488.7,283.4 488.7,285.5 C492.48,284.45 495.72,283.55 499.5,282.5 C499.5,282.5 499.5,282.5 499.5,282.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<text id="nextSibling" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="396" y="268">nextSibling</tspan>
</text>
<path d="M296.5,282.5 L182,282.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<path id="Line-decoration-1" d="M182.5,282.5 C186.28,283.55 189.52,284.45 193.3,285.5 C193.3,283.4 193.3,281.6 193.3,279.5 C189.52,280.55 186.28,281.45 182.5,282.5 C182.5,282.5 182.5,282.5 182.5,282.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
<text id="previousSibling" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="182" y="268">previousSibling</tspan>
</text>
<path d="M236.5,341.5 L294.5,305.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M445,342 L389.5,305.5" id="Line-3" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<text id="childNodes" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="302" y="323">childNodes</tspan>
</text>
<text id="firstChild--lastChil" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="257" y="347">firstChild lastChild</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -1,13 +0,0 @@
<!DOCTYPE HTML>
<html>
<head><meta charset="utf-8"></head>
<body>
<div>Начало</div>
<ul>
<li>Содержание</li>
</ul>
<div>Конец</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View file

@ -1,54 +1,26 @@
Есть много вариантов решения, вот некоторые из них: Есть много вариантов решения, вот некоторые из них:
<ol>
<li>
```js ```js
// 1
document.getElementById('age-table').getElementsByTagName('label'); document.getElementById('age-table').getElementsByTagName('label');
```
</li> // 2
<li>
```js
document.getElementById('age-table').getElementsByTagName('td')[0]; document.getElementById('age-table').getElementsByTagName('td')[0];
// в современных браузерах можно одним запросом: // в современных браузерах можно одним запросом:
var result = document.querySelector('#age-table td'); var result = document.querySelector('#age-table td');
```
</li> // 3
<li>
```js
document.getElementsByTagName('form')[1]; document.getElementsByTagName('form')[1];
```
</li> // 4
<li>
```js
document.querySelector('form[name="search"]'); document.querySelector('form[name="search"]');
```
</li> // 5
<li>
```js
document.querySelector('form[name="search-person"] input') document.querySelector('form[name="search-person"] input')
```
</li> // 6
<li>
```js
document.getElementsByName("info[0]")[0]; document.getElementsByName("info[0]")[0];
```
</li> // 7
<li>
```js
document.querySelector('form[name="search-person"] [name="info[0]"]'); document.querySelector('form[name="search-person"] [name="info[0]"]');
``` ```
</li>
</ol>

View file

@ -16,9 +16,5 @@
<li>Элемент с именем `info[0]`, внутри формы с именем `search-person`.</li> <li>Элемент с именем `info[0]`, внутри формы с именем `search-person`.</li>
</ol> </ol>
Используйте для этого консоль браузера или откройте пример в песочнице: Используйте для этого консоль браузера, открыв страницу [table.html](table.html) в отдельном окне.
```html
<!--+ autorun run src="index.html" -->
```

View file

@ -0,0 +1,18 @@
Сделаем цикл по узлам `<li>`:
```js
var lis = document.getElementsByTagName('li');
for(i=0; i<lis.length; i++) {
...
}
```
В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `<li>` является как раз текстовый узел, содержащий текст названия.
Также можно получить количество потомков, используя `lis[i].getElementsByTagName('li')`.
Напишите код с этой подсказкой.
Если уж не выйдет -- тогда откройте решение.

View file

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head><meta charset="utf-8"></head>
<body>
<ul>
<li>Животные
<ul>
<li>Млекопитающие
<ul>
<li>Коровы</li>
<li>Ослы</li>
<li>Собаки</li>
<li>Тигры</li>
</ul>
</li>
<li>Другие
<ul>
<li>Змеи</li>
<li>Птицы</li>
<li>Ящерицы</li>
</ul>
</li>
</ul>
</li>
<li>Рыбы
<ul>
<li>Аквариумные
<ul>
<li>Гуппи</li>
<li>Скалярии</li>
</ul>
</li>
<li>Морские
<ul>
<li>Морская форель</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
var lis = document.getElementsByTagName('li');
for(i=0; i<lis.length; i++) {
// получить название из текстового узла
var title = lis[i].firstChild.data;
title = title.trim(); // убрать лишние пробелы с концов
// получить количество детей
var childCount = lis[i].getElementsByTagName('li').length;
alert(title + ': ' + childCount);
}
</script>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<head><meta charset="utf-8"></head>
<body>
<ul>
<li>Животные
<ul>
<li>Млекопитающие
<ul>
<li>Коровы</li>
<li>Ослы</li>
<li>Собаки</li>
<li>Тигры</li>
</ul>
</li>
<li>Другие
<ul>
<li>Змеи</li>
<li>Птицы</li>
<li>Ящерицы</li>
</ul>
</li>
</ul>
</li>
<li>Рыбы
<ul>
<li>Аквариумные
<ul>
<li>Гуппи</li>
<li>Скалярии</li>
</ul>
</li>
<li>Морские
<ul>
<li>Морская форель</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
// .. ваш код ..
</script>
</body>
</html>

View file

@ -0,0 +1,14 @@
# Дерево
[importance 5]
Есть дерево [edit src="source"]в песочнице[/edit].
Напишите код, который для каждого элемента списка(`LI`) выведет:
<ol>
<li>Текст в нём.</li>
<li>Количество вложенных в него элементов.</li>
</ol>
[demo src="solution"]

View file

@ -2,13 +2,67 @@
Прямая навигация от родителя к потомку удобна, если элементы рядом. А если нет? Прямая навигация от родителя к потомку удобна, если элементы рядом. А если нет?
Как достать произвольный элемент откуда-то из глубины документа?
Для этого в DOM есть дополнительные методы поиска. Для этого в DOM есть дополнительные методы поиска.
[cut] [cut]
## getElementById
Мы рассматривали этот способ ранее. Он работает, если у DOM-элемента есть атрибут `id`. ## document.getElementById или просто id
Если элементу назначен специальный атрибут `id`, то можно получить его прямо по переменной с именем из значения `id`.
Например:
```html
<!--+ run -->
<div id="*!*content-holder*/!*">
<div id="*!*content*/!*">Элемент</div>
</div>
<script>
*!*
alert( content ); // DOM-элемент
alert( window['content-holder'] ); // в имени дефис, поэтому через [...]
*/!*
</script>
```
Это поведение соответствует [стандарту](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem). Оно существует, в первую очередь, для совместимости, как осколок далёкого прошлого и не очень приветствуется, поскольку использует глобальные переменные. Браузер пытается помочь нам, смешивая пространства имён JS и DOM, но при этом возможны конфликты.
**Более правильной и общепринятой практикой является доступ к элементу вызовом `document.getElementById("идентификатор")`.**
Например:
```html
<!--+ run -->
<div id="*!*content*/!*">Выделим этот элемент</div>
<script>
*!*
var elem = document.getElementById('content');
elem.style.background = 'red';
alert( elem == content ); // true
content.style.background = ""; // один и тот же элемент
*/!*
</script>
```
[smart header="Должен остаться только один"]
По стандарту значение `id` должно быть уникально, то есть в документе может быть только один элемент с данным `id`. И именно он будет возвращён.
Если в документе есть несколько элементов с уникальным `id`, то поведение неопределено. То есть, нет гарантии, что браузер вернёт именно первый или последний -- вернёт случайным образом.
Поэтому стараются следовать правилу уникальности `id`.
[/smart]
Далее в примерах я часто буду использовать прямое обращение через переменную, чтобы было меньше букв и проще было понять происходящее. Но предпочтительным методом является `document.getElementById`.
**В тех случаях, когда `id` не уникален, вызов `document.getElementById(id)` может возвратить любой из элементов с данным `id`.**
## getElementsByTagName ## getElementsByTagName
@ -16,14 +70,15 @@
Регистр тега не имеет значения. Регистр тега не имеет значения.
Можно искать и в элементе и в документе: Например:
```js ```js
// получить все div-элементы // получить все div-элементы
var elements = document.getElementsByTagName('div'); var elements = document.getElementsByTagName('div');
``` ```
Найдём все элементы `input` внутри таблицы: **Обратим внимание: в отличие от `getElementById`, который существует только в контексте `document`, метод `getElementsByTagName` может искать внутри любого элемента.**
Например, найдём все элементы `input` внутри таблицы:
```html ```html
<!--+ run height=50 --> <!--+ run height=50 -->
@ -61,21 +116,18 @@ var elements = document.getElementsByTagName('div');
</script> </script>
``` ```
**Можно получить все элементы, передав звездочку `'*'` вместо тега:** **Можно получить всех потомков, передав звездочку `'*'` вместо тега:**
```js ```js
// получить все элементы документа // получить все элементы документа
var allElems = document.getElementsByTagName('*'); document.getElementsByTagName('*');
```
Если хочется получить только один элемент -- можно указать индекс сразу же: // получить всех потомков элемента elem:
elem.getElementsByTagName('*');
```js
var element = document.getElementsByTagName('input')*!*[0]*/!*
``` ```
[warn header="Не забываем про букву `\"s\"`!"] [warn header="Не забываем про букву `\"s\"`!"]
Одна из самых частых ошибок начинающих (впрочем, иногда и не только) -- это забыть букву `"s", то есть пробовать вызывать метод `getElementByTagName` вместо <code>getElement**s**ByTagName</code>. Одна из самых частых ошибок начинающих (впрочем, иногда и не только) -- это забыть букву `"s"`, то есть пробовать вызывать метод `getElementByTagName` вместо <code>getElement<b>s</b>ByTagName</code>.
Буква `"s"` не нужна там, где элемент только один, то есть в `getElementById`, в остальных методах она обязательна. Буква `"s"` не нужна там, где элемент только один, то есть в `getElementById`, в остальных методах она обязательна.
[/warn] [/warn]
@ -84,6 +136,7 @@ var element = document.getElementsByTagName('input')*!*[0]*/!*
Другая частая ошибка -- это код вида: Другая частая ошибка -- это код вида:
```js ```js
// не работает
document.getElementsByTagName('input').value = 5; document.getElementsByTagName('input').value = 5;
``` ```
@ -92,6 +145,7 @@ document.getElementsByTagName('input').value = 5;
Коллекцию нужно или перебрать в цикле или получить элемент по номеру и уже ему присваивать `value`, например так: Коллекцию нужно или перебрать в цикле или получить элемент по номеру и уже ему присваивать `value`, например так:
```js ```js
// работает
document.getElementsByTagName('input')[0].value = 5; document.getElementsByTagName('input')[0].value = 5;
``` ```
@ -109,22 +163,9 @@ var elems = document.getElementsByName('age');
До появления стандарта HTML5 этот метод возвращал только те элементы, в которых предусмотрена поддержка атрибута `name`, в частности: `iframe`, `a`, `input` и другими. До появления стандарта HTML5 этот метод возвращал только те элементы, в которых предусмотрена поддержка атрибута `name`, в частности: `iframe`, `a`, `input` и другими.
В современных браузерах тег не имеет значения, но старое поведение можно увидеть, попробовав пример ниже в IE10 и до версии 10: В современных браузерах (IE10+) тег не имеет значения.
```html Используется этот метод весьма редко.
<!--+ run -->
<input name="test">
<div name="test"></div>
<script>
var elems = document.getElementsByName('test');
alert(elems.length); // 2 в современных браузерах, 1 в IE<10
</script>
```
**В IE9- метод не найдёт элементы, для которых в стандарте нет атрибута `name`.**
## getElementsByClassName ## getElementsByClassName
@ -152,13 +193,11 @@ alert( articles.length ); // 2, найдёт оба элемента
Вызов `elem.querySelectorAll(cssQuery)` возвращает все элементы внутри `elem`, удовлетворяющие CSS-селектору `cssQuery`. Вызов `elem.querySelectorAll(cssQuery)` возвращает все элементы внутри `elem`, удовлетворяющие CSS-селектору `cssQuery`.
Он работает во всех современных браузерах, включая IE9+. Также работает и в IE8, но с некоторыми ограничениями: Это один из самых часто используемых и полезных методов при работе с DOM.
<ol>
<li>IE8 должен быть именно в режиме IE8, а не в режиме совместимости.</li>
<li>В IE8 синтаксис `cssQuery` должен соответствовать не CSS 3, а CSS 2.1. Не так мощно, конечно, но этого хватает для большинства случаев.</li>
</ol>
Следующий запрос получает все элементы `LI`, которые являются последними потомками своих `UL`. Это будет работать и в IE8. Он есть во всех современных браузерах, включая IE8+ (IE8 должен быть в режиме соответствия стандарту).
Следующий запрос получает все элементы `LI`, которые являются последними потомками своих `UL`:
```html ```html
<!--+ run --> <!--+ run -->
@ -183,50 +222,49 @@ alert( articles.length ); // 2, найдёт оба элемента
## querySelector [#querySelector] ## querySelector [#querySelector]
То же самое, что `elem.querySelectorAll(cssQuery)`, но возвращает только первый элемент. Вызов `elem.querySelector(cssQuery)` возвращает первый элемент, соответствующий CSS-селектору `cssQuery`.
Фактически, эквивалентен `elem.querySelectorAll(cssQuery)[0]`, но быстрее, так как ищутся не все элементы, а только первый. Иначе говоря, результат -- такой же, как и при `elem.querySelectorAll(cssQuery)[0]`, но в последнем вызове сначала ищутся все элементы, а потом берётся первый, а в `elem.querySelector(cssQuery)` ищется только первый, то есть он эффективнее.
## matches ## matches
Вызов [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) проверяет, удовлетворяет ли `elem` селектору `css`. Предыдущие методы искали по DOM.
Он возвращает `true` либо `false`. Метод [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) ничего не ищет, а проверяет, удовлетворяет ли `elem` селектору `css`. Он возвращает `true` либо `false`.
Ранее в спецификации он назывался `matchesSelector`, и большинство браузеров поддерживают его под этим старым именем, либо с префиксами. Не поддерживается в IE8-.
Этот метод бывает полезным, когда мы перебираем элементы по обычным DOM-ссылкам и пытаемся отфильтровать те из них, которые нам интересны.
Ранее в спецификации он назывался `matchesSelector`, и большинство браузеров поддерживают его под этим старым именем, либо с префиксами `ms/moz/webkit`.
Например: Например:
```html ```html
<!--+ run --> <!--+ run -->
<div id="test"> <a href="http://example.com/file.zip">...</a>
<a href="http://example.com/file.zip" id="link">...</a> <a href="http://ya.ru">...</a>
</div>
<script> <script>
var testElem = document.documentElement; var elems = document.body.children;
var matchesSelector = testElem.matches ||
testElem.matchesSelector ||
testElem.msMatchesSelector;
for(var i=0; i<elems.length; i++) {
*!* *!*
var elem = document.getElementById('link'); if ( elems[i].matches('a[href$="zip"]') ) {
// проверим, подходит ли элемент под CSS-селектор?
alert( matchesSelector.call(elem, '#test a[href$="zip"]') ); // true
*/!* */!*
alert("Ссылка на архив: " + elems[i].href);
}
}
</script> </script>
``` ```
Не поддерживается в IE8-.
## XPath в современных браузерах ## XPath в современных браузерах
Для поиска в XML-документах существует <a href="http://www.w3.org/TR/xpath/">язык запросов XPath</a>. Для полноты картины рассмотрим ещё один способ поиска, который обычно используется в XML. Это <a href="http://www.w3.org/TR/xpath/">язык запросов XPath</a>.
Он очень мощный, во многом мощнее CSS, но сложнее. Например, запрос для поиска элементов `H2`, содержащих текст `"XPath"`, будет выглядеть так: `//h2[contains(., "XPath")]`. Он очень мощный, во многом мощнее CSS, но сложнее. Например, запрос для поиска элементов `H2`, содержащих текст `"XPath"`, будет выглядеть так: `//h2[contains(., "XPath")]`.
**Все современные браузеры, кроме IE, поддерживают XPath с синтаксисом, близким к [описанному в MDN](https://developer.mozilla.org/en/XPath).** Все современные браузеры, кроме IE, поддерживают XPath с синтаксисом, близким к [описанному в MDN](https://developer.mozilla.org/en/XPath).
Найдем заголовки с текстом `XPath` в текущем документе: Найдем заголовки с текстом `XPath` в текущем документе:
@ -241,6 +279,8 @@ for (var i=0; i<result.snapshotLength; i++) {
IE тоже поддерживает XPath, но эта поддержка не соответствует стандарту и работает только для XML-документов, например, полученных с помощью `XMLHTTPRequest` (AJAX). Для обычных же HTML-документов XPath в IE не поддерживается. IE тоже поддерживает XPath, но эта поддержка не соответствует стандарту и работает только для XML-документов, например, полученных с помощью `XMLHTTPRequest` (AJAX). Для обычных же HTML-документов XPath в IE не поддерживается.
Так как XPath сложнее и длиннее CSS, то используют его очень редко.
## Итого ## Итого
Есть 6 основных методов поиска элементов DOM: Есть 6 основных методов поиска элементов DOM:
@ -292,8 +332,8 @@ IE тоже поддерживает XPath, но эта поддержка не
Кроме того: Кроме того:
<ul> <ul>
<li>Есть метод `elem.matchesSelector(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (`ms`, `moz`, `webkit`).</li> <li>Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (`ms`, `moz`, `webkit`).</li>
<li>XPath поддерживается большинством браузеров, кроме IE, даже 9й версии. Кроме того, как правило, `querySelector` удобнее. Поэтому он используется редко. </li> <li>Язык запросов XPath поддерживается большинством браузеров, кроме IE, даже 9й версии, но `querySelector` удобнее. Поэтому XPath используется редко. </li>
</ul> </ul>

View file

@ -1,58 +0,0 @@
# Особые ссылки для таблиц
У конкретных элементов DOM могут быть свои дополнительные ссылки для большего удобства навигации.
В этой главе мы рассмотрим таблицу, так как это важный частный случай и просто для примера. Также дополнительные ссылки есть у форм, но работу с ними лучше осваивать отдельно.
[cut]
В списке ниже выделены наиболее полезные:
<dl>
<dt>`TABLE`</dt>
<dd>
<ul>
<li>**`table.rows`** -- список строк `TR` таблицы.</li>
<li>`table.caption/tHead/tFoot` -- ссылки на элементы таблицы `CAPTION`, `THEAD`, `TFOOT`.</li>
<li>`table.tBodies` -- список элементов таблицы `TBODY`, по спецификации их может быть несколько.</li>
</ul></dd>
<dt>`THEAD/TFOOT/TBODY`</dt>
<dd>
<ul>
<li>`tbody.rows` -- список строк `TR` секции.</li>
</ul></dd>
<dt>`TR`</dt>
<dd>
<ul>
<li>**`tr.cells`** -- список ячеек `TD/TH`</li>
<li>**`tr.sectionRowIndex`** -- номер строки в текущей секции `THEAD/TBODY`</li>
<li>`tr.rowIndex` -- номер строки в таблице</li>
</ul>
</dd>
<dt>`TD/TH`</dt>
<dd>
<ul>
<li>**`td.cellIndex`** -- номер ячейки в строке</li>
</ul>
</dd>
</dl>
Пример использования:
```html
<!--+ run height=100 -->
<table>
<tr> <td>один</td> <td>два</td> </tr>
<tr> <td>три</td> <td>четыре</td> </tr>
</table>
<script>
var table = document.body.children[0];
alert( table.*!*rows[0].cells[0]*/!*.innerHTML ) // "один"
</script>
```
Спецификация: [HTML5: tabular data](http://www.w3.org/TR/html5/tabular-data.html).
Даже если эти свойства не нужны вам прямо сейчас, имейте их в виду на будущее, когда понадобится пройтись по таблице.

View file

@ -1,101 +0,0 @@
# Менее эффективный вариант
Цикл по правым соседям в поисках узла-элемента:
```html
<!--+ run -->
<div>Первый</div>
<!-- комментарий... -->
<p>Второй</p>
<script>
*!*
function getNextElement(elem) {
var current = elem.nextSibling;
while(current && current.nodeType != 1) {
current = current.nextSibling;
}
// два варианта окончания цикла:
// current == null (нет следующего узла-элемента)
// current.nodeType == 1 (нашли)
return current;
}
*/!*
alert(getNextElement(document.body.children[0]).tagName); // "P"
alert(getNextElement(document.body.lastChild)); // null
</script>
```
# Более эффективный вариант
Все браузеры, кроме IE<9, поддерживают свойство `nextElementSibling`. Воспользуемся этим.
```html
<!--+ run -->
<div>Первый</div>
<!-- комментарий... -->
<p>Второй</p>
<script>
function getNextElement(elem) {
*!*
if (elem.nextElementSibling !== undefined) {
return elem.nextElementSibling;
}
*/!*
var current = elem.nextSibling;
while(current && current.nodeType != 1) {
current = current.nextSibling;
}
// два варианта окончания цикла:
// current == null (нет следующего узла-элемента)
// current.nodeType == 1 (нашли)
return current;
}
alert(getNextElement(document.body.children[0]).tagName); // "P"
alert(getNextElement(document.body.lastChild)); // null
</script>
```
В выделенном фрагменте мы проверяем поддержку этого свойства. Если оно поддерживается, то равно либо элементу-соседу, либо `null`, если такого соседа нет. В любом случае это не `undefined`.
Если же оно не поддерживается, то производим те же вычисления, что в предыдущем решении.
# Ещё более эффективный вариант
Поддержка свойства `nextElementSibling` в браузере либо есть, либо её нет. Зачем проверять её для каждого элемента? Можно сделать это один раз и запомнить результат. А можно поступить ещё лучше -- определить функцию по-разному, в зависимости от того, поддерживается это свойство или нет.
```html
<!--+ run -->
<div>Первый</div>
<!-- комментарий... -->
<p>Второй</p>
<script>
*!*
var getNextElement =
document.documentElement.nextElementSibling !== undefined ?
function(elem) {
return elem.nextElementSibling;
}
:
function(elem) {
var current = elem.nextSibling;
while(current && current.nodeType != 1) {
current = current.nextSibling;
}
return current;
};
*/!*
alert(getNextElement(document.body.children[0]).tagName); // "P"
alert(getNextElement(document.body.lastChild)); // null
</script>
```

View file

@ -1,20 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>Первый</div>
<!-- комментарий... -->
<p>Второй</p>
<script>
function getNextElement(elem) { /* ваш код */ }
alert(getNextElement(document.body.children[0]).tagName); // P
alert(getNextElement(document.body.lastChild)); // null
</script>
</body>
</html>

View file

@ -1,27 +0,0 @@
# Найти следующий элемент
[importance 5]
Напишите функцию `getNextElement(elem)`, которая возвращает следующий за `elem` узел-элемент (игнорирует остальные узлы).
Пример:
```html
<div>Первый</div>
<!-- комментарий... -->
<p>Второй</p>
<script>
function getNextElement(elem) { /* ваш код */ }
*!*
alert(getNextElement(document.body.children[0]).tagName); // P
alert(getNextElement(document.body.lastChild)); // null
*/!*
</script>
```
[edit src="source" /]
P.S. Функция должна работать максимально эффективно и учитывать возможности современных браузеров.

View file

@ -1,8 +1,12 @@
# Внутреннее устройство поисковых методов # Внутреннее устройство поисковых методов
Несмотря на схожесть в синтаксисе, поисковые методы `get*` и `querySelector*` внутри устроены очень по-разному. Эта глава не обязательна при первом чтении учебника.
Если вы хотите действительно глубоко понимать, что происходит, то посмотрите эту главу. Если нет -- её можно пропустить. Если вы хотите действительно глубоко понимать, что происходит при поиске, то посмотрите эту главу. Если нет -- её можно пропустить.
[cut]
Несмотря на схожесть в синтаксисе, поисковые методы `get*` и `querySelector*` внутри устроены очень по-разному.
## document.getElementById(id) ## document.getElementById(id)

Some files were not shown because too many files have changed in this diff Show more