renovations

This commit is contained in:
Ilya Kantor 2015-01-29 14:04:02 +03:00
parent 26008151b9
commit 3af3564921
15 changed files with 231 additions and 55 deletions

View file

@ -6,7 +6,7 @@
[cut]
## Метод contains
## Метод contains для проверки на вложенность
Синтаксис:
@ -16,7 +16,7 @@ var result = parent.contains(child);
Возвращает `true`, если `parent` содержит `child` или `parent == child`.
## Метод compareDocumentPosition
## Метод compareDocumentPosition для порядка узлов
Бывает, что у нас есть два элемента, к примеру, `<li>` в списке, и нужно понять, какой из них выше другого.

View file

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

View file

@ -1,23 +0,0 @@
# Удаление элементов
[importance 5]
Напишите функцию, которая удаляет элемент из DOM.
Синтаксис должен быть таким: `remove(elem)`, то есть, более короткий вариант, чем `parentNode.removeChild(elem)`.
```html
<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>
<script>
var elem = document.body.children[0];
function remove(elem) { /* ваш код */ }
*!*
remove(elem); // <-- функция должна удалить элемент
*/!*
</script>
```

View file

@ -0,0 +1,23 @@
Родителя `parentNode` можно получить из `elem`.
Вот так выглядит решение:
```html
//+ run
<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>
<script>
if (!Element.prototype.remove) {
Element.prototype.remove = function remove() {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
};
}
var elem = document.body.children[0];
elem.remove();
</script>
```

View file

@ -0,0 +1,28 @@
# Удаление элементов
[importance 5]
Напишите полифилл для метода `remove` для старых браузеров.
Вызов `elem.remove()`:
<ul>
<li>Если у `elem` нет родителя -- ничего не делает.</li>
<li>Если есть -- удаляет элемент из родителя.</li>
</ul>
```html
<div>Это</div>
<div>Все</div>
<div>Элементы DOM</div>
<script>
/* ваш код полифилла */
var elem = document.body.children[0];
*!*
elem.remove(); // <-- вызов должен удалить элемент
*/!*
</script>
```

View file

