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

@ -73,11 +73,31 @@ 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=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот!
Хорошо бы гарантировать правильность контекста.
@ -87,23 +107,32 @@ setTimeout(function() {
```js
function bind(func, context) {
return function() { // (*)
return function() { // (*)
return func.apply(context, arguments);
};
}
```
Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно:
Посмотрим, что она делает, как работает, на таком примере:
```js
var oldSayHi = user.sayHi;
var sayHi = bind(oldSayHi, user);
```
Результатом `bind(oldSayHi, user)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно:
```js
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`:
@ -117,7 +146,7 @@ function bind(func, context) {
var user = {
firstName: "Вася",
sayHi: function() {
sayHi: function() {
alert(this.firstName);
}
};
@ -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]
@ -151,7 +204,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
<dd>Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.</dd>
</dl>
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже.
Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже.
Пример со встроенным методом `bind`:
@ -166,13 +219,14 @@ var user = {
*!*
// setTimeout( bind(user.sayHi, user), 1000 );
setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод
*/!*
```
Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript.
Далее мы будем использовать именно встроенный метод `bind`.
[smart header="Привязать всё: `bindAll`"]
Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:
@ -236,8 +290,9 @@ alert( triple(5) ); // = mul(3, 5) = 15
При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.
Наш выигрыш в этом состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`.
## Функция дла задач
## Функция ask для задач
В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`:
@ -251,7 +306,7 @@ function ask(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]
<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

@ -107,9 +107,9 @@ function getOffsetSum(elem) {
var top = 0, left = 0;
while(elem) {
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
}
return {top: top, left: left};

View file

@ -1,35 +1,50 @@
# Итого
В этой главе кратко перечислены основные свойства и методы DOM, которые мы изучили.
В этой главе кратко перечислены основные свойства и методы DOM, которые мы изучили. Их уже довольно много.
Используйте её, чтобы получить быстрый итоговый обзор того, что изучали ранее.
Используйте её, чтобы по-быстрому вспомнить и прокрутить в голове то, что изучали ранее. Все ли эти свойства вам знакомы?
Кое-где стоит ограничение на версии IE, но на все свойства можно найти или сделать или найти полифилл, с которым их можно использовать везде.
[cut]
## Создание
<dl>
<dt>`document.createElement(tag)`</dt><dd>создать элемент с тегом `tag`</dd>
<dt>`document.createTextNode(txt)`</dt><dd>создать текстовый узел с текстом `txt`</dd>
<dt>`node.cloneNode(deep)`</dt><dd>клонировать существующий узел, если `deep=false`, то без потомков.</dd>
<dt>`document.createElement(tag)`</dt><dd>Создать элемент с тегом `tag`</dd>
<dt>`document.createTextNode(txt)`</dt><dd>Создать текстовый узел с текстом `txt`</dd>
<dt>`node.cloneNode(deep)`</dt><dd>Клонировать существующий узел, если `deep=false`, то без потомков.</dd>
</dl>
## Свойства узлов
<dl>
<dt>`node.nodeType`</dt><dd>тип узла: 1(элемент) / 3(текст) / другие.</dd>
<dt>`elem.tagName`</dt><dd>тег элемента.</dd>
<dt>`node.nodeType`</dt><dd>Тип узла: 1(элемент) / 3(текст) / другие.</dd>
<dt>`elem.tagName`</dt><dd>Тег элемента.</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>
<dt>`document.documentElement`</dt>
<dd>элемент `<HTML>`</dd>
<dd>Элемент `<HTML>`</dd>
<dt>`document.body`</dt>
<dd>элемент `<BODY>`</dd>
<dd>Элемент `<BODY>`</dd>
<dt>`document.head`</dt>
<dd>Элемент `<HEAD>` (IE9+)</dd>
</dl>
По всем узлам:
@ -42,14 +57,15 @@
Только по элементам:
<ul>
<li>`children`</li>
<li>`parentElement`</li>
<li>`nextElementSibling` `previousElementSibling`</li>
<li>`firstElementChild` `lastElementChild`</li>
<li>`children`, `firstElementChild` `lastElementChild`</li>
</ul>
В IE8- из них работает только `children`, причём содержит не только элементы, но и комментарии (ошибка в браузере).
Все они IE9+, кроме `children`, который работает в IE8-, но содержит не только элементы, но и комментарии (ошибка в браузере).
### Таблицы
Дополнительно у некоторых типов элементов могут быть и другие ссылки, свойства, коллекции для навигации,
например для таблиц:
<dl>
<dt>`table.rows[N]`</dt>
@ -62,17 +78,6 @@
<dd>номер ячейки в строке.</dd>
</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>
<dd>По уникальному `id`</dd>
<dt>`document.getElementsByName(name)`</dt>
<dd>По атрибуту `name`, в IE<10 работает только для элементов, где `name` предусмотрен стандартом.</dd>
<dd>По атрибуту `name`, в IE9- работает только для элементов, где `name` предусмотрен стандартом.</dd>
<dt>`*.getElementsByTagName(tag)`</dt>
<dd>По тегу `tag`</dd>
<dt>`*.getElementsByClassName(class)`</dt>
<dd>По классу, IE9+, корректно работает с элементами, у которых несколько классов.</dd>
</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.insertBefore(newChild, refNode)`</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>
## Классы и стили
@ -110,7 +140,7 @@
<dt>`elem.className`</dt>
<dd>Атрибут `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>
<dd>Стили в атрибуте `style` элемента</dd>
<dt>`getComputedStyle(elem, "")`</dd>

View file

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

View file

@ -6,9 +6,11 @@
<defs></defs>
<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">
<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">
<tspan x="137.692383" y="22">document.documentElement &lt;HTML&gt;</tspan>
</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">
<tspan x="172.84375" y="90">document.body (если внутри body)</tspan>
</text>
@ -21,8 +23,6 @@
<tspan x="221.183594" y="165" fill="#EE6B47">Element</tspan>
</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-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">
<tspan x="201.051859" y="225">&lt;DIV&gt;</tspan>
</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>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<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">
<tspan x="177.710938" y="18">document</tspan>
</text>
@ -25,9 +28,6 @@
<tspan x="172" y="228">parentNode</tspan>
</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-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">
<tspan x="189.051859" y="288">&lt;DIV&gt;</tspan>
</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
Метод `elem.closest(css)` ищет ближайшего предка, подходящего под CSS-селектор `css`.
Метод `elem.closest(css)` ищет ближайший элемент выше по иерархии DOM, подходящий под CSS-селектор `css`. Сам элемент тоже включается в поиск.
Иначе говоря, метод `closest` бежит от текущего элемента вверх по цепочке родителей и проверяет, подходит ли каждый элемент под CSS-селектор. Если подходит -- останавливается и возвращает его.
Он самый новый из методов, рассмотренных в этой главе, поэтому не все браузеры его поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе [](/dom-polyfill).
Пример использования:
Пример использования (браузер должен поддерживать `closest`):
```html
<!--+ run -->
@ -282,10 +283,15 @@ alert( articles.length ); // 2, найдёт оба элемента
<script>
var numberSpan = document.querySelector('.num');
// браузер должен поддерживать этот метод
// ближайший элемент сверху подходящий под селектор li
alert( numberSpan.closest('li').className ) // subchapter
// ближайший элемент сверху подходящий под селектор .chapter
alert( numberSpan.closest('.chapter').tagName ) // LI
// ближайший элемент сверху, подходящий под селектор span
// это сам numberSpan, так как поиск включает в себя сам элемент
alert( numberSpan.closest('span') === numberSpan ) // true
</script>
```

View file

@ -407,7 +407,7 @@ chatDiv.innerHTML += "Как дела?";
**Иными словами, `elem.textContent` возвращает конкатенацию всех текстовых узлов внутри `elem`.**
Не сказать, чтобы эта информация была часто востребована.
Не сказать, чтобы эта информация была часто востребована.
**Гораздо полезнее возможность записать текст в элемент, причём именно как текст!**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.