renovations

This commit is contained in:
Ilya Kantor 2015-02-07 12:34:26 +03:00
parent 66e2f0919d
commit 25fc5d8650
19 changed files with 268 additions and 88 deletions

View file

@ -75,9 +75,29 @@ setTimeout(function() {
Теперь код работает, так как `user` достаётся из замыкания. Теперь код работает, так как `user` достаётся из замыкания.
Это решение также позволяет передать дополнительные аргументы:
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function(who) {
alert(this.firstName + ": Привет, " + who);
}
};
*!*
setTimeout(function() {
user.sayHi("Петя"); // Вася: Привет, Петя
}, 1000);
*/!*
```
Но тут же появляется и уязвимое место в структуре кода! Но тут же появляется и уязвимое место в структуре кода!
**А что, если до срабатывания `setTimeout` в переменную `user` будет записано другое значение? К примеру, какой-то другой пользователь... В этом случае вызов неожиданно будет совсем не тот!** А что, если до срабатывания `setTimeout` (ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот!
Хорошо бы гарантировать правильность контекста. Хорошо бы гарантировать правильность контекста.
@ -93,17 +113,26 @@ function bind(func, context) {
} }
``` ```
Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно: Посмотрим, что она делает, как работает, на таком примере:
```js
var oldSayHi = user.sayHi;
var sayHi = bind(oldSayHi, user);
```
Результатом `bind(oldSayHi, user)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно:
```js ```js
function() { // (*) function() { // (*)
return func.apply(context, arguments); return oldSayHi.apply(user, arguments);
}; };
``` ```
Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`). Она запишется в переменную `sayHi`.
Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`. Далее, если её вызвать с какими-то аргументами, например `sayHi("Петя")`, то она "передаёт вызов" в `oldSayHi` -- используется `.apply(user, arguments)`, чтобы передать в качестве контекста `user` (он будет взят из замыкания) и текущие аргументы `arguments`.
Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.
Пример с `bind`: Пример с `bind`:
@ -127,8 +156,32 @@ setTimeout( bind(user.sayHi, user), 1000 );
*/!* */!*
``` ```
Теперь всё в порядке! В `setTimeout` пошла обёртка, фиксирующая контекст. Теперь всё в порядке!
Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая гарантированно вызовет `user.sayHi` в контексте `user`. В данном случае, через 1000мс.
Причём, если вызвать обёртку с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст.
```js
//+ run
var user = {
firstName: "Вася",
sayHi: function(who) {
alert(this.firstName + ": Привет, " + who);
}
};
var sayHi = bind(user.sayHi, user);
*!*
sayHi("Петя"); // Вася: Привет, Петя
sayHi("Маша"); // Вася: Привет, Маша
*/!*
```
В примере выше продемонстрирована другая частая цель использования `bind` -- "привязать" функцию к контексту, чтобы в дальнейшем "не таскать за собой" объект, а просто вызывать `sayHi`.
Результат `bind` можно передавать в любое место кода, вызывать как обычную функцию, он "помнит" свой контекст.
## Решение 3: встроенный метод bind [#bind] ## Решение 3: встроенный метод bind [#bind]
@ -151,7 +204,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd> <dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
</dl> </dl>
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже. Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже.
Пример со встроенным методом `bind`: Пример со встроенным методом `bind`:
@ -166,13 +219,14 @@ var user = {
*!* *!*
// setTimeout( bind(user.sayHi, user), 1000 ); // setTimeout( bind(user.sayHi, user), 1000 );
setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод
*/!* */!*
``` ```
Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript. Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript.
Далее мы будем использовать именно встроенный метод `bind`.
[smart header="Привязать всё: `bindAll`"] [smart header="Привязать всё: `bindAll`"]
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле: Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
@ -236,8 +290,9 @@ alert( triple(5) ); // = mul(3, 5) = 15
При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё. При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.
Наш выигрыш в этом состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`.
## Функция дла задач ## Функция ask для задач
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`: В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
@ -251,7 +306,7 @@ function ask(question, answer, ok, fail) {
Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`. Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`.
В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно. Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно.
Пример использования: Пример использования:
@ -272,9 +327,24 @@ function die() {
## Итого ## Итого
Функции и контекст к JavaScript -- как шнурки и кроссовки. Если мы куда-то отправляем шнурки (например в `setTimeout`), то кроссовки сами за ними не побегут. <ul>
<li>Функция сама по себе не запоминает контекст выполнения.</li>
<li>Чтобы гарантировать правильный контекст для вызова `obj.func()`, нужно использовать функцию-обёртку, задать её через анонимную функцию:
```js
setTimeout(function() {
obj.func();
})
```
</li>
<li>...Либо использовать `bind`:
Нужно либо передать их дополнительно, либо привязать одно к другому вызовом `bind`, либо завернуть в замыкание. ```js
setTimeout( obj.func.bind(obj) );
```
</li>
<li>Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта.</li>
<li>Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз.</li>
</ul>
[head] [head]
<script> <script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Before After
Before After

View file

@ -0,0 +1,41 @@
В стандартном режиме IE8 можно получить текущую прокрутку так:
```js
//+ run
alert( document.documentElement.scrollTop );
```
Самым простым, но неверным было бы такое решение:
```js
//+ run
// "полифилл"
window.pageYOffset = document.documentElement.scrollTop;
// использование "полифилла"
alert( window.pageYOffset );
```
Код выше не учитывает текущую прокрутку. Он присваивает `window.pageYOffset` один раз и в дальнейшем, чтобы получить текущую прокрутку, нужно снова обратиться к `document.documentElement.scrollTop` не меняет его. А задача как раз -- сделать полифилл, то есть дать возможность использовать `window.pageYOffset` для получения текущего состояния прокрутки без "танцев бубном", так же как в современных браузерах.
Для этого создадим свойство через геттер.
В IE8 для DOM-объектов работает `Object.defineProperty`:
```js
//+ run
// полифилл
Object.defineProperty(window, 'pageYOffset', {
get: function() {
return document.documentElement.scrollTop;
}
});
// использование полифилла
alert( window.pageYOffset );
```

View file

@ -0,0 +1,12 @@
# Полифилл для pageYOffset в IE8
[importance 3]
Обычно в IE8 не поддерживается свойство `pageYOffset`. Напишите полифилл для него.
При подключённом полифилле такой код должен работать в IE8:
```js
// текущая прокрутка страницы в IE8
alert( window.pageYOffset );
```

View file

@ -1,35 +1,50 @@
# Итого # Итого
В этой главе кратко перечислены основные свойства и методы DOM, которые мы изучили. В этой главе кратко перечислены основные свойства и методы DOM, которые мы изучили. Их уже довольно много.
Используйте её, чтобы получить быстрый итоговый обзор того, что изучали ранее. Используйте её, чтобы по-быстрому вспомнить и прокрутить в голове то, что изучали ранее. Все ли эти свойства вам знакомы?
Кое-где стоит ограничение на версии IE, но на все свойства можно найти или сделать или найти полифилл, с которым их можно использовать везде.
[cut] [cut]
## Создание ## Создание
<dl> <dl>
<dt>`document.createElement(tag)`</dt><dd>создать элемент с тегом `tag`</dd> <dt>`document.createElement(tag)`</dt><dd>Создать элемент с тегом `tag`</dd>
<dt>`document.createTextNode(txt)`</dt><dd>создать текстовый узел с текстом `txt`</dd> <dt>`document.createTextNode(txt)`</dt><dd>Создать текстовый узел с текстом `txt`</dd>
<dt>`node.cloneNode(deep)`</dt><dd>клонировать существующий узел, если `deep=false`, то без потомков.</dd> <dt>`node.cloneNode(deep)`</dt><dd>Клонировать существующий узел, если `deep=false`, то без потомков.</dd>
</dl> </dl>
## Свойства узлов ## Свойства узлов
<dl> <dl>
<dt>`node.nodeType`</dt><dd>тип узла: 1(элемент) / 3(текст) / другие.</dd> <dt>`node.nodeType`</dt><dd>Тип узла: 1(элемент) / 3(текст) / другие.</dd>
<dt>`elem.tagName`</dt><dd>тег элемента.</dd> <dt>`elem.tagName`</dt><dd>Тег элемента.</dd>
<dt>`elem.innerHTML`</dt><dd>HTML внутри элемента.</dd> <dt>`elem.innerHTML`</dt><dd>HTML внутри элемента.</dd>
<dt>`node.data`</dt><dd>содержимое любого узла любого типа, кроме элемента.</dd> <dt>`elem.outerHTML`</dt><dd>Весь HTML элемента, включая сам тег. На запись использовать с осторожностью, так как не модифицирует элемент, а вставляет новый вместо него.</dd>
<dt>`node.data` / `node.nodeValue`</dt><dd>Содержимое узла любого типа, кроме элемента.</dd>
<dt>`node.textContent`</dt><dd>Текстовое содержимое узла, для элементов содержит текст с вырезанными тегами (IE9+).</dd>
<dt>`elem.hidden`</dt><dd>Если поставить `true`, то элемент будет скрыт (IE10+).</dd>
</dl>
## Атрибуты
<dl>
<dt>`elem.getAttribute(name)`, `elem.hasAttribute(name)`, `elem.setAttribute(name, value)`</dt>
<dd>Чтение атрибута, проверка наличия и запись.</dd>
<dt>`elem.dataset.*`</dt><dd>Значения атрибутов вида `data-*` (IE10+).</dd>
</dl> </dl>
## Ссылки ## Ссылки
<dl> <dl>
<dt>`document.documentElement`</dt> <dt>`document.documentElement`</dt>
<dd>элемент `<HTML>`</dd> <dd>Элемент `<HTML>`</dd>
<dt>`document.body`</dt> <dt>`document.body`</dt>
<dd>элемент `<BODY>`</dd> <dd>Элемент `<BODY>`</dd>
<dt>`document.head`</dt>
<dd>Элемент `<HEAD>` (IE9+)</dd>
</dl> </dl>
По всем узлам: По всем узлам:
@ -42,14 +57,15 @@
Только по элементам: Только по элементам:
<ul> <ul>
<li>`children`</li> <li>`parentElement`</li>
<li>`nextElementSibling` `previousElementSibling`</li> <li>`nextElementSibling` `previousElementSibling`</li>
<li>`firstElementChild` `lastElementChild`</li> <li>`children`, `firstElementChild` `lastElementChild`</li>
</ul> </ul>
В IE8- из них работает только `children`, причём содержит не только элементы, но и комментарии (ошибка в браузере). Все они IE9+, кроме `children`, который работает в IE8-, но содержит не только элементы, но и комментарии (ошибка в браузере).
### Таблицы Дополнительно у некоторых типов элементов могут быть и другие ссылки, свойства, коллекции для навигации,
например для таблиц:
<dl> <dl>
<dt>`table.rows[N]`</dt> <dt>`table.rows[N]`</dt>
@ -62,17 +78,6 @@
<dd>номер ячейки в строке.</dd> <dd>номер ячейки в строке.</dd>
</dl> </dl>
### Формы
<dl>
<dt>`document.forms[N/name]`</dt>
<dd>форма по номеру/имени.</dd>
<dt>`form.elements[N/name]`</dt>
<dd>элемент формы по номеру/имени</dd>
<dt>`element.form`</dt>
<dd>форма для элемента.</dd>
</dl>
## Поиск ## Поиск
@ -84,16 +89,28 @@
<dt>`document.getElementById(id)`</dt> <dt>`document.getElementById(id)`</dt>
<dd>По уникальному `id`</dd> <dd>По уникальному `id`</dd>
<dt>`document.getElementsByName(name)`</dt> <dt>`document.getElementsByName(name)`</dt>
<dd>По атрибуту `name`, в IE<10 работает только для элементов, где `name` предусмотрен стандартом.</dd> <dd>По атрибуту `name`, в IE9- работает только для элементов, где `name` предусмотрен стандартом.</dd>
<dt>`*.getElementsByTagName(tag)`</dt> <dt>`*.getElementsByTagName(tag)`</dt>
<dd>По тегу `tag`</dd> <dd>По тегу `tag`</dd>
<dt>`*.getElementsByClassName(class)`</dt> <dt>`*.getElementsByClassName(class)`</dt>
<dd>По классу, IE9+, корректно работает с элементами, у которых несколько классов.</dd> <dd>По классу, IE9+, корректно работает с элементами, у которых несколько классов.</dd>
</dl> </dl>
При поддержки IE только версии 8 и выше, можно использовать только `querySelector/querySelectorAll`. Если не нужно поддерживать IE7-, то можно использовать только `querySelector/querySelectorAll`. Методы `getElement*` работают быстрее (за счёт более оптимальной внутренней реализации), но в 99% случаев это различие очень небольшое и роли не играет.
Дополнительно есть методы:
<dl>
<dt>`elem.matches(css)`</dt>
<dd>Проверяет, подходит ли элемент под CSS-селектор.</dd.
<dt>`elem.closest(css)`</dt>
<dd>Ищет ближайший элемент сверху по иерархии DOM, подходящий под CSS-селектор. Первым проверяется сам `elem`. Этот элемент возвращается.</dd>
<dt>`elemA.contains(elemB)`</dt>
<dd>Возвращает `true`, если `elemA` является предком (содержит) `elemB`.</dd>
<dt>`elemA.compareDocumentPosition(elemB)`</dt>
<dd>Возвращает битовую маску, которая включает в себя отношение вложенности между `elemA` и `elemB`, а также -- какой из элементов появляется в DOM первым.</dd>
</dl>
Для более старых IE нужен либо фреймворк, который сам умеет искать узлы по селектору, наподобие jQuery, либо пользоваться методами `get*`, все из которых, кроме `...ByClassName`, поддерживаются с древних времён.
## Изменение ## Изменение
@ -102,6 +119,19 @@
<li>`parent.removeChild(child)`</li> <li>`parent.removeChild(child)`</li>
<li>`parent.insertBefore(newChild, refNode)`</li> <li>`parent.insertBefore(newChild, refNode)`</li>
<li>`parent.insertAdjacentHTML("beforeBegin|afterBegin|beforeEnd|afterEnd", html)`</li> <li>`parent.insertAdjacentHTML("beforeBegin|afterBegin|beforeEnd|afterEnd", html)`</li>
<li>`parent.insertAdjacentElement("beforeBegin|...|afterEnd", text)` (кроме FF)</li>
<li>`parent.insertAdjacentText("beforeBegin|...|afterEnd", text)` (кроме FF)</li>
<li>`document.write(...)`</li>
</ul>
Скорее всего, понадобятся полифиллы для:
<ul>
<li>`node.append(...nodes)`</li>
<li>`node.prepend(...nodes)`</li>
<li>`node.after(...nodes)`,</li>
<li>`node.before(...nodes)`</li>
<li>`node.replaceWith(...nodes)`</li>
</ul> </ul>
## Классы и стили ## Классы и стили
@ -110,7 +140,7 @@
<dt>`elem.className`</dt> <dt>`elem.className`</dt>
<dd>Атрибут `class`</dt> <dd>Атрибут `class`</dt>
<dt>`elem.classList.add(class) remove(class) toggle(class) contains(class)`</dt> <dt>`elem.classList.add(class) remove(class) toggle(class) contains(class)`</dt>
<dd>Управление классами в HTML5, для IE8+ есть [эмуляция](https://github.com/eligrey/classList.js/blob/master/classList.js).</dd> <dd>Управление классами, для IE9- есть [эмуляция](https://github.com/eligrey/classList.js/blob/master/classList.js).</dd>
<dt>`elem.style`</dt> <dt>`elem.style`</dt>
<dd>Стили в атрибуте `style` элемента</dd> <dd>Стили в атрибуте `style` элемента</dd>
<dt>`getComputedStyle(elem, "")`</dd> <dt>`getComputedStyle(elem, "")`</dd>

View file

@ -23,7 +23,7 @@ DOM позволяет делать что угодно с HTML-элементо
<dd>Вторая точка входа -- `document.body`, который соответствует тегу `<body>`.</dd> <dd>Вторая точка входа -- `document.body`, который соответствует тегу `<body>`.</dd>
</dl> </dl>
В современных браузерах (кроме старых IE) также действует `document.head` -- прямой доступ к `<head>` В современных браузерах (кроме IE8-) также есть `document.head` -- прямая ссылка на `<head>`
[warn header="Есть одна тонкость: `document.body` может быть равен `null`"] [warn header="Есть одна тонкость: `document.body` может быть равен `null`"]
Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта. Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта.

View file

@ -6,9 +6,11 @@
<defs></defs> <defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> <g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="dom-links-elements.svg" sketch:type="MSArtboardGroup"> <g id="dom-links-elements.svg" sketch:type="MSArtboardGroup">
<rect id="Rectangle-8" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="131" y="5" width="194" height="24"></rect>
<text id="document.documentEle" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D"> <text id="document.documentEle" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="137.692383" y="22">document.documentElement &lt;HTML&gt;</tspan> <tspan x="137.692383" y="22">document.documentElement &lt;HTML&gt;</tspan>
</text> </text>
<rect id="Rectangle-7" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="165" y="73" width="113" height="24"></rect>
<text id="document.body-(если-" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D"> <text id="document.body-(если-" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="172.84375" y="90">document.body (если внутри body)</tspan> <tspan x="172.84375" y="90">document.body (если внутри body)</tspan>
</text> </text>
@ -21,8 +23,6 @@
<tspan x="221.183594" y="165" fill="#EE6B47">Element</tspan> <tspan x="221.183594" y="165" fill="#EE6B47">Element</tspan>
</text> </text>
<rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="183" y="208" width="75" height="24"></rect> <rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="183" y="208" width="75" height="24"></rect>
<rect id="Rectangle-7" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="165" y="73" width="113" height="24"></rect>
<rect id="Rectangle-8" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="131" y="5" 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"> <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="201.051859" y="225">&lt;DIV&gt;</tspan> <tspan x="201.051859" y="225">&lt;DIV&gt;</tspan>
</text> </text>

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Before After
Before After

View file

@ -6,6 +6,9 @@
<defs></defs> <defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> <g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="dom-links.svg" sketch:type="MSArtboardGroup"> <g id="dom-links.svg" sketch:type="MSArtboardGroup">
<rect id="Rectangle-9" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="153" y="2" width="113" height="24"></rect>
<rect id="Rectangle-7" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="153" y="136" width="113" height="24"></rect>
<rect id="Rectangle-8" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="119" y="68" width="194" height="24"></rect>
<text id="document" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D"> <text id="document" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" fill="#8A704D">
<tspan x="177.710938" y="18">document</tspan> <tspan x="177.710938" y="18">document</tspan>
</text> </text>
@ -25,9 +28,6 @@
<tspan x="172" y="228">parentNode</tspan> <tspan x="172" y="228">parentNode</tspan>
</text> </text>
<rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="171" y="271" width="75" height="24"></rect> <rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="171" y="271" width="75" height="24"></rect>
<rect id="Rectangle-7" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="153" y="136" width="113" height="24"></rect>
<rect id="Rectangle-9" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="153" y="2" width="113" height="24"></rect>
<rect id="Rectangle-8" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="119" 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"> <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="189.051859" y="288">&lt;DIV&gt;</tspan> <tspan x="189.051859" y="288">&lt;DIV&gt;</tspan>
</text> </text>

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before After
Before After

View file

@ -260,12 +260,13 @@ alert( articles.length ); // 2, найдёт оба элемента
## closest ## closest
Метод `elem.closest(css)` ищет ближайшего предка, подходящего под CSS-селектор `css`. Метод `elem.closest(css)` ищет ближайший элемент выше по иерархии DOM, подходящий под CSS-селектор `css`. Сам элемент тоже включается в поиск.
Иначе говоря, метод `closest` бежит от текущего элемента вверх по цепочке родителей и проверяет, подходит ли каждый элемент под CSS-селектор. Если подходит -- останавливается и возвращает его.
Он самый новый из методов, рассмотренных в этой главе, поэтому не все браузеры его поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе [](/dom-polyfill). Он самый новый из методов, рассмотренных в этой главе, поэтому не все браузеры его поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе [](/dom-polyfill).
Пример использования: Пример использования (браузер должен поддерживать `closest`):
```html ```html
<!--+ run --> <!--+ run -->
@ -282,10 +283,15 @@ alert( articles.length ); // 2, найдёт оба элемента
<script> <script>
var numberSpan = document.querySelector('.num'); var numberSpan = document.querySelector('.num');
// браузер должен поддерживать этот метод // ближайший элемент сверху подходящий под селектор li
alert( numberSpan.closest('li').className ) // subchapter alert( numberSpan.closest('li').className ) // subchapter
// ближайший элемент сверху подходящий под селектор .chapter
alert( numberSpan.closest('.chapter').tagName ) // LI alert( numberSpan.closest('.chapter').tagName ) // LI
// ближайший элемент сверху, подходящий под селектор span
// это сам numberSpan, так как поиск включает в себя сам элемент
alert( numberSpan.closest('span') === numberSpan ) // true
</script> </script>
``` ```

View file

@ -372,7 +372,7 @@ div.classList.add('order-state-canceled');
Проще говоря, значение атрибута -- произвольная строка, значение класса -- это "есть" или "нет", поэтому естественно, что атрибуты "мощнее" и бывают удобнее классов как в JS так и в CSS. Проще говоря, значение атрибута -- произвольная строка, значение класса -- это "есть" или "нет", поэтому естественно, что атрибуты "мощнее" и бывают удобнее классов как в JS так и в CSS.
## Свойство dataSet, data-атрибуты ## Свойство dataset, data-атрибуты
С помощью нестандартных атрибутов можно привязать к элементу данные, которые будут доступны в JavaScript. С помощью нестандартных атрибутов можно привязать к элементу данные, которые будут доступны в JavaScript.

View file

@ -1,15 +1,15 @@
# Порядок обработки событий # Порядок обработки событий
События могут возникать не только по очереди, но и пачкой, по многу сразу. Возможно и такое, что во время обработки одного события возникают другие. События могут возникать не только по очереди, но и "пачкой" по много сразу. Возможно и такое, что во время обработки одного события возникают другие, например пока выполнялся код для `onclick` -- посетитель провёл мышкой, а это уже `mousemove`.
Здесь и далее, очень важно понимать, как браузер обычно работает с событиями и важные исключения из этого правила. Это мы и разберём в этой главе. Здесь мы разберём, как браузер обычно работает с одновременно возникающими событиями и какие есть исключения из общего правила.
[cut] [cut]
## Главный поток ## Главный поток
В каждом окне выполняется только один *главный* поток, который занимается выполнением JavaScript, отрисовкой и работой с DOM. В каждом окне выполняется только один *главный* поток, который занимается выполнением JavaScript, отрисовкой и работой с DOM.
Он выполняет команды последовательно и блокируется при выводе модальных окон, таких как `alert`. Он выполняет команды последовательно, может делать только одно дело одновременно и блокируется при выводе модальных окон, таких как `alert`.
[smart header="Дополнительные потоки тоже есть"] [smart header="Дополнительные потоки тоже есть"]
@ -21,9 +21,9 @@
[smart header="Web Workers"] [smart header="Web Workers"]
Существует спецификация <a href="http://www.w3.org/TR/workers/">Web Workers</a>, которая позволяет запускать дополнительные JavaScript-процессы(workers). Существует спецификация <a href="http://www.w3.org/TR/workers/">Web Workers</a>, которая позволяет запускать дополнительные JavaScript-процессы(workers).
Они могут обмениваться сообщениями с главным процессом, но их переменные полностью независимы. Они могут обмениваться сообщениями с главным процессом, но у них свои переменные, и работают они также сами по себе.
В частности, дополнительные процессы не имеют доступа к DOM, поэтому они полезны, преимущественно, при вычислениях, чтобы загрузить несколько ядер/процессоров одновременно. Такие дополнительные процессы не имеют доступа к DOM, поэтому они полезны, преимущественно, при вычислениях, чтобы загрузить несколько ядер/процессоров одновременно.
[/smart] [/smart]
## Очередь событий ## Очередь событий
@ -36,18 +36,17 @@
**Когда происходит событие, оно попадает в очередь.** **Когда происходит событие, оно попадает в очередь.**
Внутри браузера существует главный внутренний цикл, который проверяет очередь и обрабатывает события, запускает соответствующие обработчики и т.п. Внутри браузера непрерывно работает "главный внутренний цикл", который следит за состоянием очереди и обрабатывает события, запускает соответствующие обработчики и т.п.
**Иногда события добавляются в очередь сразу пачкой.** **Иногда события добавляются в очередь сразу пачкой.**
Например, при клике на элементе генерируется несколько событий: Например, при клике на элементе генерируется несколько событий:
<ol> <ol>
<li>Сначала `mousedown` -- нажата кнопка мыши.</li> <li>Сначала `mousedown` -- нажата кнопка мыши.</li>
<li>Затем `mouseup` -- кнопка мыши отпущена.</li> <li>Затем `mouseup` -- кнопка мыши отпущена и, так как это было над одним элементом, то дополнительно генерируется `click` (два события сразу).</li>
<li>Так как это было над одним элементом, то дополнительно генерируется `click`</li>
</ol> </ol>
[online]
В действии: В действии:
```html ```html
@ -61,14 +60,17 @@
area.onclick = function(e) { this.value += "click\n"; this.scrollTop = 1e9; }; area.onclick = function(e) { this.value += "click\n"; this.scrollTop = 1e9; };
</script> </script>
``` ```
[/online]
Таким образом, при нажатии кнопки мыши в очередь попадёт событие `mousedown`, а при отпускании -- сразу два события: `mouseup` и `click`. Браузер сначала обработает первое, а потом -- второе. Таким образом, при нажатии кнопки мыши в очередь попадёт событие `mousedown`, а при отпускании -- сразу два события: `mouseup` и `click`. Браузер обработает их строго одно за другим: `mousedown` -> `mouseup` -> `click`.
**При этом каждое событие из очереди обрабатывается полностью отдельно от других.** При этом каждое событие из очереди обрабатывается полностью отдельно от других.
## Вложенные (синхронные) события ## Вложенные (синхронные) события
В тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас. Обычно возникающие события "становятся в очередь".
Но в тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас.
Рассмотрим в качестве примера событие `onfocus`. Рассмотрим в качестве примера событие `onfocus`.
@ -96,9 +98,9 @@
</script> </script>
``` ```
В главе [](/focus-blur) мы познакомимся с этим событием подробнее, а пока -- нажмите на кнопку в примере ниже. При этом обработчик `onclick` вызовет метод `focus()` на текстовом поле `text`. В главе [](/focus-blur) мы познакомимся с этим событием подробнее, а пока -- нажмите на кнопку в примере ниже.
**Событие `onfocus`, инициированное вызовом `text.focus()`, будет обработано синхронно, прямо сейчас, до завершения `onclick`.** При этом обработчик `onclick` вызовет метод `focus()` на текстовом поле `text`. Код обработчика `onfocus`, который при этом запустится, сработает синхронно, прямо сейчас, до завершения `onclick`.
```html ```html
<!--+ autorun --> <!--+ autorun -->
@ -123,10 +125,11 @@
При клике на кнопке в примере выше будет видно, что управление вошло в `onclick`, затем перешло в `onfocus`, затем вышло из `onclick`. При клике на кнопке в примере выше будет видно, что управление вошло в `onclick`, затем перешло в `onfocus`, затем вышло из `onclick`.
**Так ведут себя все браузеры, кроме IE.** [warn header="Исключение в IE"]
Так ведут себя все браузеры, кроме IE.
В нём событие `onfocus` -- всегда асинхронное, так что будет сначала полностью обработан клик, а потом -- фокус. В остальных -- фокус вызовется посередине клика. Попробуйте кликнуть в IE и в другом браузере, чтобы увидеть разницу. В нём событие `onfocus` -- всегда асинхронное, так что будет сначала полностью обработан клик, а потом -- фокус. В остальных -- фокус вызовется посередине клика. Попробуйте кликнуть в IE и в другом браузере, чтобы увидеть разницу.
[/warn]
## Делаем события асинхронными через setTimeout(...,0) ## Делаем события асинхронными через setTimeout(...,0)
@ -134,7 +137,7 @@
Можно добиться и этого. Можно добиться и этого.
Один вариант -- просто переместить строку `text.focus()` вниз кода обработчика. Один вариант -- просто переместить строку `text.focus()` вниз кода обработчика `onclick`.
Если это неудобно, можно запланировать `text.focus()` чуть позже через `setTimeout(..., 0)`, вот так Если это неудобно, можно запланировать `text.focus()` чуть позже через `setTimeout(..., 0)`, вот так
@ -173,3 +176,6 @@
<li>Синхронными являются вложенные события, инициированные из кода.</li> <li>Синхронными являются вложенные события, инициированные из кода.</li>
<li>Чтобы сделать событие гарантированно асинхронным, используется вызов через `setTimeout(func, 0)`.</li> <li>Чтобы сделать событие гарантированно асинхронным, используется вызов через `setTimeout(func, 0)`.</li>
</ul> </ul>
Отложенный вызов через `setTimeout(func, 0)` используется не только в событиях, а вообще -- всегда, когда мы хотим, чтобы некая функция `func` сработала после того, как текущий скрипт завершится.

View file

@ -11,7 +11,7 @@
<li>Мяч после перелёта должен становиться центром ровно под курсор мыши, если это возможно без вылета за край поля.</li> <li>Мяч после перелёта должен становиться центром ровно под курсор мыши, если это возможно без вылета за край поля.</li>
<li>CSS-анимация не обязательна, но желательна.</li> <li>CSS-анимация не обязательна, но желательна.</li>
<li>Мяч должен останавливаться у границ поля, ни в коем случае не вылетать за них.</li> <li>Мяч должен останавливаться у границ поля, ни в коем случае не вылетать за них.</li>
<li>При прокрутке страницы ничего не должно ломаться.</li> <li>При прокрутке страницы с полем ничего не должно ломаться.</li>
</ul> </ul>
Замечания: Замечания:

View file

@ -2,12 +2,12 @@
Чтобы хорошо обработать событие, недостаточно знать о том, что это -- "клик" или "нажатие клавиши". Могут понадобиться детали: координаты курсора, введённый символ и другие, в зависимости от события. Чтобы хорошо обработать событие, недостаточно знать о том, что это -- "клик" или "нажатие клавиши". Могут понадобиться детали: координаты курсора, введённый символ и другие, в зависимости от события.
**Детали произошедшего браузер записывает в "объект события", который передаётся первым аргументом в обработчик.** Детали произошедшего браузер записывает в "объект события", который передаётся первым аргументом в обработчик.
[cut] [cut]
## Получение объекта события ## Свойства объекта события
Пример ниже демонстрирует использования объекта события: Пример ниже демонстрирует использование объекта события:
```html ```html
<!--+ run --> <!--+ run -->
@ -15,6 +15,7 @@
<script> <script>
elem.onclick = function(*!*event*/!*) { elem.onclick = function(*!*event*/!*) {
// вывести тип события, элемент и координаты клика
alert(event.type + " на " + event.currentTarget); alert(event.type + " на " + event.currentTarget);
alert(event.clientX + ":" + event.clientY); alert(event.clientX + ":" + event.clientY);
} }
@ -26,7 +27,7 @@
<dt>`event.type`</dt> <dt>`event.type`</dt>
<dd>Тип события, в данном случае `click`</dd> <dd>Тип события, в данном случае `click`</dd>
<dt>`event.currentTarget`</dt> <dt>`event.currentTarget`</dt>
<dd>Элемент, на котором сработал обработчик -- то же, что и `this`, но бывают ситуации, когда обработчик является методом объекта и его `this` при помощи `bind` привязан к объекту, тогда `event.currentTarget` полезен.</dd> <dd>Элемент, на котором сработал обработчик. Значение -- в точности такое же, как и у `this`, но бывают ситуации, когда обработчик является методом объекта и его `this` при помощи `bind` привязан к этому объекту, тогда мы можем использовать `event.currentTarget`.</dd>
<dt>`event.clientX / event.clientY`</dt> <dt>`event.clientX / event.clientY`</dt>
<dd>Координаты курсора в момент клика (относительно окна)</dd> <dd>Координаты курсора в момент клика (относительно окна)</dd>
</dl> </dl>
@ -37,7 +38,7 @@
При назначении обработчика в HTML, тоже можно использовать переменную `event`, это будет работать кросс-браузерно: При назначении обработчика в HTML, тоже можно использовать переменную `event`, это будет работать кросс-браузерно:
```html ```html
<!--+ autorun height=auto --> <!--+ autorun height=60 -->
<input type="button" onclick="*!*alert(event.type)*/!*" value="Тип события"> <input type="button" onclick="*!*alert(event.type)*/!*" value="Тип события">
``` ```

View file

@ -5,7 +5,7 @@
Этот обработчик для `<div>` сработает, если вы кликните по вложенному тегу `<em>` или `<code>`: Этот обработчик для `<div>` сработает, если вы кликните по вложенному тегу `<em>` или `<code>`:
```html ```html
<!--+ autorun height=auto --> <!--+ autorun height=60 -->
<div onclick="alert('Обработчик для Div сработал!')"> <div onclick="alert('Обработчик для Div сработал!')">
<em>Кликните на <code>EM</code>, сработает обработчик на <code>DIV</code></em> <em>Кликните на <code>EM</code>, сработает обработчик на <code>DIV</code></em>
</div> </div>
@ -22,7 +22,11 @@
Например, есть 3 вложенных элемента `FORM > DIV > P`, с обработчиком на каждом: Например, есть 3 вложенных элемента `FORM > DIV > P`, с обработчиком на каждом:
```html ```html
<!--+ run --> <!--+ run autorun -->
<style>
body * { margin: 10px; border: 1px solid blue; }
</style>
<form onclick="alert('form')">FORM <form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV <div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p> <p onclick="alert('p')">P</p>
@ -30,10 +34,12 @@
</form> </form>
``` ```
Всплытие гарантирует, что клик по внутреннему `P` вызовет обработчик `onclick` (если есть) сначала на самом `P`, затем на элементе `DIV` далее на элементе `FORM`, и так далее вверх по цепочке родителей до самого `document`. Всплытие гарантирует, что клик по внутреннему `<p>` вызовет обработчик `onclick` (если есть) сначала на самом `<p>`, затем на элементе `<div>` далее на элементе `<form>`, и так далее вверх по цепочке родителей до самого `document`.
<img src="event-order-bubbling.png" alt="Порядок всплытия событий"> <img src="event-order-bubbling.png" alt="Порядок всплытия событий">
Поэтому если в примере выше кликнуть на `P`, то последовательно выведутся `alert`: `p` -> `div` -> `form`.
Этот процесс называется *всплытием*, потому что события "всплывают" от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырек воздуха в воде. Этот процесс называется *всплытием*, потому что события "всплывают" от внутреннего элемента вверх через родителей, подобно тому, как всплывает пузырек воздуха в воде.
[warn header="Всплывают *почти* все события."] [warn header="Всплывают *почти* все события."]
@ -54,14 +60,20 @@
<li>`this` -- это **текущий элемент**, до которого дошло всплытие, на нём сейчас выполняется обработчик.</li> <li>`this` -- это **текущий элемент**, до которого дошло всплытие, на нём сейчас выполняется обработчик.</li>
</ul> </ul>
Например, если стоит только один обработчик `form.onclick`, то он "поймает" все клики внутри него. Где бы ни был клик внутри -- он всплывёт до элемента `<form>`, на котором сработает обработчик. Например, если стоит только один обработчик `form.onclick`, то он "поймает" все клики внутри формы. Где бы ни был клик внутри -- он всплывёт до элемента `<form>`, на котором сработает обработчик.
При этом:
<ul> <ul>
<li>`event.target` будет содержать элемент, на котором произошёл клик.</li>
<li>`this` (`=event.currentTarget`) всегда будет сама форма, так как обработчик сработал на ней.</li> <li>`this` (`=event.currentTarget`) всегда будет сама форма, так как обработчик сработал на ней.</li>
<li>`event.target` будет содержать ссылку на конкретный элемент внутри формы, самый вложенный, на котором произошёл клик.</li>
</ul> </ul>
[online]
[example height=220 src="bubble-target"] [example height=220 src="bubble-target"]
[/online]
Возможна и ситуация, когда `event.target` и `this` -- один и тот же элемент, например если в форме нет других тегов и клик был на самом элементе `<form>`.
## Прекращение всплытия ## Прекращение всплытия

View file

@ -3,6 +3,8 @@
<body> <body>
<link type="text/css" rel="stylesheet" href="example.css"> <link type="text/css" rel="stylesheet" href="example.css">
Клик выведет <code>target</code> и <code>this</code>:
<form id="form">FORM <form id="form">FORM
<div>DIV <div>DIV
<p>P</p> <p>P</p>

Binary file not shown.