This commit is contained in:
Ilya Kantor 2014-11-16 01:40:20 +03:00
parent 962caebbb7
commit 87bf53d076
1825 changed files with 94929 additions and 0 deletions

View file

@ -0,0 +1,8 @@
Решение:
```js
var ul = document.body.children[0];
ul.insertAdjacentHTML("beforeEnd", "<li>3</li><li>4</li><li>5</li>");
```

View file

@ -0,0 +1,15 @@
# Вставьте элементы в конец списка
[importance 5]
Напишите код для вставки текста `html` в конец списка `ul` с использованием метода `insertAdjacentHTML`. Такая вставка, в отличие от присвоения `innerHTML+=`, не будет перезаписывать текущее содержимое.
Добавьте к списку ниже элементы `<li>3</li><li>4</li><li>5</li>`:
```html
<ul>
<li>1</li>
<li>2</li>
</ul>
```

View file

@ -0,0 +1,49 @@
# Подсказки
<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

@ -0,0 +1,34 @@
# Вставка 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

@ -0,0 +1,11 @@
Для сортировки нам поможет функция `sort` массива.
Общая идея лежит на поверхности: сделать массив из строк и отсортировать его. Тонкости кроются в деталях.
В ифрейме ниже загружен документ, описывающий и реализующий разные алгоритмы. Обратите внимание: разница в производительности может достигать нескольких раз!
[iframe height=800 border=1 src="solution" link edit]
P.S. Создавать `DocumentFragment` здесь ни к чему. Можно вытащить из документа `TBODY` и иметь дело с ним в отрыве от DOM (алгоритм 4).
P.P.S. Если нужно сделать много узлов, то обычно `innerHTML` работает быстрее, чем генерация элементов через DOM-вызовы. Но в данном случае мы не создаём элементы, а сортируем и перевставляем готовые, так что результаты могут отличаться.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,47 @@
# Отсортировать таблицу
[importance 5]
Есть таблица:
<table>
<tr>
<th>Имя</th>
<th>Фамилия</th>
<th>Отчество</th>
<th>Возраст</th>
</tr>
<tr>
<td>Вася</td>
<td>Петров</td>
<td>Александрович</td>
<td>10</td>
</tr>
<tr>
<td>Петя</td>
<td>Иванов</td>
<td>Петрович</td>
<td>15</td>
</tr>
<tr>
<td>Владимир</td>
<td>Ленин</td>
<td>Ильич</td>
<td>9</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</table>
Строк в таблице много: может быть 20, 50, 100.. Есть и другие элементы в документе.
Как бы вы предложили отсортировать содержимое таблицы по полю `Возраст`? Обдумайте алгоритм, реализуйте его.
Как сделать, чтобы сортировка работала как можно быстрее? А если в таблице 10000 строк (бывает и такое)?
P.S. Может ли здесь помочь `DocumentFragment`?
P.P.S. Если предположить, что у нас заранее есть массив данных для таблицы в JavaScript -- что быстрее: отсортировать эту таблицу или сгенерировать новую?

View file