@ -270,6 +270,13 @@ div.parentNode.insertBefore( div2, div.nextSibling );
[/smart]
[smart header="Метод `remove`"]
В современном стандарте есть также метод [elem.remove()](https://dom.spec.whatwg.org/#dom-childnode-remove), который удаляет элемент напрямую, не требуя ссылки на родителя. Это зачастую удобнее, чем `removeChild`.
Он поддерживается во всех современных браузерах, кроме IE11-. Впрочем, легко подключить или даже сделать полифилл.
[/smart]
### Удаление сообщения
Сделаем так, что через секунду сообщение пропадёт:
@ -326,6 +333,7 @@ div.parentNode.insertBefore( div2, div.nextSibling );
В современных браузерах (кроме IE8-) в качестве альтернативы можно использовать присвоение `textContent`.
## Итого
Методы для создания узлов:

View file

@ -188,6 +188,53 @@ ul.appendChild(fragment); // вместо фрагмента вставятс
Понять текущее положение вещей вы можете, запустив следующий [edit src="benchmark"]небольшой бенчмарк[/edit].
## append/prepend, before/after, replaceWith
Сравнительно недавно в [стандарте](https://dom.spec.whatwg.org/) появились методы, которые позволяют вставить что угодно и куда угодно.
Синтаксис:
<ul>
<li>`node.append(...nodes)` -- вставляет `nodes` в конец `node`,</li>
<li>`node.prepend(...nodes)` -- вставляет `nodes` в начало `node`,</li>
<li>`node.after(...nodes)` -- вставляет `nodes` после узла `node`,</li>
<li>`node.before(...nodes)` -- вставляет `nodes` перед узлом `node`,</li>
<li>`node.replaceWith(...nodes)` -- вставляет `nodes` вместо `node`.</li>
</ul>
Эти методы ничего не возвращают.
Во всех этих методах `nodes` -- DOM-узлы или строки, в любом сочетании и количестве. Причём строки вставляются именно как текстовые узлы, в отличие от `insertAdjacentHTML`.
Пример (с полифиллом):
```html
<!--+ run autorun height=80 -->
<html>
<head>
<meta charset="utf-8">
<script src="//polyfill.webservices.ft.com/v1/polyfill.js?features=Element.prototype.mutation"></script>
</head>
<body>
<script>
// добавим элемент в конец <body>
var p = document.createElement('p');
document.body.append(p);
var em = document.createElement('em');
em.append('Мир!');
// вставить в параграф текстовый и обычный узлы
p.append("Привет, ", em);
// добавить элемент после <p>
p.after(document.createElement('hr'))
</script>
</body>
</html>
```
## Итого
@ -195,9 +242,14 @@ ul.appendChild(fragment); // вместо фрагмента вставятс
<li>Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе.
Конкретная разница зависит от внутренней реализации DOM в браузере.</li>
<li>Семейство методов `elem.insertAdjacentHTML(where, html)`, `insertAdjacentElement`, `insertAdjacentText` позволяет вставлять HTML/элемент/текст в произвольное место документа.
<li>Семейство методов для вставки HTML/элемента/текста в произвольное место документа:
<ul>
<li>`elem.insertAdjacentHTML(where, html)`</li>
<li>`elem.insertAdjacentElement(where, node)`</li>
<li>`elem.insertAdjacentText(where, text)`</li>
</ul>
Метод `insertAdjacentHTML` не поддерживается в Firefox до версии 8, остальные два метода не поддерживаются в Firefox, на момент написания текста, вообще, но есть небольшой скрипт [insertAdjacentFF.js](insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox.
Два последних метода не поддерживаются в Firefox, на момент написания текста, но есть небольшой полифилл [insertAdjacentFF.js](insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox.
</li>
<li>`DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше или наоборот отрицательный.
@ -205,6 +257,14 @@ ul.appendChild(fragment); // вместо фрагмента вставятс
`DocumentFragment`, в отличие от `insertAdjacent*`, работает с коллекцией DOM-узлов.
</li>
<li>Современные методы, работают с любым количеством узлов и текста, желателен полифилл:
<ul>
<li>`append/prepend` -- вставка в конец/начало.</li>
<li>`before/after` -- вставка после/перед.</li>
<li>`replaceWith` -- замена.</li>
</ul>
</li>
</li>
</ul>

View file

@ -1,4 +1,4 @@
# Поиск: getElement* и querySelector*
# Поиск: getElement* и querySelector* и не только
Прямая навигация от родителя к потомку удобна, если элементы рядом. А если нет?
@ -191,11 +191,11 @@ alert( articles.length ); // 2, найдёт оба элемента
## querySelectorAll [#querySelectorAll]
Вызов `elem.querySelectorAll(cssQuery)` возвращает все элементы внутри `elem`, удовлетворяющие CSS-селектору `cssQuery`.
Вызов `elem.querySelectorAll(css)` возвращает все элементы внутри `elem`, удовлетворяющие CSS-селектору `css`.
Это один из самых часто используемых и полезных методов при работе с DOM.
Он есть во всех современных браузерах, включая IE8+ (IE8 должен быть в режиме соответствия стандарту).
Он есть во всех современных браузерах, включая IE8+ (в режиме соответствия стандарту).
Следующий запрос получает все элементы `LI`, которые являются последними потомками своих `UL`:
@ -222,9 +222,9 @@ alert( articles.length ); // 2, найдёт оба элемента
## querySelector [#querySelector]
Вызов `elem.querySelector(cssQuery)` возвращает первый элемент, соответствующий CSS-селектору `cssQuery`.
Вызов `elem.querySelector(css)` возвращает первый элемент, соответствующий CSS-селектору `css`.
Иначе говоря, результат -- такой же, как и при `elem.querySelectorAll(cssQuery)[0]`, но в последнем вызове сначала ищутся все элементы, а потом берётся первый, а в `elem.querySelector(cssQuery)` ищется только первый, то есть он эффективнее.
Иначе говоря, результат -- такой же, как и при `elem.querySelectorAll(css)[0]`, но в последнем вызове сначала ищутся все элементы, а потом берётся первый, а в `elem.querySelector(css)` ищется только первый, то есть он эффективнее.
## matches
@ -258,6 +258,37 @@ alert( articles.length ); // 2, найдёт оба элемента
</script>
```
## closest
Метод `elem.closest(css)` ищет ближайшего предка, подходящего под CSS-селектор `css`.
Он самый новый из методов, рассмотренных в этой главе, поэтому не все браузеры его поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе [](/dom-polyfill).
Пример использования:
```html
<!--+ run -->
<ul>
<li class="chapter">Глава I
<ul>
<li class="subchapter">Глава <span class="num">1.1</span></li>
<li class="subchapter">Глава <span class="num">1.2</span></li>
</ul>
</li>
</ul>
<script>
var numberSpan = document.querySelector('.num');
// браузер должен поддерживать этот метод
alert( numberSpan.closest('li').className ) // subchapter
alert( numberSpan.closest('.chapter').tagName ) // LI
</script>
```
## XPath в современных браузерах
Для полноты картины рассмотрим ещё один способ поиска, который обычно используется в XML. Это <a href="http://www.w3.org/TR/xpath/">язык запросов XPath</a>.

View file

@ -0,0 +1,21 @@
Код для полифилла здесь особенно прост.
Реализовывать ничего не надо, просто записать нужный метод в `Element.prototype.matches`, если его там нет:
```js
(function() {
// проверяем поддержку
if (!Element.prototype.matches) {
// определяем свойство
Element.prototype.matches = Element.prototype.matchesSelector ||
Element.prototype.webkitMatchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector,
}
})();
```

View file

@ -0,0 +1,7 @@
# Полифилл для matches
[importance 5]
Метод `elem.matches(css)` в некоторых старых браузерах поддерживается под старым именем `matchesSelector` или с префиксами, то есть: `webkitMatchesSelector` (старый Chrome, Safari), `mozMatchesSelector` (старый Firefox) или `Element.prototype.msMatchesSelector` (старый IE).
Создайте полифилл, который гарантирует стандартный синтаксис `elem.matches(css)` для всех браузеров.

View file

@ -0,0 +1,24 @@
Код для этого полифилла имеет стандартный вид:
```js
(function() {
// проверяем поддержку
if (!Element.prototype.closest) {
// реализуем
Element.prototype.closest = function(css) {
var node = this;
while (node) {
if (node.matches(selector)) return node;
else node = node.parentElement;
}
return null;
};
}
})();
```
Обратим внимание, что код этого полифилла использует `node.matches`, то есть может в свою очередь потребовать полифилла для него. Это типичная ситуация -- один полифилл тянет за собой другой. Именно поэтому сервисы и библиотеки полифиллов очень полезны.

View file

@ -0,0 +1,7 @@
# Полифилл для closest
[importance 5]
Метод `elem.closest(css)` для поиска ближайшего родителя, удовлетворяющего селектору `css`, не поддерживается некоторыми браузерами, например IE11-.
Создайте для него полифилл.

View file

@ -1,9 +1,9 @@
# Полифилл для textContent
[importance 5]
[importance 3]
Свойство `textContent` не поддерживается IE8. Однако, там есть свойство `innerText`.
Создаёте полифилл для него, который использует `innerText`. При наличии полифилла в IE8 свойство `textContent` должно стать "псевдонимом" для `innerText`.
Создаёте полифилл, который проверяет поддержку свойства `textContent`, и если её нет -- создаёт его, используя `innerText`. Получится, что в IE8 "новое" свойство `textContent` будет "псевдонимом" для `innerText`.
Хотя свойство `innerText` и работает по-иному, нежели `textContent`, но в некоторых ситуациях они могут быть взаимозаменимы. Именно на них направлен полифилл.

View file

@ -56,7 +56,7 @@ if( document.documentElement.firstElementChild === undefined ) { // (1)
## Проверка встроенного свойства
Для проверки встроенной поддержки `firstElementChild` мы можем просто обратиться к `document.documentElement`.
Для проверки встроенной поддержки `firstElementChild` мы можем просто обратиться к `document.documentElement.firstElementChild`.
Если DOM-свойство `firstElementChild` поддерживается, то его значение не может быть `undefined`. Если детей нет -- свойство равно `null`, но не `undefined`.
@ -163,9 +163,17 @@ alert( document.body.lowerTag ); // body
В IE6,7 геттеры/сеттеры совсем не работают. Когда-то для них использовалась особая "IE-магия" при помощи `.htc`-файлов, которые [более не поддерживаются](http://msdn.microsoft.com/en-us/library/ie/hh801216.aspx). Если нужно поддерживать и эти версии, то рекомендуется воспользоваться фреймворками. К счастью, для большинства проектов эти браузеры уже стали историей.
[/warn]
## А есть ли поддержка?
А нужен ли вообще полифилл? Какие браузеры поддерживают свойство или метод?
Зачастую такая информация есть в справочнике MDN, например для метода `remove()`: [](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode.remove) -- табличка совместимости внизу.
Также бывает полезен сервис [](http://caniuse.com), например для `elem.matches(css)`: [](http://caniuse.com/#feat=matchesselector).
## Итого
Если вы поддерживаете устаревшие браузеры, нет смысла ограничивать себя в использовании современных возможностей.
Если вы поддерживаете устаревшие браузеры -- и здесь речь идёт не только про старые IE, другие браузеры тоже обновляются не у всех мгновенно -- не обязательно ограничивать себя в использовании современных возможностей.
Многие из них легко полифиллятся добавлением на страницу соответствующих библиотек.
@ -193,7 +201,6 @@ alert( document.body.lowerTag ); // body
При запросе сервис анализирует заголовки, понимает, какая версия какого браузера к нему обратилась и возвращает скрипт-полифилл, добавляющий в браузер возможности, которых там нет. В параметре `features` можно указать, какие именно возможности нужны, в примере выше это функции стандарта ES6. Подробнее -- см. [примеры](http://polyfill.webservices.ft.com/v1/docs/examples) и [список возможностей](http://polyfill.webservices.ft.com/v1/docs/features/).
Также есть и другие коллекции, как правило, полифиллы организованы в виде коллекции, из которой можно как выбрать отдельные свойства и функции, так и подключить всё вместе, пачкой.
Примеры полифиллов:
@ -213,3 +220,4 @@ alert( document.body.lowerTag ); // body
Конечно, можно собрать и свою библиотеку полифиллов самостоятельно из различных коллекций, которые перечислены выше, а при необходимости и написать самому. В этой части учебника мы изучим ещё много методов работы с DOM, которые в этом помогут.