@ -0,0 +1,312 @@
# Мультивставка: insertAdjacentHTML и DocumentFragment
Обычные методы вставки работают с одним узлом. Но есть и способы вставлять множество узлов одновременно.
[cut]
## Оптимизация вставки в документ
Рассмотрим задачу: сгенерировать список `UL/LI`.
Есть две возможных последовательности:
<ol>
<li>Сначала вставить `UL` в документ, а потом добавить к нему `LI`:
```js
var ul = document.createElement('ul');
document.body.appendChild(ul); // сначала в документ
for(...) ul.appendChild(li); // потом узлы
```
</li>
<li>Полностью создать список "вне DOM", а потом -- вставить в документ:
```js
var ul = document.createElement('ul');
for(...) ul.appendChild(li); // сначала вставить узлы
document.body.appendChild(ul); // затем в документ
```
</li>
</ol>
Как ни странно, между этими последовательностями есть разница. В большинстве браузеров, второй вариант -- быстрее.
Почему же? Иногда говорят: "потому что браузер перерисовывает каждый раз при добавлении элемента". Это не так. **Дело вовсе не в перерисовке**.
Браузер достаточно "умён", чтобы ничего не перерисовывать понапрасну. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта, и на тот момент уже совершенно без разницы, в какой последовательности были изменены узлы.
**Тем не менее, при вставке узла происходят разные внутренние события и обновления внутренних структур данных, скрытые от наших глаз.**
Что именно происходит -- зависит от конкретной, внутренней браузерной реализации DOM, но это отнимает время. Конечно, браузеры развиваются и стараются свести лишние действия к минимуму.
### Бенчмарк [#insert-bench-tbody]
Чтобы легко проверить текущее состояние дел -- вот два бенчмарка.
Оба они создают таблицу 20x20, наполняя <code>TBODY</code> элементами <code>TR/TD</code>.
При этом первый вставляет все в документ тут же, второй -- задерживает вставку <code>TBODY</code> в документ до конца процесса.
Кликните, чтобы запустить.
<input type="button" onclick="alert(bench(appendFirst,50))" value="TBODY cразу в DOM"/> <input type="button" onclick="alert(bench(appendLast,50))" value="Отложенная вставка TBODY в DOM"/>
<table id="bench-table"></table>
```js
//+ hide="открыть код" src="insert-bench.js"
```
## Добавление множества узлов
Продолжим работать со вставкой узлов.
Рассмотрим случай, когда в документе *уже есть* большой список `UL`. И тут понадобилось срочно добавить еще 20 элементов `LI`.
Как это сделать?
Если новые элементы пришли в виде строки, то можно попробовать добавить их так:
```js
ul.innerHTML += "<li>1</li><li>2</li>...";
```
Но операция `+=` с `innerHTML` не работает с DOM. Она не прибавляет, а заменяет всё содержимое списка на дополненную строку. Это не только медленно, но все внешние ресурсы (картинки) будут загружены заново! Так лучше не делать.
А если нужно вставить в середину списка? Здесь `innerHTML` вообще не поможет.
Можно, конечно, вставить строку во временный DOM-элемент и перенести оттуда элементы, но есть и гораздо лучший вариант: метод `insertAdjacentHTML`!
## insertAdjacent*
Метод [insertAdjacentHTML](https://developer.mozilla.org/en/DOM/element.insertAdjacentHTML) позволяет вставлять произвольный HTML в любое место документа, в том числе *и между узлами*!
Он поддерживается всеми браузерами, кроме Firefox меньше версии 8, ну а там его можно эмулировать.
Синтаксис:
```js
elem.insertAdjacentHTML(where, html);
```
<dl>
<dt>`html`</dt>
<dd>Строка HTML, которую нужно вставить</dd>
<dt>`where`</dt>
<dd>Куда по отношению к `elem` вставлять строку. Всего четыре варианта:
<ol>
<li>`beforeBegin` -- перед `elem`.</li>
<li>`afterBegin` -- внутрь `elem`, в самое начало.</li>
<li>`beforeEnd` -- внутрь `elem`, в конец.</li>
<li>`afterEnd` -- после `elem`.</li>
</ol>
</dd>
<img src="insertAdjacentHTML.png">
Например, вставим пропущенные элементы списка *перед* `<li>5</li>`:
```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];
li5.insertAdjacentHTML("beforeBegin", "<li>3</li><li>4</li>");
</script>
```
Единственный недостаток этого метода -- он не работает в Firefox до версии 8. Но его можно легко добавить, используя следующий JavaScript: [insertAdjacentFF.js](/files/tutorial/browser/dom/insertAdjacentFF.js).
У этого метода есть "близнецы-братья", которые поддерживаются везде, кроме FF, но в него они добавляются этим же скриптом:
<ul>
<li>[elem.insertAdjacentElement(where, newElem)](http://help.dottoro.com/ljbreokf.php) -- вставляет в произвольное место не строку HTML, а элемент `newElem`.</li>
<li>[elem.insertAdjacentText(where, text)](http://help.dottoro.com/ljrsluxu.php) -- создаёт текстовый узел из строки `text` и вставляет его в указанное место относительно `elem`.</li>
</ul>
Синтаксис этих методов, за исключением последнего параметра, полностью совпадает с `insertAdjacentHTML`. Вместе они образуют "универсальный швейцарский нож" для вставки чего угодно куда угодно.
## DocumentFragment
[warn header="Важно для старых браузеров"]
Оптимизация, о которой здесь идёт речь, важна в первую очередь для старых браузеров, включая IE9-. В современных браузерах эффект от нее, как правило, не превышает 20%, а иногда может быть и отрицательным.
[/warn]
До этого мы говорили о вставке строки в DOM. А что делать в случае, когда надо в существующий `UL` вставить много *DOM-элементов*?
Можно вставлять их один за другим, вызовом `insertBefore/appendChild`, но при этом получится много операций с большим живым документом.
**Вставить пачку узлов единовременно поможет `DocumentFragment`. Это особенный *кросс-браузерный* DOM-объект, который похож на обычный DOM-узел, но им не является.**
Синтаксис для его создания:
```js
var fragment = document.createDocumentFragment();
```
В него можно добавлять другие узлы.
```js
fragment.appendChild(node);
```
Его можно клонировать:
```js
fragment.cloneNode(true); // клонирование с подэлементами
```
**У `DocumentFragment` нет обычных свойств DOM-узлов, таких как `innerHTML`, `tagName` и т.п. Это не узел.**
**"Фишка" заключается в том, что когда `DocumentFragment` вставляется в DOM -- то он исчезает, а вместо него вставляются его дети. Это свойство является уникальной особенностью `DocumentFragment`.**
Например, если добавить в него много `LI`, и потом `appendChild` к `UL`, то фрагмент растворится, и в DOM вставятся именно `LI`, причём в том же порядке, в котором были во фрагменте.
Псевдокод:
```js
// хотим вставить в список UL много LI
// делаем вспомогательный DocumentFragment
var fragment = document.createDocumentFragment();
for (цикл по li) {
fragment.appendChild(list[i]); // вставить каждый LI в DocumentFragment
}
ul.appendChild(fragment); // вместо фрагмента вставятся элементы списка
```
В современных браузерах эффект от такой оптимизации может быть различным. Чтобы понять текущее положение вещей, попробуйте в различных браузерах следующий небольшой бенчмарк.
При нажатии на кнопки ниже в список добавляются `100` элементов.
[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>
<li>**Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе.**
Конкретная разница зависит от внутренней реализации DOM в браузере.</li>
<li>**Семейство методов `elem.insertAdjacentHTML(where, html)`, `insertAdjacentElement`, `insertAdjacentText` позволяет вставлять HTML/элемент/текст в произвольное место документа.**
Метод `insertAdjacentHTML` не поддерживается в Firefox до версии 8, остальные два метода не поддерживаются в Firefox, на момент написания текста, вообще, но есть небольшой скрипт [insertAdjacentFF.js](/files/tutorial/browser/dom/insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox.
</li>
<li>**`DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше.**
Элементы сначала вставляются в него, а потом -- он вставляется в DOM. При вставке `DocumentFragment` "растворяется", и вместо него вставляются содержащиеся в нём узлы.
`DocumentFragment`, в отличие от `insertAdjacent*`, работает с коллекцией DOM-узлов.
</li>
</ul>
[head]
<script>
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;
}
/* 1. Вставляет TBODY в документ сразу. а затем элементы */
var appendFirst = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table')
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
// встаить TBODY и элементы
var tbody = document.createElement('TBODY');
benchTable.appendChild(tbody);
for(var i=0; i<20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j=0; j<20; j++) {
var td = document.createElement('td');
td.appendChild(document.createTextNode(''+i.toString(20)+j.toString(20)));
tr.appendChild(td);
}
}
}
}
/* 2. Полностью делает TBODY, а затем вставляет в документ */
var appendLast = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table');
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
var tbody = document.createElement('TBODY');
for(var i=0; i<20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j=0; j<20; j++) {
var td = document.createElement('td');
tr.appendChild(td);
td.appendChild(document.createTextNode(''+i.toString(20)+j.toString(20)));
}
}
benchTable.appendChild(tbody);
}
}
</script>
<style>
##bench-table td {
padding: 0;
}
##bench-list li {
display: inline-block;
margin: 0;
padding: 2px;
list-style-image: none;
list-style: none;
}
</style>
[/head]

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,60 @@
/* 1. Вставляет TBODY в документ сразу. а затем элементы */
var appendFirst = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table')
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
// встаить TBODY и элементы
var tbody = document.createElement('TBODY');
benchTable.appendChild(tbody);
for(var i=0; i<20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j=0; j<20; j++) {
var td = document.createElement('td');
td.appendChild(document.createTextNode(''+i.toString(20)+j.toString(20)));
tr.appendChild(td);
}
}
}
}
/* 2. Полностью делает TBODY, а затем вставляет в документ */
var appendLast = new function() {
var benchTable;
this.setup = function() {
// очистить всё
benchTable = document.getElementById('bench-table');
while(benchTable.firstChild) {
benchTable.removeChild(benchTable.firstChild);
}
}
this.work = function() {
var tbody = document.createElement('TBODY');
for(var i=0; i<20; i++) {
var tr = document.createElement('TR');
tbody.appendChild(tr);
for(var j=0; j<20; j++) {
var td = document.createElement('td');
tr.appendChild(td);
td.appendChild(document.createTextNode(''+i.toString(20)+j.toString(20)));
}
}
benchTable.appendChild(tbody);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB