diff --git a/1-js/1-getting-started/1-intro/article.md b/1-js/1-getting-started/1-intro/article.md index 8d6396cd..f4c6219d 100644 --- a/1-js/1-getting-started/1-intro/article.md +++ b/1-js/1-getting-started/1-intro/article.md @@ -1,49 +1,56 @@ # An introduction to JavaScript -Let's see what's so special about JavaScript, what we can achieve with it and what other technologies coexist with it. +Let's see what's so special about JavaScript, what we can achieve with it and which other technologies play well with it. ## What is JavaScript? *JavaScript* was initially created to *"make webpages alive"*. -The programs in this language are called *scripts*. They are put directly into HTML and execute automatically as it loads. +The programs in this language are called *scripts*. They can be written right in the HTML and execute automatically as the page loads. -Scripts are provided and executed a plain text. They don't need a special preparation or compilation to run. +Scripts are provided and executed a plain text. They don't need a special preparation or a compilation to run. In this aspect, JavaScript is very different from another language called [Java](http://en.wikipedia.org/wiki/Java). [smart header="Why JavaScript?"] When JavaScript was created, it initially had another name: "LiveScript". But Java language was very popular at that time, so it was decided that positioning a new language as a "younger brother" of Java would help. -But as it evolved, JavaScript became a fully independent language, with its own specification called [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), and now it has no relation to Java altogether. +But as it evolved, JavaScript became a fully independent language, with its own specification called [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), and now it has no relation to Java at all. -It has quite a few special features that make mastering a bit hard at first, but we'll nicely deal with them later. [/smart] -Since the time of its creation, JavaScript evolved. +At present, JavaScript can execute not only in the browser, but also on the server, or actually on any device where a special program called [an interpreter]("http://en.wikipedia.org/wiki/Interpreter_(computing)") is installed. The execution process is called "an interpretation". -As of now, JavaScript can execute not only in the browser, but also on the server, or actually on any device where a special program called [an interpreter]("http://en.wikipedia.org/wiki/Interpreter_(computing)") is installed. The execution process is called "an interpretation". - -The browser has an embedded JavaScript interpreter, of course. Sometimes it's also called a *JavaScript engine* or a "JavaScript virtual machine". +The browser has an embedded JavaScript interpreter, sometimes it's also called a "JavaScript engine" or a "JavaScript virtual machine". Different engines have different "codenames", for example: -The codenames are usually used when searching for detailed information in the internet. Also, we'll use them further to be more exact. Instead of the words "Chrome supports feature..." we'd rather say "V8 supports feature...", not just because it's more precise, but because that also implies Opera and Node.JS. +The codenames are good to know. They are used when searching for detailed information in the internet. Also, we'll sometimes reference them further in the tutorial. Instead of the words "Chrome supports feature..." we'd rather say "V8 supports feature...", not just because it's more precise, but because that also implies Opera and Node.JS. [smart header="Compilation and interpretation"] -There are in fact two general approaches to execute programs: "compilers" and "interpreters". +There are two general approaches to execute programs: "compilation" and "interpretation". -Modern interpreters actually combine these approaches into one: the script is distributed as a plain text, but prior to execution is converted to the machine language. That's why JavaScript executes very fast. +As we can see, an interpretation is simpler. No intermediate steps involved. But a compilation is more powerful, because the binary code is more "machine-friendly" and runs faster at the end user. + +Modern javascript engines actually combine these approaches into one: +
    +
  1. The script is written and distributed as a plain text (can be compressed/optimized by so-called "javascript minifiers").
  2. +
  3. The engine (in-browser for the web) reads the script and converts it to the machine language. And then it runs it. That's why JavaScript executes very fast. + +Even more than that, the binary code may be adjusted later, through the process of its execution. The engine learns more about the actual data that it works with and then can optimize it better.
  4. +
+ +So the term "interpretation" is used mostly for historical reasons. We do know what there's actually a two-stage (at least) process behind it. [/smart] @@ -51,36 +58,38 @@ Modern interpreters actually combine these approaches into one: the script is di The modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or CPU, because it was initially created for browsers which do not require it. -Other capabilities depend on the environment which runs JavaScript. For instance, Node.JS has functionality that allows JavaScript to read/write arbitrary files, perform network requests etc etc. +Other capabilities depend on the environment which runs JavaScript. For instance, Node.JS has functionality that allows JavaScript to read/write arbitrary files, perform network requests etc. In the browser JavaScript can do everything related to webpage manipulation, interaction with the user and the webserver. -In more details, in-browser JavaScript is able to: +For instance, in-browser JavaScript is able to: ## What in-browser JavaScript can NOT do? -JavaScript abilities in the browser are limited. That is for user safety, mainly not to let an evil webpage access private information or harm the user's data. +JavaScript abilities in the browser are limited for the sake of the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. + +The examples of such restrictions are: - -Most modern systems that provide scripts know about these attributes and use them. - -Before inserting an external ` -``` - -А если сделать через `var`, то всё будет хорошо. - -Это была реклама того, что надо везде ставить `var`. - - -
  • Ошибка при рекурсии через функцию-свойство `window`. Следующий код "умрет" в IE8-: - -```html - - -``` - -Проблема здесь возникает из-за того, что функция напрямую присвоена в `window.recurse = ...`. Ее не будет при обычном объявлении функции. - -**Этот пример выдаст ошибку только в настоящем IE8!** Не IE9 в режиме эмуляции. Вообще, режим эмуляции позволяет отлавливать где-то 95% несовместимостей и проблем, а для оставшихся 5% вам нужен будет настоящий IE8 в виртуальной машине. -
  • - -[/warn] - - -## Итого - -В результате инициализации, к началу выполнения кода: -
      -
    1. Функции, объявленные как `Function Declaration`, создаются полностью и готовы к использованию.
    2. -
    3. Переменные объявлены, но равны `undefined`. Присваивания выполнятся позже, когда выполнение дойдет до них.
    4. -
    - - diff --git a/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md b/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md deleted file mode 100644 index ab6567e4..00000000 --- a/1-js/5-functions-closures/2-closures/1-say-phrase-first/solution.md +++ /dev/null @@ -1,16 +0,0 @@ -Ошибки не будет, выведет `"Вася, undefined"`. - -```js -//+ run -*!* -say('Вася'); // Что выведет? Не будет ли ошибки? -*/!* - -var phrase = 'Привет'; - -function say(name) { - alert( name + ", " + phrase ); -} -``` - -Переменная как таковая существует, вот только на момент запуска функции она равна `undefined`. \ No newline at end of file diff --git a/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md b/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md deleted file mode 100644 index fe548c0c..00000000 --- a/1-js/5-functions-closures/2-closures/1-say-phrase-first/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Что выведет say в начале кода? - -[importance 5] - -Что будет, если вызов `sayHi('Вася');` стоит в самом-самом начале, в первой строке кода? - -```js -*!* -say('Вася'); // Что выведет? Не будет ли ошибки? -*/!* - -var phrase = 'Привет'; - -function say(name) { - alert( name + ", " + phrase ); -} -``` - diff --git a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md b/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md deleted file mode 100644 index eed571f3..00000000 --- a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/solution.md +++ /dev/null @@ -1,9 +0,0 @@ -**Результатом будет `true`**, т.к. `var` обработается и переменная будет создана до выполнения кода. - -Соответственно, присвоение `value=true` сработает на локальной переменной, и `alert` выведет `true`. - -**Внешняя переменная не изменится.** - -P.S. Если `var` нет, то в функции переменная не будет найдена. Интерпретатор обратится за ней в `window` и изменит её там. - -**Так что без `var` результат будет также `true`, но внешняя переменная изменится.** diff --git a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md b/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md deleted file mode 100644 index b9f72a30..00000000 --- a/1-js/5-functions-closures/2-closures/2-which-value-is-modified/task.md +++ /dev/null @@ -1,25 +0,0 @@ -# В какую переменную будет присвоено значение? - -[importance 5] - -Каков будет результат выполнения этого кода? - -```js -var value = 0; - -function f() { - if (1) { - value = true; - } else { - var value = false; - } - - alert( value ); -} - -f(); -``` - -Изменится ли внешняя переменная `value` ? - -P.S. Какими будут ответы, если из строки `var value = false` убрать `var`? \ No newline at end of file diff --git a/1-js/5-functions-closures/2-closures/3-var-window/solution.md b/1-js/5-functions-closures/2-closures/3-var-window/solution.md deleted file mode 100644 index e1609417..00000000 --- a/1-js/5-functions-closures/2-closures/3-var-window/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -Результатом будет `undefined`, затем `5`. - -```js -//+ run -function test() { - - alert( window ); - - var window = 5; - - alert( window ); -} - -test(); -``` - -Такой результат получился потому, что `window` -- это глобальная переменная, но ничто не мешает объявить такую же локальную. - -Директива `var window` обработается до начала выполнения кода функции и будет создана локальная переменная, т.е. свойство `LexicalEnvironment.window`: - -```js -LexicalEnvironment = { - window: undefined -} -``` - -Когда выполнение кода начнется и сработает `alert`, он выведет уже локальную переменную, которая на тот момент равна `undefined`. - -Затем сработает присваивание, и второй `alert` выведет уже `5`. \ No newline at end of file diff --git a/1-js/5-functions-closures/2-closures/3-var-window/task.md b/1-js/5-functions-closures/2-closures/3-var-window/task.md deleted file mode 100644 index 8c0e1c7d..00000000 --- a/1-js/5-functions-closures/2-closures/3-var-window/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# var window - -[importance 5] - -Каков будет результат выполнения этого кода? Почему? - -```js -function test() { - - alert( window ); - - var window = 5; - - alert( window ); -} - -test(); -``` - diff --git a/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md b/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md deleted file mode 100644 index 4f08292e..00000000 --- a/1-js/5-functions-closures/2-closures/4-call-inplace/solution.md +++ /dev/null @@ -1,37 +0,0 @@ -Результат - **ошибка**. Попробуйте: - -```js -//+ run no-beautify -var a = 5 - -(function() { - alert(a) -})() -``` - -Дело в том, что после `var a = 5` нет точки с запятой. - -JavaScript воспринимает этот код как если бы перевода строки не было: - -```js -//+ run no-beautify -var a = 5(function() { - alert(a) -})() -``` - -То есть, он пытается вызвать *функцию* `5`, что и приводит к ошибке. - -Если точку с запятой поставить, все будет хорошо: - -```js -//+ run no-beautify -var a = 5; - -(function() { - alert(a) -})() -``` - -Это один из наиболее частых и опасных подводных камней, приводящих к ошибкам тех, кто *не* ставит точки с запятой. - diff --git a/1-js/5-functions-closures/2-closures/4-call-inplace/task.md b/1-js/5-functions-closures/2-closures/4-call-inplace/task.md deleted file mode 100644 index aa4c1b4a..00000000 --- a/1-js/5-functions-closures/2-closures/4-call-inplace/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Вызов "на месте" - -[importance 4] - -Каков будет результат выполнения кода? Почему? - -```js -//+ no-beautify -var a = 5 - -(function() { - alert(a) -})() -``` - -P.S. *Подумайте хорошо! Здесь все ошибаются!* -P.P.S. *Внимание, здесь подводный камень! Ок, вы предупреждены.* diff --git a/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md b/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md deleted file mode 100644 index 4e0a619e..00000000 --- a/1-js/5-functions-closures/2-closures/5-access-outer-variable/solution.md +++ /dev/null @@ -1,3 +0,0 @@ -Нет, нельзя. - -Локальная переменная полностью перекрывает внешнюю. diff --git a/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md b/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md deleted file mode 100644 index 13e61904..00000000 --- a/1-js/5-functions-closures/2-closures/5-access-outer-variable/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Перекрытие переменной - -[importance 4] - -Если во внутренней функции есть своя переменная с именем `currentCount` -- можно ли в ней получить `currentCount` из внешней функции? - -```js -function makeCounter() { - var currentCount = 1; - - return function() { - var currentCount; - // можно ли здесь вывести currentCount из внешней функции (равный 1)? - }; -} -``` - diff --git a/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md b/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md deleted file mode 100644 index b9b3771c..00000000 --- a/1-js/5-functions-closures/2-closures/6-counter-window-variable/solution.md +++ /dev/null @@ -1,30 +0,0 @@ -Выведут **1,2,3,4.** - -Здесь внутренняя функция будет искать -- и находить `currentCount` каждый раз в самом внешнем объекте переменных: глобальном объекте `window`. - -В результате все счётчики будут разделять единое, глобальное текущее значение. - -```js -//+ run -var currentCount = 1; - -function makeCounter() { - return function() { - return currentCount++; - }; -} - -var counter = makeCounter(); -var counter2 = makeCounter(); - -*!* -alert( counter() ); // ? -alert( counter() ); // ? -*/!* - -*!* -alert( counter2() ); // ? -alert( counter2() ); // ? -*/!* -``` - diff --git a/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md b/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md deleted file mode 100644 index 48068df9..00000000 --- a/1-js/5-functions-closures/2-closures/6-counter-window-variable/task.md +++ /dev/null @@ -1,29 +0,0 @@ -# Глобальный счётчик - -[importance 5] - -Что выведут эти вызовы, если переменная `currentCount` находится вне `makeCounter`? - -```js -var currentCount = 1; - -function makeCounter() { - return function() { - return currentCount++; - }; -} - -var counter = makeCounter(); -var counter2 = makeCounter(); - -*!* -alert( counter() ); // ? -alert( counter() ); // ? -*/!* - -*!* -alert( counter2() ); // ? -alert( counter2() ); // ? -*/!* -``` - diff --git a/1-js/5-functions-closures/2-closures/6@2x.png b/1-js/5-functions-closures/2-closures/6@2x.png deleted file mode 100755 index 7a276a33..00000000 Binary files a/1-js/5-functions-closures/2-closures/6@2x.png and /dev/null differ diff --git a/1-js/5-functions-closures/2-closures/article.md b/1-js/5-functions-closures/2-closures/article.md deleted file mode 100644 index d4301d75..00000000 --- a/1-js/5-functions-closures/2-closures/article.md +++ /dev/null @@ -1,411 +0,0 @@ -# Замыкания, функции изнутри - -В этой главе мы продолжим рассматривать, как работают переменные, и, как следствие, познакомимся с замыканиями. От глобального объекта мы переходим к работе внутри функций. -[cut] -## Лексическое окружение - -Все переменные внутри функции -- это свойства специального внутреннего объекта `LexicalEnvironment`, который создаётся при её запуске. - -Мы будем называть этот объект "лексическое окружение" или просто "объект переменных". - -При запуске функция создает объект `LexicalEnvironment`, записывает туда аргументы, функции и переменные. Процесс инициализации выполняется в том же порядке, что и для глобального объекта, который, вообще говоря, является частным случаем лексического окружения. - -В отличие от `window`, объект `LexicalEnvironment` является внутренним, он скрыт от прямого доступа. - -### Пример - -Посмотрим пример, чтобы лучше понимать, как это работает: - -```js -function sayHi(name) { - var phrase = "Привет, " + name; - alert( phrase ); -} - -sayHi('Вася'); -``` - -При вызове функции: -
      -
    1. До выполнения первой строчки её кода, на стадии инициализации, интерпретатор создает пустой объект `LexicalEnvironment` и заполняет его. - -В данном случае туда попадает аргумент `name` и единственная переменная `phrase`: - -```js -function sayHi(name) { -*!* - // LexicalEnvironment = { name: 'Вася', phrase: undefined } -*/!* - var phrase = "Привет, " + name; - alert( phrase ); -} - -sayHi('Вася'); -``` - -
    2. -
    3. Функция выполняется. - -Во время выполнения происходит присвоение локальной переменной `phrase`, то есть, другими словами, присвоение свойству `LexicalEnvironment.phrase` нового значения: - -```js -function sayHi(name) { - // LexicalEnvironment = { name: 'Вася', phrase: undefined } - var phrase = "Привет, " + name; - -*!* - // LexicalEnvironment = { name: 'Вася', phrase: 'Привет, Вася'} -*/!* - alert( phrase ); -} - -sayHi('Вася'); -``` - -
    4. -
    5. В конце выполнения функции объект с переменными обычно выбрасывается и память очищается. В примерах выше так и происходит. Через некоторое время мы рассмотрим более сложные ситуации, при которых объект с переменными сохраняется и после завершения функции.
    6. -
    - -[smart header="Тонкости спецификации"] -Если почитать спецификацию ECMA-262, то мы увидим, что речь идёт о двух объектах: `VariableEnvironment` и `LexicalEnvironment`. - -Но там же замечено, что в реализациях эти два объекта могут быть объединены. Так что мы избегаем лишних деталей и используем везде термин `LexicalEnvironment`, это достаточно точно позволяет описать происходящее. - -Более формальное описание находится в спецификации ECMA-262, секции 10.2-10.5 и 13. -[/smart] - - -## Доступ ко внешним переменным - -Из функции мы можем обратиться не только к локальной переменной, но и к внешней: - -```js -var userName = "Вася"; - -function sayHi() { - alert( userName ); // "Вася" -} -``` - -**Интерпретатор, при доступе к переменной, сначала пытается найти переменную в текущем `LexicalEnvironment`, а затем, если её нет -- ищет во внешнем объекте переменных. В данном случае им является `window`.** - -Такой порядок поиска возможен благодаря тому, что ссылка на внешний объект переменных хранится в специальном внутреннем свойстве функции, которое называется `[[Scope]]`. Это свойство закрыто от прямого доступа, но знание о нём очень важно для понимания того, как работает JavaScript. - -**При создании функция получает скрытое свойство `[[Scope]]`, которое ссылается на лексическое окружение, в котором она была создана.** - -В примере выше таким окружением является `window`, так что создаётся свойство: -```js -//+ no-beautify -sayHi.[[Scope]] = window -``` - -Это свойство никогда не меняется. Оно всюду следует за функцией, привязывая её, таким образом, к месту своего рождения. - -При запуске функции её объект переменных `LexicalEnvironment` получает ссылку на "внешнее лексическое окружение" со значением из `[[Scope]]`. - -Если переменная не найдена в функции -- она будет искаться снаружи. - -Именно благодаря этой механике в примере выше `alert(userName)` выводит внешнюю переменную. На уровне кода это выглядит как поиск во внешней области видимости, вне функции. - -Если обобщить: - - -Выглядит настолько просто, что непонятно -- зачем вообще говорить об этом `[[Scope]]`, об объектах переменных. Сказали бы: "Функция читает переменные снаружи" -- и всё. Но знание этих деталей позволит нам легко объяснить и понять более сложные ситуации, с которыми мы столкнёмся далее. - -## Всегда текущее значение - -Значение переменной из внешней области берётся всегда текущее. Оно может быть уже не то, что было на момент создания функции. - -Например, в коде ниже функция `sayHi` берёт `phrase` из внешней области: - -```js -//+ run no-beautify - -var phrase = 'Привет'; - -function say(name) { - alert(phrase + ', ' + name); -} - -*!* -say('Вася'); // Привет, Вася (*) -*/!* - -phrase = 'Пока'; - -*!* -say('Вася'); // Пока, Вася (**) -*/!* -``` - -На момент первого запуска `(*)`, переменная `phrase` имела значение `'Привет'`, а ко второму `(**)` изменила его на `'Пока'`. - -Это естественно, ведь для доступа к внешней переменной функция по ссылке `[[Scope]]` обращается во внешний объект переменных и берёт то значение, которое там есть на момент обращения. - -## Вложенные функции - -Внутри функции можно объявлять не только локальные переменные, но и другие функции. - -К примеру, вложенная функция может помочь лучше организовать код: - -```js -//+ run -function sayHiBye(firstName, lastName) { - - alert( "Привет, " + getFullName() ); - alert( "Пока, " + getFullName() ); - -*!* - function getFullName() { - return firstName + " " + lastName; - } -*/!* - -} - -sayHiBye("Вася", "Пупкин"); // Привет, Вася Пупкин ; Пока, Вася Пупкин -``` - -Здесь, для удобства, создана вспомогательная функция `getFullName()`. - -Вложенные функции получают `[[Scope]]` так же, как и глобальные. В нашем случае: - -```js -//+ no-beautify -getFullName.[[Scope]] = объект переменных текущего запуска sayHiBye -``` - -Благодаря этому `getFullName()` получает снаружи `firstName` и `lastName`. - -Заметим, что если переменная не найдена во внешнем объекте переменных, то она ищется в ещё более внешнем (через `[[Scope]]` внешней функции), то есть, такой пример тоже будет работать: - -```js -//+ run -var phrase = 'Привет'; - -function say() { - - function go() { - alert( phrase ); // найдёт переменную снаружи - } - - go(); -} -``` - -## Возврат функции - -Рассмотрим более "продвинутый" вариант, при котором внутри одной функции создаётся другая и возвращается в качестве результата. - -В разработке интерфейсов это совершенно стандартный приём, функция затем может назначаться как обработчик действий посетителя. - -Здесь мы будем создавать функцию-счётчик, которая считает свои вызовы и возвращает их текущее число. - -В примере ниже `makeCounter` создает такую функцию: - -```js -//+ run -function makeCounter() { -*!* - var currentCount = 1; -*/!* - - return function() { // (**) - return currentCount++; - }; -} - -var counter = makeCounter(); // (*) - -// каждый вызов увеличивает счётчик и возвращает результат -alert( counter() ); // 1 -alert( counter() ); // 2 -alert( counter() ); // 3 - -// создать другой счётчик, он будет независим от первого -var counter2 = makeCounter(); -alert( counter2() ); // 1 -``` - -Как видно, мы получили два независимых счётчика `counter` и `counter2`, каждый из которых незаметным снаружи образом сохраняет текущее количество вызовов. - -Где? Конечно, во внешней переменной `currentCount`, которая у каждого счётчика своя. - -Если подробнее описать происходящее: - -
      -
    1. В строке `(*)` запускается `makeCounter()`. При этом создаётся `LexicalEnvironment` для переменных текущего вызова. В функции есть одна переменная `var currentCount`, которая станет свойством этого объекта. Она изначально инициализуется в `undefined`, затем, в процессе выполнения, получит значение `1`: - -```js -function makeCounter() { -*!* - // LexicalEnvironment = { currentCount: undefined } -*/!* - - var currentCount = 1; - -*!* - // LexicalEnvironment = { currentCount: 1 } -*/!* - - return function() { // [[Scope]] -> LexicalEnvironment (**) - return currentCount++; - }; -} - -var counter = makeCounter(); // (*) -``` - -
    2. -
    3. В процессе выполнения `makeCounter()` создаёт функцию в строке `(**)`. При создании эта функция получает внутреннее свойство `[[Scope]]` со ссылкой на текущий `LexicalEnvironment`.
    4. -
    5. Далее вызов `makeCounter()` завершается и функция `(**)` возвращается и сохраняется во внешней переменной `counter` `(*)`.
    6. -
    - -На этом создание "счётчика" завершено. - -Итоговым значением, записанным в переменную `counter`, является функция: - -```js -function() { // [[Scope]] -> {currentCount: 1} - return currentCount++; -}; -``` - -Возвращённая из `makeCounter()` функция `counter` помнит (через `[[Scope]]`) о том, в каком окружении была создана. - -Это и используется для хранения текущего значения счётчика. - -Далее, когда-нибудь, функция `counter` будет вызвана. Мы не знаем, когда это произойдёт. Может быть, прямо сейчас, но, вообще говоря, совсем не факт. - -Эта функция состоит из одной строки: `return currentCount++`, ни переменных ни параметров в ней нет, поэтому её собственный объект переменных, для краткости назовём его `LE` -- будет пуст. - -Однако, у неё есть свойство `[[Scope]]`, которое указывает на внешнее окружение. Чтобы увеличить и вернуть `currentCount`, интерпретатор ищет в текущем объекте переменных `LE`, не находит, затем идёт во внешний объект, там находит, изменяет и возвращает новое значение: - -```js -//+ run -function makeCounter() { - var currentCount = 1; - - return function() { - return currentCount++; - }; -} - -var counter = makeCounter(); // [[Scope]] -> {currentCount: 1} - -alert( counter() ); // 1, [[Scope]] -> {currentCount: 1} -alert( counter() ); // 2, [[Scope]] -> {currentCount: 2} -alert( counter() ); // 3, [[Scope]] -> {currentCount: 3} -``` - -**Переменную во внешней области видимости можно не только читать, но и изменять.** - - -В примере выше было создано несколько счётчиков. Все они взаимно независимы: - -```js -var counter = makeCounter(); - -var counter2 = makeCounter(); - -alert( counter() ); // 1 -alert( counter() ); // 2 -alert( counter() ); // 3 - -alert( counter2() ); // 1, *!*счётчики независимы*/!* -``` - -Они независимы, потому что при каждом запуске `makeCounter` создаётся свой объект переменных `LexicalEnvironment`, со своим свойством `currentCount`, на который новый счётчик получит ссылку `[[Scope]]`. - - -## Свойства функции - -Функция в JavaScript является объектом, поэтому можно присваивать свойства прямо к ней, вот так: - -```js -//+ run -function f() {} - -f.test = 5; -alert( f.test ); -``` - -Свойства функции не стоит путать с переменными и параметрами. Они совершенно никак не связаны. Переменные доступны только внутри функции, они создаются в процессе её выполнения. Это -- использование функции "как функции". - -А свойство у функции -- доступно отовсюду и всегда. Это -- использование функции "как объекта". - -Если хочется привязать значение к функции, то можно им воспользоваться вместо внешних переменных. - -В качестве демонстрации, перепишем пример со счётчиком: - -```js -//+ run -function makeCounter() { -*!* - function counter() { - return counter.currentCount++; - }; - counter.currentCount = 1; -*/!* - - return counter; -} - -var counter = makeCounter(); -alert( counter() ); // 1 -alert( counter() ); // 2 -``` - -При запуске пример работает также. - -Принципиальная разница -- во внутренней механике и в том, что свойство функции, в отличие от переменной из замыкания -- общедоступно, к нему имеет доступ любой, у кого есть объект функции. - -Например, можно взять и поменять счётчик из внешнего кода: - -```js -var counter = makeCounter(); -alert( counter() ); // 1 - -*!* -counter.currentCount = 5; -*/!* - -alert( counter() ); // 5 -``` - -[smart header="Статические переменные"] -Иногда свойства, привязанные к функции, называют "статическими переменными". - -В некоторых языках программирования можно объявлять переменную, которая сохраняет значение между вызовами функции. В JavaScript ближайший аналог -- такое вот свойство функции. -[/smart] - - -## Итого: замыкания - -[Замыкание](http://en.wikipedia.org/wiki/Closure_(computer_science)) -- это функция вместе со всеми внешними переменными, которые ей доступны. - -Таково стандартное определение, которое есть в Wikipedia и большинстве серьёзных источников по программированию. То есть, замыкание -- это функция + внешние переменные. - -Тем не менее, в JavaScript есть небольшая терминологическая особенность. - -**Обычно, говоря "замыкание функции", подразумевают не саму эту функцию, а именно внешние переменные.** - -Иногда говорят "переменная берётся из замыкания". Это означает -- из внешнего объекта переменных. - - -[smart header="Что это такое -- \"понимать замыкания?\""] -Иногда говорят "Вася молодец, понимает замыкания!". Что это такое -- "понимать замыкания", какой смысл обычно вкладывают в эти слова? - -"Понимать замыкания" в JavaScript означает понимать следующие вещи: -
      -
    1. Все переменные и параметры функций являются свойствами объекта переменных `LexicalEnvironment`. Каждый запуск функции создает новый такой объект. На верхнем уровне им является "глобальный объект", в браузере -- `window`.
    2. -
    3. При создании функция получает системное свойство `[[Scope]]`, которое ссылается на `LexicalEnvironment`, в котором она была создана.
    4. -
    5. При вызове функции, куда бы её ни передали в коде -- она будет искать переменные сначала у себя, а затем во внешних `LexicalEnvironment` с места своего "рождения".
    6. -
    - -В следующих главах мы углубим это понимание дополнительными примерами, а также рассмотрим, что происходит с памятью. -[/smart] diff --git a/1-js/5-functions-closures/3-scope-new-function/article.md b/1-js/5-functions-closures/3-scope-new-function/article.md deleted file mode 100644 index 51cebe40..00000000 --- a/1-js/5-functions-closures/3-scope-new-function/article.md +++ /dev/null @@ -1,93 +0,0 @@ -# [[Scope]] для new Function - - -## Присвоение [[Scope]] для new Function [#scope-Function] - -Есть одно исключение из общего правила присвоения `[[Scope]]`, которое мы рассматривали в предыдущей главе. - -При создании функции с использованием `new Function`, её свойство `[[Scope]]` ссылается не на текущий `LexicalEnvironment`, а на `window`. - -## Пример - -Следующий пример демонстрирует как функция, созданная `new Function`, игнорирует внешнюю переменную `a` и выводит глобальную вместо неё: - -```js -//+ run untrusted refresh -var a = 1; - -function getFunc() { - var a = 2; - -*!* - var func = new Function('', 'alert(a)'); -*/!* - - return func; -} - -getFunc()(); // *!*1*/!*, из window -``` - -Сравним с обычным поведением: - -```js -//+ run untrusted refresh -var a = 1; - -function getFunc() { - var a = 2; - -*!* - var func = function() { alert(a); }; -*/!* - - return func; -} - -getFunc()(); // *!*2*/!*, из LexicalEnvironment функции getFunc -``` - - -## Почему так сделано? - -[warn header="Продвинутые знания"] -Содержимое этой секции содержит продвинутую информацию теоретического характера, которая прямо сейчас не обязательна для дальнейшего изучения JavaScript. -[/warn] - -Эта особенность `new Function`, хоть и выглядит странно, на самом деле весьма полезна. - -Представьте себе, что нам действительно нужно создать функцию из строки кода. Текст кода этой функции неизвестен на момент написания скрипта (иначе зачем `new Function`), но станет известен позже, например получен с сервера или из других источников данных. - -Предположим, что этому коду надо будет взаимодействовать с внешними переменными основного скрипта. - -Но проблема в том, что JavaScript при выкладывании на "боевой сервер" предварительно сжимается минификатором -- специальной программой, которая уменьшает размер кода, убирая из него лишние комментарии, пробелы, что очень важно -- переименовывает локальные переменные на более короткие. - -То есть, если внутри функции есть `var userName`, то минификатор заменит её на `var a` (или другую букву, чтобы не было конфликта), предполагая, что так как переменная видна только внутри функции, то этого всё равно никто не заметит, а код станет короче. И обычно проблем нет. - -...Но если бы `new Function` могла обращаться к внешним переменным, то при попытке доступа к `userName` в сжатом коде была бы ошибка, так как минификатор переименовал её. - -**Получается, что даже если бы мы захотели использовать локальные переменные в `new Function`, то после сжатия были бы проблемы, так как минификатор переименовывает локальные переменные.** - -Описанная особенность `new Function` просто-таки спасает нас от ошибок. - -Ну а если внутри функции, создаваемой через `new Function`, всё же нужно использовать какие-то данные -- без проблем, нужно всего лишь предусмотреть соответствующие параметры и передавать их явным образом, например так: - -```js -//+ run untrusted refresh no-beautify -*!* -var sum = new Function('a, b', ' return a + b; '); -*/!* - -var a = 1, b = 2; - -*!* -alert( sum(a, b) ); // 3 -*/!* -``` - -## Итого - - diff --git a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md b/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md deleted file mode 100644 index 2e28ae36..00000000 --- a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Чтобы вторые скобки в вызове работали - первые должны возвращать функцию. - -Эта функция должна знать про `a` и уметь прибавлять `a` к `b`. Вот так: - -```js -//+ run -function sum(a) { - - return function(b) { - return a + b; // возьмет a из внешнего LexicalEnvironment - }; - -} - -alert( sum(1)(2) ); -alert( sum(5)(-1) ); -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md b/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md deleted file mode 100644 index 5de93f5e..00000000 --- a/1-js/5-functions-closures/4-closures-usage/1-closure-sum/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Сумма через замыкание - -[importance 4] - -Напишите функцию `sum`, которая работает так: `sum(a)(b) = a+b`. - -Да, именно так, через двойные скобки (это не опечатка). Например: - -```js -sum(1)(2) = 3 -sum(5)(-1) = 4 -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/_js.view/solution.js b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/_js.view/solution.js deleted file mode 100644 index bda57778..00000000 --- a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/_js.view/solution.js +++ /dev/null @@ -1,10 +0,0 @@ -function makeBuffer() { - var text = ''; - - return function(piece) { - if (arguments.length == 0) { // вызов без аргументов - return text; - } - text += piece; - }; -}; \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/_js.view/test.js b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/_js.view/test.js deleted file mode 100644 index df3c04f9..00000000 --- a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/_js.view/test.js +++ /dev/null @@ -1,21 +0,0 @@ -var buffer; -beforeEach(function() { - buffer = makeBuffer(); -}); - -it("возвращает пустую строку по умолчанию", function() { - assert.strictEqual(buffer(), ""); -}); - -it("добавляет аргументы в буффер", function() { - buffer('Замыкания'); - buffer(' Использовать'); - buffer(' Нужно!'); - assert.equal(buffer(), 'Замыкания Использовать Нужно!'); -}); - -it("приводит всё к строке", function() { - buffer(null); - buffer(false); - assert.equal(buffer(), "nullfalse"); -}); \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md deleted file mode 100644 index 5f946bfb..00000000 --- a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/solution.md +++ /dev/null @@ -1,32 +0,0 @@ -Текущее значение текста удобно хранить в замыкании, в локальной переменной `makeBuffer`: - -```js -//+ run -function makeBuffer() { - var text = ''; - - return function(piece) { - if (arguments.length == 0) { // вызов без аргументов - return text; - } - text += piece; - }; -}; - -var buffer = makeBuffer(); - -// добавить значения к буферу -buffer('Замыкания'); -buffer(' Использовать'); -buffer(' Нужно!'); -alert( buffer() ); // 'Замыкания Использовать Нужно!' - -var buffer2 = makeBuffer(); -buffer2(0); -buffer2(1); -buffer2(0); - -alert( buffer2() ); // '010' -``` - -Начальное значение `text = ''` -- пустая строка. Поэтому операция `text += piece` прибавляет `piece` к строке, автоматически преобразуя его к строковому типу, как и требовалось в условии. diff --git a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md b/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md deleted file mode 100644 index 9f28b7cd..00000000 --- a/1-js/5-functions-closures/4-closures-usage/2-stringbuffer/task.md +++ /dev/null @@ -1,45 +0,0 @@ -# Функция - строковый буфер - -[importance 5] - -В некоторых языках программирования существует объект "строковый буфер", который аккумулирует внутри себя значения. Его функционал состоит из двух возможностей: -
      -
    1. Добавить значение в буфер.
    2. -
    3. Получить текущее содержимое.
    4. -
    - -**Задача -- реализовать строковый буфер на функциях в JavaScript, со следующим синтаксисом:** - - - -Вот пример работы: - -```js -function makeBuffer() { /* ваш код */ } - -var buffer = makeBuffer(); - -// добавить значения к буферу -buffer('Замыкания'); -buffer(' Использовать'); -buffer(' Нужно!'); - -// получить текущее значение -alert( buffer() ); // Замыкания Использовать Нужно! -``` - -Буфер должен преобразовывать все данные к строковому типу: - -```js -var buffer = makeBuffer(); -buffer(0); -buffer(1); -buffer(0); - -alert( buffer() ); // '010' -``` - -Решение не должно использовать глобальные переменные. diff --git a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/_js.view/solution.js b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/_js.view/solution.js deleted file mode 100644 index b7fbc6f6..00000000 --- a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/_js.view/solution.js +++ /dev/null @@ -1,16 +0,0 @@ -function makeBuffer() { - var text = ''; - - function buffer(piece) { - if (arguments.length == 0) { // вызов без аргументов - return text; - } - text += piece; - }; - - buffer.clear = function() { - text = ""; - } - - return buffer; -}; \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/_js.view/test.js b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/_js.view/test.js deleted file mode 100644 index 0543cc90..00000000 --- a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/_js.view/test.js +++ /dev/null @@ -1,30 +0,0 @@ -var buffer; - -beforeEach(function() { - buffer = makeBuffer(); -}); - -it("возвращает пустую строку по умолчанию", function() { - assert.strictEqual(buffer(), ""); -}); - -it("добавляет аргументы в буффер", function() { - buffer('Замыкания'); - buffer(' Использовать'); - buffer(' Нужно!'); - assert.equal(buffer(), 'Замыкания Использовать Нужно!'); -}); - -it("приводит всё к строке", function() { - buffer(null); - buffer(false); - assert.equal(buffer(), "nullfalse"); -}); - -it("очищает буфер вызовом clear", function() { - buffer("test"); - buffer.clear(); - buffer("первый"); - buffer("второй"); - assert.equal(buffer(), "первыйвторой"); -}); \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md deleted file mode 100644 index c4c45809..00000000 --- a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/solution.md +++ /dev/null @@ -1,34 +0,0 @@ - - -```js -//+ run -function makeBuffer() { - var text = ''; - - function buffer(piece) { - if (arguments.length == 0) { // вызов без аргументов - return text; - } - text += piece; - }; - - buffer.clear = function() { - text = ""; - } - - return buffer; -}; - -var buffer = makeBuffer(); - -buffer("Тест"); -buffer(" тебя не съест "); -alert( buffer() ); // Тест тебя не съест - -*!* -buffer.clear(); -*/!* - -alert( buffer() ); // "" -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md b/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md deleted file mode 100644 index 319af876..00000000 --- a/1-js/5-functions-closures/4-closures-usage/3-stringbuffer-with-clear/task.md +++ /dev/null @@ -1,24 +0,0 @@ -# Строковый буфер с очисткой - -[importance 5] - -Добавьте буферу из решения задачи [](/task/stringbuffer) метод `buffer.clear()`, который будет очищать текущее содержимое буфера: - -```js -function makeBuffer() { - ...ваш код... -} - -var buffer = makeBuffer(); - -buffer("Тест"); -buffer(" тебя не съест "); -alert( buffer() ); // Тест тебя не съест - -*!* -buffer.clear(); -*/!* - -alert( buffer() ); // "" -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md b/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md deleted file mode 100644 index 43ded8f9..00000000 --- a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/solution.md +++ /dev/null @@ -1,37 +0,0 @@ - - -```js -//+ run -var users = [{ - name: "Вася", - surname: 'Иванов', - age: 20 -}, { - name: "Петя", - surname: 'Чапаев', - age: 25 -}, { - name: "Маша", - surname: 'Медведева', - age: 18 -}]; - -*!* -function byField(field) { - return function(a, b) { - return a[field] > b[field] ? 1 : -1; - } - } -*/!* - -users.sort(byField('name')); -users.forEach(function(user) { - alert( user.name ); -}); - -users.sort(byField('age')); -users.forEach(function(user) { - alert( user.name ); -}); -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md b/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md deleted file mode 100644 index 36701c23..00000000 --- a/1-js/5-functions-closures/4-closures-usage/4-sort-by-field/task.md +++ /dev/null @@ -1,53 +0,0 @@ -# Сортировка - -[importance 5] - -У нас есть массив объектов: - -```js -var users = [{ - name: "Вася", - surname: 'Иванов', - age: 20 -}, { - name: "Петя", - surname: 'Чапаев', - age: 25 -}, { - name: "Маша", - surname: 'Медведева', - age: 18 -}]; -``` - -Обычно сортировка по нужному полю происходит так: - -```js -// по полю name (Вася, Маша, Петя) -users.sort(function(a, b) { - return a.name > b.name ? 1 : -1; -}); - -// по полю age (Маша, Вася, Петя) -users.sort(function(a, b) { - return a.age > b.age ? 1 : -1; -}); -``` - -Мы хотели бы упростить синтаксис до одной строки, вот так: - -```js -users.sort(byField('name')); -users.forEach(function(user) { - alert( user.name ); -}); // Вася, Маша, Петя - -users.sort(byField('age')); -users.forEach(function(user) { - alert( user.name ); -}); // Маша, Вася, Петя -``` - -То есть, вместо того, чтобы каждый раз писать в `sort` `function...` -- будем использовать `byField(...)` - -Напишите функцию `byField(field)`, которую можно использовать в `sort` для сравнения объектов по полю `field`, чтобы пример выше заработал. diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/solution.js b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/solution.js deleted file mode 100644 index 90cf2689..00000000 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/solution.js +++ /dev/null @@ -1,24 +0,0 @@ -function filter(arr, func) { - var result = []; - - for (var i = 0; i < arr.length; i++) { - var val = arr[i]; - if (func(val)) { - result.push(val); - } - } - - return result; -} - -function inArray(arr) { - return function(x) { - return arr.indexOf(x) != -1; - }; -} - -function inBetween(a, b) { - return function(x) { - return x >= a && x <= b; - }; -} \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js deleted file mode 100644 index e512f26c..00000000 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/source.js +++ /dev/null @@ -1,11 +0,0 @@ -function filter(arr, fuc) { - // ...ваш код... -} - -function inBetween(a, b) { - // ...ваш код... -} - -function inArray(arr) { - // ...ваш код... -} \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/test.js b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/test.js deleted file mode 100644 index f03fb743..00000000 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/_js.view/test.js +++ /dev/null @@ -1,58 +0,0 @@ -var arr; - -before(function() { - arr = [1, 2, 3, 4, 5, 6, 7]; -}); - -describe("inArray", function() { - var checkInArr; - - before(function() { - checkInArr = inArray(arr); - }); - - it("возвращает фильтр для значений в массиве", function() { - assert.isTrue(checkInArr(5)); - assert.isFalse(checkInArr(0)); - }); -}); - - -describe("inBetween", function() { - var checkBetween36; - - before(function() { - checkBetween36 = inBetween(3, 6); - }); - - it("возвращает фильтрa для значений в промежутке", function() { - assert.isTrue(checkBetween36(5)); - assert.isFalse(checkBetween36(0)); - }); -}); - - -describe("filter", function() { - - it("фильтрует через func", function() { - assert.deepEqual(filter(arr, function(a) { - return a % 2 == 0; - }), [2, 4, 6]); - }); - - it("не модифицирует исходный массив", function() { - filter(arr, function(a) { - return a % 2 == 0; - }); - assert.deepEqual(arr, [1, 2, 3, 4, 5, 6, 7]); - }); - - it("поддерживает фильтр inBetween", function() { - assert.deepEqual(filter(arr, inBetween(3, 6)), [3, 4, 5, 6]); - }); - - it("поддерживает фильтр inArray", function() { - assert.deepEqual(filter(arr, inArray([1, 2, 3])), [1, 2, 3]); - }); - -}); \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md deleted file mode 100644 index 114fdd5f..00000000 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/solution.md +++ /dev/null @@ -1,82 +0,0 @@ -# Функция фильтрации - -```js -//+ run -function filter(arr, func) { - var result = []; - - for (var i = 0; i < arr.length; i++) { - var val = arr[i]; - if (func(val)) { - result.push(val); - } - } - - return result; -} - -var arr = [1, 2, 3, 4, 5, 6, 7]; - -alert(filter(arr, function(a) { - return a % 2 == 0; -})); // 2, 4, 6 -``` - -# Фильтр inBetween - -```js -//+ run -function filter(arr, func) { - var result = []; - - for (var i = 0; i < arr.length; i++) { - var val = arr[i]; - if (func(val)) { - result.push(val); - } - } - - return result; -} - -*!* -function inBetween(a, b) { - return function(x) { - return x >= a && x <= b; - }; - } -*/!* - -var arr = [1, 2, 3, 4, 5, 6, 7]; -alert( filter(arr, inBetween(3, 6)) ); // 3,4,5,6 -``` - -# Фильтр inArray - -```js -//+ run -function filter(arr, func) { - var result = []; - - for (var i = 0; i < arr.length; i++) { - var val = arr[i]; - if (func(val)) { - result.push(val); - } - } - - return result; -} - -*!* -function inArray(arr) { - return function(x) { - return arr.indexOf(x) != -1; - }; - } -*/!* - -var arr = [1, 2, 3, 4, 5, 6, 7]; -alert( filter(arr, inArray([1, 2, 10])) ); // 1,2 -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md b/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md deleted file mode 100644 index 7ee691b5..00000000 --- a/1-js/5-functions-closures/4-closures-usage/5-filter-through-function/task.md +++ /dev/null @@ -1,29 +0,0 @@ -# Фильтрация через функцию - -[importance 5] - -
      -
    1. Создайте функцию `filter(arr, func)`, которая получает массив `arr` и возвращает новый, в который входят только те элементы `arr`, для которых `func` возвращает `true`.
    2. -
    3. Создайте набор "готовых фильтров": `inBetween(a,b)` -- "между a,b", `inArray([...])` -- "в массиве `[...]`". -Использование должно быть таким: - -
    4. -
    -Пример, как это должно работать: - -```js -/* .. ваш код для filter, inBetween, inArray */ -var arr = [1, 2, 3, 4, 5, 6, 7]; - -alert(filter(arr, function(a) { - return a % 2 == 0 -})); // 2,4,6 - -alert( filter(arr, inBetween(3, 6)) ); // 3,4,5,6 - -alert( filter(arr, inArray([1, 2, 10])) ); // 1,2 -``` - diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/solution.js b/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/solution.js deleted file mode 100644 index 2455c301..00000000 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/solution.js +++ /dev/null @@ -1,19 +0,0 @@ -function makeArmy() { - - var shooters = []; - - for (var i = 0; i < 10; i++) { - - var shooter = (function(x) { - - return function() { - alert(x); - }; - - })(i); - - shooters.push(shooter); - } - - return shooters; -} \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/source.js b/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/source.js deleted file mode 100644 index f9058674..00000000 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/source.js +++ /dev/null @@ -1,13 +0,0 @@ -function makeArmy() { - - var shooters = []; - - for (var i = 0; i < 10; i++) { - var shooter = function() { // функция-стрелок - alert(i); // выводит свой номер - }; - shooters.push(shooter); - } - - return shooters; -} \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/test.js b/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/test.js deleted file mode 100644 index fdfabf66..00000000 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/_js.view/test.js +++ /dev/null @@ -1,20 +0,0 @@ -var army; -before(function() { - army = makeArmy(); - window.alert = sinon.stub(window, "alert"); -}); - -it("army[0] выводит 0", function() { - army[0](); - assert(alert.calledWith(0)); -}); - - -it("army[5] функция выводит 5", function() { - army[5](); - assert(alert.calledWith(5)); -}); - -after(function() { - window.alert.restore(); -}); \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md b/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md deleted file mode 100644 index 5628e800..00000000 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/solution.md +++ /dev/null @@ -1,218 +0,0 @@ -# Что происходит в этом коде - -Функция `makeArmy` делает следующее: -
      -
    1. Создаёт пустой массив `shooter`: - -```js -var shooters = []; -``` - -
    2. -
    3. В цикле заполняет массив элементами через `shooter.push`. -При этом каждый элемент массива -- это функция, так что в итоге после цикла массив будет таким: - -```js -//+ no-beautify -shooters = [ - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); } -]; -``` - -Этот массив возвращается из функции. -
    4. -
    5. Вызов `army[5]()` -- это получение элемента массива (им будет функция), и тут же -- её запуск.
    6. -
    - -# Почему ошибка - -Вначале разберемся, почему все стрелки выводят одно и то же значение. - -В функциях-стрелках `shooter` отсутствует переменная `i`. Когда такая функция вызывается, то `i` она берет из внешнего `LexicalEnvironment`. - -Чему же будет равно это значение `i`? - -К моменту вызова `army[0]()`, функция `makeArmy` уже закончила работу. Цикл завершился, последнее значение было `i=10`. - -В результате все функции `shooter` получают из внешнего лексического кружения это, одно и то же, последнее, значение `i=10`. - -Попробуйте исправить проблему самостоятельно. - -# Исправление (3 варианта) - -Есть несколько способов исправить ситуацию. - -
      -
    1. **Первый способ исправить код - это привязать значение непосредственно к функции-стрелку:** - -```js -//+ run -function makeArmy() { - - var shooters = []; - - for (var i = 0; i < 10; i++) { - -*!* - var shooter = function me() { - alert( me.i ); - }; - shooter.i = i; -*/!* - - shooters.push(shooter); - } - - return shooters; -} - -var army = makeArmy(); - -army[0](); // 0 -army[1](); // 1 -``` - -В этом случае каждая функция хранит в себе свой собственный номер. - -Кстати, обратите внимание на использование Named Function Expression, вот в этом участке: - -```js -... -var shooter = function me() { - alert( me.i ); -}; -... -``` - -Если убрать имя `me` и оставить обращение через `shooter`, то работать не будет: - -```js -for (var i = 0; i < 10; i++) { - var shooter = function() { -*!* - alert( shooter.i ); // вывести свой номер (не работает!) - // потому что откуда функция возьмёт переменную shooter? - // ..правильно, из внешнего объекта, а там она одна на всех -*/!* - }; - shooter.i = i; - shooters.push(shooter); -} -``` - -Вызов `alert(shooter.i)` при вызове будет искать переменную `shooter`, а эта переменная меняет значение по ходу цикла, и к моменту вызову она равна последней функции, созданной в цикле. - -Если использовать Named Function Expression, то имя жёстко привязывается к конкретной функции, и поэтому в коде выше `me.i` возвращает правильный `i`. - -
    2. -
    3. **Другое, более продвинутое решение -- использовать дополнительную функцию для того, чтобы "поймать" текущее значение `i`**: - -```js -//+ run -function makeArmy() { - - var shooters = []; - - for (var i = 0; i < 10; i++) { - -*!* - var shooter = (function(x) { - - return function() { - alert( x ); - }; - - })(i); -*/!* - - shooters.push(shooter); - } - - return shooters; -} - -var army = makeArmy(); - -army[0](); // 0 -army[1](); // 1 -``` - -Посмотрим выделенный фрагмент более внимательно, чтобы понять, что происходит: - -```js -var shooter = (function(x) { - return function() { - alert( x ); - }; -})(i); -``` - -Функция `shooter` создана как результат вызова промежуточного функционального выражения `function(x)`, которое объявляется -- и тут же выполняется, получая `x = i`. - -Так как `function(x)` тут же завершается, то значение `x` больше не меняется. Оно и будет использовано в возвращаемой функции-стрелке. - -Для красоты можно изменить название переменной `x` на `i`, суть происходящего при этом не изменится: - -```js -var shooter = (function(i) { - return function() { - alert( i ); - }; -})(i); -``` - -**Кстати, обратите внимание -- скобки вокруг `function(i)` не нужны**, можно и так: - -```js -var shooter = function(i) { // *!*без скобок вокруг function(i)*/!* - return function() { - alert( i ); - }; -}(i); -``` - -Скобки добавлены в код для лучшей читаемости, чтобы человек, который просматривает его, не подумал, что `var shooter = function`, а понял что это вызов "на месте", и присваивается его результат. -
    4. -
    5. **Еще один забавный способ - обернуть весь цикл во временную функцию**: - -```js -//+ run -function makeArmy() { - - var shooters = []; - -*!* - for (var i = 0; i < 10; i++)(function(i) { - - var shooter = function() { - alert( i ); - }; - - shooters.push(shooter); - - })(i); -*/!* - - return shooters; -} - -var army = makeArmy(); - -army[0](); // 0 -army[1](); // 1 -``` - -Вызов `(function(i) { ... })` обернут в скобки, чтобы интерпретатор понял, что это `Function Expression`. - -Плюс этого способа - в большей читаемости. Фактически, мы не меняем создание `shooter`, а просто обертываем итерацию в функцию. -
    6. -
    \ No newline at end of file diff --git a/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md b/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md deleted file mode 100644 index 15e0d2ae..00000000 --- a/1-js/5-functions-closures/4-closures-usage/6-make-army/task.md +++ /dev/null @@ -1,31 +0,0 @@ -# Армия функций - -[importance 5] - -Следующий код создает массив функций-стрелков `shooters`. По замыслу, каждый стрелок должен выводить свой номер: - -```js -//+ run -function makeArmy() { - - var shooters = []; - - for (var i = 0; i < 10; i++) { - var shooter = function() { // функция-стрелок - alert( i ); // выводит свой номер - }; - shooters.push(shooter); - } - - return shooters; -} - -var army = makeArmy(); - -army[0](); // стрелок выводит 10, а должен 0 -army[5](); // стрелок выводит 10... -// .. все стрелки выводят 10 вместо 0,1,2...9 -``` - -Почему все стрелки́ выводят одно и то же? Поправьте код, чтобы стрелки работали как задумано. Предложите несколько вариантов исправления. - diff --git a/1-js/5-functions-closures/4-closures-usage/article.md b/1-js/5-functions-closures/4-closures-usage/article.md deleted file mode 100644 index 0fa5da2c..00000000 --- a/1-js/5-functions-closures/4-closures-usage/article.md +++ /dev/null @@ -1,125 +0,0 @@ -# Локальные переменные для объекта - -Замыкания можно использовать сотнями способов. Иногда люди сами не замечают, что использовали замыкания -- настолько это просто и естественно. - -В этой главе мы рассмотрим дополнительные примеры использования замыканий и задачи на эту тему. - -[cut] - -## Счётчик-объект - -Ранее мы сделали счётчик. - -Напомню, как он выглядел: - -```js -//+ run -function makeCounter() { - var currentCount = 1; - - return function() { - return currentCount++; - }; -} - -var counter = makeCounter(); - -// каждый вызов возвращает результат, увеличивая счётчик -alert( counter() ); // 1 -alert( counter() ); // 2 -alert( counter() ); // 3 -``` - -Счётчик получился вполне рабочий, но вот только возможностей ему не хватает. Хорошо бы, чтобы можно было сбрасывать значение счётчика или начинать отсчёт с другого значения вместо `1` или... Да много чего можно захотеть от простого счётчика и, тем более, в более сложных проектах. - -**Чтобы добавить счётчику возможностей -- перейдём с функции на полноценный объект:** - -```js -//+ run -function makeCounter() { - var currentCount = 1; - - return { // возвратим объект вместо функции - getNext: function() { - return currentCount++; - }, - - set: function(value) { - currentCount = value; - }, - - reset: function() { - currentCount = 1; - } - }; -} - -var counter = makeCounter(); - -alert( counter.getNext() ); // 1 -alert( counter.getNext() ); // 2 - -counter.set(5); -alert( counter.getNext() ); // 5 -``` - -Теперь функция `makeCounter` возвращает не одну функцию, а объект с несколькими методами: - - - -Все они получают ссылку `[[Scope]]` на текущий (внешний) объект переменных. Поэтому вызов любого из этих методов будет получать или модифицировать одно и то же внешнее значение `currentCount`. - -## Объект счётчика + функция - -Изначально, счётчик делался функцией во многом ради красивого вызова: `counter()`, который увеличивал значение и возвращал результат. - -К сожалению, при переходе на объект короткий вызов пропал, вместо него теперь `counter.getNext()`. Но он ведь был таким простым и удобным... - -Поэтому давайте вернём его! - -```js -//+ run -function makeCounter() { - var currentCount = 1; - -*!* - // возвращаемся к функции - function counter() { - return currentCount++; - } -*/!* - - // ...и добавляем ей методы! - counter.set = function(value) { - currentCount = value; - }; - - counter.reset = function() { - currentCount = 0; - }; - - return counter; -} - -var counter = makeCounter(); - -*!* -alert( counter() ); // 1 -alert( counter() ); // 2 - -counter.set(5); -alert( counter() ); // 5 -*/!* -``` - -Красиво, не правда ли? Получился полноценный объект, который можно вдобавок ещё и вызывать. - -Этот трюк часто используется при разработке JavaScript-библиотек. Например, популярная библиотека [jQuery](http://jquery.com) предоставляет глобальную переменную с именем [jQuery](http://api.jquery.com/jQuery/) (доступна также под коротким именем `$`), которая с одной стороны является функцией и может вызываться как `jQuery(...)`, а с другой -- у неё есть различные методы, например `jQuery.type(123)` возвращает тип аргумента. - -Далее вы найдёте различные задачи на понимание замыканий. Рекомендуется их сделать самостоятельно. - - diff --git a/1-js/5-functions-closures/5-closures-module/article.md b/1-js/5-functions-closures/5-closures-module/article.md deleted file mode 100644 index 384a86fb..00000000 --- a/1-js/5-functions-closures/5-closures-module/article.md +++ /dev/null @@ -1,322 +0,0 @@ -# Модули через замыкания - -Приём программирования "модуль" имеет громадное количество вариаций. Он немного похож на счётчик, который мы рассматривали ранее, использует аналогичный приём, но на уровне выше. - -Его цель -- скрыть внутренние детали реализации скрипта. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п. - -## Зачем нужен модуль? - -Допустим, мы хотим разработать скрипт, который делает что-то полезное на странице. - -Умея работать со страницей, мы могли бы сделать много чего, но так как пока этого не было (скоро научимся), то пусть скрипт просто выводит сообщение: - -Файл `hello.js` - -```js -//+ run -// глобальная переменная нашего скрипта -var message = "Привет"; - -// функция для вывода этой переменной -function showMessage() { - alert( message ); -} - -// выводим сообщение -showMessage(); -``` - -У этого скрипта есть свои внутренние переменные и функции. - -В данном случае это `message` и `showMessage`. - -Предположим, что мы хотели бы распространять этот скрипт в виде библиотеки. Каждый, кто хочет, чтобы посетителям выдавалось "Привет" -- может просто подключить этот скрипт. Достаточно скачать и подключить, например, как внешний файл `hello.js` -- и готово. - -**Если подключить подобный скрипт к странице "как есть", то возможен конфликт с переменными, которые она использует.** - -То есть, при подключении к такой странице он её "сломает": - -```html - - - - - -``` - -[edit src="hello-conflict"/] - -Автор страницы ожидает, что библиотека `"hello.js"` просто отработает, без побочных эффектов. А она вместе с этим переопределила `message` в `"Привет"`. - -Если же убрать скрипт `hello.js`, то страница будет выводить правильное сообщение. - -Зная внутреннее устройство `hello.js` нам, конечно, понятно, что проблема возникла потому, что переменная `message` из скрипта `hello.js` перезаписала объявленную на странице. - -## Приём проектирования "Модуль" - -Чтобы проблемы не было, всего-то нужно, чтобы у скрипта была *своя собственная область видимости*, чтобы его переменные не попали на страницу. - -Для этого мы завернём всё его содержимое в функцию, которую тут же запустим. - -Файл `hello.js`, оформленный как модуль: - -```js -//+ run -(function() { - - // глобальная переменная нашего скрипта - var message = "Привет"; - - // функция для вывода этой переменной - function showMessage() { - alert( message ); - } - - // выводим сообщение - showMessage(); - -})(); -``` - -[edit src="hello-module"/] - -Этот скрипт при подключении к той же странице будет работать корректно. - -Будет выводиться "Привет", а затем "Пожалуйста, нажмите на кнопку". - - -### Зачем скобки вокруг функции? - -В примере выше объявление модуля выглядит так: - -```js -//+ run -(function() { - - alert( "объявляем локальные переменные, функции, работаем" ); - // ... - -}()); -``` - -В начале и в конце стоят скобки, так как иначе была бы ошибка. - -Вот, для сравнения, неверный вариант: - -```js -//+ run -function() { - // будет ошибка -}(); -``` - -Ошибка при его запуске произойдет потому, что браузер, видя ключевое слово `function` в основном потоке кода, попытается прочитать `Function Declaration`, а здесь имени нет. - -Впрочем, даже если имя поставить, то работать тоже не будет: - -```js -//+ run -function work() { - // ... -}(); // syntax error -``` - -**Дело в том, что "на месте" разрешено вызывать *только* `Function Expression`.** - -Общее правило таково: - - - -Для этого и нужны скобки -- показать, что у нас `Function Expression`, который по правилам JavaScript можно вызвать "на месте". - -Можно показать это другим способом, например поставив перед функцией оператор: - -```js -//+ run no-beautify -+function() { - alert('Вызов на месте'); -}(); - -!function() { - alert('Так тоже будет работать'); -}(); -``` - -## Экспорт значения - -Приём "модуль" используется почти во всех современных библиотеках. - -Ведь что такое библиотека? Это полезные функции, ради которых её подключают, плюс временные переменные и вспомогательные функции, которые библиотека использует внутри себя. - -Посмотрим, к примеру, на библиотеку [Lodash](http://lodash.com/), хотя могли бы и [jQuery](http://jquery.com/), там почти то же самое. - -Если её подключить, то появится специальная переменная `lodash` (короткое имя `_`), которую можно использовать как функцию, и кроме того в неё записаны различные полезных свойства, например: - - - - -Есть и много других функций, подробнее описанных в [документации](https://lodash.com/docs). - -Пример использования: - -```html - -

    Подключим библиотеку

    - - -

    Функция _.defaults() добавляет отсутствующие свойства.

    - -``` - - -Здесь нам не важно, какие, нас интересует именно как описана эта библиотека, как в ней применяется приём "модуль". - -Вот примерная выдержка из исходного файла: - -```js -//+ run no-beautify -;(function() { - -*!* - // lodash - основная функция для библиотеки -*/!* - function lodash(value) { - // ... - } - -*!* - // вспомогательная переменная -*/!* - var version = '2.4.1'; - // ... другие вспомогательные переменные и функции - -*!* - // код функции size, пока что доступен только внутри -*/!* - function size(collection) { - return Object.keys(collection).length; - } - -*!* - // присвоим в lodash size и другие функции, которые нужно вынести из модуля -*/!* - lodash.size = size - // lodash.defaults = ... - // lodash.cloneDeep = ... - -*!* - // "экспортировать" lodash наружу из модуля -*/!* - window._ = lodash; // в оригинальном коде здесь сложнее, но смысл тот же - -}()); -``` - -Внутри внешней функции: -
      -
    1. Происходит что угодно, объявляются свои локальные переменные, функции.
    2. -
    3. В `window` выносится то, что нужно снаружи.
    4. -
    - -Технически, мы могли бы вынести в `window` не только `lodash`, но и вообще все объекты и функции. На практике, как раз наоборот, всё прячут внутри модуля, глобальную область во избежание конфликтов хранят максимально чистой. - -[smart header="Зачем точка с запятой в начале?"] -В начале кода выше находится точка с запятой `;` -- это не опечатка, а особая "защита от дураков". - -Если получится, что несколько JS-файлы объединены в один (и, скорее всего, сжаты минификатором, но это не важно), и программист забыл поставить точку с запятой, то будет ошибка. - -Например, первый файл `a.js`: -```js -var a = 5 -``` - -Второй файл `lib.js`: -```js -//+ no-beautify -(function() { - // без точки с запятой в начале -})() -``` - -После объединения в один файл: - -```js -//+ run no-beautify -*!* -var a = 5 -*/!* - -// библиотека -(function() { - // ... -})(); -``` - -При запуске будет ошибка, потому что интерпретатор перед скобкой сам не вставит точку с запятой. Он просто поймёт код как `var a = 5(function ...)`, то есть пытается вызвать число `5` как функцию. - -Таковы правила языка, и поэтому рекомендуется явно ставить точку с запятой. В данном случае автор lodash ставит `;` перед функцией, чтобы предупредить эту ошибку. -[/smart] - - -## Экспорт через return - -Можно оформить модуль и чуть по-другому, например передать значение через `return`: - -```js -//+ no-beautify -var lodash = (function() { - - var version; - function assignDefaults() { ... } - - return { - defaults: function() { } - } - -})(); -``` - -Здесь, кстати, скобки вокруг внешней `function() { ... }` не обязательны, ведь функция и так объявлена внутри выражения присваивания, а значит -- является Function Expression. - -Тем не менее, лучше их ставить, для улучшения читаемости кода, чтобы было сразу видно, что это не простое присвоение функции. - -## Итого - -Модуль при помощи замыканий -- это оборачивание пакета функционала в единую внешнюю функцию, которая тут же выполняется. - -Все функции модуля будут иметь доступ к другим переменным и внутренним функциям этого же модуля через замыкание. - -Например, `defaults` из примера выше имеет доступ к `assignDefaults`. - -Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы. Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля. - -Можно придумать и много других вариаций такого подхода. В конце концов, "модуль" -- это всего лишь функция-обёртка для скрытия переменных. - - diff --git a/1-js/5-functions-closures/5-closures-module/hello-conflict.view/hello.js b/1-js/5-functions-closures/5-closures-module/hello-conflict.view/hello.js deleted file mode 100755 index 56fa804b..00000000 --- a/1-js/5-functions-closures/5-closures-module/hello-conflict.view/hello.js +++ /dev/null @@ -1,10 +0,0 @@ -// глобальная переменная нашего скрипта -var message = "Привет"; - -// функция для вывода этой переменной -function showMessage() { - alert(message); -} - -// выводим сообщение -showMessage(); \ No newline at end of file diff --git a/1-js/5-functions-closures/5-closures-module/hello-conflict.view/index.html b/1-js/5-functions-closures/5-closures-module/hello-conflict.view/index.html deleted file mode 100755 index ab1ef1e2..00000000 --- a/1-js/5-functions-closures/5-closures-module/hello-conflict.view/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/5-functions-closures/5-closures-module/hello-module.view/hello.js b/1-js/5-functions-closures/5-closures-module/hello-module.view/hello.js deleted file mode 100755 index be40a230..00000000 --- a/1-js/5-functions-closures/5-closures-module/hello-module.view/hello.js +++ /dev/null @@ -1,14 +0,0 @@ -(function() { - - // глобальная переменная нашего скрипта - var message = "Привет"; - - // функция для вывода этой переменной - function showMessage() { - alert(message); - } - - // выводим сообщение - showMessage(); - -})(); \ No newline at end of file diff --git a/1-js/5-functions-closures/5-closures-module/hello-module.view/index.html b/1-js/5-functions-closures/5-closures-module/hello-module.view/index.html deleted file mode 100755 index ab1ef1e2..00000000 --- a/1-js/5-functions-closures/5-closures-module/hello-module.view/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/5-functions-closures/6-memory-management/article.md b/1-js/5-functions-closures/6-memory-management/article.md deleted file mode 100644 index 0a8c2b36..00000000 --- a/1-js/5-functions-closures/6-memory-management/article.md +++ /dev/null @@ -1,326 +0,0 @@ -# Управление памятью в JavaScript - -Управление памятью в JavaScript обычно происходит незаметно. Мы создаём примитивы, объекты, функции... Всё это занимает память. - -Что происходит с объектом, когда он становится "не нужен"? Возможно ли "переполнение" памяти? Для ответа на эти вопросы -- залезем "под капот" интерпретатора. - -[cut] -## Управление памятью в JavaScript - -Главной концепцией управления памятью в JavaScript является принцип *достижимости* (англ. reachability). - -
      -
    1. Определённое множество значений считается достижимым изначально, в частности: - - -Эти значения гарантированно хранятся в памяти. Мы будем называть их *корнями*. -
    2. -
    3. **Любое другое значение сохраняется в памяти лишь до тех пор, пока доступно из корня по ссылке или цепочке ссылок.**
    4. -
    - -Для очистки памяти от недостижимых значений в браузерах используется автоматический Сборщик мусора (англ. Garbage collection, GC), встроенный в интерпретатор, который наблюдает за объектами и время от времени удаляет недостижимые. - -Самая простая ситуация здесь с примитивами. При присвоении они копируются целиком, ссылок на них не создаётся, так что если в переменной была одна строка, а её заменили на другую, то предыдущую можно смело выбросить. - -Именно объекты требуют специального "сборщика мусора", который наблюдает за ссылками, так как на один объект может быть много ссылок из разных переменных и, при перезаписи одной из них, объект может быть всё ещё доступен из другой. - -Далее мы посмотрим ряд примеров, которые помогут в этом разобраться. - -### Достижимость и наличие ссылок - -Есть одно упрощение для работы с памятью: "значение остаётся в памяти, пока на него есть ссылка". - -Но такое упрощение будет верным лишь в одну сторону. - - - -## Алгоритм сборки мусора - -Сборщик мусора идёт от корня по ссылкам и запоминает все найденные объекты. По окончанию -- он смотрит, какие объекты в нём отсутствуют и удаляет их. - -Например, рассмотрим пример объекта "семья": - -```js -function marry(man, woman) { - woman.husband = man; - man.wife = woman; - - return { - father: man, - mother: woman - } -} - -var family = marry({ - name: "Василий" -}, { - name: "Мария" -}); -``` - -Функция `marry` принимает два объекта, даёт им ссылки друг на друга и возвращает третий, содержащий ссылки на оба. - -Получившийся объект `family` можно изобразить так: - - - -Здесь стрелочками показаны ссылки, а вот свойство `name` ссылкой не является, там хранится примитив, поэтому оно внутри самого объекта. - -Чтобы запустить сборщик мусора, удалим две ссылки: - -``` -delete family.father; -delete family.mother.husband; -``` - -Обратим внимание, удаление только одной из этих ссылок ни к чему бы не привело. Пока до объекта можно добраться из корня `window`, объект остаётся жив. - -А если две, то получается, что от бывшего `family.father` ссылки выходят, но в него -- ни одна не идёт: - - - -**Совершенно неважно, что из объекта выходят какие-то ссылки, они не влияют на достижимость этого объекта.** - -Бывший `family.father` стал недостижимым и будет удалён вместе со своми данными, которые также более недоступны из программы. - - - -А теперь -- рассмотрим более сложный случай. Что будет, если удалить главную ссылку `family`? - -Исходный объект -- тот же, что и в начале, а затем: - -```js -window.family = null; -``` - -Результат: - - - -Как видим, объекты в конструкции всё ещё связаны между собой. Однако, поиск от корня их не находит, они не достижимы, и значит сборщик мусора удалит их из памяти. - -[smart header="Оптимизации"] -Проблема описанного алгоритма -- в больших задержках. Если объектов много, то на поиск всех достижимых уйдёт довольно много времени. А ведь выполнение скрипта при этом должно быть остановлено, уже просканированные объекты не должны поменяться до окончания процесса. Получатся небольшие, но неприятные паузы-зависания в работе скрипта. - -Поэтому современные интерпретаторы применяют различные оптимизации. - -Самая частая -- это деление объектов на два вида "старые" и "новые". Для каждого типа выделяется своя область памяти. Каждый объект создаётся в "новой" области и, если прожил достаточно долго, мигрирует в старую. "Новая" область обычно небольшая. Она очищается часто. "Старая" -- редко. - -На практике получается эффективно, обычно большинство объектов создаются и умирают почти сразу, к примеру, служа локальными переменными функции: -```js -function showTime() { - alert( new Date() ); // этот объект будет создан и умрёт сразу -} -``` - -Если вы знаете низкоуровневые языки программирования, то более подробно об организации сборки мусора в V8 можно почитать, например, в статье [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). - -[/smart] - -## Замыкания - -Объекты переменных, о которых шла речь ранее, в главе про замыкания, также подвержены сборке мусора. Они следуют тем же правилам, что и обычные объекты. - -Объект переменных внешней функции существует в памяти до тех пор, пока существует хоть одна внутренняя функция, ссылающаяся на него через свойство `[[Scope]]`. - -Например: - - - -### Оптимизация в V8 и её последствия - -Современные JS-движки делают оптимизации замыканий по памяти. Они анализируют использование переменных и в случае, когда переменная из замыкания абсолютно точно не используется, удаляют её. - -В коде выше переменная `value` никак не используется. Поэтому она будет удалена из памяти. - -**Важный побочный эффект в V8 (Chrome, Opera) состоит в том, что удалённая переменная станет недоступна и при отладке!** - -Попробуйте запустить пример ниже с открытой консолью Chrome. Когда он остановится, в консоли наберите `alert(value)`. - -```js -//+ run -function f() { - var value = Math.random(); - - function g() { - debugger; // выполните в консоли alert( value ); Нет такой переменной! - } - - return g; -} - -var g = f(); -g(); -``` - -Как вы могли увидеть -- нет такой переменной! Недоступна она изнутри `g`. Интерпретатор решил, что она нам не понадобится и удалил. - -Это может привести к забавным казусам при отладке, вплоть до того что вместо этой переменной будет другая, внешняя: - -```js -//+ run -var value = "Сюрприз"; - -function f() { - var value = "самое близкое значение"; - - function g() { - debugger; // выполните в консоли alert( value ); Сюрприз! - } - - return g; -} - -var g = f(); -g(); -``` - -[warn header="Ещё увидимся"] -Об этой особенности важно знать. Если вы отлаживаете под Chrome/Opera, то наверняка рано или поздно с ней встретитесь! - -Это не глюк отладчика, а особенность работы V8, которая, возможно, будет когда-нибудь изменена. Вы всегда сможете проверить, не изменилось ли чего, запустив примеры на этой странице. -[/warn] - -## Влияние управления памятью на скорость - -На создание новых объектов и их удаление тратится время. Это важно иметь в виду в случае, когда важна производительность. - -В качестве примера рассмотрим рекурсию. При вложенных вызовах каждый раз создаётся новый объект с переменными и помещается в стек. Потом память из-под него нужно очистить. Поэтому рекурсивный код будет всегда медленнее использующего цикл, но насколько? - -Пример ниже тестирует сложение чисел до данного через рекурсию по сравнению с обычным циклом: - -```js -//+ run -function sumTo(n) { // обычный цикл 1+2+...+n - var result = 0; - for (var i = 1; i <= n; i++) { - result += i; - } - return result; -} - -function sumToRec(n) { // рекурсия sumToRec(n) = n+SumToRec(n-1) - return n == 1 ? 1 : n + sumToRec(n - 1); -} - -var timeLoop = performance.now(); -for (var i = 1; i < 1000; i++) sumTo(1000); // цикл -timeLoop = performance.now() - timeLoop; - -var timeRecursion = performance.now(); -for (var i = 1; i < 1000; i++) sumToRec(1000); // рекурсия -timeRecursion = performance.now() - timeRecursion; - -alert( "Разница в " + (timeRecursion / timeLoop) + " раз" ); -``` - -Различие в скорости на таком примере может составлять, в зависимости от интерпретатора, 2-10 раз. - -Вообще, этот пример -- не показателен. Ещё раз обращаю ваше внимание на то, что такие искусственные "микротесты" часто врут. Правильно их делать -- отдельная наука, которая выходит за рамки этой главы. Но и на практике ускорение в 2-10 раз оптимизацией по количеству объектов (и вообще, любых значений) -- отнюдь не миф, а вполне достижимо. - -В реальной жизни в большинстве ситуаций такая оптимизация несущественна, просто потому что "JavaScript и так достаточно быстр". Но она может быть эффективной для "узких мест" кода. diff --git a/1-js/5-functions-closures/6-memory-management/family-no-family.png b/1-js/5-functions-closures/6-memory-management/family-no-family.png deleted file mode 100644 index f37ef2a6..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-family.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png b/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png deleted file mode 100644 index 9764e2e8..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-family@2x.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father-2.png b/1-js/5-functions-closures/6-memory-management/family-no-father-2.png deleted file mode 100644 index 663a361e..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father-2.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png b/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png deleted file mode 100644 index b0062c97..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father-2@2x.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father.png b/1-js/5-functions-closures/6-memory-management/family-no-father.png deleted file mode 100644 index 3b410731..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png b/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png deleted file mode 100644 index 2eaa35f2..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family-no-father@2x.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family.png b/1-js/5-functions-closures/6-memory-management/family.png deleted file mode 100644 index 58edaa55..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family.png and /dev/null differ diff --git a/1-js/5-functions-closures/6-memory-management/family@2x.png b/1-js/5-functions-closures/6-memory-management/family@2x.png deleted file mode 100644 index 7d9e98a5..00000000 Binary files a/1-js/5-functions-closures/6-memory-management/family@2x.png and /dev/null differ diff --git a/1-js/5-functions-closures/7-with/1-with-function/solution.md b/1-js/5-functions-closures/7-with/1-with-function/solution.md deleted file mode 100644 index d4a5284b..00000000 --- a/1-js/5-functions-closures/7-with/1-with-function/solution.md +++ /dev/null @@ -1,21 +0,0 @@ -Вторая (`2`), т.к. при обращении к любой переменной внутри `with` -- она ищется прежде всего в объекте. - -Соответственно, будет выведено `2`: - -```js -//+ run -function f() { - alert(1) -} - -var obj = { - f: function() { - alert(2) - } -}; - -with(obj) { - f(); -} -``` - diff --git a/1-js/5-functions-closures/7-with/1-with-function/task.md b/1-js/5-functions-closures/7-with/1-with-function/task.md deleted file mode 100644 index bdf540ec..00000000 --- a/1-js/5-functions-closures/7-with/1-with-function/task.md +++ /dev/null @@ -1,22 +0,0 @@ -# With + функция - -[importance 5] - -Какая из функций будет вызвана? - -```js -function f() { - alert(1) -} - -var obj = { - f: function() { - alert(2) - } -}; - -with(obj) { - f(); -} -``` - diff --git a/1-js/5-functions-closures/7-with/2-with-variables/solution.md b/1-js/5-functions-closures/7-with/2-with-variables/solution.md deleted file mode 100644 index 531c8a81..00000000 --- a/1-js/5-functions-closures/7-with/2-with-variables/solution.md +++ /dev/null @@ -1,22 +0,0 @@ -Выведет `3`. - -**Конструкция `with` не создаёт области видимости,** её создают только функции. Поэтому объявление `var b` внутри конструкции работает также, как если бы оно было вне её. - -Код в задаче эквивалентен такому: - -```js -//+ run -var a = 1; -*!* -var b; -*/!* - -var obj = { - b: 2 -} - -with(obj) { - alert( a + b ); -} -``` - diff --git a/1-js/5-functions-closures/7-with/2-with-variables/task.md b/1-js/5-functions-closures/7-with/2-with-variables/task.md deleted file mode 100644 index 13705a1d..00000000 --- a/1-js/5-functions-closures/7-with/2-with-variables/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# With + переменные - -[importance 5] - -Что выведет этот код? - -```js -var a = 1; - -var obj = { - b: 2 -}; - -with(obj) { - var b; - alert( a + b ); -} -``` - diff --git a/1-js/5-functions-closures/7-with/article.md b/1-js/5-functions-closures/7-with/article.md deleted file mode 100644 index 9c6434ff..00000000 --- a/1-js/5-functions-closures/7-with/article.md +++ /dev/null @@ -1,190 +0,0 @@ -# Устаревшая конструкция "with" - -Конструкция `with` позволяет использовать в качестве области видимости для переменных произвольный объект. - -В современном JavaScript от этой конструкции отказались. С `use strict` она не работает, но её ещё можно найти в старом коде, так что стоит познакомиться с ней, чтобы если что -- понимать, о чём речь. - -[cut] -Синтаксис: - -```js -with(obj) { - ...код... -} -``` - -Любое обращение к переменной внутри `with` сначала ищет её среди свойств `obj`, а только потом -- вне `with`. - -## Пример - -В примере ниже переменная будет взята не из глобальной области, а из `obj`: - -```js -//+ run -var a = 5; - -var obj = { - a: 10 -}; - -*!* -with(obj) { - alert( a ); // 10, из obj - } -*/!* -``` - -Попробуем получить переменную, которой в `obj` нет: - -```js -//+ run -var b = 1; - -var obj = { - a: 10 -}; - -*!* -with(obj) { - alert( b ); // 1, из window - } -*/!* -``` - -Здесь интерпретатор сначала проверяет наличие `obj.b`, не находит и идет вне `with`. - -Особенно забавно выглядит применение вложенных `with`: - -```js -//+ run -var obj = { - weight: 10, - size: { - width: 5, - height: 7 - } -}; - -with(obj) { - with(size) { // size будет взят из obj -*!* - alert( width * height / weight ); // width,height из size, weight из obj -*/!* - } -} -``` - -Свойства из разных объектов используются как обычные переменные... Магия! Порядок поиска переменных в выделенном коде: `size => obj => window`. - - -## Изменения переменной - -При использовании `with`, как и во вложенных функциях -- переменная изменяется в той области, где была найдена. - -Например: - -```js -//+ run -var obj = { - a: 10 -} - -*!* -with(obj) { - a = 20; - } -*/!* -alert( obj.a ); // 20, переменная была изменена в объекте -``` - -## Почему отказались от with? - -Есть несколько причин. - -
      -
    1. В современном стандарте `JavaScript` отказались от `with`, потому что конструкция `with` подвержена ошибкам и непрозрачна. - -Проблемы возникают в том случае, когда в `with(obj)` присваивается переменная, которая по замыслу должна быть в свойствах `obj`, но ее там нет. - -Например: - -```js -//+ run -var obj = { - weight: 10 -}; - -with(obj) { - weight = 20; // (1) - size = 35; // (2) -} - -alert( obj.size ); -alert( window.size ); -``` - -В строке `(2)` присваивается свойство, отсутствующее в `obj`. В результате интерпретатор, не найдя его, создает новую глобальную переменную `window.size`. - -Такие ошибки редки, но очень сложны в отладке, особенно если `size` изменилась не в `window`, а где-нибудь во внешнем `LexicalEnvironment`. -
    2. -
    3. Еще одна причина -- алгоритмы сжатия JavaScript не любят `with`. Перед выкладкой на сервер JavaScript сжимают. Для этого есть много инструментов, например [Closure Compiler](http://code.google.com/intl/ru-RU/closure/compiler/) и [UglifyJS](https://github.com/mishoo/UglifyJS). Обычно они переименовывают локальные переменные в более короткие имена, но не свойства объектов. С конструкцией `with` до запуска кода непонятно -- откуда будет взята переменная. Поэтому выходит, что, на всякий случай (если это свойство), лучше её не переименовывать. Таким образом, качество сжатия кода страдает.
    4. -
    5. Ну и, наконец, производительность -- усложнение поиска переменной из-за `with` влечет дополнительные накладные расходы. - -Современные движки применяют много внутренних оптимизаций, ряд которых не могут быть применены к коду, в котором есть `with`. - -Вот, к примеру, запустите этот код в современном браузере. Производительность функции `fast` существенно отличается `slow` с пустым(!) `with`. И дело тут именно в `with`, т.к. наличие этой конструкции препятствует оптимизации. - -```js -//+ run -var i = 0; - -function fast() { - i++; -} - -function slow() { - with(i) {} - i++; -} - - -var time = performance.now(); -while (i < 1000000) fast(); -alert( "Без with: " + (performance.now() - time) ); - -var time = performance.now(); -i = 0; -while (i < 1000000) slow(); -alert( "С with: " + (performance.now() - time) ); -``` - -
    6. -
    - -### Замена with - -Вместо `with` рекомендуется использовать временную переменную, например: - -```js -/* вместо -with(elem.style) { - top = '10px'; - left = '20px'; -} -*/ - -var s = elem.style; - -s.top = '10px'; -s.left = '0'; -``` - -Это не так элегантно, но убирает лишний уровень вложенности и абсолютно точно понятно, что будет происходить и куда присвоятся свойства. - -## Итого - - - diff --git a/1-js/5-functions-closures/index.md b/1-js/5-functions-closures/index.md deleted file mode 100644 index faff5532..00000000 --- a/1-js/5-functions-closures/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Замыкания, область видимости - -Понимание "области видимости" и "замыканий" -- ключевое в изучении JavaScript, без них "каши не сваришь". - -В этом разделе мы более глубоко изучаем переменные и функции -- и замыкания в том числе. \ No newline at end of file diff --git a/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md b/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md deleted file mode 100644 index dad88eea..00000000 --- a/1-js/6-objects-more/1-object-methods/1-call-array-this/solution.md +++ /dev/null @@ -1,15 +0,0 @@ -Вызов `arr[2]()` -- это обращение к методу объекта `obj[method]()`, в роли `obj` выступает `arr`, а в роли метода: `2`. - -Поэтому, как это бывает при вызове функции как метода, функция `arr[2]` получит `this = arr` и выведет массив: - -```js -//+ run -var arr = ["a", "b"]; - -arr.push(function() { - alert( this ); -}) - -arr[2](); // "a","b",function -``` - diff --git a/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md b/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md deleted file mode 100644 index 6ab74bfc..00000000 --- a/1-js/6-objects-more/1-object-methods/1-call-array-this/task.md +++ /dev/null @@ -1,16 +0,0 @@ -# Вызов в контексте массива - -[importance 5] - -Каким будет результат? Почему? - -```js -var arr = ["a", "b"]; - -arr.push(function() { - alert( this ); -}) - -arr[2](); // ? -``` - diff --git a/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md b/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md deleted file mode 100644 index b030823d..00000000 --- a/1-js/6-objects-more/1-object-methods/2-check-syntax/solution.md +++ /dev/null @@ -1,27 +0,0 @@ -**Ошибка**! - -Попробуйте: - -```js -//+ run -var obj = { - go: function() { - alert(this) - } -} - -(obj.go)() // error! -``` - -Причем сообщение об ошибке в большинстве браузеров не даёт понять, что на самом деле не так. - -**Ошибка возникла из-за того, что после объявления `obj` пропущена точка с запятой.** - -JavaScript игнорирует перевод строки перед скобкой `(obj.go)()` и читает этот код как: - -```js -//+ no-beautify -var obj = { go:... }(obj.go)() -``` - -Интерпретатор попытается вычислить это выражение, которое обозначает вызов объекта `{ go: ... }` как функции с аргументом `(obj.go)`. При этом, естественно, возникнет ошибка. diff --git a/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md b/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md deleted file mode 100644 index 44a93141..00000000 --- a/1-js/6-objects-more/1-object-methods/2-check-syntax/task.md +++ /dev/null @@ -1,16 +0,0 @@ -# Проверка синтаксиса - -[importance 2] - -Каков будет результат этого кода? - -```js -//+ no-beautify -var obj = { - go: function() { alert(this) } -} - -(obj.go)() -``` - -P.S. Есть подвох :) \ No newline at end of file diff --git a/1-js/6-objects-more/1-object-methods/3-why-this/solution.md b/1-js/6-objects-more/1-object-methods/3-why-this/solution.md deleted file mode 100644 index 693d3944..00000000 --- a/1-js/6-objects-more/1-object-methods/3-why-this/solution.md +++ /dev/null @@ -1,32 +0,0 @@ -
      -
    1. Обычный вызов функции в контексте объекта.
    2. -
    3. То же самое, скобки ни на что не влияют.
    4. -
    5. Здесь не просто вызов `obj.method()`, а более сложный вызов вида `(выражение).method()`. Такой вызов работает, как если бы он был разбит на две строки: - -```js -//+ no-beautify -f = obj.go; // сначала вычислить выражение -f(); // потом вызвать то, что получилось -``` - -При этом `f()` выполняется как обычная функция, без передачи `this`. -
    6. -
    7. Здесь также слева от точки находится выражение, вызов аналогичен двум строкам.
    8. -
    - -В спецификации это объясняется при помощи специального внутреннего типа [Reference Type](http://es5.github.com/x8.html#x8.7). - -Если подробнее -- то `obj.go()` состоит из двух операций: -
      -
    1. Сначала получить свойство `obj.go`.
    2. -
    3. Потом вызвать его как функцию.
    4. -
    - -Но откуда на шаге 2 получить `this`? Как раз для этого операция получения свойства `obj.go` возвращает значение особого типа `Reference Type`, который в дополнение к свойству `go` содержит информацию об `obj`. Далее, на втором шаге, вызов его при помощи скобок `()` правильно устанавливает `this`. - -**Любые другие операции, кроме вызова, превращают `Reference Type` в обычный тип, в данном случае -- функцию `go` (так уж этот тип устроен).** - -Поэтому получается, что `(method = obj.go)` присваивает в переменную `method` функцию `go`, уже без всякой информации об объекте `obj`. - -Аналогичная ситуация и в случае `(4)`: оператор ИЛИ `||` делает из `Reference Type` обычную функцию. - diff --git a/1-js/6-objects-more/1-object-methods/3-why-this/task.md b/1-js/6-objects-more/1-object-methods/3-why-this/task.md deleted file mode 100644 index 486c3eb7..00000000 --- a/1-js/6-objects-more/1-object-methods/3-why-this/task.md +++ /dev/null @@ -1,26 +0,0 @@ -# Почему this присваивается именно так? - -[importance 3] - -Вызовы `(1)` и `(2)` в примере ниже работают не так, как `(3)` и `(4)`: - -```js -//+ run no-beautify -"use strict" - -var obj, f; - -obj = { - go: function() { alert(this); } -}; - -obj.go(); // (1) object - -(obj.go)(); // (2) object - -(method = obj.go)(); // (3) undefined - -(obj.go || obj.stop)(); // (4) undefined -``` - -В чём дело? Объясните логику работы `this`. diff --git a/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md b/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md deleted file mode 100644 index 82f562d9..00000000 --- a/1-js/6-objects-more/1-object-methods/4-object-property-this/solution.md +++ /dev/null @@ -1,22 +0,0 @@ -**Ответ: пустая строка.** - -```js -//+ run -var name = ""; - -var user = { - name: "Василий", - -*!* - export: this // (*) -*/!* -}; - -alert( user.export.name ); -``` - -Объявление объекта само по себе не влияет на `this`. Никаких функций, которые могли бы повлиять на контекст, здесь нет. - -Так как код находится вообще вне любых функций, то `this` в нём равен `window` (при `use strict` было бы `undefined`). - -Получается, что в строке `(*)` мы имеем `export: window`, так что далее `alert(user.export.name)` выводит свойство `window.name`, то есть глобальную переменную `name`, которая равна пустой строке. diff --git a/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md b/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md deleted file mode 100644 index 6b2ec0ac..00000000 --- a/1-js/6-objects-more/1-object-methods/4-object-property-this/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Значение this в объявлении объекта - -[importance 5] - -Что выведет `alert` в этом коде? Почему? - -```js -var name = ""; - -var user = { - name: "Василий", - - export: this -}; - -alert( user.export.name ); -``` - diff --git a/1-js/6-objects-more/1-object-methods/5-return-this/solution.md b/1-js/6-objects-more/1-object-methods/5-return-this/solution.md deleted file mode 100644 index b34977b4..00000000 --- a/1-js/6-objects-more/1-object-methods/5-return-this/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -**Ответ: `Василий`.** - -Вызов `user.export()` использует `this`, который равен объекту до точки, то есть внутри `user.export()` строка `return this` возвращает объект `user`. - -В итоге выводится свойство `name` объекта `user`, равное `"Василий"`. diff --git a/1-js/6-objects-more/1-object-methods/5-return-this/task.md b/1-js/6-objects-more/1-object-methods/5-return-this/task.md deleted file mode 100644 index 03ebd8cb..00000000 --- a/1-js/6-objects-more/1-object-methods/5-return-this/task.md +++ /dev/null @@ -1,21 +0,0 @@ -# Возврат this - -[importance 5] - -Что выведет `alert` в этом коде? Почему? - -```js -var name = ""; - -var user = { - name: "Василий", - - export: function() { - return this; - } - -}; - -alert( user.export().name ); -``` - diff --git a/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md b/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md deleted file mode 100644 index 2df08422..00000000 --- a/1-js/6-objects-more/1-object-methods/6-return-object-this/solution.md +++ /dev/null @@ -1,26 +0,0 @@ -**Ответ: `Василий`.** - -Во время выполнения `user.export()` значение `this = user`. - -При создании объекта `{ value: this }`, в свойство `value` копируется ссылка на текущий контекст, то есть на `user`. - -Получается что `user.export().value == user`. - - -```js -//+ run -var name = ""; - -var user = { - name: "Василий", - - export: function() { - return { - value: this - }; - } - -}; - -alert( user.export().value == user ); // true -``` \ No newline at end of file diff --git a/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md b/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md deleted file mode 100644 index b78e6553..00000000 --- a/1-js/6-objects-more/1-object-methods/6-return-object-this/task.md +++ /dev/null @@ -1,23 +0,0 @@ -# Возврат объекта с this - -[importance 5] - -Что выведет `alert` в этом коде? Почему? - -```js -var name = ""; - -var user = { - name: "Василий", - - export: function() { - return { - value: this - }; - } - -}; - -alert( user.export().value.name ); -``` - diff --git a/1-js/6-objects-more/1-object-methods/7-calculator/_js.view/solution.js b/1-js/6-objects-more/1-object-methods/7-calculator/_js.view/solution.js deleted file mode 100644 index 5f276b9c..00000000 --- a/1-js/6-objects-more/1-object-methods/7-calculator/_js.view/solution.js +++ /dev/null @@ -1,14 +0,0 @@ -var calculator = { - sum: function() { - return this.a + this.b; - }, - - mul: function() { - return this.a * this.b; - }, - - read: function() { - this.a = +prompt('a?', 0); - this.b = +prompt('b?', 0); - } -} \ No newline at end of file diff --git a/1-js/6-objects-more/1-object-methods/7-calculator/_js.view/test.js b/1-js/6-objects-more/1-object-methods/7-calculator/_js.view/test.js deleted file mode 100644 index cd2c9b05..00000000 --- a/1-js/6-objects-more/1-object-methods/7-calculator/_js.view/test.js +++ /dev/null @@ -1,22 +0,0 @@ -sinon.stub(window, "prompt"); - -prompt.onCall(0).returns("2"); -prompt.onCall(1).returns("3"); - -describe("calculator", function() { - before(function() { - calculator.read(); - }); - - it("при вводе 2 и 3 сумма равна 5", function() { - assert.equal(calculator.sum(), 5); - }); - - it("при вводе 2 и 3 произведение равно 6", function() { - assert.equal(calculator.mul(), 6); - }); -}); - -after(function() { - prompt.restore(); -}); \ No newline at end of file diff --git a/1-js/6-objects-more/1-object-methods/7-calculator/solution.md b/1-js/6-objects-more/1-object-methods/7-calculator/solution.md deleted file mode 100644 index 1849cc94..00000000 --- a/1-js/6-objects-more/1-object-methods/7-calculator/solution.md +++ /dev/null @@ -1,24 +0,0 @@ - - -```js -//+ run demo -var calculator = { - sum: function() { - return this.a + this.b; - }, - - mul: function() { - return this.a * this.b; - }, - - read: function() { - this.a = +prompt('a?', 0); - this.b = +prompt('b?', 0); - } -} - -calculator.read(); -alert( calculator.sum() ); -alert( calculator.mul() ); -``` - diff --git a/1-js/6-objects-more/1-object-methods/7-calculator/task.md b/1-js/6-objects-more/1-object-methods/7-calculator/task.md deleted file mode 100644 index 5e5790fc..00000000 --- a/1-js/6-objects-more/1-object-methods/7-calculator/task.md +++ /dev/null @@ -1,22 +0,0 @@ -# Создайте калькулятор - -[importance 5] - -Создайте объект `calculator` с тремя методами: - - -```js -var calculator = { - ...ваш код... -} - -calculator.read(); -alert( calculator.sum() ); -alert( calculator.mul() ); -``` - -[demo /] diff --git a/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md b/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md deleted file mode 100644 index 8d89b88e..00000000 --- a/1-js/6-objects-more/1-object-methods/8-chain-calls/solution.md +++ /dev/null @@ -1,23 +0,0 @@ -Решение состоит в том, чтобы каждый раз возвращать текущий объект. Это делается добавлением `return this` в конце каждого метода: - -```js -//+ run -var ladder = { - step: 0, - up: function() { - this.step++; - return this; - }, - down: function() { - this.step--; - return this; - }, - showStep: function() { - alert( this.step ); - return this; - } -} - -ladder.up().up().down().up().down().showStep(); // 1 -``` - diff --git a/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md b/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md deleted file mode 100644 index 73dc4e8e..00000000 --- a/1-js/6-objects-more/1-object-methods/8-chain-calls/task.md +++ /dev/null @@ -1,40 +0,0 @@ -# Цепочка вызовов - -[importance 2] - -Есть объект "лестница" ladder: - -```js -var ladder = { - step: 0, - up: function() { // вверх по лестнице - this.step++; - }, - down: function() { // вниз по лестнице - this.step--; - }, - showStep: function() { // вывести текущую ступеньку - alert( this.step ); - } -}; -``` - -Сейчас, если нужно последовательно вызвать несколько методов объекта, это можно сделать так: - -```js -ladder.up(); -ladder.up(); -ladder.down(); -ladder.showStep(); // 1 -``` - -Модифицируйте код методов объекта, чтобы вызовы можно было делать цепочкой, вот так: - -```js -ladder.up().up().down().up().down().showStep(); // 1 -``` - -Как видно, такая запись содержит "меньше букв" и может быть более наглядной. - -Такой подход называется "чейнинг" (chaining) и используется, например, во фреймворке jQuery. - diff --git a/1-js/6-objects-more/1-object-methods/article.md b/1-js/6-objects-more/1-object-methods/article.md deleted file mode 100644 index 4a70f595..00000000 --- a/1-js/6-objects-more/1-object-methods/article.md +++ /dev/null @@ -1,244 +0,0 @@ -# Методы объектов, this - -До этого мы говорили об объекте лишь как о хранилище значений. Теперь пойдём дальше и поговорим об объектах как о сущностях со своими функциями ("методами"). -[cut] - -## Методы у объектов - -При объявлении объекта можно указать свойство-функцию, например: - -```js -//+ run -var user = { - name: 'Василий', - -*!* - // метод -*/!* - sayHi: function() { - alert( 'Привет!' ); - } - -}; - -*!* -// Вызов -user.sayHi(); -*/!* -``` - -Свойства-функции называют "методами" объектов. Их можно добавлять и удалять в любой момент, в том числе и явным присваиванием: - -```js -//+ run -var user = { - name: 'Василий' -}; - -*!* -user.sayHi = function() { // присвоили метод после создания объекта - alert('Привет!'); -}; -*/!* - -// Вызов метода: -*!*user.sayHi();*/!* -``` - -## Доступ к объекту через this - -Для полноценной работы метод должен иметь доступ к данным объекта. В частности, вызов `user.sayHi()` может захотеть вывести имя пользователя. - -**Для доступа к текущему объекту из метода используется ключевое слово `this`**. - -Значением `this` является объект перед "точкой", в контексте которого вызван метод, например: - -```js -//+ run -var user = { - name: 'Василий', - - sayHi: function() { - alert( *!*this.name*/!* ); - } -}; - -user.sayHi(); // sayHi в контексте user -``` - -Здесь при выполнении функции `user.sayHi()` в `this` будет храниться ссылка на текущий объект `user`. - -Вместо `this` внутри `sayHi` можно было бы обратиться к объекту, используя переменную `user`: - -```js -... - sayHi: function() { - alert( *!*user.name*/!* ); - } -... -``` - -...Однако, такое решение нестабильно. Если мы решим скопировать объект в другую переменную, например `admin = user`, а в переменную `user` записать что-то другое -- обращение будет совсем не по адресу: - -```js -//+ run -var user = { - name: 'Василий', - - sayHi: function() { - alert( *!*user.name*/!* ); // приведёт к ошибке - } -}; - -var admin = user; -user = null; - -admin.sayHi(); // упс! внутри sayHi обращение по старому имени, ошибка! -``` - -Использование `this` гарантирует, что функция работает именно с тем объектом, в контексте которого вызвана. - -Через `this` метод может не только обратиться к любому свойству объекта, но и передать куда-то ссылку на сам объект целиком: - -```js -//+ run no-beautify -var user = { - name: 'Василий', - -*!* - sayHi: function() { - showName(this); // передать текущий объект в showName - } -*/!* -}; - -function showName(namedObj) { - alert( namedObj.name ); -} - -user.sayHi(); // Василий -``` - -## Подробнее про this - -Любая функция может иметь в себе `this`. Совершенно неважно, объявлена ли она в объекте или отдельно от него. - -Значение `this` называется *контекстом вызова* и будет определено в момент вызова функции. - -Например, такая функция, объявленная без объекта, вполне допустима: - -```js -function sayHi() { - alert( *!*this.firstName*/!* ); -} -``` - -Эта функция ещё не знает, каким будет `this`. Это выяснится при выполнении программы. - -**Если одну и ту же функцию запускать в контексте разных объектов, она будет получать разный `this`:** - -```js -//+ run no-beautify -var user = { firstName: "Вася" }; -var admin = { firstName: "Админ" }; - -function func() { - alert( this.firstName ); -} - -user.f = func; -admin.g = func; - -*!* -// this равен объекту перед точкой: -user.f(); // Вася -admin.g(); // Админ -admin['g'](); // Админ (не важно, доступ к объекту через точку или квадратные скобки) -*/!* -``` - -Итак, значение `this` не зависит от того, как функция была создана, оно определяется исключительно в момент вызова. - -## Значение this при вызове без контекста - -Если функция использует `this` -- это подразумевает работу с объектом. Но и прямой вызов `func()` технически возможен. - -Как правило, такая ситуация возникает при ошибке в разработке. - -При этом `this` получает значение `window`, глобального объекта: - -```js -//+ run -function func() { - alert( this ); // выведет [object Window] или [object global] -} - -func(); -``` - -Таково поведение в старом стандарте. - -А в режиме `use strict` вместо глобального объекта `this` будет `undefined`: - -```js -//+ run -function func() { - "use strict"; - alert( this ); // выведет undefined (кроме IE9-) -} - -func(); -``` - -Обычно если в функции используется `this`, то она, всё же, служит для вызова в контексте объекта, так что такая ситуация -- скорее исключение. - -## Ссылочный тип - -Контекст `this` никак не привязан к функции, даже если она создана в объявлении объекта. Чтобы `this` передался, нужно вызвать функцию именно через точку (или квадратные скобки). - -Любой более хитрый вызов приведёт к потере контекста, например: - -```js -//+ run no-beautify -var user = { - name: "Вася", - hi: function() { alert(this.name); }, - bye: function() { alert("Пока"); } -}; - -user.hi(); // Вася (простой вызов работает) - -*!* -// а теперь вызовем user.hi или user.bye в зависимости от имени -(user.name == "Вася" ? user.hi : user.bye)(); // undefined -*/!* -``` - -В последней строке примера метод получен в результате выполнения тернарного оператора и тут же вызван. Но `this` при этом теряется. - -Если хочется понять, почему, то причина кроется в деталях работы вызова `obj.method()`. - -Он ведь, на самом деле, состоит из двух независимых операций: точка `.` -- получение свойства и скобки `()` -- его вызов (предполагается, что это функция). - -Функция, как мы говорили раньше, сама по себе не запоминает контекст. Чтобы "донести его" до скобок, JavaScript применяет "финт ушами" -- точка возвращает не функцию, а значение специального "ссылочного" типа [Reference Type](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-reference-specification-type). - -Этот тип представляет собой связку "base-name-strict", где: - - -То есть, ссылочный тип (Reference Type) -- это своеобразное "три-в-одном". Он существует исключительно для целей спецификации, мы его не видим, поскольку любой оператор тут же от него избавляется: - - - -Поэтому любая операция над результатом операции получения свойства, кроме вызова, приводит к потере контекста. - -Аналогично работает и получение свойства через квадратные скобки `obj[method]`. - - - diff --git a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md b/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md deleted file mode 100644 index b7a70199..00000000 --- a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/solution.md +++ /dev/null @@ -1,15 +0,0 @@ -Если с одной стороны -- объект, а с другой -- нет, то сначала приводится объект. - -В данном случае сравнение означает численное приведение. У массивов нет `valueOf`, поэтому вызывается `toString`, который возвращает список элементов через запятую. - -В данном случае, элемент только один - он и возвращается. Так что `['x']` становится `'x'`. Получилось `'x' == 'x'`, верно. - -P.S. -По той же причине верны равенства: - -```js -//+ run -alert( ['x', 'y'] == 'x,y' ); // true -alert( [] == '' ); // true -``` - diff --git a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md b/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md deleted file mode 100644 index 190bbeb4..00000000 --- a/1-js/6-objects-more/2-object-conversion/1-array-equals-string/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# ['x'] == 'x' - -[importance 5] - -Почему результат `true` ? - -```js -//+ run -alert( ['x'] == 'x' ); -``` - diff --git a/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/solution.md b/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/solution.md deleted file mode 100644 index 9b2108d0..00000000 --- a/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/solution.md +++ /dev/null @@ -1,10 +0,0 @@ -# Первый alert(foo) - -Возвращает строковое представление объекта, используя `toString`, т.е. `"foo"`. - -# Второй alert(foo + 1) -Оператор `'+'` преобразует объект к примитиву, используя `valueOf`, так что результат: `3`. - -# Третий alert(foo + '3') - -То же самое, что и предыдущий случай, объект превращается в примитив `2`. Затем происходит сложение `2 + '3'`. Оператор `'+'` при сложении чего-либо со строкой приводит и второй операнд к строке, а затем применяет конкатенацию, так что результат -- строка `"23"`. \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md b/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md deleted file mode 100644 index 86bd17cb..00000000 --- a/1-js/6-objects-more/2-object-conversion/2-tostring-valueof/task.md +++ /dev/null @@ -1,24 +0,0 @@ -# Преобразование - -[importance 5] - -Объявлен объект с `toString` и `valueOf`. - -Какими будут результаты `alert`? - -```js -var foo = { - toString: function() { - return 'foo'; - }, - valueOf: function() { - return 2; - } -}; - -alert( foo ); -alert( foo + 1 ); -alert( foo + "3" ); -``` - -Подумайте, прежде чем ответить. \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md b/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md deleted file mode 100644 index ef69d31c..00000000 --- a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/solution.md +++ /dev/null @@ -1,34 +0,0 @@ -# Ответ по первому равенству - -Два объекта равны только тогда, когда это один и тот же объект. - -В первом равенстве создаются два массива, это разные объекты, так что они неравны. - -# Ответ по второму равенству - -
      -
    1. Первым делом, обе части сравнения вычисляются. Справа находится `![]`. Логическое НЕ `'!'` преобразует аргумент к логическому типу. Массив является объектом, так что это `true`. Значит, правая часть становится `![] = !true = false`. Так что получили: - -```js -alert( [] == false ); -``` - -
    2. -
    3. Проверка равенства между объектом и примитивом вызывает численное преобразование объекта. - -У массива нет `valueOf`, сработает `toString` и преобразует массив в список элементов, то есть - в пустую строку: - -```js -alert( '' == false ); -``` - -
    4. -
    5. Сравнение различных типов вызывает численное преобразование слева и справа: - -```js -alert( 0 == 0 ); -``` - -Теперь результат очевиден. -
    6. -
    \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md b/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md deleted file mode 100644 index 6945834b..00000000 --- a/1-js/6-objects-more/2-object-conversion/3-compare-empty-arrays/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Почему [] == [] неверно, а [ ] == ![ ] верно? - -[importance 5] - -Почему первое равенство -- неверно, а второе -- верно? - -```js -//+ run -alert( [] == [] ); // false -alert( [] == ![] ); // true -``` - -Какие преобразования происходят при вычислении? \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md b/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md deleted file mode 100644 index 3d7c6689..00000000 --- a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/solution.md +++ /dev/null @@ -1,35 +0,0 @@ - - -```js -//+ no-beautify -new Date(0) - 0 = 0 // (1) -new Array(1)[0] + "" = "undefined" // (2) -({})[0]
 = undefined // (3) -[1] + 1 = "11" // (4) -[1,2] + [3,4] = "1,23,4" // (5) -[] + null + 1 = "null1" // (6) -[[0]][0][0] = 0 // (7) -({} + {}) = "[object Object][object Object]" // (8) -``` - -
      -
    1. `new Date(0)` -- дата, созданная по миллисекундам и соответствующая 0мс от 1 января 1970 года 00:00:00 UTC. Оператор минус `-` преобразует дату обратно в число миллисекунд, то есть в `0`.
    2. -
    3. `new Array(num)` при вызове с единственным аргументом-числом создаёт массив данной длины, без элементов. Поэтому его нулевой элемент равен `undefined`, при сложении со строкой получается строка `"undefined"`.
    4. -
    5. Фигурные скобки -- это создание пустого объекта, у него нет свойства `'0'`. Так что значением будет `undefined`. -Обратите внимание на внешние, круглые скобки. Если их убрать и запустить `{}[0]` в отладочной консоли браузера -- будет `0`, т.к. скобки `{}` будут восприняты как пустой блок кода, после которого идёт массив.
    6. -
    7. Массив преобразуется в строку `"1"`. Оператор `"+"` при сложении со строкой приводит второй аргумент к строке -- значит будет `"1" + "1" = "11"`.
    8. -
    9. Массивы приводятся к строке и складываются.
    10. -
    11. Массив преобразуется в пустую строку `"" + null + 1`, оператор `"+"` видит, что слева строка и преобразует `null` к строке, получается `"null" + 1`, и в итоге `"null1"`.
    12. -
    13. `[[0]]` -- это вложенный массив `[0]` внутри внешнего `[ ]`. Затем мы берём от него нулевой элемент, и потом еще раз. - -Если это непонятно, то посмотрите на такой пример: - -```js -//+ no-beautify -alert( [1,[0],2][1] ); -``` - -Квадратные скобки после массива/объекта обозначают не другой массив, а взятие элемента. -
    14. -
    15. Каждый объект преобразуется к примитиву. У встроенных объектов `Object` нет подходящего `valueOf`, поэтому используется `toString`, так что складываются в итоге строковые представления объектов.
    16. -
    diff --git a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md b/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md deleted file mode 100644 index 85f767a5..00000000 --- a/1-js/6-objects-more/2-object-conversion/4-object-types-conversion-questions/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Вопросник по преобразованиям, для объектов - -[importance 5] - -Подумайте, какой результат будет у выражений ниже. Когда закончите -- сверьтесь с решением. - -```js -//+ no-beautify -new Date(0) - 0 -new Array(1)[0] + "" -({})[0]
 -[1] + 1 -[1,2] + [3,4] -[] + null + 1 -[[0]][0][0] -({} + {}) -``` - diff --git a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md b/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md deleted file mode 100644 index 4b36c185..00000000 --- a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/solution.md +++ /dev/null @@ -1,61 +0,0 @@ -# Подсказка - -Чтобы `sum(1)`, а также `sum(1)(2)` можно было вызвать новыми скобками -- результатом `sum` должна быть функция. - -Но эта функция также должна уметь превращаться в число. Для этого нужно дать ей соответствующий `valueOf`. А если мы хотим, чтобы и в строковом контексте она вела себя так же -- то `toString`. - -# Решение - -Функция, которая возвращается `sum`, должна накапливать значение при каждом вызове. - -Удобнее всего хранить его в замыкании, в переменной `currentSum`. Каждый вызов прибавляет к ней очередное значение: - -```js -//+ run -function sum(a) { - - var currentSum = a; - - function f(b) { - currentSum += b; - return f; - } - - f.toString = function() { - return currentSum; - }; - - return f; -} - -alert( sum(1)(2) ); // 3 -alert( sum(5)(-1)(2) ); // 6 -alert( sum(6)(-1)(-2)(-3) ); // 0 -alert( sum(0)(1)(2)(3)(4)(5) ); // 15 -``` - -При внимательном взгляде на решение легко заметить, что функция `sum` срабатывает только один раз. Она возвращает функцию `f`. - -Затем, при каждом запуске функция `f` добавляет параметр к сумме `currentSum`, хранящейся в замыкании, и возвращает сама себя. - -**В последней строчке `f` нет рекурсивного вызова.** - -Вот так была бы рекурсия: - -```js -function f(b) { - currentSum += b; - return f(); // <-- подвызов -} -``` - -А в нашем случае, мы просто возвращаем саму функцию, ничего не вызывая. - -```js -function f(b) { - currentSum += b; - return f; // <-- не вызывает сама себя, а возвращает ссылку на себя -} -``` - -Эта `f` используется при следующем вызове, опять возвратит себя, и так сколько нужно раз. Затем, при использовании в строчном или численном контексте -- сработает `toString`, который вернет текущую сумму `currentSum`. \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md b/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md deleted file mode 100644 index b220c704..00000000 --- a/1-js/6-objects-more/2-object-conversion/5-sum-many-brackets/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Сумма произвольного количества скобок - -[importance 2] - -Напишите функцию `sum`, которая будет работать так: - -```js -sum(1)(2) == 3; // 1 + 2 -sum(1)(2)(3) == 6; // 1 + 2 + 3 -sum(5)(-1)(2) == 6 -sum(6)(-1)(-2)(-3) == 0 -sum(0)(1)(2)(3)(4)(5) == 15 -``` - -Количество скобок может быть любым. - -Пример такой функции для двух аргументов -- есть в решении задачи [](/task/closure-sum). \ No newline at end of file diff --git a/1-js/6-objects-more/2-object-conversion/article.md b/1-js/6-objects-more/2-object-conversion/article.md deleted file mode 100644 index e473266b..00000000 --- a/1-js/6-objects-more/2-object-conversion/article.md +++ /dev/null @@ -1,281 +0,0 @@ -# Преобразование объектов: toString и valueOf - -Ранее, в главе [](/types-conversion) мы рассматривали преобразование типов для примитивов. Теперь добавим в нашу картину мира объекты. - -Бывают операции, при которых объект должен быть преобразован в примитив. -[cut] -Например: - - - -Рассмотрим эти преобразования по очереди. - -## Логическое преобразование - -Проще всего -- с логическим преобразованием. - -**Любой объект в логическом контексте -- `true`, даже если это пустой массив `[]` или объект `{}`.** - -```js -//+ run -if ({} && []) { - alert( "Все объекты - true!" ); // alert сработает -} -``` - -## Строковое преобразование - -Строковое преобразование проще всего увидеть, если вывести объект при помощи `alert`: - -```js -//+ run -var user = { - firstName: 'Василий' -}; - -alert( user ); // [object Object] -``` - -Как видно, содержимое объекта не вывелось. Это потому, что стандартным строковым представлением пользовательского объекта является строка `"[object Object]"`. - -Такой вывод объекта не содержит интересной информации. Поэтому имеет смысл его поменять на что-то более полезное. - -**Если в объекте присутствует метод `toString`, который возвращает примитив, то он используется для преобразования.** - -```js -//+ run -var user = { - - firstName: 'Василий', - - *!*toString:*/!* function() { - return 'Пользователь ' + this.firstName; - } -}; - -alert( user ); // Пользователь Василий -``` - -[smart header="Результатом `toString` может быть любой примитив"] -Метод `toString` не обязан возвращать именно строку. - -Его результат может быть любого примитивного типа. Например, это может быть число, как в примере ниже: - -```js -//+ run -var obj = { - toString: function() { - return 123; - } -}; - -alert( obj ); // 123 -``` - -Поэтому мы и называем его здесь *"строковое преобразование"*, а не "преобразование к строке". -[/smart] - -Все объекты, включая встроенные, имеют свои реализации метода `toString`, например: - -```js -//+ run -alert( [1, 2] ); // toString для массивов выводит список элементов "1,2" -alert( new Date ); // toString для дат выводит дату в виде строки -alert( function() {} ); // toString для функции выводит её код -``` - -## Численное преобразование - -Для численного преобразования объекта используется метод `valueOf`, а если его нет -- то `toString`: - -```js -//+ run -var room = { - number: 777, - - valueOf: function() { return this.number; }, - toString: function() { return this.number; } -}; - -alert( +room ); // 777, *!*вызвался valueOf*/!* - -delete room.valueOf; // *!*valueOf удалён*/!* - -alert( +room ); // 777, *!*вызвался toString*/!* -``` - -Метод `valueOf` обязан возвращать примитивное значение, иначе его результат будет проигнорирован. При этом -- не обязательно числовое. - -[smart header="У большинства объектов нет `valueOf`"] -У большинства встроенных объектов такого `valueOf` нет, поэтому численное и строковое преобразования для них работают одинаково. - -Исключением является объект `Date`, который поддерживает оба типа преобразований: - -```js -//+ run -alert( new Date() ); // toString: Дата в виде читаемой строки -alert( +new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970 -``` - -[/smart] - -[smart header="Детали спецификации"] -Если посмотреть в стандарт, то в пункте [15.2.4.4](http://es5.github.com/x15.2.html#x15.2.4.4) говорится о том, что `valueOf` есть у любых объектов. Но он ничего не делает, просто возвращает сам объект (не-примитивное значение!), а потому игнорируется. -[/smart] - -## Две стадии преобразования - -Итак, объект преобразован в примитив при помощи `toString` или `valueOf`. - -Но на этом преобразования не обязательно заканчиваются. Вполне возможно, что в процессе вычислений этот примитив будет преобразован во что-то другое. - -Например, рассмотрим применение к объекту операции `==`: - -```js -//+ run -var obj = { - valueOf: function() { - return 1; - } -}; - -alert( obj == true ); // true -``` - -Объект `obj` был сначала преобразован в примитив, используя численное преобразование, получилось `1 == true`. - -Далее, так как значения всё ещё разных типов, применяются правила преобразования примитивов, результат: `true`. - -То же самое -- при сложении с объектом при помощи `+`: - -```js -//+ run -var obj = { - valueOf: function() { - return 1; - } -}; - -alert( obj + "test" ); // 1test -``` - -Или вот, для разности объектов: - -```js -//+ run -var a = { - valueOf: function() { - return "1"; - } -}; -var b = { - valueOf: function() { - return "2"; - } -}; - -alert( a + b ); // "12" -alert( a - b ); // "1" - "2" = -1 -``` - -[warn header="Исключение: `Date`"] -Объект `Date`, по историческим причинам, является исключением. - -Бинарный оператор плюс `+` обычно использует числовое преобразование и метод `valueOf`. Как мы уже знаем, если подходящего `valueOf` нет (а его нет у большинства объектов), то используется `toString`, так что в итоге преобразование происходит к строке. Но если есть `valueOf`, то используется `valueOf`. Выше в примере как раз `a + b` это демонстрируют. - -У объектов `Date` есть и `valueOf` -- возвращает количество миллисекунд, и `toString` -- возвращает строку с датой. - -...Но оператор `+` для `Date` использует именно `toString` (хотя должен бы `valueOf`). - -Это и есть исключение: - -```js -//+ run -// бинарный плюс для даты toString, для остальных объектов valueOf -alert( new Date + "" ); // "строка даты" -``` - -Других подобных исключений нет. -[/warn] - -[warn header="Как испугать Java-разработчика"] -В языке Java (это не JavaScript, другой язык, здесь приведён для примера) логические значения можно создавать, используя синтаксис `new Boolean(true/false)`, например `new Boolean(true)`. - -В JavaScript тоже есть подобная возможность, которая возвращает "объектную обёртку" для логического значения. - -Эта возможность давно существует лишь для совместимости, она и не используется на практике, поскольку приводит к странным результатам. Некоторые из них могут сильно удивить человека, не привыкшего к JavaScript, например: - -```js -//+ run -var value = new Boolean(false); -if (value) { - alert( true ); // сработает! -} -``` - -Почему запустился `alert`? Ведь в `if` находится `false`... Проверим: - -```js -//+ run -var value = new Boolean(false); - -*!* -alert( value ); // выводит false, все ок.. -*/!* - -if (value) { - alert( true ); // ..но тогда почему выполняется alert в if ?!? -} -``` - -Дело в том, что `new Boolean` -- это не примитивное значение, а объект. Поэтому в логическом контексте он преобразуется к `true`, в результате работает первый пример. - -А второй пример вызывает `alert`, который преобразует объект к строке, и он становится `"false"`. - -**В JavaScript вызовы `new Boolean/String/Number` не используются, а используются простые вызовы соответствующих функций, они преобразуют значение в примитив нужного типа, например `Boolean(val) === !!val`.** -[/warn] - -## Итого - - - -Полный алгоритм преобразований есть в спецификации EcmaScript, смотрите пункты [11.8.5](http://es5.github.com/x11.html#x11.8.5), [11.9.3](http://es5.github.com/x11.html#x11.9.3), а также [9.1](http://es5.github.com/x9.html#x9.1) и [9.3](http://es5.github.com/x9.html#x9.3). - - -Заметим, для полноты картины, что некоторые тесты знаний в интернет предлагают вопросы типа: -```js -//+ no-beautify -{}[0] // чему равно? -{} + {} // а так? -``` - -Если вы запустите эти выражения в консоли, то результат может показаться странным. Подвох здесь в том, что если фигурные скобки `{...}` идут не в выражении, а в основном потоке кода, то JavaScript считает, что это не объект, а "блок кода" (как `if`, `for`, но без оператора, просто группировка команд вместе, используется редко). - -Вот блок кода с командой: -```js -//+run -{ - alert("Блок") -} -``` - -А если команду изъять, то будет пустой блок `{}`, который ничего не делает. Два примера выше как раз содержат пустой блок в начале, который ничего не делает. Иначе говоря: -```js -//+ no-beautify -{}[0] // то же что и: [0] -{} + {} // то же что и: + {} -``` - -То есть, такие вопросы -- не на преобразование типов, а на понимание, что если `{ ... }` находится вне выражений, то это не объект, а блок. - - - diff --git a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md b/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md deleted file mode 100644 index 5b27cec2..00000000 --- a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/solution.md +++ /dev/null @@ -1,19 +0,0 @@ -Да, возможны. - -Они должны возвращать одинаковый объект. При этом если функция возвращает объект, то `this` не используется. - -Например, они могут вернуть один и тот же объект `obj`, определённый снаружи: - -```js -//+ run no-beautify -var obj = {}; - -function A() { return obj; } -function B() { return obj; } - -var a = new A; -var b = new B; - -alert( a == b ); // true -``` - diff --git a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md b/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md deleted file mode 100644 index 72c4e8eb..00000000 --- a/1-js/6-objects-more/3-constructor-new/1-two-functions-one-object/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Две функции один объект - -[importance 2] - -Возможны ли такие функции `A` и `B` в примере ниже, что соответствующие объекты `a,b` равны (см. код ниже)? - -```js -//+ no-beautify -function A() { ... } -function B() { ... } - -var a = new A; -var b = new B; - -alert( a == b ); // true -``` - -Если да -- приведите пример кода с такими функциями. \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/_js.view/solution.js b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/_js.view/solution.js deleted file mode 100644 index 3b51b2e6..00000000 --- a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/_js.view/solution.js +++ /dev/null @@ -1,15 +0,0 @@ -function Calculator() { - - this.read = function() { - this.a = +prompt('a?', 0); - this.b = +prompt('b?', 0); - }; - - this.sum = function() { - return this.a + this.b; - }; - - this.mul = function() { - return this.a * this.b; - }; -} \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/_js.view/test.js b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/_js.view/test.js deleted file mode 100644 index ef881c45..00000000 --- a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/_js.view/test.js +++ /dev/null @@ -1,25 +0,0 @@ -sinon.stub(window, "prompt") - -prompt.onCall(0).returns("2"); -prompt.onCall(1).returns("3"); - -describe("calculator", function() { - var calculator; - before(function() { - calculator = new Calculator(); - calculator.read(); - }); - - it("при вводе 2 и 3 сумма равна 5", function() { - assert.equal(calculator.sum(), 5); - }); - - it("при вводе 2 и 3 произведение равно 6", function() { - assert.equal(calculator.mul(), 6); - }); - -}); - -after(function() { - prompt.restore(); -}); \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md deleted file mode 100644 index a78010d9..00000000 --- a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/solution.md +++ /dev/null @@ -1,27 +0,0 @@ - - -```js -//+ run demo -function Calculator() { - - this.read = function() { - this.a = +prompt('a?', 0); - this.b = +prompt('b?', 0); - }; - - this.sum = function() { - return this.a + this.b; - }; - - this.mul = function() { - return this.a * this.b; - }; -} - -var calculator = new Calculator(); -calculator.read(); - -alert( "Сумма=" + calculator.sum() ); -alert( "Произведение=" + calculator.mul() ); -``` - diff --git a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md b/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md deleted file mode 100644 index 31258cb7..00000000 --- a/1-js/6-objects-more/3-constructor-new/2-calculator-constructor/task.md +++ /dev/null @@ -1,22 +0,0 @@ -# Создать Calculator при помощи конструктора - -[importance 5] - -Напишите *функцию-конструктор* `Calculator`, которая создает объект с тремя методами: - - -Пример использования: - -```js -var calculator = new Calculator(); -calculator.read(); - -alert( "Сумма=" + calculator.sum() ); -alert( "Произведение=" + calculator.mul() ); -``` - -[demo /] diff --git a/1-js/6-objects-more/3-constructor-new/3-accumulator/_js.view/solution.js b/1-js/6-objects-more/3-constructor-new/3-accumulator/_js.view/solution.js deleted file mode 100644 index bd744597..00000000 --- a/1-js/6-objects-more/3-constructor-new/3-accumulator/_js.view/solution.js +++ /dev/null @@ -1,8 +0,0 @@ -function Accumulator(startingValue) { - this.value = startingValue; - - this.read = function() { - this.value += +prompt('Сколько добавлять будем?', 0); - }; - -} \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/3-accumulator/_js.view/test.js b/1-js/6-objects-more/3-constructor-new/3-accumulator/_js.view/test.js deleted file mode 100644 index aa651b11..00000000 --- a/1-js/6-objects-more/3-constructor-new/3-accumulator/_js.view/test.js +++ /dev/null @@ -1,37 +0,0 @@ -describe("Accumulator(1)", function() { - var accumulator; - before(function() { - accumulator = new Accumulator(1); - }); - - beforeEach(function() { - sinon.stub(window, "prompt") - }); - - afterEach(function() { - prompt.restore(); - }); - - it("начальное значение 1", function() { - assert.equal(accumulator.value, 1); - }); - - it("после ввода 0 значение 1", function() { - prompt.returns("0"); - accumulator.read(); - assert.equal(accumulator.value, 1); - }); - - it("после ввода 1 значение 2", function() { - prompt.returns("1"); - accumulator.read(); - assert.equal(accumulator.value, 2); - }); - - it("после ввода 2 значение 4", function() { - prompt.returns("2"); - accumulator.read(); - assert.equal(accumulator.value, 4); - }); - -}); \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md b/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md deleted file mode 100644 index f5b2ba43..00000000 --- a/1-js/6-objects-more/3-constructor-new/3-accumulator/solution.md +++ /dev/null @@ -1,19 +0,0 @@ - - -```js -//+ run demo -function Accumulator(startingValue) { - this.value = startingValue; - - this.read = function() { - this.value += +prompt('Сколько добавлять будем?', 0); - }; - -} - -var accumulator = new Accumulator(1); -accumulator.read(); -accumulator.read(); -alert( accumulator.value ); -``` - diff --git a/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md b/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md deleted file mode 100644 index 7852e561..00000000 --- a/1-js/6-objects-more/3-constructor-new/3-accumulator/task.md +++ /dev/null @@ -1,24 +0,0 @@ -# Создать Accumulator при помощи конструктора - -[importance 5] - -Напишите *функцию-конструктор* `Accumulator(startingValue)`. -Объекты, которые она создает, должны хранить текущую сумму и прибавлять к ней то, что вводит посетитель. - -Более формально, объект должен: - -Таким образом, свойство `value` является текущей суммой всего, что ввел посетитель при вызовах метода `read()`, с учетом начального значения `startingValue`. - -Ниже вы можете посмотреть работу кода: - -```js -var accumulator = new Accumulator(1); // начальное значение 1 -accumulator.read(); // прибавит ввод prompt к текущему значению -accumulator.read(); // прибавит ввод prompt к текущему значению -alert( accumulator.value ); // выведет текущее значение -``` - -[demo /] diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js deleted file mode 100644 index 4bf9f22b..00000000 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/solution.js +++ /dev/null @@ -1,29 +0,0 @@ -function Calculator() { - - var methods = { - "-": function(a, b) { - return a - b; - }, - "+": function(a, b) { - return a + b; - } - }; - - this.calculate = function(str) { - - var split = str.split(' '), - a = +split[0], - op = split[1], - b = +split[2] - - if (!methods[op] || isNaN(a) || isNaN(b)) { - return NaN; - } - - return methods[op](+a, +b); - } - - this.addMethod = function(name, func) { - methods[name] = func; - }; -} \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/test.js b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/test.js deleted file mode 100644 index 2c6891a9..00000000 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/_js.view/test.js +++ /dev/null @@ -1,26 +0,0 @@ -var calculator; -before(function() { - calculator = new Calculator; -}); - -it("calculate(12 + 34) = 46", function() { - assert.equal(calculator.calculate("12 + 34"), 46); -}); - -it("calculate(34 - 12) = 22", function() { - assert.equal(calculator.calculate("34 - 12"), 22); -}); - -it("добавили умножение: calculate(2 * 3) = 6", function() { - calculator.addMethod("*", function(a, b) { - return a * b; - }); - assert.equal(calculator.calculate("2 * 3"), 6); -}); - -it("добавили возведение в степень: calculate(2 ** 3) = 8", function() { - calculator.addMethod("**", function(a, b) { - return Math.pow(a, b); - }); - assert.equal(calculator.calculate("2 ** 3"), 8); -}); \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md deleted file mode 100644 index 3e5f26e5..00000000 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/solution.md +++ /dev/null @@ -1,55 +0,0 @@ - - -```js -//+ run -function Calculator() { - - var methods = { - "-": function(a, b) { - return a - b; - }, - "+": function(a, b) { - return a + b; - } - }; - - this.calculate = function(str) { - - var split = str.split(' '), - a = +split[0], - op = split[1], - b = +split[2] - - if (!methods[op] || isNaN(a) || isNaN(b)) { - return NaN; - } - - return methods[op](+a, +b); - } - - this.addMethod = function(name, func) { - methods[name] = func; - }; -} - -var calc = new Calculator; - -calc.addMethod("*", function(a, b) { - return a * b; -}); -calc.addMethod("/", function(a, b) { - return a / b; -}); -calc.addMethod("**", function(a, b) { - return Math.pow(a, b); -}); - -var result = calc.calculate("2 ** 3"); -alert( result ); // 8 -``` - - - diff --git a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md b/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md deleted file mode 100644 index 244a932c..00000000 --- a/1-js/6-objects-more/3-constructor-new/4-calculator-extendable/task.md +++ /dev/null @@ -1,47 +0,0 @@ -# Создайте калькулятор - -[importance 5] - -Напишите конструктор `Calculator`, который создаёт расширяемые объекты-калькуляторы. - -Эта задача состоит из двух частей, которые можно решать одна за другой. -
      -
    1. Первый шаг задачи: вызов `calculate(str)` принимает строку, например "1 + 2", с жёстко заданным форматом "ЧИСЛО операция ЧИСЛО" (по одному пробелу вокруг операции), и возвращает результат. Понимает плюс `+` и минус `-`. - -Пример использования: - -```js -var calc = new Calculator; - -alert( calc.calculate("3 + 7") ); // 10 -``` - -
    2. -
    3. Второй шаг -- добавить калькулятору метод `addMethod(name, func)`, который учит калькулятор новой операции. Он получает имя операции `name` и функцию от двух аргументов `func(a,b)`, которая должна её реализовывать. - -Например, добавим операции умножить `*`, поделить `/` и возвести в степень `**`: - -```js -var powerCalc = new Calculator; -powerCalc.addMethod("*", function(a, b) { - return a * b; -}); -powerCalc.addMethod("/", function(a, b) { - return a / b; -}); -powerCalc.addMethod("**", function(a, b) { - return Math.pow(a, b); -}); - -var result = powerCalc.calculate("2 ** 3"); -alert( result ); // 8 -``` - -
    4. -
    - - \ No newline at end of file diff --git a/1-js/6-objects-more/3-constructor-new/article.md b/1-js/6-objects-more/3-constructor-new/article.md deleted file mode 100644 index f4ce62cf..00000000 --- a/1-js/6-objects-more/3-constructor-new/article.md +++ /dev/null @@ -1,216 +0,0 @@ -# Создание объектов через "new" - -Обычный синтаксис `{...}` позволяет создать один объект. Но зачастую нужно создать много однотипных объектов. - -Для этого используют "функции-конструкторы", запуская их при помощи специального оператора `new`. - -[cut] -## Конструктор - -Конструктором становится любая функция, вызванная через `new`. - -Например: - -```js -function Animal(name) { - this.name = name; - this.canWalk = true; -} - -*!* -var animal = new Animal("ёжик"); -*/!* -``` - -Заметим, что, технически, любая функция может быть использована как конструктор. То есть, любую функцию можно вызвать при помощи `new`. Как-то особым образом указывать, что она -- конструктор -- не надо. - -Но, чтобы выделить функции, задуманные как конструкторы, их называют с большой буквы: `Animal`, а не `animal`. - -Детальнее -- функция, запущенная через `new`, делает следующее: - -
      -
    1. Создаётся новый пустой объект.
    2. -
    3. Ключевое слово `this` получает ссылку на этот объект.
    4. -
    5. Функция выполняется. Как правило, она модифицирует `this`, добавляет методы, свойства.
    6. -
    7. Возвращается `this`.
    8. -
    - - -В результате вызова `new Animal("ёжик");` получаем такой объект: - -```js -animal = { - name: "ёжик", - canWalk: true -} -``` - -Иными словами, при вызове `new Animal` происходит что-то в таком духе (первая и последняя строка -- это то, что делает интерпретатор): - -```js -function Animal(name) { -*!* - // this = {}; -*/!* - - // в this пишем свойства, методы - this.name = name; - this.canWalk = true; - -*!* - // return this; -*/!* -} -``` - -Теперь многократными вызовами `new Animal` с разными параметрами мы можем создать столько объектов, сколько нужно. Поэтому такую функцию и называют *конструктором* -- она предназначена для "конструирования" объектов. - -[smart header="new function() { ... }"] -Иногда функцию-конструктор объявляют и тут же используют, вот так: -```js -var animal = new function() { - this.name = "Васька"; - this.canWalk = true; -}; -``` -Так делают, когда хотят создать единственный объект данного типа. Примере выше с тем же успехом можно было бы переписать как: -```js -var animal = { - name: "Васька", - canWalk: true -} -``` -...Но обычный синтаксис `{...}` не подходит, когда при создании свойств объекта нужны более сложные вычисления. Их можно проделать в функции-конструкторе и записать результат в `this`. -[/smart] - -## Правила обработки return - -Как правило, конструкторы ничего не возвращают. Их задача -- записать всё, что нужно, в `this`, который автоматически станет результатом. - -Но если явный вызов `return` всё же есть, то применяется простое правило: - - -Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- возвратит, как обычно, `this`. - -Например, возврат объекта: - -```js -//+ run no-beautify -function BigAnimal() { - - this.name = "Мышь"; - - return { name: "Годзилла" }; // <-- возвратим объект -} - -alert( new BigAnimal().name ); // Годзилла, получили объект вместо this -``` - -А вот пример с возвратом строки: - -```js -//+ run -function BigAnimal() { - - this.name = "Мышь"; - - return "Годзилла"; // <-- возвратим примитив -} - -alert( new BigAnimal().name ); // Мышь, получили this (а Годзилла пропал) -``` - -Эта особенность работы `new` прописана в стандарте, но используется она весьма редко. - -[smart header="Можно без скобок"] -Кстати, при вызове `new` без аргументов скобки можно не ставить: - -```js -var animal = new BigAnimal; // <-- без скобок -// то же самое что -var animal = new BigAnimal(); -``` - -Не сказать, что выбрасывание скобок -- "хороший стиль", но такой синтаксис допустим стандартом. -[/smart] - -## Создание методов в конструкторе - -Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать, и он будет "клепать" объекты заданным образом. - -Добавим в создаваемый объект ещё и метод. - -Например, `new User(name)` создает объект с заданным значением свойства `name` и методом `sayHi`: - -```js -//+ run -function User(name) { - this.name = name; - - this.sayHi = function() { - alert( "Моё имя: " + this.name ); - }; -} - -*!* -var ivan = new User("Иван"); - -ivan.sayHi(); // Моё имя: Иван -*/!* - -/* -ivan = { - name: "Иван", - sayHi: функция -} -*/ -``` - -## Локальные переменные - -В функции-конструкторе бывает удобно объявить вспомогательные локальные переменные и вложенные функции, которые будут видны только внутри: - -```js -//+ run -function User(firstName, lastName) { -*!* - // вспомогательная переменная - var phrase = "Привет"; - - // вспомогательная вложенная функция - function getFullName() { - return firstName + " " + lastName; - } -*/!* - - this.sayHi = function() { - alert( phrase + ", " + getFullName() ); // использование - }; -} - -var vasya = new User("Вася", "Петров"); -vasya.sayHi(); // Привет, Вася Петров -``` - -Мы уже говорили об этом подходе ранее, в главе [](/closures-usage). - -Те функции и данные, которые должны быть доступны для внешнего кода, мы пишем в `this` -- и к ним можно будет обращаться, как например `vasya.sayHi()`, а вспомогательные, которые нужны только внутри самого объекта, сохраняем в локальной области видимости. - -[] - -## Итого - -Объекты могут быть созданы при помощи функций-конструкторов: - - - - - - - diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md deleted file mode 100644 index f00b3bc8..00000000 --- a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md +++ /dev/null @@ -1,48 +0,0 @@ - -```js -//+ run -function User(fullName) { - this.fullName = fullName; - - Object.defineProperties(this, { - - firstName: { - - get: function() { - return this.fullName.split(' ')[0]; - }, - - set: function(newFirstName) { - this.fullName = newFirstName + ' ' + this.lastName; - } - - }, - - lastName: { - - get: function() { - return this.fullName.split(' ')[1]; - }, - - set: function(newLastName) { - this.fullName = this.firstName + ' ' + newLastName; - } - - } - - - }); -} - -var vasya = new User("Василий Попкин"); - -// чтение firstName/lastName -alert( vasya.firstName ); // Василий -alert( vasya.lastName ); // Попкин - -// запись в lastName -vasya.lastName = 'Сидоров'; - -alert( vasya.fullName ); // Василий Сидоров -``` - diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md deleted file mode 100644 index 81152200..00000000 --- a/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md +++ /dev/null @@ -1,32 +0,0 @@ -# Добавить get/set-свойства - -[importance 5] - -Вам попал в руки код объекта `User`, который хранит имя и фамилию в свойстве `this.fullName`: - -```js -function User(fullName) { - this.fullName = fullName; -} - -var vasya = new User("Василий Попкин"); -``` - -Имя и фамилия всегда разделяются пробелом. - -Сделайте, чтобы были доступны свойства `firstName` и `lastName`, причём не только на чтение, но и на запись, вот так: - -```js -var vasya = new User("Василий Попкин"); - -// чтение firstName/lastName -alert( vasya.firstName ); // Василий -alert( vasya.lastName ); // Попкин - -// запись в lastName -vasya.lastName = 'Сидоров'; - -alert( vasya.fullName ); // Василий Сидоров -``` - -Важно: в этой задаче `fullName` должно остаться свойством, а `firstName/lastName` -- реализованы через `get/set`. Лишнее дублирование здесь ни к чему. \ No newline at end of file diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/article.md b/1-js/6-objects-more/4-descriptors-getters-setters/article.md deleted file mode 100644 index 551b1f1e..00000000 --- a/1-js/6-objects-more/4-descriptors-getters-setters/article.md +++ /dev/null @@ -1,403 +0,0 @@ -# Дескрипторы, геттеры и сеттеры свойств - -В этой главе мы рассмотрим возможности, которые позволяют очень гибко и мощно управлять всеми свойствами объекта, включая их аспекты -- изменяемость, видимость в цикле `for..in` и даже незаметно делать их функциями. - -Они поддерживаются всеми современными браузерами, но не IE8-. Впрочем, даже в IE8 их поддерживает, но только для DOM-объектов (используются при работе со страницей, это сейчас вне нашего рассмотрения). - -[cut] -## Дескрипторы в примерах - -Основной метод для управления свойствами -- [Object.defineProperty](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty). - -Он позволяет объявить свойство объекта и, что самое главное, тонко настроить его особые аспекты, которые никак иначе не изменить. - -Синтаксис: - -```js -Object.defineProperty(obj, prop, descriptor) -``` - -Аргументы: -
    -
    `obj`
    -
    Объект, в котором объявляется свойство.
    -
    `prop`
    -
    Имя свойства, которое нужно объявить или модифицировать.
    -
    `descriptor`
    -
    Дескриптор -- объект, который описывает поведение свойства. - -В нём могут быть следующие поля: - - - -Чтобы избежать конфликта, запрещено одновременно указывать значение `value` и функции `get/set`. Либо значение, либо функции для его чтения-записи, одно из двух. Также запрещено и не имеет смысла указывать `writable` при наличии `get/set`-функций. - -Далее мы подробно разберём эти свойства на примерах. - -## Обычное свойство - -Обычное свойство добавить очень просто. - -Два таких вызова работают одинаково: - -```js -//+ no-beautify -var user = {}; - -// 1. простое присваивание -user.name = "Вася"; - -// 2. указание значения через дескриптор -Object.defineProperty(user, "name", { value: "Вася" }); -``` - -## Свойство-константа - -Для того, чтобы сделать свойство неизменяемым, добавим ему флаги `writable` и `configurable`: - -```js -//+ run -*!* -"use strict"; -*/!* - -var user = {}; - -Object.defineProperty(user, "name", { - value: "Вася", - writable: false, // запретить присвоение "user.name=" - configurable: false // запретить удаление "delete user.name" -}); - -// Теперь попытаемся изменить это свойство. - -// в strict mode присвоение "user.name=" вызовет ошибку -*!* -user.name = "Петя"; -*/!* -``` - -Заметим, что без `use strict` операция записи "молча" не сработает, а при `use strict` дополнительно генерируется ошибка. - -## Свойство, скрытое для for..in - -Встроенный метод `toString`, как и большинство встроенных методов, не участвует в цикле `for..in`. Это удобно, так как обычно такое свойство является "служебным". - -К сожалению, свойство `toString`, объявленное обычным способом, будет видно в цикле `for..in`, например: - -```js -//+ run no-beautify -var user = { - name: "Вася", - toString: function() { return this.name; } -}; - -*!* -for(var key in user) alert(key); // name, toString -*/!* -``` - -Мы бы хотели, чтобы поведение нашего метода `toString` было таким же, как и стандартного. - -`Object.defineProperty` может исключить `toString` из списка итерации, поставив ему флаг `enumerable: false`. По стандарту, у встроенного `toString` этот флаг уже стоит. - -```js -//+ run no-beautify -var user = { - name: "Вася", - toString: function() { return this.name; } -}; - -*!* -// помечаем toString как не подлежащий перебору в for..in -Object.defineProperty(user, "toString", {enumerable: false}); - -for(var key in user) alert(key); // name -*/!* -``` - -Обратим внимание, вызов `defineProperty` не перезаписал свойство, а просто модифицировал настройки у существующего `toString`. - -## Свойство-функция - -Дескриптор позволяет задать свойство, которое на самом деле работает как функция. Для этого в нём нужно указать эту функцию в `get`. - -Например, у объекта `user` есть обычные свойства: имя `firstName` и фамилия `surname`. - -Создадим свойство `fullName`, которое на самом деле является функцией: - -```js -//+ run -var user = { - firstName: "Вася", - surname: "Петров" -} - -Object.defineProperty(user, "fullName", { - *!*get*/!*: function() { - return this.firstName + ' ' + this.surname; - } -}); - -*!* -alert(user.fullName); // Вася Петров -*/!* -``` - -Обратим внимание, снаружи `fullName` -- это обычное свойство `user.fullName`. Но дескриптор указывает, что на самом деле его значение возвращается функцией. - -Также можно указать функцию, которая используется для записи значения, при помощи дескриптора `set`. - -Например, добавим возможность присвоения `user.fullName` к примеру выше: - -```js -//+ run -var user = { - firstName: "Вася", - surname: "Петров" -} - -Object.defineProperty(user, "fullName", { - - get: function() { - return this.firstName + ' ' + this.surname; - }, - -*!* - set: function(value) { - var split = value.split(' '); - this.firstName = split[0]; - this.surname = split[1]; - } -*/!* -}); - -*!* -user.fullName = "Петя Иванов"; -*/!* -alert( user.firstName ); // Петя -alert( user.surname ); // Иванов -``` - -## Указание get/set в литералах - -Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать свойства-функции можно прямо в его определении. - -Для этого используется особый синтаксис: `get свойство` или `set свойство`. - -Например, ниже объявлен геттер-сеттер `fullName`: - -```js -//+ run -var user = { - firstName: "Вася", - surname: "Петров", - -*!* - get fullName() { -*/!* - return this.firstName + ' ' + this.surname; - }, - -*!* - set fullName(value) { -*/!* - var split = value.split(' '); - this.firstName = split[0]; - this.surname = split[1]; - } -}; - -*!* -alert( user.fullName ); // Вася Петров (из геттера) - -user.fullName = "Петя Иванов"; -alert( user.firstName ); // Петя (поставил сеттер) -alert( user.surname ); // Иванов (поставил сеттер) -*/!* -``` - -## Да здравствуют get/set! - -Казалось бы, зачем нам назначать get/set для свойства через всякие хитрые вызовы, когда можно сделать просто функции с самого начала? Например, `getFullName`, `setFullName`... - -Конечно, в ряде случаев свойства выглядят короче, такое решение просто может быть красивым. Но основной бонус -- это гибкость, возможность получить контроль над свойством в любой момент! - -Например, в начале разработки мы используем обычные свойства, например у `User` будет имя `name` и возраст `age`: - -```js -function User(name, age) { - this.name = name; - this.age = age; -} - -var pete = new User("Петя", 25); - -alert( pete.age ); // 25 -``` - -С обычными свойствами в коде меньше букв, они удобны, причины использовать функции пока нет. - -...Но рано или поздно могут произойти изменения. Например, в `User` может стать более целесообразно вместо возраста `age` хранить дату рождения `birthday`: - -```js -function User(name, birthday) { - this.name = name; - this.birthday = birthday; -} - -var pete = new User("Петя", new Date(1987, 6, 1)); -``` - -Что теперь делать со старым кодом, который выводит свойство `age`? - -Можно, конечно, найти все места и поправить их, но это долго, а иногда и невозможно, скажем, если вы взаимодействуете со сторонней библиотекой, код в которой -- чужой и влезать в него нежелательно. - -Добавление `get`-функции `age` позволяет обойти проблему легко и непринуждённо: - -```js -//+ run no-beautify -function User(name, birthday) { - this.name = name; - this.birthday = birthday; - -*!* - // age будет высчитывать возраст по birthday - Object.defineProperty(this, "age", { - get: function() { - var todayYear = new Date().getFullYear(); - return todayYear - this.birthday.getFullYear(); - } - }); -*/!* -} - -var pete = new User("Петя", new Date(1987, 6, 1)); - -alert( pete.birthday ); // и дата рождения доступна -alert( pete.age ); // и возраст -``` - -Заметим, что `pete.age` снаружи как было свойством, так и осталось. То есть, переписывать внешний код на вызов функции `pete.age()` не нужно. - -Таким образом, `defineProperty` позволяет нам начать с обычных свойств, а в будущем, при необходимости, можно в любой момент заменить их на функции, реализующие более сложную логику. - -## Другие методы работы со свойствами - -
    -
    [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperties)
    -
    Позволяет объявить несколько свойств сразу: - -```js -//+ run -var user = {} - -Object.defineProperties(user, { -*!* - firstName: { -*/!* - value: "Петя" - }, - -*!* - surname: { -*/!* - value: "Иванов" - }, - -*!* - fullName: { -*/!* - get: function() { - return this.firstName + ' ' + this.surname; - } - } -}); - -alert( user.fullName ); // Петя Иванов -``` - -
    -
    [Object.keys(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys), [Object.getOwnPropertyNames(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames)
    -
    Возвращают массив -- список свойств объекта. - -`Object.keys` возвращает только `enumerable`-свойства. - -`Object.getOwnPropertyNames` -- возвращает все: - -```js -//+ run -var obj = { - a: 1, - b: 2, - internal: 3 -}; - -Object.defineProperty(obj, "internal", { - enumerable: false -}); - -*!* -alert( Object.keys(obj) ); // a,b -alert( Object.getOwnPropertyNames(obj) ); // a, internal, b -*/!* -``` - -
    -
    [Object.getOwnPropertyDescriptor(obj, prop)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)
    -
    Возвращает дескриптор для свойства `obj[prop]`. - -Полученный дескриптор можно изменить и использовать `defineProperty` для сохранения изменений, например: - -```js -//+ run -var obj = { - test: 5 -}; -*!* -var descriptor = Object.getOwnPropertyDescriptor(obj, 'test'); -*/!* - -*!* -// заменим value на геттер, для этого... -*/!* -delete descriptor.value; // ..нужно убрать value/writable -delete descriptor.writable; -descriptor.get = function() { // и поставить get - alert( "Preved :)" ); -}; - -*!* -// поставим новое свойство вместо старого -*/!* - -// если не удалить - defineProperty объединит старый дескриптор с новым -delete obj.test; - -Object.defineProperty(obj, 'test', descriptor); - -obj.test; // Preved :) -``` - -
    -
    - -...И несколько методов, которые используются очень редко: -
    -
    [Object.preventExtensions(obj)](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)
    -
    Запрещает добавление свойств в объект.
    -
    [Object.seal(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal)
    -
    Запрещает добавление и удаление свойств, все текущие свойства делает `configurable: false`.
    -
    [Object.freeze(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze)
    -
    Запрещает добавление, удаление и изменение свойств, все текущие свойства делает `configurable: false, writable: false`.
    -
    [Object.isExtensible(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isExtensible), [Object.isSealed(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isSealed), [Object.isFrozen(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isFrozen)
    -
    Возвращают `true`, если на объекте были вызваны методы `Object.preventExtensions/seal/freeze`.
    -
    - diff --git a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/solution.js b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/solution.js deleted file mode 100644 index 03b48506..00000000 --- a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/solution.js +++ /dev/null @@ -1,11 +0,0 @@ -function Article() { - this.created = new Date; - - Article.count++; // увеличиваем счетчик при каждом вызове - Article.last = this.created; // и запоминаем дату -} -Article.count = 0; // начальное значение - -Article.showStats = function() { - alert('Всего: ' + this.count + ', Последняя: ' + this.last); -}; \ No newline at end of file diff --git a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/test.js b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/test.js deleted file mode 100644 index 7bf37039..00000000 --- a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/test.js +++ /dev/null @@ -1,28 +0,0 @@ -describe("Article.showStats", function() { - before(function() { - sinon.stub(window, "alert"); - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - window.alert.restore(); - this.clock.restore(); - }); - - it("Выводит число статей и дату создания последней", function() { - new Article(); - this.clock.tick(100); - new Article(); - Article.showStats(); - - assert(alert.calledWith('Всего: 2, Последняя: ' + new Date())); - }); - - it("и ещё одна статья...", function() { - this.clock.tick(100); - new Article(); - Article.showStats(); - - assert(alert.calledWith('Всего: 3, Последняя: ' + new Date())); - }); -}); \ No newline at end of file diff --git a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md deleted file mode 100644 index 45ce7d39..00000000 --- a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -Решение (как вариант): - -```js -//+ run -function Article() { - this.created = new Date; - -*!* - Article.count++; // увеличиваем счетчик при каждом вызове - Article.last = this.created; // и запоминаем дату -*/!* -} -Article.count = 0; // начальное значение -// (нельзя оставить undefined, т.к. Article.count++ будет NaN) - -Article.showStats = function() { - alert( 'Всего: ' + this.count + ', Последняя: ' + this.last ); -}; - -new Article(); -new Article(); - -Article.showStats(); // Всего: 2, Последняя: (дата) - -new Article(); - -Article.showStats(); // Всего: 3, Последняя: (дата) -``` - diff --git a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md b/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md deleted file mode 100644 index 513ed072..00000000 --- a/1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md +++ /dev/null @@ -1,33 +0,0 @@ -# Счетчик объектов - -[importance 5] - -Добавить в конструктор `Article`: - -Используйте для этого статические свойства. - -Пусть вызов `Article.showStats()` выводит то и другое. - -Использование: - -```js -function Article() { - this.created = new Date(); -*!* - // ... ваш код ... -*/!* -} - -new Article(); -new Article(); - -Article.showStats(); // Всего: 2, Последняя: (дата) - -new Article(); - -Article.showStats(); // Всего: 3, Последняя: (дата) -``` - diff --git a/1-js/6-objects-more/5-static-properties-and-methods/article.md b/1-js/6-objects-more/5-static-properties-and-methods/article.md deleted file mode 100644 index 3595a734..00000000 --- a/1-js/6-objects-more/5-static-properties-and-methods/article.md +++ /dev/null @@ -1,255 +0,0 @@ -# Статические и фабричные методы - -Методы и свойства, которые не привязаны к конкретному экземпляру объекта, называют "статическими". Их записывают прямо в саму функцию-конструктор. - -[cut] - -## Статические свойства - -В коде ниже используются статические свойства `Article.count` и `Article.DEFAULT_FORMAT`: - -```js -function Article() { - Article.count++; -} - -Article.count = 0; // статическое свойство-переменная -Article.DEFAULT_FORMAT = "html"; // статическое свойство-константа -``` - -Они хранят данные, специфичные не для одного объекта, а для всех статей целиком. - -Как правило, это чаще константы, такие как формат "по умолчанию" `Article.DEFAULT_FORMAT`. - -## Статические методы - -С примерами статических методов мы уже знакомы: это встроенные методы [String.fromCharCode](http://javascript.ru/String.fromCharCode), [Date.parse](http://javascript.ru/Date.parse). - -Создадим для `Article` статический метод `Article.showCount()`: - -```js -//+ run -function Article() { - Article.count++; - - //... -} -Article.count = 0; - -Article.showCount = function() { -*!* - alert( this.count ); // (1) -*/!* -} - -// использование -new Article(); -new Article(); -Article.showCount(); // (2) -``` - -Здесь `Article.count` -- статическое свойство, а `Article.showCount` -- статический метод. - -Обратим внимание на использование `this` в примере выше. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`. - -## Пример: сравнение объектов - -Ещё один хороший способ применения -- сравнение объектов. - -Например, у нас есть объект `Journal` для журналов. Журналы можно сравнивать -- по толщине, по весу, по другим параметрам. - -Объявим "стандартную" функцию сравнения, которая будет сравнивать по дате издания. Эта функция сравнения, естественно, не привязана к конкретному журналу, но относится к журналам вообще. - -Поэтому зададим её как статический метод `Journal.compare`: - -```js -function Journal(date) { - this.date = date; - // ... -} - -// возвращает значение, большее 0, если A больше B, иначе меньшее 0 -Journal.compare = function(journalA, journalB) { - return journalA.date - journalB.date; -}; -``` - -В примере ниже эта функция используется для поиска самого раннего журнала из массива: - -```js -//+ run -function Journal(date) { - this.date = date; - - this.formatDate = function(date) { - return date.getDate() + '.' + (date.getMonth() + 1) + '.' + date.getFullYear(); - }; - - this.getTitle = function() { - return "Выпуск от " + this.formatDate(this.date); - }; - -} - -*!* -Journal.compare = function(journalA, journalB) { - return journalA.date - journalB.date; -}; -*/!* - -// использование: -var journals = [ - new Journal(new Date(2012, 1, 1)), - new Journal(new Date(2012, 0, 1)), - new Journal(new Date(2011, 11, 1)) -]; - -function findMin(journals) { - var min = 0; - for (var i = 0; i < journals.length; i++) { -*!* - // используем статический метод - if (Journal.compare(journals[min], journals[i]) > 0) min = i; -*/!* - } - return journals[min]; -} - -alert( findMin(journals).getTitle() ); -``` - -**Статический метод также можно использовать для функций, которые вообще не требуют наличия объекта.** - -Например, метод `formatDate(date)` можно сделать статическим. Он будет форматировать дату "как это принято в журналах", при этом его можно использовать в любом месте кода, не обязательно создавать журнал. - -Например: - -```js -//+ run -function Journal() { /*...*/ } - -Journal.formatDate = function(date) { - return date.getDate() + '.' + (date.getMonth()+1) + '.' + date.getFullYear(); -} - -// ни одного объекта Journal нет, просто форматируем дату -alert( *!*Journal.formatDate(new Date)*/!* ); -``` - -## Фабричные методы - -Рассмотрим ситуацию, когда объект нужно создавать различными способами. Например, это реализовано во встроенном объекте [Date](/datetime). Он по-разному обрабатывает аргументы разных типов: - - - -**"Фабричный статический метод" -- удобная альтернатива такому конструктору. Так называется статический метод, который служит для создания новых объектов (поэтому и называется "фабричным").** - -Пример встроенного фабричного метода -- [String.fromCharCode(code)](http://javascript.ru/String.fromCharCode). Этот метод создает строку из кода символа: - -```js -//+ run -var str = String.fromCharCode(65); -alert( str ); // 'A' -``` - -Но строки -- слишком простой пример, посмотрим что-нибудь посложнее. - -Допустим, нам нужно создавать объекты `User`: анонимные `new User()` и с данными `new User({name: 'Вася', age: 25})`. - -Можно, конечно, создать полиморфную функцию-конструктор `User`: - -```js -//+ run -function User(userData) { - if (userData) { // если указаны данные -- одна ветка if - this.name = userData.name; - this.age = userData.age; - } else { // если не указаны -- другая - this.name = 'Аноним'; - } - - this.sayHi = function() { - alert(this.name) - }; - // ... -} - -// Использование - -var guest = new User(); -guest.sayHi(); // Аноним - -var knownUser = new User({ - name: 'Вася', - age: 25 -}); -knownUser.sayHi(); // Вася -``` - -Подход с использованием фабричных методов был бы другим. Вместо разбора параметров в конструкторе -- делаем два метода: `User.createAnonymous` и `User.createFromData`. - -Код: - -```js -//+ run -function User() { - this.sayHi = function() { - alert(this.name) - }; -} - -User.createAnonymous = function() { - var user = new User; - user.name = 'Аноним'; - return user; -} - -User.createFromData = function(userData) { - var user = new User; - user.name = userData.name; - user.age = userData.age; - return user; -} - -// Использование - -*!* -var guest = User.createAnonymous(); -guest.sayHi(); // Аноним - -var knownUser = User.createFromData({ - name: 'Вася', - age: 25 -}); -knownUser.sayHi(); // Вася -*/!* -``` - -Преимущества использования фабричных методов: - -[compare] -+Лучшая читаемость кода. Как конструктора -- вместо одной большой функции несколько маленьких, так и вызывающего кода -- явно видно, что именно создаётся. -+Лучший контроль ошибок, т.к. если в `createFromData` ничего не передали, то будет ошибка, а полиморфный конструктор создал бы анонимного посетителя. -+Удобная расширяемость. Например, нужно добавить создание администратора, без аргументов. Фабричный метод сделать легко: `User.createAdmin = function() { ... }`. А для полиморфного конструктора вызов без аргумента создаст анонима, так что нужно добавить параметр -- "тип посетителя" и усложнить этим код. -[/compare] - -**Поэтому полиморфные конструкторы лучше использовать там, где нужна именно полиморфность**, т.е. когда непонятно, какого типа аргумент передадут, и хочется в одном конструкторе охватить все варианты. - -А в остальных случаях отличная альтернатива -- фабричные методы. - -## Итого - -Статические свойства и методы объекта удобно применять в следующих случаях: - - - diff --git a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md deleted file mode 100644 index 9e5ca007..00000000 --- a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md +++ /dev/null @@ -1,31 +0,0 @@ -# Первый вариант - -```js -//+ run -function sumArgs() { - // скопируем reduce из массива - arguments.reduce = [].reduce; - return arguments.reduce(function(a, b) { - return a + b; - }); -} - -alert( sumArgs(4, 5, 6) ); // 15 -``` - -# Второй вариант - -Метод `call` здесь вполне подойдёт, так как требуется вызвать `reduce` в контексте `arguments` с одним аргументом. - -```js -//+ run -function sumArgs() { - // запустим reduce из массива напрямую - return [].reduce.call(arguments, function(a, b) { - return a + b; - }); -} - -alert( sumArgs(4, 5, 6) ); // 15 -``` - diff --git a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md deleted file mode 100644 index b03a9fc7..00000000 --- a/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md +++ /dev/null @@ -1,30 +0,0 @@ -# Перепишите суммирование аргументов - -[importance 5] - -Есть функция `sum`, которая суммирует все элементы массива: - -```js -//+ run -function sum(arr) { - return arr.reduce(function(a, b) { - return a + b; - }); -} - -alert( sum([1, 2, 3]) ); // 6 (=1+2+3) -``` - -Создайте аналогичную функцию `sumArgs()`, которая будет суммировать все свои аргументы: - -```js -function sumArgs() { - /* ваш код */ -} - -alert( sumArgs(1, 2, 3) ); // 6, аргументы переданы через запятую, без массива -``` - -Для решения примените метод `reduce` к `arguments`, используя `call`, `apply` или одалживание метода. - -P.S. Функция `sum` вам не понадобится, она приведена в качестве примера использования `reduce` для похожей задачи. \ No newline at end of file diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/solution.js b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/solution.js deleted file mode 100644 index b49aeb62..00000000 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/solution.js +++ /dev/null @@ -1,3 +0,0 @@ -function applyAll(func) { - return func.apply(this, [].slice.call(arguments, 1)); -} \ No newline at end of file diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js deleted file mode 100644 index 871a19a7..00000000 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js +++ /dev/null @@ -1,15 +0,0 @@ -describe("applyAll", function() { - - it("применяет функцию ко всем аргументам, начиная со 2го", function() { - var min = applyAll(Math.min, 1, 2, 3); - assert.equal(min, 1); - }); - - it("при отсутствии аргументов просто вызывает функцию", function() { - var spy = sinon.spy(); - applyAll(spy); - assert(spy.calledOnce); - assert.equal(spy.firstCall.args.length, 0); - }); - -}); \ No newline at end of file diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md deleted file mode 100644 index c8579b0a..00000000 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md +++ /dev/null @@ -1,28 +0,0 @@ - - -```js -//+ run -function sum() { - return [].reduce.call(arguments, function(a, b) { - return a + b; - }); -} - -function mul() { - return [].reduce.call(arguments, function(a, b) { - return a * b; - }); -} - -*!* -function applyAll(func) { - return func.apply(this, [].slice.call(arguments, 1)); - } -*/!* - -alert( applyAll(sum, 1, 2, 3) ); // 6 -alert( applyAll(mul, 2, 3, 4) ); // 24 -alert( applyAll(Math.max, 2, -2, 3) ); // 3 -alert( applyAll(Math.min, 2, -2, 3) ); // -2 -``` - diff --git a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md b/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md deleted file mode 100644 index 95bd2413..00000000 --- a/1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md +++ /dev/null @@ -1,40 +0,0 @@ -# Примените функцию к аргументам - -[importance 5] - -Напишите функцию `applyAll(func, arg1, arg2...)`, которая получает функцию `func` и произвольное количество аргументов. - -Она должна вызвать `func(arg1, arg2...)`, то есть передать в `func` все аргументы, начиная со второго, и возвратить результат. - -Например: - -```js -// Применить Math.max к аргументам 2, -2, 3 -alert( applyAll(Math.max, 2, -2, 3) ); // 3 - -// Применить Math.min к аргументам 2, -2, 3 -alert( applyAll(Math.min, 2, -2, 3) ); // -2 -``` - -Область применения `applyAll`, конечно, шире, можно вызывать её и со своими функциями: - -```js -//+ run -function sum() { // суммирует аргументы: sum(1,2,3) = 6 - return [].reduce.call(arguments, function(a, b) { - return a + b; - }); -} - -function mul() { // перемножает аргументы: mul(2,3,4) = 24 - return [].reduce.call(arguments, function(a, b) { - return a * b; - }); -} - -*!* -alert( applyAll(sum, 1, 2, 3) ); // -> sum(1, 2, 3) = 6 -alert( applyAll(mul, 2, 3, 4) ); // -> mul(2, 3, 4) = 24 -*/!* -``` - diff --git a/1-js/6-objects-more/6-call-apply/article.md b/1-js/6-objects-more/6-call-apply/article.md deleted file mode 100644 index 08f9f386..00000000 --- a/1-js/6-objects-more/6-call-apply/article.md +++ /dev/null @@ -1,311 +0,0 @@ -# Явное указание this: "call", "apply" - -Итак, мы знаем, что `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`. - -В этой главе наша цель получить окончательное и полное понимание `this` в JavaScript. Для этого не хватает всего одного элемента: способа явно указать `this` при помощи методов `call` и `apply`. - -[cut] - -## Метод call - -Синтаксис метода `call`: - -```js -func.call(context, arg1, arg2, ...) -``` - -При этом вызывается функция `func`, первый аргумент `call` становится её `this`, а остальные передаются "как есть". - -**Вызов `func.call(context, a, b...)` -- то же, что обычный вызов `func(a, b...)`, но с явно указанным `this(=context)`.** - -Например, у нас есть функция `showFullName`, которая работает с `this`: - -```js -function showFullName() { - alert( this.firstName + " " + this.lastName ); -} -``` - -Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска. - -Вызов `showFullName.call(user)` запустит функцию, установив `this = user`, вот так: - -```js -//+ run -function showFullName() { - alert( this.firstName + " " + this.lastName ); -} - -var user = { - firstName: "Василий", - lastName: "Петров" -}; - -*!* -// функция вызовется с this=user -showFullName.call(user) // "Василий Петров" -*/!* -``` - -После контекста в `call` можно передать аргументы для функции. Вот пример с более сложным вариантом `showFullName`, который конструирует ответ из указанных свойств объекта: - -```js -//+ run -var user = { - firstName: "Василий", - surname: "Петров", - patronym: "Иванович" -}; - -function showFullName(firstPart, lastPart) { - alert( this[firstPart] + " " + this[lastPart] ); -} - -*!* -// f.call(контекст, аргумент1, аргумент2, ...) -showFullName.call(user, 'firstName', 'surname') // "Василий Петров" -showFullName.call(user, 'firstName', 'patronym') // "Василий Иванович" -*/!* -``` - -## "Одалживание метода" - -При помощи `call` можно легко взять метод одного объекта, в том числе встроенного, и вызвать в контексте другого. - -Это называется "одалживание метода" (на англ. *method borrowing*). - -**Используем эту технику для упрощения манипуляций с `arguments`.** - -Как мы знаем, `arguments` не массив, а обычный объект, поэтому таких полезных методов как `push`, `pop`, `join` и других у него нет. Но иногда так хочется, чтобы были... - -Нет ничего проще! Давайте скопируем метод `join` из обычного массива: - -```js -//+ run -function printArgs() { - arguments.join = [].join; // одолжили метод (1) - - var argStr = arguments.join(':'); // (2) - - alert( argStr ); // сработает и выведет 1:2:3 -} - -printArgs(1, 2, 3); -``` - -
      -
    1. В строке `(1)` объявлен пустой массив `[]` и скопирован его метод `[].join`. Обратим внимание, мы не вызываем его, а просто копируем. Функция, в том числе встроенная -- обычное значение, мы можем скопировать любое свойство любого объекта, и `[].join` здесь не исключение.
    2. -
    3. В строке `(2)` запустили `join` в контексте `arguments`, как будто он всегда там был.
    4. - -[smart header="Почему вызов сработает?"] - -Здесь метод join массива скопирован и вызван в контексте `arguments`. Не произойдёт ли что-то плохое от того, что `arguments` -- не массив? Почему он, вообще, сработал? - -Ответ на эти вопросы простой. В соответствии [со спецификацией](http://es5.github.com/x15.4.html#x15.4.4.5), внутри `join` реализован примерно так: - -```js -function join(separator) { - if (!this.length) return ''; - - var str = this[0]; - - for (var i = 1; i < this.length; i++) { - str += separator + this[i]; - } - - return str; -} -``` - -Как видно, используется `this`, числовые индексы и свойство `length`. Если эти свойства есть, то все в порядке. А больше ничего и не нужно. - -В качестве `this` подойдёт даже обычный объект: - -```js -//+ run -var obj = { // обычный объект с числовыми индексами и length - 0: "А", - 1: "Б", - 2: "В", - length: 3 -}; - -*!* -obj.join = [].join; -alert( obj.join(';') ); // "A;Б;В" -*/!* -``` - -[/smart] - -...Однако, копирование метода из одного объекта в другой не всегда приемлемо! - -Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`. - -Поэтому копировать `[].join`, как сделано выше, нельзя: если он перезапишет собственный `join` объекта, то будет страшный бардак и путаница. - -Безопасно вызвать метод нам поможет `call`: - -```js -//+ run -function printArgs() { - var join = [].join; // скопируем ссылку на функцию в переменную - -*!* - // вызовем join с this=arguments, - // этот вызов эквивалентен arguments.join(':') из примера выше - var argStr = join.call(arguments, ':'); -*/!* - - alert( argStr ); // сработает и выведет 1:2:3 -} - -printArgs(1, 2, 3); -``` - -Мы вызвали метод без копирования. Чисто, безопасно. - -## Ещё пример: [].slice.call(arguments) - -В JavaScript есть очень простой способ сделать из `arguments` настоящий массив. Для этого возьмём метод массива: slice. - -По стандарту вызов `arr.slice(start, end)` создаёт новый массив и копирует в него элементы массива `arr` от `start` до `end`. А если `start` и `end` не указаны, то копирует весь массив. - -Вызовем его в контексте `arguments`: - -```js -//+ run -function printArgs() { - // вызов arr.slice() скопирует все элементы из this в новый массив -*!* - var args = [].slice.call(arguments); -*/!* - alert( args.join(', ') ); // args - полноценный массив из аргументов -} - -printArgs('Привет', 'мой', 'мир'); // Привет, мой, мир -``` - -Как и в случае с `join`, такой вызов технически возможен потому, что `slice` для работы требует только нумерованные свойства и `length`. Всё это в `arguments` есть. - -## Метод apply - -Если нам неизвестно, с каким количеством аргументов понадобится вызвать функцию, можно использовать более мощный метод: `apply`. - -**Вызов функции при помощи `func.apply` работает аналогично `func.call`, но принимает массив аргументов вместо списка.** - -```js -func.call(context, arg1, arg2); -// идентичен вызову -func.apply(context, [arg1, arg2]); -``` - -В частности, эти две строчки cработают одинаково: - -```js -showFullName.call(user, 'firstName', 'surname'); - -showFullName.apply(user, ['firstName', 'surname']); -``` - -Преимущество `apply` перед `call` отчётливо видно, когда мы формируем массив аргументов динамически. - -Например, в JavaScript есть встроенная функция `Math.max(a, b, c...)`, которая возвращает максимальное значение из аргументов: - -```js -//+ run -alert( Math.max(1, 5, 2) ); // 5 -``` - -При помощи `apply` мы могли бы найти максимум в произвольном массиве, вот так: - -```js -//+ run -var arr = []; -arr.push(1); -arr.push(5); -arr.push(2); - -// получить максимум из элементов arr -alert( Math.max.apply(null, arr) ); // 5 -``` - -В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`. - -Строго говоря, полным эквивалентом вызову `Math.max(1,2,3)` был бы вызов `Math.max.apply(Math, [1,2,3])`. В обоих этих вызовах контекстом будет объект `Math`. - -Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max` не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве! - -[smart header="Вызов `call/apply` с `null` или `undefined`"] - -В современном стандарте `call/apply` передают `this` "как есть". А в старом, без `use strict`, при указании первого аргумента `null` или `undefined` в `call/apply`, функция получает `this = window`, например: - -Современный стандарт: -```js -//+ run -function f() { - "use strict"; -*!* - alert( this ); // null -*/!* -} - -f.call(null); -``` - -Без `use strict`: - -```js -//+ run -function f() { - alert( this ); // window -} - -f.call(null); -``` - -[/smart] - -## Итого про this - -Значение `this` устанавливается в зависимости от того, как вызвана функция: - -
      -
      При вызове функции как метода
      -
      - -```js -//+ no-beautify -obj.func(...) // this = obj -obj["func"](...) -``` - -
      -
      При обычном вызове
      -
      - -```js -func(...) // this = window (ES3) /undefined (ES5) -``` - -
      -
      В `new`
      -
      - -```js -new func() // this = {} (новый объект) -``` - -
      -
      Явное указание
      -
      - -```js -func.apply(context, args) // this = context (явная передача) -func.call(context, arg1, arg2, ...) -``` - -
      -
      - - diff --git a/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md b/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md deleted file mode 100644 index 28b52dcb..00000000 --- a/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md +++ /dev/null @@ -1,8 +0,0 @@ - -Страшновато выглядит, да? Работает так (по строкам): -
        -
      1. Вызов `bind` сохраняет дополнительные аргументы `args` (они идут со 2го номера) в массив `bindArgs`.
      2. -
      3. ... и возвращает обертку `wrapper`.
      4. -
      5. Эта обёртка делает из `arguments` массив `args` и затем, используя метод [concat](http://javascript.ru/Array/concat), прибавляет их к аргументам `bindArgs` (карринг).
      6. -
      7. Затем передаёт вызов `func` с контекстом и общим массивом аргументов.
      8. -
      diff --git a/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md b/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md deleted file mode 100644 index ce481712..00000000 --- a/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md +++ /dev/null @@ -1,23 +0,0 @@ -# Кросс-браузерная эмуляция bind - -[importance 3] - -Если вы вдруг захотите копнуть поглубже -- аналог `bind` для IE8- и старых версий других браузеров будет выглядеть следующим образом: - -```js -//+ no-beautify -function bind(func, context /*, args*/) { - var bindArgs = [].slice.call(arguments, 2); // (1) - function wrapper() { // (2) - var args = [].slice.call(arguments); - var unshiftArgs = bindArgs.concat(args); // (3) - return func.apply(context, unshiftArgs); // (4) - } - return wrapper; -} -``` - -Использование -- вместо `mul.bind(null, 2)` вызывать `bind(mul, null, 2)`. - -Не факт, что он вам понадобится, но в качестве упражнение попробуйте разобраться, как это работает. - diff --git a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md b/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md deleted file mode 100644 index ccd6ca0c..00000000 --- a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md +++ /dev/null @@ -1,22 +0,0 @@ -Ответ: `Hello`. - -```js -//+ run -function f() { - alert( this ); -} - -var user = { - g: f.bind("Hello") -} - -user.g(); -``` - -Так как вызов идёт в контексте объекта `user.g()`, то внутри функции `g` контекст `this = user`. - -Однако, функции `g` совершенно без разницы, какой `this` она получила. - -Её единственное предназначение -- это передать вызов в `f` вместе с аргументами и ранее указанным контекстом `"Hello"`, что она и делает. - -Эта задача демонстрирует, что изменить однажды привязанный контекст уже нельзя. \ No newline at end of file diff --git a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md b/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md deleted file mode 100644 index a189eeb3..00000000 --- a/1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Запись в объект после bind - -[importance 5] - -Что выведет функция? - -```js -function f() { - alert( this ); -} - -var user = { - g: f.bind("Hello") -} - -user.g(); -``` - diff --git a/1-js/6-objects-more/7-bind/3-second-bind/solution.md b/1-js/6-objects-more/7-bind/3-second-bind/solution.md deleted file mode 100644 index 2a8b26d8..00000000 --- a/1-js/6-objects-more/7-bind/3-second-bind/solution.md +++ /dev/null @@ -1,58 +0,0 @@ -Ответ: `"Вася"`. - -```js -//+ run no-beautify -function f() { - alert(this.name); -} - -f = f.bind( {name: "Вася"} ).bind( {name: "Петя"} ); - -f(); // Вася -``` - -Первый вызов `f.bind(..Вася..)` возвращает "обёртку", которая устанавливает контекст для `f` и передаёт вызов `f`. - -Следующий вызов `bind` будет устанавливать контекст уже для этой обёртки. Это ни на что не повлияет. - -Чтобы это проще понять, используем наш собственный вариант `bind` вместо встроенного: - -```js -function bind(func, context) { - return function() { - return func.apply(context, arguments); - }; -} -``` - -Код станет таким: - -```js -//+ no-beautify -function f() { - alert(this.name); -} - -f = bind(f, {name: "Вася"} ); // (1) -f = bind(f, {name: "Петя"} ); // (2) - -f(); // Вася -``` - -Здесь видно, что первый вызов `bind`, в строке `(1)`, возвращает обёртку вокруг `f`, которая выглядит так (выделена): - -```js -function bind(func, context) { -*!* - return function() { - // здесь this не используется - return func.apply(context, arguments); - }; -*/!* -} -``` - -В этой обёртке нигде не используется `this`, контекст `context` берётся из замыкания. Посмотрите на код, там нигде нет `this`. - -Поэтому следующий `bind` в строке `(2)`, который выполняется уже над обёрткой и фиксирует в ней `this`, ни на что не влияет. Какая разница, что будет в качестве `this` в функции, которая этот `this` не использует? Контекст `context`, как видно в коде выше, она получает через замыкание из аргументов первого `bind`. - diff --git a/1-js/6-objects-more/7-bind/3-second-bind/task.md b/1-js/6-objects-more/7-bind/3-second-bind/task.md deleted file mode 100644 index 3b0f06b2..00000000 --- a/1-js/6-objects-more/7-bind/3-second-bind/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Повторный bind - -[importance 5] - -Что выведет этот код? - -```js -//+ no-beautify -function f() { - alert(this.name); -} - -f = f.bind( {name: "Вася"} ).bind( {name: "Петя" } ); - -f(); -``` - diff --git a/1-js/6-objects-more/7-bind/4-function-property-after-bind/solution.md b/1-js/6-objects-more/7-bind/4-function-property-after-bind/solution.md deleted file mode 100644 index a882a35e..00000000 --- a/1-js/6-objects-more/7-bind/4-function-property-after-bind/solution.md +++ /dev/null @@ -1,4 +0,0 @@ -Ответ: `undefined`. - -Результатом работы `bind` является функция-обёртка над `sayHi`. Эта функция -- самостоятельный объект, у неё уже нет свойства `test`. - diff --git a/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md b/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md deleted file mode 100644 index b182b4e5..00000000 --- a/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md +++ /dev/null @@ -1,22 +0,0 @@ -# Свойство функции после bind - -[importance 5] - -В свойство функции записано значение. Изменится ли оно после применения `bind`? Обоснуйте ответ. - -```js -function sayHi() { - alert( this.name ); -} -sayHi.test = 5; -alert( sayHi.test ); // 5 - -*!* -var bound = sayHi.bind({ - name: "Вася" -}); - -alert( bound.test ); // что выведет? почему? -*/!* -``` - diff --git a/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md b/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md deleted file mode 100644 index 7eb5963e..00000000 --- a/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md +++ /dev/null @@ -1,110 +0,0 @@ -# Решение с bind - -Ошибка происходит потому, что `ask` получает только функцию, без объекта-контекста. - -Используем `bind`, чтобы передать в `ask` функцию с уже привязанным контекстом: - -```js -//+ run -"use strict"; - -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} - -var user = { - login: 'Василий', - password: '12345', - - loginOk: function() { - alert( this.login + ' вошёл в сайт' ); - }, - - loginFail: function() { - alert( this.login + ': ошибка входа' ); - }, - - checkPassword: function() { -*!* - ask("Ваш пароль?", this.password, this.loginOk.bind(this), this.loginFail.bind(this)); -*/!* - } -}; - -var vasya = user; -user = null; -vasya.checkPassword(); -``` - -# Решение через замыкание - -Альтернативное решение -- сделать функции-обёртки над `user.loginOk/loginFail`: - -```js -//+ no-beautify -var user = { - ... - checkPassword: function() { -*!* - ask("Ваш пароль?", this.password, - function() { user.loginOk(); }, function() { user.loginFail(); }); -*/!* - } -} -``` - -...Но такой код использует переменную `user`, так что если объект переместить из неё, к примеру, так, то работать он не будет: - -```js -var vasya = user; // переместим user в vasya -user = null; -vasya.checkPassword(); // упс будет ошибка, ведь в коде объекта остался user -``` - -Для того, чтобы избежать проблем, можно использовать `this`. Внутри `checkPassword` он всегда будет равен текущему объекту, так что скопируем его в переменную, которую назовём `self`: - -```js -//+ run -"use strict"; - -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} - -var user = { - login: 'Василий', - password: '12345', - - loginOk: function() { - alert( this.login + ' вошёл в сайт' ); - }, - - loginFail: function() { - alert( this.login + ': ошибка входа' ); - }, - - checkPassword: function() { -*!* - var self = this; - ask("Ваш пароль?", this.password, - function() { - self.loginOk(); - }, - function() { - self.loginFail(); - } - ); -*/!* - } -}; - -var vasya = user; -user = null; -vasya.checkPassword(); -``` - -Теперь всё работает. Анонимные функции достают правильный контекст из замыкания, где он сохранён в переменной `self`. \ No newline at end of file diff --git a/1-js/6-objects-more/7-bind/5-question-use-bind/task.md b/1-js/6-objects-more/7-bind/5-question-use-bind/task.md deleted file mode 100644 index a36bb858..00000000 --- a/1-js/6-objects-more/7-bind/5-question-use-bind/task.md +++ /dev/null @@ -1,50 +0,0 @@ -# Использование функции вопросов - -[importance 5] - -Вызов `user.checkPassword()` в коде ниже должен, при помощи `ask`, спрашивать пароль и вызывать `loginOk/loginFail` в зависимости от правильности ответа. - -Однако, его вызов приводит к ошибке. Почему? - -Исправьте выделенную строку, чтобы всё работало (других строк изменять не надо). - -```js -//+ run -"use strict"; - -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} - -var user = { - login: 'Василий', - password: '12345', - - loginOk: function() { - alert( this.login + ' вошёл в сайт' ); - }, - - loginFail: function() { - alert( this.login + ': ошибка входа' ); - }, - - checkPassword: function() { -*!* - ask("Ваш пароль?", this.password, this.loginOk, this.loginFail); -*/!* - } -}; - -user.checkPassword(); -``` - -P.S. Ваше решение должно также срабатывать, если переменная `user` будет перезаписана, например вместо `user.checkPassword()` в конце будут строки: - -```js -var vasya = user; -user = null; -vasya.checkPassword(); -``` - diff --git a/1-js/6-objects-more/7-bind/6-ask-currying/solution.md b/1-js/6-objects-more/7-bind/6-ask-currying/solution.md deleted file mode 100644 index 86877551..00000000 --- a/1-js/6-objects-more/7-bind/6-ask-currying/solution.md +++ /dev/null @@ -1,73 +0,0 @@ -# Решение с bind - -Первое решение -- передать в `ask` функции с привязанным контекстом и аргументами. - -```js -//+ run -"use strict"; - -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} - -var user = { - login: 'Василий', - password: '12345', - - loginDone: function(result) { - alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') ); - }, - - checkPassword: function() { -*!* - ask("Ваш пароль?", this.password, this.loginDone.bind(this, true), this.loginDone.bind(this, false)); -*/!* - } -}; - -user.checkPassword(); -``` - -# Решение с локальной переменной - -Второе решение -- это скопировать `this` в локальную переменную (чтобы внешняя перезапись не повлияла): - -```js -//+ run -"use strict"; - -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} - -var user = { - login: 'Василий', - password: '12345', - - loginDone: function(result) { - alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') ); - }, - - checkPassword: function() { - var self = this; -*!* - ask("Ваш пароль?", this.password, - function() { - self.loginDone(true); - }, - function() { - self.loginDone(false); - } - ); -*/!* - } -}; - -user.checkPassword(); -``` - -Оба решения хороши, вариант с `bind` короче. \ No newline at end of file diff --git a/1-js/6-objects-more/7-bind/6-ask-currying/task.md b/1-js/6-objects-more/7-bind/6-ask-currying/task.md deleted file mode 100644 index 7b48e190..00000000 --- a/1-js/6-objects-more/7-bind/6-ask-currying/task.md +++ /dev/null @@ -1,57 +0,0 @@ -# Использование функции вопросов с каррингом - -[importance 5] - -Эта задача -- усложнённый вариант задачи [](/task/question-use-bind). В ней объект `user` изменён. - -Теперь заменим две функции `user.loginOk()` и `user.loginFail()` на единый метод: `user.loginDone(true/false)`, который нужно вызвать с `true` при верном ответе и `fail` -- при неверном. - -Код ниже делает это, соответствующий фрагмент выделен. - -**Сейчас он обладает важным недостатком: при записи в `user` другого значения объект перестанет корректно работать, вы увидите это, запустив пример ниже (будет ошибка).** - -Как бы вы написали правильно? - -**Исправьте выделенный фрагмент, чтобы код заработал.** - -```js -//+ run -"use strict"; - -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} - -var user = { - login: 'Василий', - password: '12345', - - // метод для вызова из ask - loginDone: function(result) { - alert( this.login + (result ? ' вошёл в сайт' : ' ошибка входа') ); - }, - - checkPassword: function() { -*!* - ask("Ваш пароль?", this.password, - function() { - user.loginDone(true); - }, - function() { - user.loginDone(false); - } - ); -*/!* - } -}; - -var vasya = user; -user = null; -vasya.checkPassword(); -``` - -Изменения должны касаться только выделенного фрагмента. - -Если возможно, предложите два решения, одно -- с использованием `bind`, другое -- без него. Какое решение лучше? diff --git a/1-js/6-objects-more/7-bind/article.md b/1-js/6-objects-more/7-bind/article.md deleted file mode 100644 index a18a910e..00000000 --- a/1-js/6-objects-more/7-bind/article.md +++ /dev/null @@ -1,434 +0,0 @@ -# Привязка контекста и карринг: "bind" - -Функции в JavaScript никак не привязаны к своему контексту `this`, с одной стороны, здорово -- это позволяет быть максимально гибкими, одалживать методы и так далее. - -Но с другой стороны -- в некоторых случаях контекст может быть потерян. То есть мы вроде как вызываем метод объекта, а на самом деле он получает `this = undefined`. - -Такая ситуация является типичной для начинающих разработчиков, но бывает и у "зубров" тоже. Конечно, "зубры" при этом знают, что с ней делать. - -[cut] - -## Пример потери контекста - -В браузере есть встроенная функция `setTimeout(func, ms)`, которая вызывает выполение функции `func` через `ms` миллисекунд (=1/1000 секунды). - -Мы подробно остановимся на ней и её тонкостях позже, в главе [](/settimeout-setinterval), а пока просто посмотрим пример. - -Этот код выведет "Привет" через 1000мс, то есть 1 секунду: - -```js -//+ run -setTimeout(function() { - alert( "Привет" ); -}, 1000); -``` - -Попробуем сделать то же самое с методом объекта, следующий код должен выводить имя пользователя через 1 секунду: - -```js -//+ run -var user = { - firstName: "Вася", - sayHi: function() { - alert( this.firstName ); - } -}; - -*!* -setTimeout(user.sayHi, 1000); // undefined (не Вася!) -*/!* -``` - -При запуске кода выше через секунду выводится вовсе не `"Вася"`, а `undefined`! - -Это произошло потому, что в примере выше `setTimeout` получил функцию `user.sayHi`, но не её контекст. То есть, последняя строчка аналогична двум таким: - -```js -var f = user.sayHi; -setTimeout(f, 1000); // контекст user потеряли -``` - - -Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно? - -Есть несколько способов решения, среди которых мы, в зависимости от ситуации, можем выбирать. - -## Решение 1: сделать обёртку - -Самый простой вариант решения -- это обернуть вызов в анонимную функцию: - -```js -//+ run -var user = { - firstName: "Вася", - sayHi: function() { - alert( this.firstName ); - } -}; - -*!* -setTimeout(function() { - user.sayHi(); // Вася -}, 1000); -*/!* -``` - -Теперь код работает, так как `user` достаётся из замыкания. - -Это решение также позволяет передать дополнительные аргументы: - - -```js -//+ run -var user = { - firstName: "Вася", - sayHi: function(who) { - alert( this.firstName + ": Привет, " + who ); - } -}; - -*!* -setTimeout(function() { - user.sayHi("Петя"); // Вася: Привет, Петя -}, 1000); -*/!* -``` - - -Но тут же появляется и уязвимое место в структуре кода! - -А что, если до срабатывания `setTimeout` (ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот! - -Хорошо бы гарантировать правильность контекста. - -## Решение 2: bind для привязки контекста - -Напишем вспомогательную функцию `bind(func, context)`, которая будет жёстко фиксировать контекст для `func`: - -```js -function bind(func, context) { - return function() { // (*) - return func.apply(context, arguments); - }; -} -``` - -Посмотрим, что она делает, как работает, на таком примере: - -```js -//+ run -function f() { - alert( this ); -} - -var g = bind(f, "Context"); -g(); // Context -``` - -То есть, `bind(f, "Context")` привязывает `"Context"` в качестве `this` для `f`. - -Посмотрим, за счёт чего это происходит. - -Результатом `bind(f, "Context")`, как видно из кода, будет анонимная функция `(*)`. - -Вот она отдельно: - -```js -function() { // (*) - return func.apply(context, arguments); -}; -``` - -Если подставить наши конкретные аргументы, то есть `f` и `"Context"`, то получится так: - -```js -function() { // (*) - return f.apply("Context", arguments); -}; -``` - -Эта функция запишется в переменную `g`. - -Далее, если вызвать `g`, то вызов будет передан в `f`, причём `f.apply("Context", arguments)` передаст в качестве контекста `"Context"`, который и будет выведен. - -Если вызвать `g` с аргументами, то также будет работать: - -```js -//+ run -function f(a, b) { - alert( this ); - alert( a + b ); -} - -var g = bind(f, "Context"); -g(1, 2); // Context, затем 3 -``` - -Аргументы, которые получила `g(...)`, передаются в `f` также благодаря методу `.apply`. - -**Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`.** - -Вернёмся к `user.sayHi`. Вариант с `bind`: - -```js -//+ run -function bind(func, context) { - return function() { - return func.apply(context, arguments); - }; -} - -var user = { - firstName: "Вася", - sayHi: function() { - alert( this.firstName ); - } -}; - -*!* -setTimeout(bind(user.sayHi, user), 1000); -*/!* -``` - -Теперь всё в порядке! - -Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая привязывает `user.sayHi` к контексту `user`. Она будет вызвана через 1000мс. - -Полученную обёртку можно вызвать и с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст. - -```js -//+ run -var user = { - firstName: "Вася", -*!* - sayHi: function(who) { // здесь у sayHi есть один аргумент -*/!* - alert( this.firstName + ": Привет, " + who ); - } -}; - -var sayHi = bind(user.sayHi, user); - -*!* -// контекст Вася, а аргумент передаётся "как есть" -sayHi("Петя"); // Вася: Привет, Петя -sayHi("Маша"); // Вася: Привет, Маша -*/!* -``` - -В примере выше продемонстрирована другая частая цель использования `bind` -- "привязать" функцию к контексту, чтобы в дальнейшем "не таскать за собой" объект, а просто вызывать `sayHi`. - -Результат `bind` можно передавать в любое место кода, вызывать как обычную функцию, он "помнит" свой контекст. - -## Решение 3: встроенный метод bind [#bind] - -В современном JavaScript (или при подключении библиотеки [es5-shim](https://github.com/kriskowal/es5-shim) для IE8-) у функций уже есть встроенный метод [bind](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind), который мы можем использовать. - -Он работает примерно так же, как `bind`, который описан выше. - -Изменения очень небольшие: - -```js -//+ run -function f(a, b) { - alert( this ); - alert( a + b ); -} - -*!* -// вместо -// var g = bind(f, "Context"); -var g = f.bind("Context"); -*/!* -g(1, 2); // Context, затем 3 -``` - -Синтаксис встроенного `bind`: - -```js -var wrapper = func.bind(context[, arg1, arg2...]) -``` - -
      -
      `func`
      -
      Произвольная функция
      -
      `context`
      -
      Контекст, который привязывается к `func`
      -
      `arg1`, `arg2`, ...
      -
      Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.
      -
      - -Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже. - -Пример со встроенным методом `bind`: - -```js -//+ run -var user = { - firstName: "Вася", - sayHi: function() { - alert( this.firstName ); - } -}; - -*!* -// setTimeout( bind(user.sayHi, user), 1000 ); -setTimeout(user.sayHi.bind(user), 1000); // аналог через встроенный метод -*/!* -``` - -Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript. - -Далее мы будем использовать именно встроенный метод `bind`. - -[warn header="bind не похож на call/apply"] -Методы `bind` и `call/apply` близки по синтаксису, но есть важнейшее отличие. - -Методы `call/apply` вызывают функцию с заданным контекстом и аргументами. - -А `bind` не вызывает функцию. Он только возвращает "обёртку", которую мы можем вызвать позже, и которая передаст вызов в исходную функцию, с привязанным контекстом. -[/warn] - -[smart header="Привязать всё: `bindAll`"] -Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле: - -```js -for (var prop in user) { - if (typeof user[prop] == 'function') { - user[prop] = user[prop].bind(user); - } -} -``` - -В некоторых JS-фреймворках есть даже встроенные функции для этого, например [_.bindAll(obj)](http://lodash.com/docs#bindAll). -[/smart] - - -## Карринг - -До этого мы говорили о привязке контекста. Теперь пойдём на шаг дальше. Привязывать можно не только контекст, но и аргументы. Используется это реже, но бывает полезно. - -[Карринг](http://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%80%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) (currying) или *каррирование* -- термин [функционального программирования](http://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), который означает создание новой функции путём фиксирования аргументов существующей. - -Как было сказано выше, метод `func.bind(context, ...)` может создавать обёртку, которая фиксирует не только контекст, но и ряд аргументов функции. - -Например, есть функция умножения двух чисел `mul(a, b)`: - -```js -function mul(a, b) { - return a * b; -}; -``` - -При помощи `bind` создадим функцию `double`, удваивающую значения. Это будет вариант функции `mul` с фиксированным первым аргументом: - -```js -//+ run -*!* -// double умножает только на два -var double = mul.bind(null, 2); // контекст фиксируем null, он не используется -*/!* - -alert( double(3) ); // = mul(2, 3) = 6 -alert( double(4) ); // = mul(2, 4) = 8 -alert( double(5) ); // = mul(2, 5) = 10 -``` - -При вызове `double` будет передавать свои аргументы исходной функции `mul` после тех, которые указаны в `bind`, то есть в данном случае после зафиксированного первого аргумента `2`. - -**Говорят, что `double` является "частичной функцией" (partial function) от `mul`.** - -Другая частичная функция `triple` утраивает значения: - -```js -//+ run -*!* -var triple = mul.bind(null, 3); // контекст фиксируем null, он не используется -*/!* - -alert( triple(3) ); // = mul(3, 3) = 9 -alert( triple(4) ); // = mul(3, 4) = 12 -alert( triple(5) ); // = mul(3, 5) = 15 -``` - -При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё. - -Наш выигрыш состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`. - -## Функция ask для задач - -В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`: - -```js -function ask(question, answer, ok, fail) { - var result = prompt(question, ''); - if (result.toLowerCase() == answer.toLowerCase()) ok(); - else fail(); -} -``` - -Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`. - -Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно. - -Пример использования: - -```js -//+ run -*!* -ask("Выпустить птичку?", "да", fly, die); -*/!* - -function fly() { - alert( 'улетела :)' ); -} - -function die() { - alert( 'птичку жалко :(' ); -} -``` - -## Итого - -
        -
      • Функция сама по себе не запоминает контекст выполнения.
      • -
      • Чтобы гарантировать правильный контекст для вызова `obj.func()`, нужно использовать функцию-обёртку, задать её через анонимную функцию: -```js -setTimeout(function() { - obj.func(); -}) -``` -
      • -
      • ...Либо использовать `bind`: - -```js -setTimeout(obj.func.bind(obj)); -``` -
      • -
      • Вызов `bind` часто используют для привязки функции к контексту, чтобы затем присвоить её в обычную переменную и вызывать уже без явного указания объекта.
      • -
      • Вызов `bind` также позволяет фиксировать первые аргументы функции ("каррировать" её), и таким образом из общей функции получить её "частные" варианты -- чтобы использовать их многократно без повтора одних и тех же аргументов каждый раз.
      • -
      - -[head] - -[/head] diff --git a/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/solution.js b/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/solution.js deleted file mode 100644 index 1aa8f279..00000000 --- a/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/solution.js +++ /dev/null @@ -1,9 +0,0 @@ -function makeLogging(f, log) { - - function wrapper(a) { - log.push(a); - return f.call(this, a); - } - - return wrapper; -} \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/test.js b/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/test.js deleted file mode 100644 index 2257ee89..00000000 --- a/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/test.js +++ /dev/null @@ -1,50 +0,0 @@ -describe("makeLogging", function() { - it("записывает вызовы в массив log", function() { - var work = sinon.spy(); - - var log = []; - work = makeLogging(work, log); - assert.deepEqual(log, []); - - work(1); - assert.deepEqual(log, [1]); - - work(2); - assert.deepEqual(log, [1, 2]); - }); - - it("передаёт вызов функции, возвращает её результат", function() { - var log = []; - - function work(x) { - return x * 2; - } - - work = sinon.spy(work); - var spy = work; - work = makeLogging(work, log); - - assert.equal(work(1), 2); - assert(spy.calledWith(1)); - }); - - - it("сохраняет контекст вызова для методов объекта", function() { - var log = []; - - var calculator = { - double: function(x) { - return x * 2; - } - } - - calculator.double = sinon.spy(calculator.double); - var spy = calculator.double; - calculator.double = makeLogging(calculator.double, log); - - assert.equal(calculator.double(1), 2); - assert(spy.calledWith(1)); - assert(spy.calledOn(calculator)); - }); - -}); \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md b/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md deleted file mode 100644 index 103ff4e5..00000000 --- a/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md +++ /dev/null @@ -1,44 +0,0 @@ -Возвратим декоратор `wrapper` который будет записывать аргумент в `log` и передавать вызов в `f`: - -```js -//+ run -function work(a) { - /*...*/ // work - произвольная функция, один аргумент -} - -function makeLogging(f, log) { - -*!* - function wrapper(a) { - log.push(a); - return f.call(this, a); - } -*/!* - - return wrapper; -} - -var log = []; -work = makeLogging(work, log); - -work(1); // 1 -work(5); // 5 - -for (var i = 0; i < log.length; i++) { - alert( 'Лог:' + log[i] ); // "Лог:1", затем "Лог:5" -} -``` - -**Обратите внимание, вызов функции осуществляется как `f.call(this, a)`, а не просто `f(a)`.** - -Передача контекста необходима, чтобы декоратор корректно работал с методами объекта. Например: - -```js -user.method = makeLogging(user.method, log); -``` - -Теперь при вызове `user.method(...)` в декоратор будет передаваться контекст `this`, который надо передать исходной функции через `call/apply`. - - - - diff --git a/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md b/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md deleted file mode 100644 index e1efa4f8..00000000 --- a/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md +++ /dev/null @@ -1,32 +0,0 @@ -# Логирующий декоратор (1 аргумент) - -[importance 5] - -Создайте декоратор `makeLogging(f, log)`, который берет функцию `f` и массив `log`. - -Он должен возвращать обёртку вокруг `f`, которая при каждом вызове записывает ("логирует") аргументы в `log`, а затем передает вызов в `f`. - -**В этой задаче можно считать, что у функции `f` ровно один аргумент.** - -Работать должно так: - -```js -function work(a) { - /* ... */ // work - произвольная функция, один аргумент -} - -function makeLogging(f, log) { /* ваш код */ } - -var log = []; -work = makeLogging(work, log); - -work(1); // 1, добавлено в log -work(5); // 5, добавлено в log - -for (var i = 0; i < log.length; i++) { -*!* - alert( 'Лог:' + log[i] ); // "Лог:1", затем "Лог:5" -*/!* -} -``` - diff --git a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/solution.js b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/solution.js deleted file mode 100644 index f7052f65..00000000 --- a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/solution.js +++ /dev/null @@ -1,9 +0,0 @@ -function makeLogging(f, log) { - - function wrapper() { - log.push([].slice.call(arguments)); - return f.apply(this, arguments); - } - - return wrapper; -} \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/test.js b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/test.js deleted file mode 100644 index eb29c837..00000000 --- a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/test.js +++ /dev/null @@ -1,55 +0,0 @@ -describe("makeLogging", function() { - it("записывает вызовы в массив log", function() { - var work = sinon.spy(); - - var log = []; - work = makeLogging(work, log); - assert.deepEqual(log, []); - - work(1, 2); - assert.deepEqual(log, [ - [1, 2] - ]); - - work(3, 4); - assert.deepEqual(log, [ - [1, 2], - [3, 4] - ]); - }); - - it("передаёт вызов функции, возвращает её результат", function() { - var log = []; - - function sum(a, b) { - return a + b; - } - - sum = sinon.spy(sum); - var spy = sum; - sum = makeLogging(sum, log); - - assert.equal(sum(1, 2), 3); - assert(spy.calledWith(1, 2)); - }); - - - it("сохраняет контекст вызова для методов объекта", function() { - var log = []; - - var calculator = { - sum: function(a, b) { - return a + b; - } - } - - calculator.sum = sinon.spy(calculator.sum); - var spy = calculator.sum; - calculator.sum = makeLogging(calculator.sum, log); - - assert.equal(calculator.sum(1, 2), 3); - assert(spy.calledWith(1, 2)); - assert(spy.calledOn(calculator)); - }); - -}); \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md deleted file mode 100644 index dfc2b902..00000000 --- a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md +++ /dev/null @@ -1,34 +0,0 @@ -Решение аналогично задаче [](/task/logging-decorator), разница в том, что в лог вместо одного аргумента идет весь объект `arguments`. - -Для передачи вызова с произвольным количеством аргументов используем `f.apply(this, arguments)`. - -```js -//+ run -function work(a, b) { - alert( a + b ); // work - произвольная функция -} - -function makeLogging(f, log) { - -*!* - function wrapper() { - log.push([].slice.call(arguments)); - return f.apply(this, arguments); - } -*/!* - - return wrapper; -} - -var log = []; -work = makeLogging(work, log); - -work(1, 2); // 3 -work(4, 5); // 9 - -for (var i = 0; i < log.length; i++) { - var args = log[i]; // массив из аргументов i-го вызова - alert( 'Лог:' + args.join() ); // "Лог:1,2", "Лог:4,5" -} -``` - diff --git a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md deleted file mode 100644 index e5ab5b6b..00000000 --- a/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md +++ /dev/null @@ -1,29 +0,0 @@ -# Логирующий декоратор (много аргументов) - -[importance 3] - -Создайте декоратор `makeLogging(func, log)`, для функции `func` возвращающий обёртку, которая при каждом вызове добавляет её аргументы в массив `log`. - -Условие аналогично задаче [](/task/logging-decorator), но допускается `func` с любым набором аргументов. - -Работать должно так: - -```js -function work(a, b) { - alert( a + b ); // work - произвольная функция -} - -function makeLogging(f, log) { /* ваш код */ } - -var log = []; -work = makeLogging(work, log); - -work(1, 2); // 3 -work(4, 5); // 9 - -for (var i = 0; i < log.length; i++) { - var args = log[i]; // массив из аргументов i-го вызова - alert( 'Лог:' + args.join() ); // "Лог:1,2", "Лог:4,5" -} -``` - diff --git a/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/solution.js b/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/solution.js deleted file mode 100644 index dc865601..00000000 --- a/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/solution.js +++ /dev/null @@ -1,11 +0,0 @@ -function makeCaching(f) { - var cache = {}; - - return function(x) { - if (!(x in cache)) { - cache[x] = f.call(this, x); - } - return cache[x]; - }; - -} \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/test.js b/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/test.js deleted file mode 100644 index aed6f0fe..00000000 --- a/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/test.js +++ /dev/null @@ -1,31 +0,0 @@ -describe("makeCaching", function() { - - it("запоминает предыдущее значение функции с таким аргументом", function() { - function f(x) { - return Math.random() * x; - } - - f = makeCaching(f); - - var a = f(1); - var b = f(1); - assert.equal(a, b); - - var anotherValue = f(2); - // почти наверняка другое значение - assert.notEqual(a, anotherValue); - }); - - it("сохраняет контекст вызова", function() { - var obj = { - spy: sinon.spy() - }; - - var spy = obj.spy; - obj.spy = makeCaching(obj.spy); - obj.spy(123); - assert(spy.calledWith(123)); - assert(spy.calledOn(obj)); - }); - -}); \ No newline at end of file diff --git a/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md b/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md deleted file mode 100644 index 9b413f92..00000000 --- a/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md +++ /dev/null @@ -1,34 +0,0 @@ -Запоминать результаты вызова функции будем в замыкании, в объекте `cache: { ключ:значение }`. - -```js -//+ run no-beautify -function f(x) { - return Math.random()*x; -} - -*!* -function makeCaching(f) { - var cache = {}; - - return function(x) { - if (!(x in cache)) { - cache[x] = f.call(this, x); - } - return cache[x]; - }; - -} -*/!* - -f = makeCaching(f); - -var a = f(1); -var b = f(1); -alert( a == b ); // true (значение закешировано) - -b = f(2); -alert( a == b ); // false, другой аргумент => другое значение -``` - -Обратите внимание: проверка на наличие уже подсчитанного значения выглядит так: `if (x in cache)`. Менее универсально можно проверить так: `if (cache[x])`, это если мы точно знаем, что `cache[x]` никогда не будет `false`, `0` и т.п. - diff --git a/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md b/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md deleted file mode 100644 index f0b3a78f..00000000 --- a/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md +++ /dev/null @@ -1,34 +0,0 @@ -# Кеширующий декоратор - -[importance 5] - -Создайте декоратор `makeCaching(f)`, который берет функцию `f` и возвращает обертку, которая кеширует её результаты. - -**В этой задаче функция `f` имеет только один аргумент, и он является числом.** - -
        -
      1. При первом вызове обертки с определенным аргументом -- она вызывает `f` и запоминает значение.
      2. -
      3. При втором и последующих вызовах с тем же аргументом возвращается запомненное значение.
      4. -
      - -Должно работать так: - -```js -function f(x) { - return Math.random() * x; // random для удобства тестирования -} - -function makeCaching(f) { /* ваш код */ } - -f = makeCaching(f); - -var a, b; - -a = f(1); -b = f(1); -alert( a == b ); // true (значение закешировано) - -b = f(2); -alert( a == b ); // false, другой аргумент => другое значение -``` - diff --git a/1-js/6-objects-more/8-decorators/article.md b/1-js/6-objects-more/8-decorators/article.md deleted file mode 100644 index f089df90..00000000 --- a/1-js/6-objects-more/8-decorators/article.md +++ /dev/null @@ -1,232 +0,0 @@ -# Функции-обёртки, декораторы - -JavaScript предоставляет удивительно гибкие возможности по работе с функциями: их можно передавать, в них можно записывать данные как в объекты, у них есть свои встроенные методы... - -Конечно, этим нужно уметь пользоваться. В этой главе, чтобы более глубоко понимать работу с функциями, мы рассмотрим создание функций-обёрток или, иначе говоря, "декораторов". - -[cut] - -[Декоратор](http://en.wikipedia.org/wiki/Decorator_pattern) -- приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение. - -*Декоратор* получает функцию и возвращает обертку, которая делает что-то своё "вокруг" вызова основной функции. - -## bind -- привязка контекста - -Один простой декоратор вы уже видели ранее -- это функция [bind](/bind): - -```js -function bind(func, context) { - return function() { - return func.apply(context, arguments); - }; -} -``` - -Вызов `bind(func, context)` возвращает обёртку, которая ставит `this` и передаёт основную работу функции `func`. - -## Декоратор-таймер - -Создадим более сложный декоратор, замеряющий время выполнения функции. - -Он будет называться `timingDecorator` и получать функцию вместе с "названием таймера", а возвращать -- функцию-обёртку, которая измеряет время и прибавляет его в специальный объект `timer` по свойству-названию. - -Использование: -```js -function f(x) {} // любая функция - -var timers = {}; // объект для таймеров - -// отдекорировали -f = timingDecorator(f, "myFunc"); - -// запускаем -f(1); -f(2); -f(3); // функция работает как раньше, но время подсчитывается - -alert( timers.myFunc ); // общее время выполнения всех вызовов f -``` - -При помощи декоратора `timingDecorator` мы сможем взять произвольную функцию и одним движением руки прикрутить к ней измеритель времени. - -Его реализация: - -```js -//+ run -var timers = {}; - -// прибавит время выполнения f к таймеру timers[timer] -function timingDecorator(f, timer) { - return function() { - var start = performance.now(); - - var result = f.apply(this, arguments); // (*) - - if (!timers[timer]) timers[timer] = 0; - timers[timer] += performance.now() - start; - - return result; - } -} - -// функция может быть произвольной, например такой: -function fibonacci(n) { - return (n > 2) ? fibonacci(n - 1) + fibonacci(n - 2) : 1; -} - -*!* -// использование: завернём fibonacci в декоратор -fibonacci = timingDecorator(fibonacci, "fibo"); -*/!* - -// неоднократные вызовы... -alert( fibonacci(10) ); // 55 -alert( fibonacci(20) ); // 6765 -// ... - -*!* -// в любой момент можно получить общее количество времени на вызовы -alert( timers.fibo + 'мс' ); -*/!* -``` - -Обратим внимание на строку `(*)` внутри декоратора, которая и осуществляет передачу вызова: - -```js -var result = f.apply(this, arguments); // (*) -``` - -Этот приём называется "форвардинг вызова" (от англ. forwarding): текущий контекст и аргументы через `apply` передаются в функцию `f`, так что изнутри `f` всё выглядит так, как была вызвана она напрямую, а не декоратор. - -## Декоратор для проверки типа - -В JavaScript, как правило, пренебрегают проверками типа. В функцию, которая должна получать число, может быть передана строка, булево значение или даже объект. - -Например: - -```js -//+ no-beautify -function sum(a, b) { - return a + b; -} - -// передадим в функцию для сложения чисел нечисловые значения -alert( sum(true, { name: "Вася", age: 35 }) ); // true[Object object] -``` - -Функция "как-то" отработала, но в реальной жизни передача в `sum` подобных значений, скорее всего, будет следствием программной ошибки. Всё-таки `sum` предназначена для суммирования чисел, а не объектов. - -Многие языки программирования позволяют прямо в объявлении функции указать, какие типы данных имеют параметры. И это удобно, поскольку повышает надёжность кода. - -В JavaScript же проверку типов приходится делать дополнительным кодом в начале функции, который во-первых обычно лень писать, а во-вторых он увеличивает общий объем текста, тем самым ухудшая читаемость. - -**Декораторы способны упростить рутинные, повторяющиеся задачи, вынести их из кода функции.** - -Например, создадим декоратор, который принимает функцию и массив, который описывает для какого аргумента какую проверку типа применять: - -```js -//+ run -// вспомогательная функция для проверки на число -function checkNumber(value) { - return typeof value == 'number'; -} - -// декоратор, проверяющий типы для f -// второй аргумент checks - массив с функциями для проверки -function typeCheck(f, checks) { - return function() { - for (var i = 0; i < arguments.length; i++) { - if (!checks[i](arguments[i])) { - alert( "Некорректный тип аргумента номер " + i ); - return; - } - } - return f.apply(this, arguments); - } -} - -function sum(a, b) { - return a + b; -} - -*!* -// обернём декоратор для проверки -sum = typeCheck(sum, [checkNumber, checkNumber]); // оба аргумента - числа -*/!* - -// пользуемся функцией как обычно -alert( sum(1, 2) ); // 3, все хорошо - -*!* -// а вот так - будет ошибка -sum(true, null); // некорректный аргумент номер 0 -sum(1, ["array", "in", "sum?!?"]); // некорректный аргумент номер 1 -*/!* -``` - -Конечно, этот декоратор можно ещё расширять, улучшать, дописывать проверки, но... Вы уже поняли принцип, не правда ли? - -**Один раз пишем декоратор и дальше просто применяем этот функционал везде, где нужно.** - -## Декоратор проверки доступа - -И наконец посмотрим ещё один, последний пример. - -Предположим, у нас есть функция `isAdmin()`, которая возвращает `true`, если у посетителя есть права администратора. - -Можно создать декоратор `checkPermissionDecorator`, который добавляет в любую функцию проверку прав: - -Например, создадим декоратор `checkPermissionDecorator(f)`. Он будет возвращать обертку, которая передает вызов `f` в том случае, если у посетителя достаточно прав: - -```js -function checkPermissionDecorator(f) { - return function() { - if (isAdmin()) { - return f.apply(this, arguments); - } - alert( 'Недостаточно прав' ); - } -} -``` - -Использование декоратора: - -```js -//+ no-beautify -function save() { ... } - -save = checkPermissionDecorator(save); -// Теперь вызов функции save() проверяет права -``` - -## Итого - -Декоратор -- это обёртка над функцией, которая модифицирует её поведение. При этом основную работу по-прежнему выполняет функция. - -**Декораторы можно не только повторно использовать, но и комбинировать!** - -Это кардинально повышает их выразительную силу. Декораторы можно рассматривать как своего рода "фичи" или возможности, которые можно "нацепить" на любую функцию. Можно один, а можно несколько. - -Скажем, используя декораторы, описанные выше, можно добавить к функции возможности по проверке типов данных, замеру времени и проверке доступа буквально одной строкой, не залезая при этом в её код, то есть (!) не увеличивая его сложность. - -Предлагаю вашему вниманию задачи, которые помогут выяснить, насколько вы разобрались в декораторах. Далее в учебнике мы ещё встретимся с ними. - - - -[head] - -[/head] \ No newline at end of file diff --git a/1-js/6-objects-more/index.md b/1-js/6-objects-more/index.md deleted file mode 100644 index 94f1be86..00000000 --- a/1-js/6-objects-more/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Методы объектов и контекст вызова - -Начинаем изучать объектно-ориентированную разработку -- как работают объекты и функции, что такое контекст вызова и способы его передачи. \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js deleted file mode 100644 index 48113fa4..00000000 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js +++ /dev/null @@ -1,32 +0,0 @@ -function formatDate(date) { - if (typeof date == 'number') { - // перевести секунды в миллисекунды и преобразовать к Date - date = new Date(date * 1000); - } else if (typeof date == 'string') { - // строка в стандартном формате автоматически будет разобрана в дату - date = new Date(date); - } else if (Array.isArray(date)) { - date = new Date(date[0], date[1], date[2]); - } - // преобразования для поддержки полиморфизма завершены, - // теперь мы работаем с датой (форматируем её) - - return date.toLocaleString("ru", {day: '2-digit', month: '2-digit', year: '2-digit'}); - - /* - // можно и вручную, если лень добавлят в старый IE поддержку локализации - var day = date.getDate(); - if (day < 10) day = '0' + day; - - var month = date.getMonth() + 1; - if (month < 10) month = '0' + month; - - // взять 2 последние цифры года - var year = date.getFullYear() % 100; - if (year < 10) year = '0' + year; - - var formattedDate = day + '.' + month + '.' + year; - - return formattedDate; - */ -} \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/test.js b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/test.js deleted file mode 100644 index a458b62f..00000000 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/test.js +++ /dev/null @@ -1,18 +0,0 @@ -describe("formatDate", function() { - it("читает дату вида гггг-мм-дд из строки", function() { - assert.equal(formatDate('2011-10-02'), "02.10.11"); - }); - - it("читает дату из числа 1234567890 (миллисекунды)", function() { - assert.equal(formatDate(1234567890), "14.02.09"); - }); - - it("читает дату из массива вида [гггг, м, д]", function() { - assert.equal(formatDate([2014, 0, 1]), "01.01.14"); - }); - - it("читает дату из объекта Date", function() { - assert.equal(formatDate(new Date(2014, 0, 1)), "01.01.14"); - }); - -}); \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md deleted file mode 100644 index 878033e9..00000000 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md +++ /dev/null @@ -1,15 +0,0 @@ -Для определения примитивного типа строка/число подойдет оператор [typeof](#type-typeof). - -Примеры его работы: - -```js -//+ run -alert( typeof 123 ); // "number" -alert( typeof "строка" ); // "string" -alert( typeof new Date() ); // "object" -alert( typeof [] ); // "object" -``` - -Оператор `typeof` не умеет различать разные типы объектов, они для него все на одно лицо: `"object"`. Поэтому он не сможет отличить `Date` от `Array`. - -Для отличия `Array` используем вызов `Array.isArray`. Если он неверен, значит у нас дата. \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md deleted file mode 100644 index 4d4176da..00000000 --- a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md +++ /dev/null @@ -1,26 +0,0 @@ -# Полиморфная функция formatDate - -[importance 5] - -Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`. - -Ее первый аргумент должен содержать дату в одном из видов: -
        -
      1. Как объект `Date`.
      2. -
      3. Как строку, например `yyyy-mm-dd` или другую в стандартном формате даты.
      4. -
      5. Как число *секунд* с `01.01.1970`.
      6. -
      7. Как массив `[гггг, мм, дд]`, месяц начинается с нуля
      8. -
      -Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат. - -Пример работы: - -```js -function formatDate(date) { /* ваш код */ } - -alert( formatDate('2011-10-02') ); // 02.10.11 -alert( formatDate(1234567890) ); // 14.02.09 -alert( formatDate([2014, 0, 1]) ); // 01.01.14 -alert( formatDate(new Date(2014, 0, 1)) ); // 01.01.14 -``` - diff --git a/1-js/7-js-misc/1-class-instanceof/article.md b/1-js/7-js-misc/1-class-instanceof/article.md deleted file mode 100644 index 54b519b2..00000000 --- a/1-js/7-js-misc/1-class-instanceof/article.md +++ /dev/null @@ -1,252 +0,0 @@ -# Типы данных: [[Class]], instanceof и утки - -Время от времени бывает удобно создавать так называемые "полиморфные" функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты. - -Для реализации такой возможности нужен способ определить тип переменной. - -## Оператор typeof - -Мы уже знакомы с простейшим способом -- оператором [typeof](#type-typeof). - -Оператор `typeof` надежно работает с примитивными типами, кроме `null`, а также с функциями. Он возвращает для них тип в виде строки: - -```js -//+ run no-beautify -alert( typeof 1 ); // 'number' -alert( typeof true ); // 'boolean' -alert( typeof "Текст" ); // 'string' -alert( typeof undefined ); // 'undefined' -alert( typeof null ); // 'object' (ошибка в языке) -alert( typeof alert ); // 'function' -``` - -...Но все объекты, включая массивы и даты для `typeof` -- на одно лицо, они имеют один тип `'object'`: - -```js -//+ run -alert( typeof {} ); // 'object' -alert( typeof [] ); // 'object' -alert( typeof new Date ); // 'object' -``` - -Поэтому различить их при помощи `typeof` нельзя, и в этом его основной недостаток. - -## Секретное свойство [[Class]] - -Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`. - -Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе. - -Оно взято в квадратные скобки, так как это свойство -- внутреннее. Явно получить его нельзя, но можно прочитать его "в обход", воспользовавшись методом `toString` стандартного объекта `Object`. - -Его внутренняя реализация выводит `[[Class]]` в небольшом обрамлении, как `"[object значение]"`. - -Например: - -```js -//+ run -var toString = {}.toString; - -var arr = [1, 2]; -alert( toString.call(arr) ); // [object Array] - -var date = new Date; -alert( toString.call(date) ); // [object Date] - -var user = { name: "Вася" }; -alert( toString.call(user) ); // [object Object] -``` - -В первой строке мы взяли метод `toString`, принадлежащий именно стандартному объекту `{}`. Нам пришлось это сделать, так как у `Date` и `Array` -- свои собственные методы `toString`, которые работают иначе. - -Затем мы вызываем этот `toString` в контексте нужного объекта `obj`, и он возвращает его внутреннее, невидимое другими способами, свойство `[[Class]]`. - -**Для получения `[[Class]]` нужна именно внутренняя реализация `toString` стандартного объекта `Object`, другая не подойдёт.** - -К счастью, методы в JavaScript -- это всего лишь функции-свойства объекта, которые можно скопировать в переменную и применить на другом объекте через `call/apply`. Что мы и делаем для `{}.toString`. - -Метод также можно использовать с примитивами: - -```js -//+ run -alert( {}.toString.call(123) ); // [object Number] -alert( {}.toString.call("строка") ); // [object String] -``` - -[warn header="Вызов `{}.toString` в консоли может выдать ошибку"] -При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку `{}.toString.call(...)` -- будет ошибка. С другой стороны, вызов `alert( {}.toString... )` -- работает. - -Эта ошибка возникает потому, что фигурные скобки `{ }` в основном потоке кода интерпретируются как блок. Интерпретатор читает `{}.toString.call(...)` так: - -```js -//+ no-beautify -{ } // пустой блок кода -.toString.call(...) // а что это за точка в начале? не понимаю, ошибка! -``` - -Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки `( {}.toString... )` тоже сработает нормально. -[/warn] - - -Для большего удобства можно сделать функцию `getClass`, которая будет возвращать только сам `[[Class]]`: - -```js -//+ run -function getClass(obj) { - return {}.toString.call(obj).slice(8, -1); -} - -alert( getClass(new Date) ); // Date -alert( getClass([1, 2, 3]) ); // Array -``` - -Заметим, что свойство `[[Class]]` есть и доступно для чтения указанным способом -- у всех *встроенных* объектов. Но его нет у объектов, которые создают *наши функции*. Точнее, оно есть, но равно всегда `"Object"`. - -Например: - -```js -//+ run -function User() {} - -var user = new User(); - -alert( {}.toString.call(user) ); // [object Object], не [object User] -``` - -Поэтому узнать тип таким образом можно только для встроенных объектов. - -## Метод Array.isArray() - -Для проверки на массивов есть специальный метод: `Array.isArray(arr)`. Он возвращает `true` только если `arr` -- массив: - -```js -//+ run -alert( Array.isArray([1,2,3]) ); // true -alert( Array.isArray("not array")); // false -``` - -Но этот метод -- единственный в своём роде. - -Других аналогичных, типа `Object.isObject`, `Date.isDate` -- нет. - - -## Оператор instanceof - -Оператор `instanceof` позволяет проверить, создан ли объект данной функцией, причём работает для любых функций -- как встроенных, так и наших. - -```js -//+ run -function User() {} - -var user = new User(); - -alert( user instanceof User ); // true -``` - -Таким образом, `instanceof`, в отличие от `[[Class]]` и `typeof` может помочь выяснить тип для новых объектов, созданных нашими конструкторами. - -Заметим, что оператор `instanceof` -- сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим, и затем вернёмся к `instanceof` в главе [](/instanceof). - - -## Утиная типизация - -Альтернативный подход к типу -- "утиная типизация", которая основана на одной известной пословице: *"If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)"*. - -В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*. - -Смысл утиной типизации -- в проверке необходимых методов и свойств. - -Например, мы можем проверить, что объект -- массив, не вызывая `Array.isArray`, а просто уточнив наличие важного для нас метода, например `splice`: - -```js -//+ run -var something = [1, 2, 3]; - -if (something.splice) { - alert( 'Это утка! То есть, массив!' ); -} -``` - -Обратите внимание -- в `if` мы не вызываем метод `something.splice()`, а пробуем получить само свойство `something.splice`. Для массивов оно всегда есть и является функцией, т.е. даст в логическом контексте `true`. - -Проверить на дату можно, определив наличие метода `getTime`: - -```js -//+ run -var x = new Date(); - -if (x.getTime) { - alert( 'Дата!' ); - alert( x.getTime() ); // работаем с датой -} -``` - -С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом. - -Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле). - -То есть, мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным. - -[smart header="Проверка интерфейса"] -Если говорить словами "классического программирования", то "duck typing" -- это проверка реализации объектом требуемого интерфейса. Если реализует -- ок, используем его. Если нет -- значит это что-то другое. -[/smart] - - -## Пример полиморфной функции - -Пример полиморфной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому: - -```js -//+ run -function sayHi(who) { - - if (Array.isArray(who)) { - who.forEach(sayHi); - } else { - alert( 'Привет, ' + who ); - } -} - -// Вызов с примитивным аргументом -sayHi("Вася"); // Привет, Вася - -// Вызов с массивом -sayHi(["Саша", "Петя"]); // Привет, Саша... Петя - -// Вызов с вложенными массивами - тоже работает! -sayHi(["Саша", "Петя", ["Маша", "Юля"]]); // Привет Саша..Петя..Маша..Юля -``` - -Проверку на массив в этом примере можно заменить на "утиную" -- нам ведь нужен только метод `forEach`: - -```js -//+ run -function sayHi(who) { - - if (who.forEach) { // если есть forEach - who.forEach(sayHi); // предполагаем, что он ведёт себя "как надо" - } else { - alert( 'Привет, ' + who ); - } -} -``` - -## Итого - -Для написания полиморфных (это удобно!) функций нам нужна проверка типов. - -
        -
      • Для примитивов с ней отлично справляется оператор `typeof`. - -У него две особенности: -
          -
        1. Он считает `null` объектом, это внутренняя ошибка в языке.
        2. -
        3. Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.
        4. -
        -
      • -
      • Для встроенных объектов мы можем получить тип из скрытого свойства `[[Class]]`, при помощи вызова `{}.toString.call(obj).slice(8, -1)`. Не работает для конструкторов, которые объявлены нами. -
      • -
      • Оператор `obj instanceof Func` проверяет, создан ли объект `obj` функцией `Func`, работает для любых конструкторов. Более подробно мы разберём его в главе [](/instanceof).
      • -
      • И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется "утиная типизация".
      • -
      - diff --git a/1-js/7-js-misc/2-json/1-serialize-object/solution.md b/1-js/7-js-misc/2-json/1-serialize-object/solution.md deleted file mode 100644 index 0893b44e..00000000 --- a/1-js/7-js-misc/2-json/1-serialize-object/solution.md +++ /dev/null @@ -1,12 +0,0 @@ - - -```js -var leader = { - name: "Василий Иванович", - age: 35 -}; - -var leaderStr = JSON.stringify(leader); -leader = JSON.parse(leaderStr); -``` - diff --git a/1-js/7-js-misc/2-json/1-serialize-object/task.md b/1-js/7-js-misc/2-json/1-serialize-object/task.md deleted file mode 100644 index cc46554d..00000000 --- a/1-js/7-js-misc/2-json/1-serialize-object/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Превратите объект в JSON - -[importance 3] - -Превратите объект `leader` из примера ниже в JSON: - -```js -var leader = { - name: "Василий Иванович", - age: 35 -}; -``` - -После этого прочитайте получившуюся строку обратно в объект. diff --git a/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md b/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md deleted file mode 100644 index 0ec62c2b..00000000 --- a/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md +++ /dev/null @@ -1,64 +0,0 @@ -# Ответ на первый вопрос - -Обычный вызов `JSON.stringify(team)` выдаст ошибку, так как объекты `leader` и `soldier` внутри структуры ссылаются друг на друга. - -Формат JSON не предусматривает средств для хранения ссылок. - -# Варианты решения - -Чтобы превращать такие структуры в JSON, обычно используются два подхода: - -
        -
      1. Добавить в `team` свой код `toJSON`: - -```js -team.toJSON = function() { - /* свой код, который может создавать копию объекта без круговых ссылок и передавать управление JSON.stringify */ -} -``` - -При этом, конечно, понадобится и своя функция чтения из JSON, которая будет восстанавливать объект, а затем дополнять его круговыми ссылками. -
      2. -
      3. Можно учесть возможную проблему в самой структуре, используя вместо ссылок `id`. Как правило, это несложно, ведь на сервере у данных тоже есть идентификаторы. - -Изменённая структура может выглядеть так: - -```js -var leader = { - id: 12, - name: "Василий Иванович" -}; - -var soldier = { - id: 51, - name: "Петька" -}; - -*!* -// поменяли прямую ссылку на ID -leader.soldierId = 51; -soldier.leaderId = 12; -*/!* - -var team = { - 12: leader, - 51: soldier -}; -``` - -..Но действительно ли это решение будет оптимальным? Использовать структуру стало сложнее, и вряд ли это изменение стоит делать лишь из-за JSON. Вот если есть другие преимущества, тогда можно подумать. -
      4. -
      - -Универсальный вариант подхода, описанного выше -- это использование особой реализации JSON, которая не входит в стандарт и поддерживает расширенный формат для поддержки ссылок. - -Она, к примеру, есть во фреймворке Dojo. - -При вызове `dojox.json.ref.toJson(team)` будет создано следующее строковое представление: - -```js -//+ no-beautify -[{"name":"Василий Иванович","soldier":{"name":"Петька","leader":{"$ref":"#0"}}},{"$ref":"#0.soldier"}] -``` - -Метод разбора такой строки -- также свой: `dojox.json.ref.fromJson`. \ No newline at end of file diff --git a/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md b/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md deleted file mode 100644 index 7aa131ef..00000000 --- a/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md +++ /dev/null @@ -1,26 +0,0 @@ -# Превратите объекты со ссылками в JSON - -[importance 3] - -Превратите объект `team` из примера ниже в JSON: - -```js -var leader = { - name: "Василий Иванович" -}; - -var soldier = { - name: "Петька" -}; - -// эти объекты ссылаются друг на друга! -leader.soldier = soldier; -soldier.leader = leader; - -var team = [leader, soldier]; -``` - -
        -
      1. Может ли это сделать прямой вызов `JSON.stringify(team)`? Если нет, то почему?
      2. -
      3. Какой подход вы бы предложили для чтения и восстановления таких объектов?
      4. -
      \ No newline at end of file diff --git a/1-js/7-js-misc/2-json/article.md b/1-js/7-js-misc/2-json/article.md deleted file mode 100644 index ad99b881..00000000 --- a/1-js/7-js-misc/2-json/article.md +++ /dev/null @@ -1,366 +0,0 @@ -# Формат JSON, метод toJSON - -В этой главе мы рассмотрим работу с форматом [JSON](http://ru.wikipedia.org/wiki/JSON), который используется для представления объектов в виде строки. - -Это один из наиболее удобных форматов данных при взаимодействии с JavaScript. Если нужно с сервера взять объект с данными и передать на клиенте, то в качестве промежуточного формата -- для передачи по сети, почти всегда используют именно его. - -В современных браузерах есть замечательные методы, знание тонкостей которых делает операции с JSON простыми и комфортными. - -[cut] - -## Формат JSON - -Данные в формате JSON ([RFC 4627](http://tools.ietf.org/html/rfc4627)) представляют собой: -
        -
      • JavaScript-объекты `{ ... }` или
      • -
      • Массивы `[ ... ]` или
      • -
      • Значения одного из типов: -
          -
        • строки в двойных кавычках,
        • -
        • число,
        • -
        • логическое значение `true`/`false`,
        • -
        • `null`.
        • -
        -
      • -
      - -Почти все языки программирования имеют библиотеки для преобразования объектов в формат JSON. - -Основные методы для работы с JSON в JavaScript -- это: -
        -
      • `JSON.parse` -- читает объекты из строки в формате JSON.
      • -
      • `JSON.stringify` -- превращает объекты в строку в формате JSON, используется, когда нужно из JavaScript передать данные по сети.
      • -
      - -## Метод JSON.parse - -Вызов `JSON.parse(str)` превратит строку с данными в формате JSON в JavaScript-объект/массив/значение. - -Например: - -```js -//+ run -var numbers = "[0, 1, 2, 3]"; - -numbers = JSON.parse(numbers); - -alert( numbers[1] ); // 1 -``` - -Или так: - -```js -//+ run -var user = '{ "name": "Вася", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; - -user = JSON.parse(user); - -alert( user.friends[1] ); // 1 -``` - -Данные могут быть сколь угодно сложными, объекты и массивы могут включать в себя другие объекты и массивы. Главное чтобы они соответствовали формату. - -[warn header="JSON-объекты ≠ JavaScript-объекты"] -Объекты в формате JSON похожи на обычные JavaScript-объекты, но отличаются от них более строгими требованиями к строкам -- они должны быть именно в двойных кавычках. - -В частности, первые два свойства объекта ниже -- некорректны: - -```js -{ - *!*name*/!*: "Вася", // ошибка: ключ name без кавычек! - "surname": *!*'Петров'*/!*,// ошибка: одинарные кавычки у значения 'Петров'! - "age": 35 // .. а тут всё в порядке. - "isAdmin": false // и тут тоже всё ок -} -``` - -Кроме того, в формате JSON не поддерживаются комментарии. Он предназначен только для передачи данных. - -Есть нестандартное расширение формата JSON, которое называется [JSON5](http://json5.org/) и как раз разрешает ключи без кавычек, комментарии и т.п, как в обычном JavaScript. На данном этапе, это отдельная библиотека. -[/warn] - -## Умный разбор: JSON.parse(str, reviver) - -Метод `JSON.parse` поддерживает и более сложные алгоритмы разбора. - -Например, мы получили с сервера объект с данными события `event`. - -Он выглядит так: - -```js -// title: название собятия, date: дата события -var str = '{"title":"Конференция","date":"2012-11-30T12:00:00.000Z"}'; -``` - -...И теперь нужно *восстановить* его, то есть превратить в JavaScript-объект. - -Попробуем вызвать для этого `JSON.parse`: - -```js -//+ run -var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}'; - -var event = JSON.parse(str); - -*!* -alert( event.date.getDate() ); // ошибка! -*/!* -``` - -...Увы, ошибка! - -Дело в том, что значением `event.date` является строка, а отнюдь не объект `Date`. Откуда методу `JSON.parse` знать, что нужно превратить строку именно в дату? - -**Для интеллектуального восстановления из строки у `JSON.parse(str, reviver)` есть второй параметр `reviver`, который является функцией `function(key, value)`.** - -Если она указана, то в процессе чтения объекта из строки `JSON.parse` передаёт ей по очереди все создаваемые пары ключ-значение и может возвратить либо преобразованное значение, либо `undefined`, если его нужно пропустить. - -В данном случае мы можем создать правило, что ключ `date` всегда означает дату: - -```js -//+ run -// дата в строке - в формате UTC -var str = '{"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}'; - -*!* -var event = JSON.parse(str, function(key, value) { - if (key == 'date') return new Date(value); - return value; -}); -*/!* - -alert( event.date.getDate() ); // теперь сработает! -``` - -Кстати, эта возможность работает и для вложенных объектов тоже: - -```js -//+ run -var schedule = '{ \ - "events": [ \ - {"title":"Конференция","date":"2014-11-30T12:00:00.000Z"}, \ - {"title":"День рождения","date":"2015-04-18T12:00:00.000Z"} \ - ]\ -}'; - -schedule = JSON.parse(schedule, function(key, value) { - if (key == 'date') return new Date(value); - return value; -}); - -*!* -alert( schedule.events[1].date.getDate() ); // сработает! -*/!* -``` - -## Сериализация, метод JSON.stringify - -Метод `JSON.stringify(value, replacer, space)` преобразует ("сериализует") значение в JSON-строку. - -Пример использования: - -```js -//+ run -var event = { - title: "Конференция", - date: "сегодня" -}; - -var str = JSON.stringify(event); -alert( str ); // {"title":"Конференция","date":"сегодня"} - -// Обратное преобразование. -event = JSON.parse(str); -``` - -**При сериализации объекта вызывается его метод `toJSON`.** - -Если такого метода нет -- перечисляются его свойства, кроме функций. - -Посмотрим это в примере посложнее: - -```js -//+ run -var room = { - number: 23, - occupy: function() { - alert( this.number ); - } -}; - -event = { - title: "Конференция", - date: new Date(Date.UTC(2014, 0, 1)), - room: room -}; - -alert( JSON.stringify(event) ); -/* - { - "title":"Конференция", - "date":"2014-01-01T00:00:00.000Z", // (1) - "room": {"number":23} // (2) - } -*/ -``` - -Обратим внимание на два момента: -
        -
      1. Дата превратилась в строку. Это не случайно: у всех дат есть встроенный метод `toJSON`. Его результат в данном случае -- строка в таймзоне UTC.
      2. -
      3. У объекта `room` нет метода `toJSON`. Поэтому он сериализуется перечислением свойств. - -Мы, конечно, могли бы добавить такой метод, тогда в итог попал бы его результат: - -```js -//+ run -var room = { - number: 23, -*!* - toJSON: function() { - return this.number; - } -*/!* -}; - -alert( JSON.stringify(room) ); // 23 -``` - -
      4. -
      - -### Исключение свойств - -Попытаемся преобразовать в JSON объект, содержащий ссылку на DOM. - -Например: - -```js -//+ run -var user = { - name: "Вася", - age: 25, - window: window -}; - -*!* -alert( JSON.stringify(user) ); // ошибка! -// TypeError: Converting circular structure to JSON (текст из Chrome) -*/!* -``` - -Произошла ошибка! В чём же дело, неужели некоторые объекты запрещены? Как видно из текста ошибки -- дело совсем в другом. Глобальный объект `window` -- сложная структура с кучей встроенных свойств и круговыми ссылками, поэтому его преобразовать невозможно. Да и нужно ли? - -**Во втором параметре `JSON.stringify(value, replacer)` можно указать массив свойств, которые подлежат сериализации.** - -Например: - -```js -//+ run -var user = { - name: "Вася", - age: 25, - window: window -}; - -*!* -alert( JSON.stringify(user, ["name", "age"]) ); -// {"name":"Вася","age":25} -*/!* -``` - -Для более сложных ситуаций вторым параметром можно передать функцию `function(key, value)`, которая возвращает сериализованное `value` либо `undefined`, если его не нужно включать в результат: - -```js -//+ run -var user = { - name: "Вася", - age: 25, - window: window -}; - -*!* -var str = JSON.stringify(user, function(key, value) { - if (key == 'window') return undefined; - return value; -}); -*/!* - -alert( str ); // {"name":"Вася","age":25} -``` - -В примере выше функция пропустит свойство с названием `window`. Для остальных она просто возвращает значение, передавая его стандартному алгоритму. А могла бы и как-то обработать. - -[smart header="Функция `replacer` работает рекурсивно"] -То есть, если объект содержит вложенные объекты, массивы и т.п., то все они пройдут через `replacer`. -[/smart] - -### Красивое форматирование - -В методе `JSON.stringify(value, replacer, space)` есть ещё третий параметр `space`. - -Если он является числом -- то уровни вложенности в JSON оформляются указанным количеством пробелов, если строкой -- вставляется эта строка. - -Например: - -```js -//+ run -var user = { - name: "Вася", - age: 25, - roles: { - isAdmin: false, - isEditor: true - } -}; - -*!* -var str = JSON.stringify(user, "", 4); -*/!* - -alert( str ); -/* Результат -- красиво сериализованный объект: -{ - "name": "Вася", - "age": 25, - "roles": { - "isAdmin": false, - "isEditor": true - } -} -*/ -``` - -## Итого - -
        -
      • JSON -- формат для представления объектов (и не только) в виде строки.
      • -
      • Методы [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) и [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) позволяют интеллектуально преобразовать объект в строку и обратно.
      • -
      - - - -[head] - -[/head] \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/solution.md deleted file mode 100644 index f6e7fbcd..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/solution.md +++ /dev/null @@ -1,17 +0,0 @@ - - -```js -//+ run -function printNumbersInterval() { - var i = 1; - var timerId = setInterval(function() { - console.log(i); - if (i == 20) clearInterval(timerId); - i++; - }, 100); -} - -// вызов -printNumbersInterval(); -``` - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/task.md b/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/task.md deleted file mode 100644 index dc2364f4..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/1-output-numbers-100ms/task.md +++ /dev/null @@ -1,21 +0,0 @@ -# Вывод чисел каждые 100мс - -[importance 5] - -Напишите функцию `printNumbersInterval()`, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100мс. То есть, весь вывод должен занимать 2000мс, в течение которых каждые 100мс в консоли появляется очередное число. - -Нажмите на кнопку, открыв консоль, для демонстрации: - - - - -P.S. Функция должна использовать `setInterval`. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/2-output-numbers-100ms-settimeout/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/2-output-numbers-100ms-settimeout/solution.md deleted file mode 100644 index bc23dd04..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/2-output-numbers-100ms-settimeout/solution.md +++ /dev/null @@ -1,17 +0,0 @@ - - -```js -//+ run -function printNumbersTimeout20_100() { - var i = 1; - var timerId = setTimeout(function go() { - console.log(i); - if (i < 20) setTimeout(go, 100); - i++; - }, 100); -} - -// вызов -printNumbersTimeout20_100(); -``` - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/2-output-numbers-100ms-settimeout/task.md b/1-js/7-js-misc/3-settimeout-setinterval/2-output-numbers-100ms-settimeout/task.md deleted file mode 100644 index 8334753f..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/2-output-numbers-100ms-settimeout/task.md +++ /dev/null @@ -1,5 +0,0 @@ -# Вывод чисел каждые 100мс, через setTimeout - -[importance 5] - -Сделайте то же самое, что в задаче [](/task/output-numbers-100ms), но с использованием рекурсивного `setTimeout` вместо `setInterval`. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/3-highlight-tactics/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/3-highlight-tactics/solution.md deleted file mode 100644 index 13c70041..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/3-highlight-tactics/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -**Нужно выбрать вариант 2, который гарантирует браузеру свободное время между выполнениями `highlight`.** - -Первый вариант может загрузить процессор на 100%, если `highlight` занимает время, близкое к 10мс или, тем более, большее чем 10мс, т.к. таймер не учитывает время выполнения функции. - -Что интересно, в обоих случаях браузер не будет выводить предупреждение о том, что скрипт занимает много времени. Но от 100% загрузки процессора возможны притормаживания других операций. В общем, это совсем не то, что мы хотим, поэтому вариант 2. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/3-highlight-tactics/task.md b/1-js/7-js-misc/3-settimeout-setinterval/3-highlight-tactics/task.md deleted file mode 100644 index afe1d6fd..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/3-highlight-tactics/task.md +++ /dev/null @@ -1,34 +0,0 @@ -# Для подсветки setInterval или setTimeout? - -[importance 5] - -Стоит задача: реализовать подсветку синтаксиса в длинном коде при помощи JavaScript, для онлайн-редактора кода. Это требует сложных вычислений, особенно загружает процессор генерация дополнительных элементов страницы, визуально осуществляющих подсветку. - -Поэтому решаем обрабатывать не весь код сразу, что привело бы к зависанию скрипта, а разбить работу на части: подсвечивать по 20 строк раз в 10мс. - -Как мы знаем, есть два варианта реализации такой подсветки: - -
        -
      1. Через `setInterval`, с остановкой по окончании работы: - -```js -timer = setInterval(function() { - if (есть еще что подсветить) highlight(); - else clearInterval(timer); -}, 10); -``` - -
      2. -
      3. Через рекурсивный `setTimeout`: - -```js -setTimeout(function go() { - highlight(); - if (есть еще что подсветить) setTimeout(go, 10); -}, 10); -``` - -
      4. -
      - -Какой из них стоит использовать? Почему? \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/4-settimeout-result/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/4-settimeout-result/solution.md deleted file mode 100644 index ea95474b..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/4-settimeout-result/solution.md +++ /dev/null @@ -1,8 +0,0 @@ -Ответы: -
        -
      • `alert` выведет `100000000`.
      • -
      • **3**, срабатывание будет после окончания работы `hardWork`.
      • -
      - - -Так будет потому, что вызов планируется на `100мс` от времени вызова `setTimeout`, но функция выполняется больше, чем `100мс`, поэтому к моменту ее окончания время уже подошло и отложенный вызов выполняется тут же. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/4-settimeout-result/task.md b/1-js/7-js-misc/3-settimeout-setinterval/4-settimeout-result/task.md deleted file mode 100644 index 8c2de36d..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/4-settimeout-result/task.md +++ /dev/null @@ -1,31 +0,0 @@ -# Что выведет setTimeout? - -[importance 5] - -В коде ниже запланирован запуск `setTimeout`, а затем запущена тяжёлая функция `hardWork`, выполнение которой занимает более долгое время, чем интервал до срабатывания таймера. - -Когда сработает `setTimeout`? Выберите нужный вариант: -
        -
      1. До выполнения `hardWork`.
      2. -
      3. Во время выполнения `hardWork`.
      4. -
      5. Сразу же по окончании `hardWork`.
      6. -
      7. Через 100мс после окончания `hardWork`.
      8. -
      - -Что выведет `alert` в коде ниже? - -```js -setTimeout(function() { - alert( i ); -}, 100); - -var i; - -function hardWork() { - // время выполнения этого кода >100мс, сам код неважен - for (i = 0; i < 1e8; i++) hardWork[i % 2] = i; -} - -hardWork(); -``` - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/5-setinterval-result/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/5-setinterval-result/solution.md deleted file mode 100644 index 1fe34f89..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/5-setinterval-result/solution.md +++ /dev/null @@ -1,37 +0,0 @@ -Вызов `alert(i)` в `setTimeout` введет `100000001`. - -Можете проверить это запуском: - -```js -//+ run -var timer = setInterval(function() { - i++; -}, 10); - -setTimeout(function() { - clearInterval(timer); -*!* - alert( i ); // (*) -*/!* -}, 50); - -var i; - -function f() { - // точное время выполнения не играет роли - // здесь оно заведомо больше 100мс - for (i = 0; i < 1e8; i++) f[i % 2] = i; -} - -f(); -``` - -Правильный вариант срабатывания: **3** (сразу же по окончании `f` один раз). - -Планирование `setInterval` будет вызывать функцию каждые `10мс` после текущего времени. Но так как интерпретатор занят долгой функцией, то до конца ее работы никакого вызова не происходит. - -За время выполнения `f` может пройти время, на которое запланированы несколько вызовов `setInterval`, но в этом случае остается только один, т.е. накопления вызовов не происходит. Такова логика работы `setInterval`. - -После окончания текущего скрипта интерпретатор обращается к очереди запланированных вызовов, видит в ней `setInterval` и выполняет. А затем тут же выполняется `setTimeout`, очередь которого тут же подошла. - -Итого, как раз и видим, что `setInterval` выполнился ровно 1 раз по окончании работы функции. Такое поведение кросс-браузерно. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/5-setinterval-result/task.md b/1-js/7-js-misc/3-settimeout-setinterval/5-setinterval-result/task.md deleted file mode 100644 index 84461c2d..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/5-setinterval-result/task.md +++ /dev/null @@ -1,44 +0,0 @@ -# Что выведет после setInterval? - -[importance 5] - -В коде ниже запускается `setInterval` каждые 10мс, и через 50мс запланирована его отмена. - -После этого запущена тяжёлая функция `f`, выполнение которой (мы точно знаем) потребует более 100мс. - -Сработает ли `setInterval`, как и когда? - -Варианты: -
        -
      1. Да, несколько раз, *в процессе* выполнения `f`.
      2. -
      3. Да, несколько раз, *сразу после* выполнения `f`.
      4. -
      5. Да, один раз, *сразу после* выполнения `f`.
      6. -
      7. Нет, не сработает.
      8. -
      9. Может быть по-разному, как повезёт.
      10. -
      - -Что выведет `alert` в строке `(*)`? - -```js -var i; -var timer = setInterval(function() { // планируем setInterval каждые 10мс - i++; -}, 10); - -setTimeout(function() { // через 50мс - отмена setInterval - clearInterval(timer); -*!* - alert( i ); // (*) -*/!* -}, 50); - -// и запускаем тяжёлую функцию -function f() { - // точное время выполнения не играет роли - // здесь оно заведомо больше 100мс - for (i = 0; i < 1e8; i++) f[i % 2] = i; -} - -f(); -``` - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/6-who-runs-faster/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/6-who-runs-faster/solution.md deleted file mode 100644 index 9d8afe0d..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/6-who-runs-faster/solution.md +++ /dev/null @@ -1,55 +0,0 @@ -Задача -- с небольшим "нюансом". - -Есть браузеры, в которых на время работы JavaScript таймер "застывает", например таков IE. В них количество шагов будет почти одинаковым, +-1. - -В других браузерах (Chrome) первый бегун будет быстрее. - -Создадим реальные объекты `Runner` и запустим их для проверки: - -```js -//+ run -function Runner() { - this.steps = 0; - - this.step = function() { - this.doSomethingHeavy(); - this.steps++; - }; - - function fib(n) { - return n <= 1 ? n : fib(n - 1) + fib(n - 2); - } - - this.doSomethingHeavy = function() { - for (var i = 0; i < 25; i++) { - this[i] = fib(i); - } - }; - -} - -var runner1 = new Runner(); -var runner2 = new Runner(); - -// запускаем бегунов -var t1 = setInterval(function() { - runner1.step(); -}, 15); - -var t2 = setTimeout(function go() { - runner2.step(); - t2 = setTimeout(go, 15); -}, 15); - -// кто сделает больше шагов? -setTimeout(function() { - clearInterval(t1); - clearTimeout(t2); - alert( runner1.steps ); - alert( runner2.steps ); -}, 5000); -``` - -Если бы в шаге `step()` не было вызова `doSomethingHeavy()`, то есть он бы не требовал времени, то количество шагов было бы почти равным. - -Но так как у нас шаг, всё же, что-то делает, и функция `doSomethingHeavy()` специально написана таким образом, что она требует (небольшого) времени, то первый бегун успеет сделать больше шагов. Ведь в `setTimeout` пауза `15` мс будет *между* шагами, а `setInterval` шагает равномерно, каждые `15` мс. Получается чаще. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/6-who-runs-faster/task.md b/1-js/7-js-misc/3-settimeout-setinterval/6-who-runs-faster/task.md deleted file mode 100644 index 85d20a37..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/6-who-runs-faster/task.md +++ /dev/null @@ -1,35 +0,0 @@ -# Кто быстрее? - -[importance 5] - -Есть два бегуна: - -```js -var runner1 = new Runner(); -var runner2 = new Runner(); -``` - -У каждого есть метод `step()`, который делает шаг, увеличивая свойство `steps`. - -Конкретный код метода `step()` не имеет значения, важно лишь что шаг делается не мгновенно, он требует небольшого времени. - -Если запустить первого бегуна через `setInterval`, а второго -- через вложенный `setTimeout` -- какой сделает больше шагов за 5 секунд? - -```js -// первый? -setInterval(function() { - runner1.step(); -}, 15); - -// или второй? -setTimeout(function go() { - runner2.step(); - setTimeout(go, 15); -}, 15); - -setTimeout(function() { - alert( runner1.steps ); - alert( runner2.steps ); -}, 5000); -``` - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/_js.view/solution.js b/1-js/7-js-misc/3-settimeout-setinterval/7-delay/_js.view/solution.js deleted file mode 100644 index 62271941..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/_js.view/solution.js +++ /dev/null @@ -1,12 +0,0 @@ -function delay(f, ms) { - - return function() { - var savedThis = this; - var savedArgs = arguments; - - setTimeout(function() { - f.apply(savedThis, savedArgs); - }, ms); - }; - -} \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/_js.view/test.js b/1-js/7-js-misc/3-settimeout-setinterval/7-delay/_js.view/test.js deleted file mode 100644 index 964ccf60..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/_js.view/test.js +++ /dev/null @@ -1,46 +0,0 @@ -describe("delay", function() { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - it("вызывает функцию через указанный таймаут", function() { - var start = Date.now(); - - function f(x) { - assert.equal(Date.now() - start, 1000); - } - f = sinon.spy(f); - - var f1000 = delay(f, 1000); - f1000("test"); - this.clock.tick(2000); - assert(f.calledOnce, 'calledOnce check fails'); - }); - - it("передаёт аргументы и контекст", function() { - var start = Date.now(); - var user = { - sayHi: function(phrase, who) { - assert.equal(this, user); - assert.equal(phrase, "Привет"); - assert.equal(who, "Вася"); - assert.equal(Date.now() - start, 1500); - } - }; - - user.sayHi = sinon.spy(user.sayHi); - - var spy = user.sayHi; - user.sayHi = delay(user.sayHi, 1500); - - user.sayHi("Привет", "Вася"); - - this.clock.tick(2000); - - assert(spy.calledOnce, 'проверка calledOnce не сработала'); - }); -}); \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/7-delay/solution.md deleted file mode 100644 index 2044ff9b..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/solution.md +++ /dev/null @@ -1,46 +0,0 @@ - - -```js -//+ run -function delay(f, ms) { - -*!* - return function() { - var savedThis = this; - var savedArgs = arguments; - - setTimeout(function() { - f.apply(savedThis, savedArgs); - }, ms); - }; -*/!* - -} - -function f(x) { - alert( x ); -} - -var f1000 = delay(f, 1000); -var f1500 = delay(f, 1500); - -f1000("тест"); // выведет "тест" через 1000 миллисекунд -f1500("тест2"); // выведет "тест2" через 1500 миллисекунд -``` - -Обратим внимание на то, как работает обёртка: - -```js -return function() { - var savedThis = this; - var savedArgs = arguments; - - setTimeout(function() { - f.apply(savedThis, savedArgs); - }, ms); -}; -``` - -Именно обёртка возвращается декоратором `delay` и будет вызвана. Чтобы передать аргумент и контекст функции, вызываемой через `ms` миллисекунд, они копируются в локальные переменные `savedThis` и `savedArgs`. - -Это один из самых простых, и в то же время удобных способов передать что-либо в функцию, вызываемую через `setTimeout`. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/task.md b/1-js/7-js-misc/3-settimeout-setinterval/7-delay/task.md deleted file mode 100644 index f198d7b9..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/7-delay/task.md +++ /dev/null @@ -1,23 +0,0 @@ -# Функция-задержка - -[importance 5] - -Напишите функцию `delay(f, ms)`, которая возвращает обёртку вокруг `f`, задерживающую вызов на `ms` миллисекунд. - -Например: - -```js -function f(x) { - alert( x ); -} - -var f1000 = delay(f, 1000); -var f1500 = delay(f, 1500); - -f1000("тест"); // выведет "тест" через 1000 миллисекунд -f1500("тест2"); // выведет "тест2" через 1500 миллисекунд -``` - -Упрощённо можно сказать, что `delay` возвращает "задержанный на `ms`" вариант `f`. - -В примере выше у функции только один аргумент, но `delay` должна быть универсальной: передавать любое количество аргументов и контекст `this`. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/_js.view/solution.js b/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/_js.view/solution.js deleted file mode 100644 index e52efb87..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/_js.view/solution.js +++ /dev/null @@ -1,19 +0,0 @@ -function debounce(f, ms) { - - var state = null; - - var COOLDOWN = 1; - - return function() { - if (state) return; - - f.apply(this, arguments); - - state = COOLDOWN; - - setTimeout(function() { - state = null - }, ms); - } - -} \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/_js.view/test.js b/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/_js.view/test.js deleted file mode 100644 index 860cd6dd..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/_js.view/test.js +++ /dev/null @@ -1,47 +0,0 @@ -describe("debounce", function() { - before(function() { - this.clock = sinon.useFakeTimers(); - }); - - after(function() { - this.clock.restore(); - }); - - it("вызывает функцию не чаще чем раз в ms миллисекунд", function() { - var log = ''; - - function f(a) { - log += a; - } - - f = debounce(f, 1000); - - f(1); // выполнится сразу же - f(2); // игнор - - setTimeout(function() { - f(3) - }, 100); // игнор (рановато) - setTimeout(function() { - f(4) - }, 1100); // выполнится (таймаут прошёл) - setTimeout(function() { - f(5) - }, 1500); // игнор - - this.clock.tick(5000); - assert.equal(log, "14"); - }); - - it("сохраняет контекст вызова", function() { - var obj = { - f: function() { - assert.equal(this, obj); - } - }; - - obj.f = debounce(obj.f, 1000); - obj.f("test"); - }); - -}); \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/solution.md deleted file mode 100644 index d5fc55e1..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/solution.md +++ /dev/null @@ -1,38 +0,0 @@ - - -```js -//+ run no-beautify -function debounce(f, ms) { - - var state = null; - - var COOLDOWN = 1; - - return function() { - if (state) return; - - f.apply(this, arguments); - - state = COOLDOWN; - - setTimeout(function() { state = null }, ms); - } - -} - -function f(x) { alert(x) } -var f = debounce(f, 1000); - -f(1); // 1, выполнится сразу же -f(2); // игнор - -setTimeout( function() { f(3) }, 100); // игнор (прошло только 100мс) -setTimeout( function() { f(4) }, 1100); // 4, выполнится -setTimeout( function() { f(5) }, 1500); // игнор -``` - -Вызов `debounce` возвращает функцию-обёртку. Все необходимые данные для неё хранятся в замыкании. - -При вызове ставится таймер и состояние `state` меняется на константу `COOLDOWN` ("в процессе охлаждения"). - -Последующие вызовы игнорируются, пока таймер не обнулит состояние. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/task.md b/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/task.md deleted file mode 100644 index 31b9f540..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/8-debounce/task.md +++ /dev/null @@ -1,25 +0,0 @@ -# Вызов не чаще чем в N миллисекунд - -[importance 5] - -Напишите функцию `debounce(f, ms)`, которая возвращает обёртку, которая передаёт вызов `f` не чаще, чем раз в `ms` миллисекунд. - -"Лишние" вызовы игнорируются. Все аргументы и контекст -- передаются. - -Например: - -```js -//+ no-beautify -function f() { ... } - -var f = debounce(f, 1000); - -f(1); // выполнится сразу же -f(2); // игнор - -setTimeout( function() { f(3) }, 100); // игнор (прошло только 100мс) -setTimeout( function() { f(4) }, 1100); // выполнится -setTimeout( function() { f(5) }, 1500); // игнор -``` - -Упрощённо можно сказать, что `debounce` возвращает вариант `f`, срабатывающий не чаще чем раз в `ms` миллисекунд. \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/_js.view/solution.js b/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/_js.view/solution.js deleted file mode 100644 index a8011c6b..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/_js.view/solution.js +++ /dev/null @@ -1,29 +0,0 @@ -function throttle(func, ms) { - - var isThrottled = false, - savedArgs, - savedThis; - - function wrapper() { - - if (isThrottled) { - savedArgs = arguments; - savedThis = this; - return; - } - - func.apply(this, arguments); - - isThrottled = true; - - setTimeout(function() { - isThrottled = false; - if (savedArgs) { - wrapper.apply(savedThis, savedArgs); - savedArgs = savedThis = null; - } - }, ms); - } - - return wrapper; -} \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/_js.view/test.js b/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/_js.view/test.js deleted file mode 100644 index 1627e857..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/_js.view/test.js +++ /dev/null @@ -1,47 +0,0 @@ -describe("throttle(f, 1000)", function() { - var f1000; - var log = ""; - - function f(a) { - log += a; - } - - before(function() { - f1000 = throttle(f, 1000); - this.clock = sinon.useFakeTimers(); - }); - - it("первый вызов срабатывает тут же", function() { - f1000(1); // такой вызов должен сработать тут же - assert.equal(log, "1"); - }); - - it("тормозит второе срабатывание до 1000мс", function() { - f1000(2); // (тормозим, не прошло 1000мс) - f1000(3); // (тормозим, не прошло 1000мс) - // через 1000 мс запланирован вызов с последним аргументом - - assert.equal(log, "1"); // пока что сработал только первый вызов - - this.clock.tick(1000); // прошло 1000мс времени - assert.equal(log, "13"); // log==13, т.к. сработал вызов f1000(3) - }); - - it("тормозит третье срабатывание до 1000мс после второго", function() { - this.clock.tick(100); - f1000(4); // (тормозим, с последнего вызова прошло 100мс - менее 1000мс) - this.clock.tick(100); - f1000(5); // (тормозим, с последнего вызова прошло 200мс - менее 1000мс) - this.clock.tick(700); - f1000(6); // (тормозим, с последнего вызова прошло 900мс - менее 1000мс) - - this.clock.tick(100); // сработал вызов с 6 - - assert.equal(log, "136"); - }); - - after(function() { - this.clock.restore(); - }); - -}); \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/solution.md b/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/solution.md deleted file mode 100644 index 306f2977..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/solution.md +++ /dev/null @@ -1,42 +0,0 @@ - - -```js -function throttle(func, ms) { - - var isThrottled = false, - savedArgs, - savedThis; - - function wrapper() { - - if (isThrottled) { // (2) - savedArgs = arguments; - savedThis = this; - return; - } - - func.apply(this, arguments); // (1) - - isThrottled = true; - - setTimeout(function() { - isThrottled = false; // (3) - if (savedArgs) { - wrapper.apply(savedThis, savedArgs); - savedArgs = savedThis = null; - } - }, ms); - } - - return wrapper; -} -``` - -Шаги работы этой функции: -
        -
      1. Декоратор `throttle` возвращает функцию-обёртку `wrapper`, которая при первом вызове запускает `func` и переходит в состояние "паузы" (`isThrottled = true`).
      2. -
      3. В этом состоянии все новые вызовы запоминаются в замыкании через `savedArgs/savedThis`. Обратим внимание, что и контекст вызова и аргументы для нас одинаково важны и запоминаются одновременно. Только зная и то и другое, можно воспроизвести вызов правильно.
      4. -
      5. Далее, когда пройдёт таймаут `ms` миллисекунд -- пауза будет снята, а `wrapper` -- запущен с последними аргументами и контекстом (если во время паузы были вызовы).
      6. -
      - -Шаг `(3)` запускает именно не саму функцию, а снова `wrapper`, так как необходимо не только выполнить `func`, но и снова поставить выполнение на паузу. Получается последовательность "вызов - пауза.. вызов - пауза .. вызов - пауза ...", каждое выполнение в обязательном порядке сопровождается паузой после него. Это удобно описывается рекурсией. diff --git a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/task.md b/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/task.md deleted file mode 100644 index aaf5956b..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/9-throttle/task.md +++ /dev/null @@ -1,52 +0,0 @@ -# Тормозилка - -[importance 5] - -Напишите функцию `throttle(f, ms)` -- "тормозилку", которая возвращает обёртку, передающую вызов `f` не чаще, чем раз в `ms` миллисекунд. - -**У этой функции должно быть важное существенное отличие от `debounce`:** если игнорируемый вызов оказался последним, т.е. после него до окончания задержки ничего нет -- то он выполнится. - -Чтобы лучше понять, откуда взялось это требование, и как `throttle` должна работать -- разберём реальное применение, на которое и ориентирована эта задача. - -**Например, нужно обрабатывать передвижения мыши.** - -В JavaScript это делается функцией, которая будет запускаться при каждом микро-передвижении мыши и получать координаты курсора. По мере того, как мышь двигается, эта функция может запускаться очень часто, может быть 100 раз в секунду (каждые 10мс). - -**Функция обработки передвижения должна обновлять некую информацию на странице.** - -При этом обновление -- слишком "тяжёлый" процесс, чтобы делать его при каждом микро-передвижении. Имеет смысл делать его раз в 100мс, не чаще. - -Пусть функция, которая осуществляет это обновление по передвижению, называется `onmousemove`. - -Вызов `throttle(onmousemove, 100)`, по сути, предназначен для того, чтобы "притормаживать" обработку `onmousemove`. Технически, он должен возвращать обёртку, которая передаёт все вызовы `onmousemove`, но не чаще чем раз в 100мс. - -**При этом промежуточные движения можно игнорировать, но мышь в конце концов где-то остановится. И это последнее, итоговое положение мыши обязательно нужно обработать!** - -Визуально это даст следующую картину обработки перемещений мыши: -
        -
      1. Первое обновление произойдёт сразу (это важно, посетитель тут же видит реакцию на своё действие).
      2. -
      3. Дальше может быть много вызовов (микро-передвижений) с разными координатами, но пока не пройдёт 100мс -- ничего не будет.
      4. -
      5. По истечении 100мс -- опять обновление, с последними координатами. Промежуточные микро-передвижения игнорированы.
      6. -
      7. В конце концов мышь где-то остановится, обновление по окончании очередной паузы 100мс сработает с последними координатами.
      8. -
      - -Ещё раз заметим -- задача из реальной жизни, и в ней принципиально важно, что *последнее* передвижение обрабатывается. Пользователь должен увидеть, где остановил мышь. - -Пример использования: - -```js -var f = function(a) { - console.log(a) -}; - -// затормозить функцию до одного раза в 1000 мс -var f1000 = throttle(f, 1000); - -f1000(1); // выведет 1 -f1000(2); // (тормозим, не прошло 1000мс) -f1000(3); // (тормозим, не прошло 1000мс) - -// когда пройдёт 1000мс... -// выведет 3, промежуточное значение 2 игнорируется -``` - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/article.md b/1-js/7-js-misc/3-settimeout-setinterval/article.md deleted file mode 100644 index a85c0550..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/article.md +++ /dev/null @@ -1,326 +0,0 @@ -# setTimeout и setInterval - -Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени. - -В частности, эта возможность поддерживается в браузерах и в сервере Node.JS. - -[cut] -## setTimeout - -Синтаксис: - -```js -var timerId = setTimeout(func / code, delay[, arg1, arg2...]) -``` - -Параметры: - -
      -
      `func/code`
      -
      Функция или строка кода для исполнения. -Строка поддерживается для совместимости, использовать её не рекомендуется.
      -
      `delay`
      -
      Задержка в милисекундах, 1000 милисекунд равны 1 секунде.
      -
      `arg1`, `arg2`...
      -
      Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.
      -
      - -Исполнение функции произойдёт спустя время, указанное в параметре `delay`. - -Например, следующий код вызовет `func()` через одну секунду: - -```js -//+ run -function func() { - alert( 'Привет' ); -} - -*!* -setTimeout(func, 1000); -*/!* -``` - -С передачей аргументов (не сработает в IE9-): - -```js -//+ run -function func(phrase, who) { - alert( phrase + ', ' + who ); -} - -*!* -setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася -*/!* -``` - -Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки. - -То есть такая запись тоже сработает: - -```js -//+ run no-beautify -setTimeout("alert('Привет')", 1000); -``` - -Однако, использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости. - -Вместо них используйте анонимные функции, вот так: - -```js -//+ run no-beautify -setTimeout(function() { alert('Привет') }, 1000); -``` - -### Отмена исполнения clearTimeout - -Функция `setTimeout` возвращает числовой идентификатор таймера `timerId`, который можно использовать для отмены действия. - -Синтаксис: - -```js -var timerId = setTimeout(...); -clearTimeout(timerId); -``` - -В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит. - -```js -//+ run no-beautify -var timerId = setTimeout(function() { alert(1) }, 1000); -alert(timerId); // число - идентификатор таймера - -clearTimeout(timerId); -alert(timerId); // всё ещё число, оно не обнуляется после отмены -``` - -Как видно из `alert`, в браузере идентификатор таймера является обычным числом. Другие JavaScript-окружения, например Node.JS, могут возвращать объект таймера, с дополнительными методами. - -**Такие разночтения вполне соответствуют стандарту просто потому, что в спецификации JavaScript про таймеры нет ни слова.** - -Таймеры -- это надстройка над JavaScript, которая описана в [секции Timers](http://www.w3.org/TR/html5/webappapis.html#timers) стандарта HTML5 для браузеров и в [документации к Node.JS](http://nodejs.org/docs/latest/api/timers.html) -- для сервера. - -## setInterval - -Метод `setInterval` имеет синтаксис, аналогичный `setTimeout`. - -```js -var timerId = setInterval(func / code, delay[, arg1, arg2...]) -``` - -Смысл аргументов -- тот же самый. Но, в отличие от `setTimeout`, он запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом `clearInterval(timerId)`. - -Следующий пример при запуске станет выводить сообщение каждые две секунды, пока не пройдёт 5 секунд: - -```js -//+ run -// начать повторы с интервалом 2 сек -var timerId = setInterval(function() { - alert( "тик" ); -}, 2000); - -// через 5 сек остановить повторы -setTimeout(function() { - clearInterval(timerId); - alert( 'стоп' ); -}, 5000); -``` - -[smart header="Модальные окна замораживают время в Chrome/Opera/Safari"] -Что будет, если долго не жать `OK` на появившемся `alert`? Это зависит от браузера. - -В браузерах Chrome, Opera и Safari внутренний таймер "заморожен" во время показа `alert/confirm/prompt`. А вот в IE и Firefox внутренний таймер продолжит идти. - -Поэтому, если закрыть `alert` после небольшой паузы, то в Firefox/IE следующий `alert` будет показан сразу же (время подошло), а в Chrome/Opera/Safari -- только через 2 секунды после закрытия. -[/smart] - - -### Рекурсивный setTimeout - -Важная альтернатива `setInterval` -- рекурсивный `setTimeout`: - -```js -/** вместо: -var timerId = setInterval(function() { - alert( "тик" ); -}, 2000); -*/ - -var timerId = setTimeout(function tick() { - alert( "тик" ); -*!* - timerId = setTimeout(tick, 2000); -*/!* -}, 2000); -``` - -В коде выше следующее выполнение планируется сразу после окончания предыдущего. - -**Рекурсивный `setTimeout` -- более гибкий метод тайминга, чем `setInterval`, так как время до следующего выполнения можно запланировать по-разному, в зависимости от результатов текущего.** - -Например, у нас есть сервис, который в 5 секунд опрашивает сервер на предмет новых данных. В случае, если сервер перегружен, можно увеличивать интервал опроса до 10, 20, 60 секунд... А потом вернуть обратно, когда всё нормализуется. - -Если у нас регулярно проходят грузящие процессор задачи, то мы можем оценивать время, потраченное на их выполнение, и планировать следующий запуск раньше или позже. - -**Рекурсивный `setTimeout` гарантирует паузу между вызовами, `setInterval` -- нет.** - -Давайте сравним два кода. Первый использует `setInterval`: - -```js -var i = 1; -setInterval(function() { - func(i); -}, 100); -``` - -Второй использует рекурсивный `setTimeout`: - -```js -var i = 1; -setTimeout(function run() { - func(i); - setTimeout(run, 100); -}, 100); -``` - -При `setInterval` внутренний таймер будет срабатывать чётко каждые `100` мс и вызывать `func(i)`: - - - -Вы обратили внимание?... - -**Реальная пауза между вызовами `func` при `setInterval` меньше, чем указана в коде!** - -Это естественно, ведь время работы функции никак не учитывается, оно "съедает" часть интервала. - -Возможно и такое что `func` оказалась сложнее, чем мы рассчитывали и выполнялась дольше, чем 100мс. - -В этом случае интерпретатор будет ждать, пока функция завершится, затем проверит таймер и, если время вызова `setInterval` уже подошло (или прошло), то следующий вызов произойдёт *сразу же*. - -**Если функция и выполняется дольше, чем пауза `setInterval`, то вызовы будут происходить вообще без перерыва.** - -Исключением является IE, в котором таймер "застывает" во время выполнения JavaScript. - -А так будет выглядеть картинка с рекурсивным `setTimeout`: - - - -**При рекурсивном `setTimeout` задержка всегда фиксирована и равна 100мс.** - -Это происходит потому, что каждый новый запуск планируется только после окончания текущего. - -[smart header="Управление памятью"] -Сборщик мусора в JavaScript не чистит функции, назначенные в таймерах, пока таймеры актуальны. - -При передаче функции в `setInterval/setTimeout` создаётся внутренняя ссылка на неё, через которую браузер её будет запускать, и которая препятствует удалению из памяти, даже если функция анонимна. - -```js -// Функция будет жить в памяти, пока не сработал (или не был очищен) таймер -setTimeout(function() {}, 100); -``` - -
        -
      • Для `setTimeout` -- внутренняя ссылка исчезнет после исполнения функции.
      • -
      • Для `setInterval` -- ссылка исчезнет при очистке таймера.
      • -
      - -Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые `setInterval` могут приводить к излишним тратам памяти. -[/smart] - - -## Минимальная задержка таймера - -У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс. - -По [стандарту](http://www.w3.org/TR/html5/webappapis.html#timers), минимальная задержка составляет 4мс. Так что нет разницы между `setTimeout(..,1)` и `setTimeout(..,4)`. - -Посмотреть минимальное разрешение "вживую" можно на следующем примере. - -**В примере ниже каждая полоска удлиняется вызовом `setInterval` с указанной на ней задержкой -- от 0мс (сверху) до 20мс (внизу).** - -Позапускайте его в различных браузерах. Вы заметите, что несколько первых полосок анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает. - -[iframe border="1" src="setinterval-anim" link edit] - -[warn] -В Internet Explorer, нулевая задержка `setInterval(.., 0)` не сработает. Это касается именно `setInterval`, т.е. `setTimeout(.., 0)` работает нормально. -[/warn] - -[smart header="Откуда взялись эти 4мс?"] -Почему минимальная задержка -- 4мс, а не 1мс? Зачем она вообще существует? - -Это -- "привет" от прошлого. Браузер Chrome как-то пытался убрать минимальную задержку в своих ранних версиях, но оказалось, что существуют сайты, которые используют `setTimeout(..,0)` рекурсивно, создавая тем самым "асинхронный цикл". И, если задержку совсем убрать, то будет 100% загрузка процессора, такой сайт "подвесит" браузер. - -Поэтому, чтобы не ломать существующие скрипты, решили сделать задержку. По возможности, небольшую. На время создания стандарта оптимальным числом показалось 4мс. -[/smart] - -## Реальная частота срабатывания - -В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами `setInterval(..., 4)` может быть не 4мс, а 30мс или даже 1000мс. - -
        -
      • Большинство браузеров (десктопных в первую очередь) продолжают выполнять `setTimeout/setInterval`, даже если вкладка неактивна. - -При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в "фоновой" вкладке будет срабатывать таймер, но редко.
      • -
      • При работе от батареи, в ноутбуке -- браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек.
      • -
      • При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски `setInterval` будут пропущены.
      • -
      - -**Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.** - -[online] -Посмотрим снижение частоты в действии на небольшом примере. - - -При клике на кнопку ниже запускается `setInterval(..., 90)`, который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь. - -
      - - - - - - -Если ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные красным. - -Кроме того, вы заметите, что таймер не является идеально точным ;) -[/online] - -## Разбивка долгих скриптов - -Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения "тяжелых" скриптов. - -Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ -- на большом файле это займёт много времени, браузер может даже подвиснуть, что неприемлемо. - -Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время. - -Например, осуществляется анализ и подсветка первых 100 строк, затем через 20 мс -- следующие 100 строк и так далее. При этом можно подстраиваться под CPU посетителя: замерять время на анализ 100 строк и, если процессор хороший, то в следующий раз обработать 200 строк, а если плохой -- то 50. В итоге подсветка будет работать с адекватной быстротой и без тормозов на любых текстах и компьютерах. - -## Итого - -
        -
      • Методы `setInterval(func, delay)` и `setTimeout(func, delay)` позволяют запускать `func` регулярно/один раз через `delay` миллисекунд.
      • -
      • Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом `clearInterval/clearTimeout`.
      • -
      • В случаях, когда нужно гарантировать задержку между регулярными вызовами или гибко её менять, вместо `setInterval` используют рекурсивный `setTimeout`.
      • -
      • Минимальная задержка по стандарту составляет `4мс`. Браузеры соблюдают этот стандарт, но некоторые другие среды для выполнения JS, например Node.JS, могут предоставить и меньше задержки.
      • -
      • В реальности срабатывания таймера могут быть гораздо реже, чем назначено, например если процессор перегружен, вкладка находится в фоновом режиме, ноутбук работает от батареи или по какой-то иной причине.
      • - -Браузерных особенностей почти нет, разве что вызов `setInterval(..., 0)` с нулевой задержкой в IE недопустим, нужно указывать `setInterval(..., 1)`. - - - - - diff --git a/1-js/7-js-misc/3-settimeout-setinterval/setinterval-anim.view/index.html b/1-js/7-js-misc/3-settimeout-setinterval/setinterval-anim.view/index.html deleted file mode 100644 index caa33217..00000000 --- a/1-js/7-js-misc/3-settimeout-setinterval/setinterval-anim.view/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/7-js-misc/3-settimeout-setinterval/setinterval-interval.png b/1-js/7-js-misc/3-settimeout-setinterval/setinterval-interval.png deleted file mode 100644 index 0a815bdb..00000000 Binary files a/1-js/7-js-misc/3-settimeout-setinterval/setinterval-interval.png and /dev/null differ diff --git a/1-js/7-js-misc/3-settimeout-setinterval/setinterval-interval@2x.png b/1-js/7-js-misc/3-settimeout-setinterval/setinterval-interval@2x.png deleted file mode 100644 index 1e764160..00000000 Binary files a/1-js/7-js-misc/3-settimeout-setinterval/setinterval-interval@2x.png and /dev/null differ diff --git a/1-js/7-js-misc/3-settimeout-setinterval/settimeout-interval.png b/1-js/7-js-misc/3-settimeout-setinterval/settimeout-interval.png deleted file mode 100644 index b6909cc2..00000000 Binary files a/1-js/7-js-misc/3-settimeout-setinterval/settimeout-interval.png and /dev/null differ diff --git a/1-js/7-js-misc/3-settimeout-setinterval/settimeout-interval@2x.png b/1-js/7-js-misc/3-settimeout-setinterval/settimeout-interval@2x.png deleted file mode 100644 index 657ec0db..00000000 Binary files a/1-js/7-js-misc/3-settimeout-setinterval/settimeout-interval@2x.png and /dev/null differ diff --git a/1-js/7-js-misc/4-eval/1-eval-calculator/solution.md b/1-js/7-js-misc/4-eval/1-eval-calculator/solution.md deleted file mode 100644 index 0ad14328..00000000 --- a/1-js/7-js-misc/4-eval/1-eval-calculator/solution.md +++ /dev/null @@ -1,12 +0,0 @@ -Вычислить любое выражение нам поможет `eval`: - -```js -//+ demo run -var expr = prompt("Введите выражение?", '2*3+2'); - -alert( eval(expr) ); -``` - -При этом посетитель потенциально может делать все, что угодно. - -Чтобы ограничить выражения только математикой, вводимую строку нужно проверять при помощи [регулярных выражений](/regular-expressions-javascript) на наличие любых символов, кроме букв, пробелов и знаков пунктуации. diff --git a/1-js/7-js-misc/4-eval/1-eval-calculator/task.md b/1-js/7-js-misc/4-eval/1-eval-calculator/task.md deleted file mode 100644 index 771b005f..00000000 --- a/1-js/7-js-misc/4-eval/1-eval-calculator/task.md +++ /dev/null @@ -1,9 +0,0 @@ -# Eval-калькулятор - -[importance 4] - -Напишите интерфейс, который принимает математическое выражение (`prompt`) и возвращает его результат. - -Проверять выражение на корректность не требуется. - -[demo /] diff --git a/1-js/7-js-misc/4-eval/article.md b/1-js/7-js-misc/4-eval/article.md deleted file mode 100644 index 7f8ab9e2..00000000 --- a/1-js/7-js-misc/4-eval/article.md +++ /dev/null @@ -1,266 +0,0 @@ -# Запуск кода из строки: eval - -Функция `eval(code)` позволяет выполнить код, переданный ей в виде строки. - -Этот код будет выполнен в *текущей области видимости*. -[cut] - -## Использование eval - -В простейшем случае `eval` всего лишь выполняет код, например: - -```js -//+ run no-beautify -var a = 1; - -(function() { - - var a = 2; - -*!* - eval(' alert(a) '); // 2 -*/!* - -})() -``` - -Но он может не только выполнить код, но и вернуть результат. - -**Вызов `eval` возвращает последнее вычисленное выражение**: - -Например: - -```js -//+ run -alert( eval('1+1') ); // 2 -``` - -**При вызове `eval` имеет полный доступ к локальным переменным.** - -Это означает, что текущие переменные могут быть изменены или дополнены: - -```js -//+ untrusted refresh run -var x = 5; -eval(" alert( x ); x = 10"); // 5, доступ к старому значению -alert( x ); // 10, значение изменено внутри eval -``` - -[smart header="В строгом режиме `eval` имеет свою область видимости "] -В строгом режиме функционал `eval` чуть-чуть меняется. - -При `use strict` код внутри `eval` по-прежнему сможет читать и менять внешние переменные, однако переменные и функции, объявленные внутри `eval`, не попадут наружу. - -```js -//+ untrusted refresh run -"use strict"; - -*!* -eval("var a = 5; function f() { }"); -*/!* -alert( a ); // ошибка, переменная не определена -// функция f тоже не видна снаружи -``` - -Иными словами, в новом стандарте `eval` имеет свою область видимости, а к внешним переменным обращается через замыкание, аналогично тому, как работают обычные функции. -[/smart] - -## Неграмотное использование eval - -Начнём с того, что `eval` применяется очень редко. Действительно редко. Есть даже такое выражение "eval is evil" (eval -- зло). - -Причина проста: когда-то JavaScript был гораздо более слабым языком, чем сейчас, и некоторые вещи без `eval` было сделать невозможно. Но те времена давно прошли. И теперь найти тот случай, когда действительно надо выполнить код из строки -- это надо постараться. - -Но если вы действительно знаете, что это именно тот случай и вам необходим `eval` -- есть ряд вещей, которые нужно иметь в виду. - -Доступ к локальным переменным -- худшее, что можно сделать при `eval`. - -Дело в том, что локальные переменные могут быть легко переименованы: - -```js -function sayHi() { - var phrase = "Привет"; - eval(str); -} -``` - -Переменная `phrase` может быть переименована в `hello`, и если строка `str` обращается к ней -- будет ошибка. - -Современные средства сжатия JavaScript переименовывают локальные переменные автоматически. Это считается безопасным, так как локальная переменная видна лишь внутри функции и если в ней везде поменять `phrase` на `p`, то никто этого не заметит. - -До сжатия: - -```js -function sayHi() { - var phrase = "Привет"; - alert( phrase ); -} -``` - -После сжатия: - -```js -function sayHi() { - var a = "Привет"; - alert( a ); -} -``` - -На самом деле всё ещё проще -- в данном случае утилита сжатия автоматически уберёт переменную `a` и код станет таким: - -```js -function sayHi() { - alert( "Привет" ); -} -``` - -Итак, если где-то в функции есть `eval`, то его взаимодействие с локальными переменными будет нарушено с непредсказуемыми побочными эффектами. - -Некоторые инструменты сжатия предупреждают, когда видят `eval` или стараются вообще не сжимать такой код вместе с его внешними функциями, но всё это борьба с последствиями кривого кода. - -Как правило, `eval` не нужен, именно поэтому говорят, "eval is evil". - -## Запуск скрипта в глобальной области - -Ок, взаимодействовать с локальными переменными нельзя. - -Но допустим мы загрузили с сервера или вручную сгенерировали скрипт, который нужно выполнить. Желательно, в глобальной области, вне любых функций, чтобы он уж точно к локальным переменным отношения не имел. - -Здесь `eval` может пригодиться. Есть два трюка для выполнения кода в глобальной области: - -
          -
        1. Везде, кроме IE8-, достаточно вызвать `eval` не напрямую, а через `window.eval`. - -Вот так: - -```js -//+ run no-beautify -var a = 1; - -(function() { - - var a = 2; -*!* - window.eval(' alert(a) '); // 1, выполнено глобально везде, кроме IE8- -*/!* -})(); -``` - -
        2. -
        3. В IE8- можно применить нестандартную фунцию [execScript](http://msdn.microsoft.com/en-us/library/ie/ms536420%28v=vs.85%29.aspx). Она, как и `eval`, выполняет код, но всегда в глобальной области видимости и не возвращает значение.
        4. -
        - -Оба способа можно объединить в единой функции `globalEval(code)`, выполняющей код без доступа к локальным переменным: - -```js -//+ run no-beautify -*!* -function globalEval(code) { // объединим два способа в одну функцию - window.execScript ? execScript(code) : window.eval(code); -} -*/!* - -var a = 1; - -(function() { - - var a = 2; - - globalEval(' alert(a) '); // 1, во всех браузерах - -})(); -``` - -## Внешние данные через new Function - -Итак, у нас есть код, который, всё же, нужно выполнить динамически, через `eval`, но не просто скрипт -- а ему нужно передать какие-то значения. - -Как мы говорили ранее, считать их из локальных переменных нельзя: это подвержено ошибкам при переименовании переменных и сразу ломается при сжатии JavaScript. Да и вообще, неочевидно и криво. - -**К счастью, существует отличная альтернатива `eval`, которая позволяет корректно взаимодействовать c внешним кодом: `new Function`.** - -Вызов `new Function('a,b', '..тело..')` создает функцию с указанными аргументами `a,b` и телом. Как мы помним, доступа к текущему замыканию у такой функции не будет, но можно передать параметры и получить результат. - -Например: - -```js -//+ run -var a = 2, - b = 3; - -*!* -// вместо обращения к a,b через eval -// будем принимать их как аргументы динамически созданной функции -var mul = new Function('a, b', ' return a * b;'); -*/!* - -alert( mul(a, b) ); // 6 -``` - -## JSON и eval - -В браузерах IE7- не было методов `JSON.stringify` и `JSON.parse`, поэтому работа с JSON происходила через `eval`. - -Этот способ работы с JSON давно устарел, но его можно встретить кое-где в старом коде, так что для примера рассмотрим его. - -Вызов `eval(code)` выполняет код и, если это выражение, то возвращает его значение, поэтому можно в качестве кода передать JSON. - -Например: - -```js -//+ run -var str = '{ \ - "name": "Вася", \ - "age": 25 \ -}'; - -*!* -var user = eval('(' + str + ')'); -*/!* - -alert( user.name ); // Вася -``` - -Зачем здесь нужны скобки `eval( '(' + str + ')' )`, почему не просто `eval(str)`? - -...Всё дело в том, что в JavaScript с фигурной скобки `{` начинаются не только объекты, а в том числе и "блоки кода". Что имеется в виду в данном случае -- интерпретатор определяет по контексту. Если в основном потоке кода -- то блок, если в контексте выражения, то объект. - -Поэтому если передать в `eval` объект напрямую, то интерпретатор подумает, что это на самом деле блок кода, а там внутри какие-то двоеточия... - -Вот, для примера, `eval` без скобок, он выдаст ошибку: - -```js -//+ run -var user = eval('{ "name": "Вася", "age": 25 }'); -``` - -А если `eval` получает выражение в скобках `( ... )`, то интерпретатор точно знает, что это не блок кода, а объект: - -```js -//+ run -var user = eval('( { "name": "Вася", "age": 25 } )'); -alert( user.age ); // 25 -``` - -[warn header="Осторожно, злой JSON!"] -Если мы получаем JSON из недоверенного источника, например с чужого сервера, то разбор через `eval` может быть опасен. - -Например, чужой сервер может быть взломан (за свой-то код мы отвечаем, а за чужой -- нет) и вместо JSON вставлен злонамеренный JavaScript-код. - -**Поэтому рекомендуется, всё же, использовать `JSON.parse`.** - -При разборе через `JSON.parse` некорректный JSON просто приведёт к ошибке, а вот при разборе через `eval` этот код реально выполнится, он может вывести что-то на странице, перенаправить посетителя куда-то и т.п. -[/warn] - - -## Итого - -
          -
        • Функция `eval(str)` выполняет код и возвращает последнее вычисленное выражение. В современном JavaScript она используется редко.
        • -
        • Вызов `eval` может читать и менять локальные переменные. Это -- зло, которого нужно избегать.
        • -
        • Для выполнения скрипта в глобальной области используются трюк с `window.eval/execScript`. При этом локальные переменные не будут затронуты, так что такое выполнение безопасно и иногда, в редких архитектурах, может быть полезным.
        • -
        • Если нужно выполняемый код всё же должен взаимодействовать с локальными переменными -- используйте `new Function`. Создавайте функцию из строки и передавайте переменные ей, это надёжно и безопасно.
        • -
        - -Ещё примеры использования `eval` вы найдёте далее, в главе [](/json). - diff --git a/1-js/7-js-misc/5-exception/1-finally-or-code-after/solution.md b/1-js/7-js-misc/5-exception/1-finally-or-code-after/solution.md deleted file mode 100644 index 5befa284..00000000 --- a/1-js/7-js-misc/5-exception/1-finally-or-code-after/solution.md +++ /dev/null @@ -1,43 +0,0 @@ -Разница в поведении станет очевидной, если рассмотреть код внутри функции. - -Поведение будет различным, если управление каким-то образом выпрыгнет из `try..catch`. - -Например, `finally` сработает после `return`: - -```js -function f() { - try { - ... -*!* - return result; -*/!* - } catch (e) { - ... - } finally { - очистить ресурсы - } -} -``` - -Или же управление может выпрыгнуть из-за `throw`: - -```js -function f() { - try { - ... - - } catch (e) { - ... - if(не умею обрабатывать эту ошибку) { -*!* - throw e; -*/!* - } - - } finally { - очистить ресурсы - } -} -``` - -В этих случаях именно `finally` гарантирует выполнение кода до окончания работы `f`, просто код не будет вызван. \ No newline at end of file diff --git a/1-js/7-js-misc/5-exception/1-finally-or-code-after/task.md b/1-js/7-js-misc/5-exception/1-finally-or-code-after/task.md deleted file mode 100644 index 5c93daf5..00000000 --- a/1-js/7-js-misc/5-exception/1-finally-or-code-after/task.md +++ /dev/null @@ -1,43 +0,0 @@ -# Finally или просто код? - -[importance 5] - -Сравните два фрагмента кода. - -
          -
        1. Первый использует `finally` для выполнения кода по выходу из `try..catch`: - -```js -try { - начать работу - работать -} catch (e) { - обработать ошибку -} finally { -*!* - финализация: завершить работу -*/!* -} -``` - -
        2. -
        3. Второй фрагмент просто ставит очистку ресурсов за `try..catch`: - -```js -try { - начать работу -} catch (e) { - обработать ошибку -} - -*!* -финализация: завершить работу -*/!* -``` - -
        4. -
        - -Нужно, чтобы код финализации всегда выполнялся при выходе из блока `try..catch` и, таким образом, заканчивал начатую работу. Имеет ли здесь `finally` какое-то преимущество или оба фрагмента работают одинаково? - -Если имеет, то дайте пример когда код с `finally` работает верно, а без -- неверно. diff --git a/1-js/7-js-misc/5-exception/2-eval-calculator-errors/solution.md b/1-js/7-js-misc/5-exception/2-eval-calculator-errors/solution.md deleted file mode 100644 index a9cd06aa..00000000 --- a/1-js/7-js-misc/5-exception/2-eval-calculator-errors/solution.md +++ /dev/null @@ -1,36 +0,0 @@ -Вычислить любое выражение нам поможет `eval`: - -```js -//+ run -alert( eval("2+2") ); // 4 -``` - -Считываем выражение в цикле `while(true)`. Если при вычислении возникает ошибка -- ловим её в `try..catch`. - -Ошибкой считается, в том числе, получение `NaN` из `eval`, хотя при этом исключение не возникает. Можно бросить своё исключение в этом случае. - -Код решения: - -```js -//+ run demo -var expr, res; - -while (true) { - expr = prompt("Введите выражение?", '2-'); - if (expr == null) break; - - try { - res = eval(expr); - if (isNaN(res)) { - throw new Error("Результат неопределён"); - } - - break; - } catch (e) { - alert( "Ошибка: " + e.message + ", повторите ввод" ); - } -} - -alert( res ); -``` - diff --git a/1-js/7-js-misc/5-exception/2-eval-calculator-errors/task.md b/1-js/7-js-misc/5-exception/2-eval-calculator-errors/task.md deleted file mode 100644 index 73e5108f..00000000 --- a/1-js/7-js-misc/5-exception/2-eval-calculator-errors/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Eval-калькулятор с ошибками - -[importance 5] - -Напишите интерфейс, который принимает математическое выражение (в `prompt`) и результат его вычисления через `eval`. - -**При ошибке нужно выводить сообщение и просить переввести выражение**. - -Ошибкой считается не только некорректное выражение, такое как `2+`, но и выражение, возвращающее `NaN`, например `0/0`. - -[demo /] - - diff --git a/1-js/7-js-misc/5-exception/article.md b/1-js/7-js-misc/5-exception/article.md deleted file mode 100644 index 42b30917..00000000 --- a/1-js/7-js-misc/5-exception/article.md +++ /dev/null @@ -1,590 +0,0 @@ -# Перехват ошибок, "try..catch" - -Как бы мы хорошо ни программировали, в коде бывают ошибки. Или, как их иначе называют, "исключительные ситуации" (исключения). - -Обычно скрипт при ошибке, как говорят, "падает", с выводом ошибки в консоль. - -Но бывают случаи, когда нам хотелось бы как-то контролировать ситуацию, чтобы скрипт не просто "упал", а сделал что-то разумное. - -Для этого в JavaScript есть замечательная конструкция `try..catch`. - -[cut] - -## Конструкция try..catch - -Конструкция `try..catch` состоит из двух основных блоков: `try`, и затем `catch`: - -```js -try { - - // код ... - -} catch (err) { - - // обработка ошибки - -} -``` - -Работает она так: -
          -
        1. Выполняется код внутри блока `try`.
        2. -
        3. Если в нём ошибок нет, то блок `catch(err)` игнорируется, то есть выполнение доходит до конца `try` и потом прыгает через `catch`.
        4. -
        5. Если в нём возникнет ошибка, то выполнение `try` на ней прерывается, и управление прыгает в начало блока `catch(err)`. - -При этом переменная `err` (можно выбрать и другое название) будет содержать объект ошибки с подробной информацией о произошедшем.
        6. -
        - -**Таким образом, при ошибке в `try` скрипт не "падает", и мы получаем возможность обработать ошибку внутри `catch`.** - -Посмотрим это на примерах. - -
          -
        • Пример без ошибок: при запуске сработают `alert` `(1)` и `(2)`: - -```js -//+ run -try { - - alert('Начало блока try'); // *!*(1) <--*/!* - - // .. код без ошибок - - alert('Конец блока try'); // *!*(2) <--*/!* - -} catch(e) { - - alert('Блок catch не получит управление, так как нет ошибок'); // (3) - -} - -alert("Потом код продолжит выполнение..."); -``` - -
        • -
        • Пример с ошибкой: при запуске сработают `(1)` и `(3)`: - -```js -//+ run -try { - - alert('Начало блока try'); // *!*(1) <--*/!* - -*!* - lalala; // ошибка, переменная не определена! -*/!* - - alert('Конец блока try'); // (2) - -} catch(e) { - - alert('Ошибка ' + e.name + ":" + e.message + "\n" + e.stack); // *!*(3) <--*/!* - -} - -alert("Потом код продолжит выполнение..."); -``` - -
        • -
        - -[warn header="`try..catch` подразумевает, что код синтаксически верен"] -Если грубо нарушена структура кода, например не закрыта фигурная скобка или где-то стоит лишняя запятая, то никакой `try..catch` здесь не поможет. Такие ошибки называются *синтаксическими*, интерпретатор не может понять такой код. - -Здесь же мы рассматриваем ошибки *семантические*, то есть происходящие в корректном коде, в процессе выполнения. -[/warn] - - -[warn header="`try..catch` работает только в синхронном коде"] -Ошибку, которая произойдёт в коде, запланированном "на будущее", например, в `setTimeout`, `try..catch` не поймает: - -```js -//+ run -try { - setTimeout(function() { - throw new Error(); // вылетит в консоль - }, 1000); -} catch (e) { - alert( "не сработает" ); -} -``` - -На момент запуска функции, назначенной через `setTimeout`, этот код уже завершится, интерпретатор выйдет из блока `try..catch`. - -Чтобы поймать ошибку внутри функции из `setTimeout`, и `try..catch` должен быть в той же функции. -[/warn] - - -## Объект ошибки - -В примере выше мы видим объект ошибки. У него есть три основных свойства: -
        -
        `name`
        -
        Тип ошибки. Например, при обращении к несуществующей переменной: `"ReferenceError"`.
        -
        `message`
        -
        Текстовое сообщение о деталях ошибки.
        -
        `stack`
        -
        Везде, кроме IE8-, есть также свойство `stack`, которое содержит строку с информацией о последовательности вызовов, которая привела к ошибке.
        -
        - -В зависимости от браузера, у него могут быть и дополнительные свойства, см. Error в MDN и Error в MSDN. - -## Пример использования - -В JavaScript есть встроенный метод [JSON.parse(str)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse), который используется для чтения JavaScript-объектов (и не только) из строки. - -Обычно он используется для того, чтобы обрабатывать данные, полученные по сети, с сервера или из другого источника. - -Мы получаем их и вызываем метод `JSON.parse`, вот так: - -```js -//+ run -var data = '{"name":"Вася", "age": 30}'; // строка с данными, полученная с сервера - -var user = JSON.parse(data); // преобразовали строку в объект - -// теперь user -- это JS-объект с данными из строки -alert( user.name ); // Вася -alert( user.age ); // 30 -``` - -Более детально формат JSON разобран в главе [](/json). - -**В случае, если данные некорректны, `JSON.parse` генерирует ошибку, то есть скрипт "упадёт".** - -Устроит ли нас такое поведение? Конечно нет! - -Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. - -А люди очень-очень не любят, когда что-то "просто падает", без всякого объявления об ошибке. - -**Бывают ситуации, когда без `try..catch` не обойтись, это -- одна из таких.** - -Используем `try..catch`, чтобы обработать некорректный ответ: - -```js -//+ run -var data = "Has Error"; // в данных ошибка - -try { - - var user = JSON.parse(data); // <-- ошибка при выполнении - alert( user.name ); // не сработает - -} catch (e) { - // ...выполнится catch - alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз" ); - alert( e.name ); - alert( e.message ); -} -``` - -Здесь в `alert` только выводится сообщение, но область применения гораздо шире: можно повторять запрос, можно предлагать посетителю использовать альтернативный способ, можно отсылать информацию об ошибке на сервер... Свобода действий. - -## Генерация своих ошибок - -Представим на минуту, что данные являются корректным JSON... Но в этом объекте нет нужного свойства `name`: - -```js -//+ run -var data = '{ "age": 30 }'; // данные неполны - -try { - - var user = JSON.parse(data); // <-- выполнится без ошибок -*!* - alert( user.name ); // undefined -*/!* - -} catch (e) { - // не выполнится - alert( "Извините, в данных ошибка" ); -} -``` - -Вызов `JSON.parse` выполнится без ошибок, но ошибка в данных есть. И, так как свойство `name` обязательно должно быть, то для нас это такие же некорректные данные как и `"Has Error"`. - -Для того, чтобы унифицировать и объединить обработку ошибок парсинга и ошибок в структуре, мы воспользуемся оператором `throw`. - -### Оператор throw - -Оператор `throw` генерирует ошибку. - -Синтаксис: `throw <объект ошибки>`. - -Технически, в качестве объекта ошибки можно передать что угодно, это может быть даже не объект, а число или строка, но всё же лучше, чтобы это был объект, желательно -- совместимый со стандартным, то есть чтобы у него были как минимум свойства `name` и `message`. - -**В качестве конструктора ошибок можно использовать встроенный конструктор: `new Error(message)` или любой другой.** - -В JavaScript встроен ряд конструкторов для стандартных ошибок: `SyntaxError`, `ReferenceError`, `RangeError` и некоторые другие. Можно использовать и их, но только чтобы не было путаницы. - -В данном случае мы используем конструктор `new SyntaxError(message)`. Он создаёт ошибку того же типа, что и `JSON.parse`. - -```js -//+ run -var data = '{ "age": 30 }'; // данные неполны - -try { - - var user = JSON.parse(data); // <-- выполнится без ошибок - -*!* - if (!user.name) { - throw new SyntaxError("Данные некорректны"); - } -*/!* - - alert( user.name ); - -} catch (e) { - alert( "Извините, в данных ошибка" ); -} -``` - -Получилось, что блок `catch` -- единое место для обработки ошибок во всех случаях: когда ошибка выявляется при `JSON.parse` или позже. - -## Проброс исключения - -В коде выше мы предусмотрели обработку ошибок, которые возникают при некорректных данных. Но может ли быть так, что возникнет какая-то другая ошибка? - -Конечно, может! Код -- это вообще мешок с ошибками, бывает даже так что библиотеку выкладывают в открытый доступ, она там 10 лет лежит, её смотрят миллионы людей и на 11й год находятся опаснейшие ошибки. Такова жизнь, таковы люди. - -Блок `catch` в нашем примере предназначен для обработки ошибок, возникающих при некорректных данных. Если же в него попала какая-то другая ошибка, то вывод сообщения о "некорректных данных" будет дезинформацией посетителя. - -**Ошибку, о которой `catch` не знает, он не должен обрабатывать.** - -Такая техника называется *"проброс исключения"*: в `catch(e)` мы анализируем объект ошибки, и если он нам не подходит, то делаем `throw e`. - -При этом ошибка "выпадает" из `try..catch` наружу. Далее она может быть поймана либо внешним блоком `try..catch` (если есть), либо "повалит" скрипт. - -В примере ниже `catch` обрабатывает только ошибки `SyntaxError`, а остальные -- выбрасывает дальше: - -```js -//+ run -var data = '{ "name": "Вася", "age": 30 }'; // данные корректны - -try { - - var user = JSON.parse(data); - - if (!user.name) { - throw new SyntaxError("Ошибка в данных"); - } - -*!* - blabla(); // произошла непредусмотренная ошибка -*/!* - - alert( user.name ); - -} catch (e) { - -*!* - if (e.name == "SyntaxError") { - alert( "Извините, в данных ошибка" ); - } else { - throw e; - } -*/!* - -} -``` - -Заметим, что ошибка, которая возникла внутри блока `catch`, "выпадает" наружу, как если бы была в обычном коде. - -В следующем примере такие ошибки обрабатываются ещё одним, "более внешним" `try..catch`: - -```js -//+ run -function readData() { - var data = '{ "name": "Вася", "age": 30 }'; - - try { - // ... -*!* - blabla(); // ошибка! -*/!* - } catch (e) { - // ... -*!* - if (e.name != 'SyntaxError') { - throw e; // пробрасываем - } -*/!* - } -} - - -try { - readData(); -} catch (e) { -*!* - alert( "Поймал во внешнем catch: " + e ); // ловим -*/!* -} -``` - -В примере выше `try..catch` внутри `readData` умеет обрабатывать только `SyntaxError`, а внешний -- все ошибки. - -Без внешнего проброшенная ошибка "вывалилась" бы в консоль, с остановкой скрипта. - -## Оборачивание исключений - -И, для полноты картины -- последняя, самая продвинутая техника по работе с ошибками. Она, впрочем, является стандартной практикой во многих объектно-ориентированных языках. - -Цель функции `readData` в примере выше -- прочитать данные. При чтении могут возникать разные ошибки, не только `SyntaxError`, но и, возможно, к примеру, `URIError` (неправильное применение функций работы с URI), да и другие. - -Код, который вызвал `readData`, хотел бы иметь либо результат, либо информацию об ошибке. - -При этом очень важным является вопрос: обязан ли этот внешний код знать о всевозможных типах ошибок, которые могут возникать при чтении данных, и уметь перехватывать их? - -Обычно внешний код хотел бы работать "на уровень выше", и получать либо результат, либо "ошибку чтения данных", при этом какая именно ошибка произошла -- ему неважно. Ну, или, если будет важно, то хотелось бы иметь возможность это узнать, но обычно не требуется. - -Это важнейший общий подход к проектированию -- каждый участок функционала должен получать информацию на том уровне, который ему необходим. - -Мы его видим везде в грамотно построенном коде, но не всегда отдаём себе в этом отчёт. - -В данном случае, если при чтении данных происходит ошибка, то мы будем генерировать её в виде объекта `ReadError`, с соответствующим сообщением. А "исходную" ошибку -- на всякий случай тоже сохраним, присвоим в свойство `cause` (англ. -- причина). - -Выглядит это так: -```js -//+ run -function ReadError(message, cause) { - this.message = message; - this.cause = cause; - this.name = 'ReadError'; - this.stack = cause.stack; -} - -function readData() { - var data = '{ bad data }'; - - try { - // ... - JSON.parse(data); - // ... - } catch (e) { - // ... - if (e.name == 'URIError') { - throw new ReadError("Ошибка в URI", e); - } else if (e.name == 'SyntaxError') { -*!* - throw new ReadError("Синтаксическая ошибка в данных", e); -*/!* - } else { - throw e; // пробрасываем - } - } -} - - -try { - readData(); -} catch (e) { - if (e.name == 'ReadError') { - alert( e.message ); - alert( e.cause ); // оригинальная ошибка-причина - } else { - throw e; - } -} -``` - -Этот подход называют "оборачиванием" исключения, поскольку мы берём ошибки "более низкого уровня" и "заворачиваем" их в `ReadError`, которая соответствует текущей задаче. - -## Секция finally - -Конструкция `try..catch` может содержать ещё один блок: `finally`. - -Выглядит этот расширенный синтаксис так: - -```js -*!*try*/!* { - .. пробуем выполнить код .. -} *!*catch*/!*(e) { - .. перехватываем исключение .. -} *!*finally*/!* { - .. выполняем всегда .. -} -``` - -Секция `finally` не обязательна, но если она есть, то она выполняется всегда: -
          -
        • после блока `try`, если ошибок не было,
        • -
        • после `catch`, если они были.
        • -
        - -Попробуйте запустить такой код? - -```js -//+ run -try { - alert( 'try' ); - if (confirm('Сгенерировать ошибку?')) BAD_CODE(); -} catch (e) { - alert( 'catch' ); -} finally { - alert( 'finally' ); -} -``` - -У него два варианта работы: -
          -
        1. Если вы ответите на вопрос "Сгенерировать ошибку?" утвердительно, то `try -> catch -> finally`.
        2. -
        3. Если ответите отрицательно, то `try -> finally`. -
        - -**Секцию `finally` используют, чтобы завершить начатые операции при любом варианте развития событий.** - -Например, мы хотим подсчитать время на выполнение функции `sum(n)`, которая должна возвратить сумму чисел от `1` до `n` и работает рекурсивно: - -```js -//+ run -function sum(n) { - return n ? (n + sum(n - 1)) : 0; -} - -var n = +prompt('Введите n?', 100); - -var start = new Date(); - -try { - var result = sum(n); -} catch (e) { - result = 0; -*!* -} finally { - var diff = new Date() - start; -} -*/!* - -alert( result ? result : 'была ошибка' ); -alert( "Выполнение заняло " + diff ); -``` - -Здесь секция `finally` гарантирует, что время будет подсчитано в любых ситуациях -- при ошибке в `sum` или без неё. - -Вы можете проверить это, запустив код с указанием `n=100` -- будет без ошибки, `finally` выполнится после `try`, а затем с `n=100000` -- будет ошибка из-за слишком глубокой рекурсии, управление прыгнет в `finally` после `catch`. - -[smart header="`finally` и `return`"] - -Блок `finally` срабатывает при *любом* выходе из `try..catch`, в том числе и `return`. - -В примере ниже, из `try` происходит `return`, но `finally` получает управление до того, как контроль возвращается во внешний код. - -```js -//+ run -function func() { - - try { - // сразу вернуть значение - return 1; - - } catch (e) { - /* ... */ - } finally { -*!* - alert( 'finally' ); -*/!* - } -} - -alert( func() ); // сначала finally, потом 1 -``` - -Если внутри `try` были начаты какие-то процессы, которые нужно завершить по окончании работы, во в `finally` это обязательно будет сделано. - -Кстати, для таких случаев иногда используют `try..finally` вообще без `catch`: - -```js -//+ run -function func() { - try { - return 1; - } finally { - alert( 'Вызов завершён' ); - } -} - -alert( func() ); // сначала finally, потом 1 -``` - -В примере выше `try..finally` вообще не обрабатывает ошибки. Задача в другом -- выполнить код при любом выходе из `try` -- с ошибкой ли, без ошибок или через `return`. -[/smart] - - -## Последняя надежда: window.onerror - -Допустим, ошибка произошла вне блока `try..catch` или выпала из `try..catch` наружу, во внешний код. Скрипт упал. - -Можно ли как-то узнать о том, что произошло? Да, конечно. - -В браузере существует специальное свойство `window.onerror`, если в него записать функцию, то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда "выпала" ошибка. - -Необходимо лишь позаботиться, чтобы функция была назначена заранее. - -Например: - -```html - - -``` - -Как правило, роль `window.onerror` заключается в том, чтобы не оживить скрипт -- скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают. - -Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: [](https://errorception.com/) или [](http://www.muscula.com/). - - -## Итого - -Обработка ошибок -- большая и важная тема. - -В JavaScript для этого предусмотрены: - -
          -
        • Конструкция `try..catch..finally` -- она позволяет обработать произвольные ошибки в блоке кода. - -Это удобно в тех случаях, когда проще сделать действие и потом разбираться с результатом, чем долго и нудно проверять, не упадёт ли чего. - -Кроме того, иногда проверить просто невозможно, например `JSON.parse(str)` не позволяет "проверить" формат строки перед разбором. В этом случае блок `try..catch` необходим. - -Полный вид конструкции: - -```js -*!*try*/!* { - .. пробуем выполнить код .. -} *!*catch*/!*(e) { - .. перехватываем исключение .. -} *!*finally*/!* { - .. выполняем всегда .. -} -``` - -Возможны также варианты `try..catch` или `try..finally`.
        • -
        • Оператор `throw err` генерирует свою ошибку, в качестве `err` рекомендуется использовать объекты, совместимые с встроенным типом [Error](http://javascript.ru/Error), содержащие свойства `message` и `name`.
        • -
        - -Кроме того, мы рассмотрели некоторые важные приёмы: - -
          -
        • Проброс исключения -- `catch(err)` должен обрабатывать только те ошибки, которые мы рассчитываем в нём увидеть, остальные -- пробрасывать дальше через `throw err`. - -Определить, нужная ли это ошибка, можно, например, по свойству `name`.
        • -
        • Оборачивание исключений -- функция, в процессе работы которой возможны различные виды ошибок, может "обернуть их" в одну общую ошибку, специфичную для её задачи, и уже её пробросить дальше. Чтобы, при необходимости, можно было подробно определить, что произошло, исходную ошибку обычно присваивают в свойство этой, общей. Обычно это нужно для логирования.
        • -
        • В `window.onerror` можно присвоить функцию, которая выполнится при любой "выпавшей" из скрипта ошибке. Как правило, это используют в информационных целях, например отправляют информацию об ошибке на специальный сервис.
        • -
        - - diff --git a/1-js/7-js-misc/index.md b/1-js/7-js-misc/index.md deleted file mode 100644 index 091a171f..00000000 --- a/1-js/7-js-misc/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Некоторые другие возможности - -Различные возможности JavaScript, которые достаточно важны, но не заслужили отдельного раздела. \ No newline at end of file diff --git a/1-js/8-oop/1-about-oop/article.md b/1-js/8-oop/1-about-oop/article.md deleted file mode 100644 index 9039a50c..00000000 --- a/1-js/8-oop/1-about-oop/article.md +++ /dev/null @@ -1,56 +0,0 @@ -# Введение - -На протяжении долгого времени в программировании применялся [процедурный подход](http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D0%B4%D1%83%D1%80%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5). При этом программа состоит из функций, вызывающих друг друга. - -Гораздо позже появилось [объектно-ориентированное программирование](http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) (ООП), которое позволяет группировать функции и данные в единой сущности -- "объекте". - - -При объектно-ориентированной разработке мы описываем происходящее на уровне объектов, которые создаются, меняют свои свойства, взаимодействуют друг с другом и (в случае браузера) со страницей, в общем, живут. - -Например, "пользователь", "меню", "компонент интерфейса"... При объектно-ориентированном подходе каждый объект должен представлять собой интуитивно понятную сущность, у которой есть методы и данные. - -[warn header="ООП -- это не просто объекты"] -В JavaScript объекты часто используются просто как коллекции. - -Например, встроенный объект [Math](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math) содержит функции (`Math.sin`, `Math.pow`, ...) и данные (константа `Math.PI`). - -При таком использовании объектов мы не можем сказать, что "применён объектно-ориентированный подход". В частности, никакую "единую сущность" `Math` из себя не представляет, это просто коллекция независимых функций с общим префиксом `Math`. -[/warn] - - -Мы уже работали в ООП-стиле, создавая объекты такого вида: - -```js -//+ run -function User(name) { - - this.sayHi = function() { - alert( "Привет, я " + name ); - }; - -} - -var vasya = new User("Вася"); // создали пользователя -vasya.sayHi(); // пользователь умеет говорить "Привет" -``` - -Здесь мы видим ярко выраженную сущность -- `User` (посетитель). Используя терминологию ООП, такие конструкторы часто называют *классами*, то есть можно сказать "класс `User`". - -[smart header="Класс в ООП"] -[Классом]("https://en.wikipedia.org/wiki/Class_(computer_programming)") в объектно-ориентированной разработке называют шаблон/программный код, предназначенный для создания объектов и методов. - -В JavaScript классы можно организовать по-разному. Говорят, что класс `User` написан в "функциональном" стиле. Далее мы также увидим "прототипный" стиль. -[/smart] - -ООП -- это наука о том, как делать правильную архитектуру. У неё есть свои принципы, например [SOLID](https://ru.wikipedia.org/wiki/SOLID_%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29). - -По приёмам объектно-ориентированной разработки пишут книги, к примеру: - - - -Здесь мы не имеем возможности углубиться в теорию ООП, поэтому чтение таких книг рекомендуется. Хотя основные принципы, как использовать ООП правильно, мы, всё же, затронем. \ No newline at end of file diff --git a/1-js/8-oop/2-internal-external-interface/1-add-method-property-coffeemachine/solution.md b/1-js/8-oop/2-internal-external-interface/1-add-method-property-coffeemachine/solution.md deleted file mode 100644 index 5c92acb6..00000000 --- a/1-js/8-oop/2-internal-external-interface/1-add-method-property-coffeemachine/solution.md +++ /dev/null @@ -1,42 +0,0 @@ -Кофеварка с новым методом: - -```js -//+ run -function CoffeeMachine(power) { - this.waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; -*!* - var timerId; -*/!* - var self = this; - - function getBoilTime() { - return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { -*!* - timerId = setTimeout(onReady, getBoilTime()); -*/!* - }; - -*!* - this.stop = function() { - clearTimeout(timerId) - }; -*/!* -} - - -var coffeeMachine = new CoffeeMachine(50000); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -coffeeMachine.stop(); // кофе приготовлен не будет -``` - diff --git a/1-js/8-oop/2-internal-external-interface/1-add-method-property-coffeemachine/task.md b/1-js/8-oop/2-internal-external-interface/1-add-method-property-coffeemachine/task.md deleted file mode 100644 index 62177288..00000000 --- a/1-js/8-oop/2-internal-external-interface/1-add-method-property-coffeemachine/task.md +++ /dev/null @@ -1,43 +0,0 @@ -# Добавить метод и свойство кофеварке - -[importance 5] - -Улучшите готовый код кофеварки, который дан ниже: добавьте в кофеварку *публичный* метод `stop()`, который будет останавливать кипячение (через `clearTimeout`). - -```js -//+ run -function CoffeeMachine(power) { - this.waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - var self = this; - - function getBoilTime() { - return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { - setTimeout(onReady, getBoilTime()); - }; - -} -``` - -Вот такой код должен ничего не выводить: - -```js -var coffeeMachine = new CoffeeMachine(50000); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -coffeeMachine.stop(); // кофе приготовлен не будет -``` - -P.S. Текущую температуру воды вычислять и хранить не требуется. - -P.P.S. При решении вам, скорее всего, понадобится добавить *приватное* свойство `timerId`, которое будет хранить текущий таймер. \ No newline at end of file diff --git a/1-js/8-oop/2-internal-external-interface/article.md b/1-js/8-oop/2-internal-external-interface/article.md deleted file mode 100644 index b4058737..00000000 --- a/1-js/8-oop/2-internal-external-interface/article.md +++ /dev/null @@ -1,338 +0,0 @@ -# Внутренний и внешний интерфейс - -Один из важнейших принципов ООП -- отделение внутреннего интерфейса от внешнего. - -Это -- обязательная практика в разработке чего угодно сложнее hello world. - -Чтобы это понять, отвлечемся от разработки и переведем взгляд на объекты реального мира. - -Как правило, устройства, с которыми мы имеем дело, весьма сложны. Но *разделение интерфейса на внешний и внутренний* позволяет использовать их без малейших проблем. -[cut] -## Пример из жизни - -Например, кофеварка. Простая снаружи: кнопка, индикатор, отверстия,... И, конечно, результат -- кофе :) - - - -Но внутри... (картинка из пособия по ремонту) - - - -Масса деталей. Но мы можем пользоваться ей, совершенно не зная об этом. - -Кофеварки -- довольно-таки надежны, не правда ли? Можно пользоваться годами, и только когда что-то пойдет не так -- придется нести к мастеру. - -Секрет надежности и простоты кофеварки -- в том, что все детали отлажены и *спрятаны* внутри. - -Если снять с кофеварки защитный кожух, то использование её будет более сложным (куда нажимать?) и опасным (током ударить может). - -Как мы увидим, объекты очень схожи с кофеварками. - -Только для того, чтобы прятать внутренние детали, используется не кожух, а специальные средства языка и соглашения. - -## Внутренний и внешний интерфейс - -В программировании мы будем разделять методы и свойства объекта на две группы: - -
          -
        • *Внутренний интерфейс* -- это свойства и методы, доступ к которым может быть осуществлен только из других методов объекта, их также называют "приватными" (есть и другие термины, встретим их далее).
        • - -
        • *Внешний интерфейс* -- это свойства и методы, доступные снаружи объекта, их называют "публичными".
        • -
        - -Если продолжить аналогию с кофеваркой -- то, что спрятано внутри кофеварки: трубка кипятильника, нагревательный элемент, тепловой предохранитель и так далее -- это её внутренний интерфейс. - -Внутренний интерфейс используется для обеспечения работоспособности объекта, его детали используют друг друга. Например, трубка кипятильника подключена к нагревательному элементу. - -Но снаружи кофеварка закрыта специальным кожухом, чтобы никто к ним не подобрался. Детали скрыты и недоступны. Виден лишь внешний интерфейс. - -Получив объект, всё, что нужно для пользования им -- это знать внешний интерфейс. О внутреннем же знать вообще не обязательно. - -Это были общие слова по теории программирования. - -Далее мы реализуем кофеварку на JavaScript с приватными и публичными свойствами. В кофеварке много деталей, мы конечно, не будем моделировать каждый винтик, а сосредоточимся на основных приёмах разработки. - -## Шаг 1: публичное и приватное свойство - -Конструктор кофеварок будет называться `CoffeeMachine`. - -```js -//+ run -function CoffeeMachine(power) { - this.waterAmount = 0; // количество воды в кофеварке - - alert( 'Создана кофеварка мощностью: ' + power + ' ватт' ); -} - -// создать кофеварку -var coffeeMachine = new CoffeeMachine(100); - -// залить воды -coffeeMachine.waterAmount = 200; -``` - -**Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.** - -В примере выше это `power` -- мощность кофеварки, которая указывается при создании и далее будет использована для расчёта времени кипячения. - -К локальным переменным конструктора нельзя обратиться снаружи, но они доступны внутри самого конструктора. - -**Свойства, записанные в `this`, можно считать публичными.** - -Здесь свойство `waterAmount` записано в объект, а значит -- доступно для модификации снаружи. Можно доливать и выливать воду в любом количестве. - -[smart header="Вопрос терминологии"] -Далее мы будем называть `power` как "локальной переменной", так и "приватным свойством" объекта. - -Это, смотря, с какой стороны посмотреть. - -Термины "приватное свойство/метод", "публичное свойство/метод" относятся к общей теории ООП. А их конкретная реализация в языке программирования может быть различной. - -Здесь ООП-принцип "приватного свойства" реализован через локальные переменные, поэтому и "локальная переменная" и "приватное свойство" -- правильные термины, в зависимости от того, с какой точки зрения взглянуть -- кода или архитектуры ООП. -[/smart] - - -## Шаг 2: публичный и приватный методы - -Добавим публичный метод `run`, запускающий кофеварку, а также вспомогательные внутренние методы `getBoilTime` и `onReady`: - -```js -//+ run -function CoffeeMachine(power) { - - this.waterAmount = 0; - -*!* - // расчёт времени для кипячения - function getBoilTime() { - return 1000; // точная формула расчета будет позже - } - - // что делать по окончании процесса - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { - // setTimeout - встроенная функция, - // она запустит onReady через getBoilTime() миллисекунд - setTimeout(onReady, getBoilTime()); - }; -*/!* -} - -var coffeeMachine = new CoffeeMachine(100); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -``` - -Приватные методы, такие как `onReady`, `getBoilTime` могут быть объявлены как вложенные функции. - -В результате естественным образом получается, что доступ к ним (через замыкание) имеют только другие функции, объявленные в том же конструкторе. - -## Шаг 3: константа - -Для расчёта времени на кипячение воды используется формула `c*m*ΔT / power`, где: -
          -
        • `c` -- коэффициент теплоёмкости воды, физическая константа равная `4200`.
        • -
        • `m` -- масса воды, которую нужно согреть.
        • -
        • `ΔT` -- температура, на которую нужно подогреть, будем считать, что изначально вода -- комнатной температуры 20°С, то есть до 100° нужно греть на `ΔT=80`.
        • -
        • `power` -- мощность.
        • -
        - -Используем её в более реалистичном варианте `getBoilTime()`, включающем использование приватных свойств и константу: - -```js -//+ run -"use strict" - -function CoffeeMachine(power) { - - this.waterAmount = 0; - -*!* - // физическая константа - удельная теплоёмкость воды для getBoilTime - var WATER_HEAT_CAPACITY = 4200; - - // расчёт времени для кипячения - function getBoilTime() { - return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; // ошибка! - } -*/!* - - // что делать по окончании процесса - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { - setTimeout(onReady, getBoilTime()); - }; - -} - -var coffeeMachine = new CoffeeMachine(1000); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -``` - -Удельная теплоёмкость `WATER_HEAT_CAPACITY` выделена большими буквами, так как это константа. - -Внимание, при запуске кода выше в методе `getBoilTime` будет ошибка. Как вы думаете, почему? - -## Шаг 4: доступ к объекту из внутреннего метода - -Внутренний метод вызывается так: `getBoilTime()`. А чему при этом равен `this`?... Как вы наверняка помните, в современном стандарте он будет `undefined` (в старом -- `window`), из-за этого при чтении `this.waterAmount` возникнет ошибка! - -Её можно решить, если вызвать `getBoilTime` с явным указанием контекста: `getBoilTime.call(this)`: - -```js -//+ run -function CoffeeMachine(power) { - this.waterAmount = 0; - var WATER_HEAT_CAPACITY = 4200; - - function getBoilTime() { - return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { -*!* - setTimeout(onReady, getBoilTime.call(this)); -*/!* - }; - -} - -// создаю кофеварку, мощностью 100000W чтобы кипятила быстро -var coffeeMachine = new CoffeeMachine(100000); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -``` - -Такой подход будет работать, но он не очень-то удобен. Ведь получается, что теперь везде, где мы хотим вызвать `getBoilTime`, нужно явно указывать контекст, т.е. писать `getBoilTime.call(this)`. - -К счастью существуют более элегантные решения. - -### Привязка через bind - -Можно при объявлении привязать `getBoilTime` к объекту через `bind`, тогда вопрос контекста отпадёт сам собой: - -```js -//+ run -function CoffeeMachine(power) { - this.waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - -*!* - var getBoilTime = function() { - return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; - }.bind(this); -*/!* - - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { -*!* - setTimeout(onReady, getBoilTime()); -*/!* - }; - -} - -var coffeeMachine = new CoffeeMachine(100000); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -``` - -Это решение будет работать, теперь функцию можно просто вызывать без `call`. Но объявление функции стало менее красивым. - -### Сохранение this в замыкании - -Пожалуй, самый удобный и часто применяемый путь решения состоит в том, чтобы предварительно скопировать `this` во вспомогательную переменную и обращаться из внутренних функций уже к ней. - -Вот так: - -```js -//+ run -function CoffeeMachine(power) { - this.waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - -*!* - var self = this; - - function getBoilTime() { - return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } -*/!* - - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { - setTimeout(onReady, getBoilTime()); - }; - -} - -var coffeeMachine = new CoffeeMachine(100000); -coffeeMachine.waterAmount = 200; - -coffeeMachine.run(); -``` - -Теперь `getBoilTime` получает `self` из замыкания. - -**Конечно, чтобы это работало, мы не должны изменять `self`, а все приватные методы, которые хотят иметь доступ к текущему объекту, должны использовать внутри себя `self` вместо `this`.** - -Вместо `self` можно использовать любое другое имя переменной, например `var me = this`. - -## Итого - -Итак, мы сделали кофеварку с публичными и приватными методами и заставили их корректно работать. - -В терминологии ООП отделение и защита внутреннего интерфейса называется [инкапсуляция](http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%BA%D0%B0%D0%BF%D1%81%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F_%28%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29). - -Кратко перечислим бонусы, которые она даёт: - -
        -
        Защита пользователей от выстрела себе в ногу
        -
        Представьте, команда разработчиков пользуется кофеваркой. Кофеварка создана фирмой "Лучшие Кофеварки" и, в общем, работает хорошо, но с неё сняли защитный кожух и, таким образом, внутренний интерфейс стал доступен. - -Все разработчики цивилизованны -- и пользуются кофеваркой как обычно. Но хитрый Вася решил, что он самый умный, и подкрутил кое-что внутри кофеварки, чтобы кофе заваривался покрепче. Вася не знал, что те изменения, которые он произвёл, приведут к тому, что кофеварка испортится через два дня. - -Виноват, разумеется, не только Вася, но и тот, кто снял защитный кожух с кофеварки, и тем самым позволил Васе проводить манипуляции. - -В программировании -- то же самое. Если пользователь объекта будет менять то, что не рассчитано на изменение снаружи -- последствия могут быть непредсказуемыми. -
        -
        Удобство в поддержке
        -
        Ситуация в программировании сложнее, чем с кофеваркой, т.к. кофеварку один раз купили и всё, а программа может улучшаться и дорабатываться. - -**При наличии чётко выделенного внешнего интерфейса, разработчик может свободно менять внутренние свойства и методы, без оглядки на коллег.** - -Гораздо легче разрабатывать, если знаешь, что ряд методов (все внутренние) можно переименовывать, менять их параметры, и вообще, переписать как угодно, так как внешний код к ним абсолютно точно не обращается. - -Ближайшая аналогия в реальной жизни -- это когда выходит "новая версия" кофеварки, которая работает гораздо лучше. Разработчик мог переделать всё внутри, но пользоваться ей по-прежнему просто, так как внешний интерфейс сохранён.
        -
        Управление сложностью
        -
        Люди обожают пользоваться вещами, которые просты с виду. А что внутри -- дело десятое. - -Программисты здесь не исключение. - -**Всегда удобно, когда детали реализации скрыты, и доступен простой, понятно документированный внешний интерфейс.** -
        -
        - diff --git a/1-js/8-oop/2-internal-external-interface/coffee-inside.jpg b/1-js/8-oop/2-internal-external-interface/coffee-inside.jpg deleted file mode 100755 index 60f84664..00000000 Binary files a/1-js/8-oop/2-internal-external-interface/coffee-inside.jpg and /dev/null differ diff --git a/1-js/8-oop/2-internal-external-interface/coffee.jpg b/1-js/8-oop/2-internal-external-interface/coffee.jpg deleted file mode 100755 index ee26e1c0..00000000 Binary files a/1-js/8-oop/2-internal-external-interface/coffee.jpg and /dev/null differ diff --git a/1-js/8-oop/3-getters-setters/1-object-with-getters-setters/solution.md b/1-js/8-oop/3-getters-setters/1-object-with-getters-setters/solution.md deleted file mode 100644 index bb0bb14d..00000000 --- a/1-js/8-oop/3-getters-setters/1-object-with-getters-setters/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -Решение: - -```js -//+ run -function User() { - - var firstName, surname; - - this.setFirstName = function(newFirstName) { - firstName = newFirstName; - }; - - this.setSurname = function(newSurname) { - surname = newSurname; - }; - - this.getFullName = function() { - return firstName + ' ' + surname; - } -} - -var user = new User(); -user.setFirstName("Петя"); -user.setSurname("Иванов"); - -alert( user.getFullName() ); // Петя Иванов -``` - -Обратим внимание, что для "геттера" `getFullName` нет соответствующего свойства объекта, он конструирует ответ "на лету". Это нормально. Одна из целей существования геттеров/сеттеров -- как раз и есть изоляция внутренних свойств объекта, чтобы можно было их как угодно менять, генерировать "на лету", а внешний интерфейс оставался тем же. \ No newline at end of file diff --git a/1-js/8-oop/3-getters-setters/1-object-with-getters-setters/task.md b/1-js/8-oop/3-getters-setters/1-object-with-getters-setters/task.md deleted file mode 100644 index d9efe056..00000000 --- a/1-js/8-oop/3-getters-setters/1-object-with-getters-setters/task.md +++ /dev/null @@ -1,25 +0,0 @@ -# Написать объект с геттерами и сеттерами - -[importance 4] - -Напишите конструктор `User` для создания объектов: -
          -
        • С приватными свойствами имя `firstName` и фамилия `surname`.
        • -
        • С сеттерами для этих свойств.
        • -
        • С геттером `getFullName()`, который возвращает полное имя.
        • -
        - -Должен работать так: - -```js -function User() { - /* ваш код */ -} - -var user = new User(); -user.setFirstName("Петя"); -user.setSurname("Иванов"); - -alert( user.getFullName() ); // Петя Иванов -``` - diff --git a/1-js/8-oop/3-getters-setters/2-getter-power/solution.md b/1-js/8-oop/3-getters-setters/2-getter-power/solution.md deleted file mode 100644 index b82c109a..00000000 --- a/1-js/8-oop/3-getters-setters/2-getter-power/solution.md +++ /dev/null @@ -1,28 +0,0 @@ - - -```js -function CoffeeMachine(power, capacity) { - //... - this.setWaterAmount = function(amount) { - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить воды больше, чем " + capacity); - } - - waterAmount = amount; - }; - - this.getWaterAmount = function() { - return waterAmount; - }; - -*!* - this.getPower = function() { - return power; - }; -*/!* -} -``` - diff --git a/1-js/8-oop/3-getters-setters/2-getter-power/task.md b/1-js/8-oop/3-getters-setters/2-getter-power/task.md deleted file mode 100644 index 7301c6f5..00000000 --- a/1-js/8-oop/3-getters-setters/2-getter-power/task.md +++ /dev/null @@ -1,32 +0,0 @@ -# Добавить геттер для power - -[importance 5] - -Добавьте кофеварке геттер для приватного свойства `power`, чтобы внешний код мог узнать мощность кофеварки. - -Исходный код: - -```js -function CoffeeMachine(power, capacity) { - //... - this.setWaterAmount = function(amount) { - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить воды больше, чем " + capacity); - } - - waterAmount = amount; - }; - - this.getWaterAmount = function() { - return waterAmount; - }; - -} -``` - -Обратим внимание, что ситуация, когда у свойства `power` есть геттер, но нет сеттера -- вполне обычна. - -Здесь это означает, что мощность `power` можно указать лишь при создании кофеварки и в дальнейшем её можно прочитать, но нельзя изменить. \ No newline at end of file diff --git a/1-js/8-oop/3-getters-setters/3-add-public-coffeemachine/solution.md b/1-js/8-oop/3-getters-setters/3-add-public-coffeemachine/solution.md deleted file mode 100644 index 41aa318a..00000000 --- a/1-js/8-oop/3-getters-setters/3-add-public-coffeemachine/solution.md +++ /dev/null @@ -1,47 +0,0 @@ -В решении ниже `addWater` будет просто вызывать `setWaterAmount`. - -```js -//+ run -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - this.setWaterAmount = function(amount) { - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить больше, чем " + capacity); - } - - waterAmount = amount; - }; - -*!* - this.addWater = function(amount) { - this.setWaterAmount(waterAmount + amount); - }; -*/!* - - function onReady() { - alert( 'Кофе готов!' ); - } - - this.run = function() { - setTimeout(onReady, getTimeToBoil()); - }; - -} - -var coffeeMachine = new CoffeeMachine(100000, 400); -coffeeMachine.addWater(200); -coffeeMachine.addWater(100); -coffeeMachine.addWater(300); // Нельзя залить больше.. -coffeeMachine.run(); -``` - diff --git a/1-js/8-oop/3-getters-setters/3-add-public-coffeemachine/task.md b/1-js/8-oop/3-getters-setters/3-add-public-coffeemachine/task.md deleted file mode 100644 index e123fed4..00000000 --- a/1-js/8-oop/3-getters-setters/3-add-public-coffeemachine/task.md +++ /dev/null @@ -1,52 +0,0 @@ -# Добавить публичный метод кофеварке - -[importance 5] - -Добавьте кофеварке публичный метод `addWater(amount)`, который будет добавлять воду. - -При этом, конечно же, должны происходить все необходимые проверки -- на положительность и превышение ёмкости. - -Исходный код: - -```js -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - this.setWaterAmount = function(amount) { - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить больше, чем " + capacity); - } - - waterAmount = amount; - }; - - function onReady() { - alert( 'Кофе готов!' ); - } - - this.run = function() { - setTimeout(onReady, getTimeToBoil()); - }; - -} -``` - -Вот такой код должен приводить к ошибке: - -```js -var coffeeMachine = new CoffeeMachine(100000, 400); -coffeeMachine.addWater(200); -coffeeMachine.addWater(100); -coffeeMachine.addWater(300); // Нельзя залить больше, чем 400 -coffeeMachine.run(); -``` - diff --git a/1-js/8-oop/3-getters-setters/4-setter-onready/solution.md b/1-js/8-oop/3-getters-setters/4-setter-onready/solution.md deleted file mode 100644 index 2aca1b6f..00000000 --- a/1-js/8-oop/3-getters-setters/4-setter-onready/solution.md +++ /dev/null @@ -1,69 +0,0 @@ - - -```js -//+ run -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - this.setWaterAmount = function(amount) { - // ... проверки пропущены для краткости - waterAmount = amount; - }; - - this.getWaterAmount = function(amount) { - return waterAmount; - }; - - function onReady() { - alert( 'Кофе готов!' ); - } - -*!* - this.setOnReady = function(newOnReady) { - onReady = newOnReady; - }; -*/!* - - this.run = function() { -*!* - setTimeout(function() { - onReady(); - }, getTimeToBoil()); -*/!* - }; - -} - -var coffeeMachine = new CoffeeMachine(20000, 500); -coffeeMachine.setWaterAmount(150); - -coffeeMachine.run(); - -*!* -coffeeMachine.setOnReady(function() { - var amount = coffeeMachine.getWaterAmount(); - alert( 'Готов кофе: ' + amount + 'мл' ); // Готов кофе: 150 мл -}); -*/!* -``` - -Обратите внимание на два момента в решении: -
          -
        1. В сеттере `setOnReady` параметр называется `newOnReady`. Мы не можем назвать его `onReady`, так как тогда изнутри сеттера мы никак не доберёмся до внешнего (старого значения): - -```js -// нерабочий вариант -this.setOnReady = function(onReady) { - onReady = onReady; // ??? внешняя переменная onReady недоступна -}; -``` - -
        2. -
        3. Чтобы `setOnReady` можно было вызывать в любое время, в `setTimeout` передаётся не `onReady`, а анонимная функция `function() { onReady() }`, которая возьмёт текущий (установленный последним) `onReady` из замыкания.
        4. -
        \ No newline at end of file diff --git a/1-js/8-oop/3-getters-setters/4-setter-onready/task.md b/1-js/8-oop/3-getters-setters/4-setter-onready/task.md deleted file mode 100644 index 2e960697..00000000 --- a/1-js/8-oop/3-getters-setters/4-setter-onready/task.md +++ /dev/null @@ -1,59 +0,0 @@ -# Создать сеттер для onReady - -[importance 5] - -Обычно когда кофе готов, мы хотим что-то сделать, например выпить его. - -Сейчас при готовности срабатывает функция `onReady`, но она жёстко задана в коде: - -```js -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - this.setWaterAmount = function(amount) { - // ... проверки пропущены для краткости - waterAmount = amount; - }; - - this.getWaterAmount = function(amount) { - return waterAmount; - }; - -*!* - function onReady() { - alert( 'Кофе готов!' ); - } -*/!* - - this.run = function() { - setTimeout(onReady, getTimeToBoil()); - }; - -} -``` - -Создайте сеттер `setOnReady`, чтобы код снаружи мог назначить свой `onReady`, вот так: - -```js -var coffeeMachine = new CoffeeMachine(20000, 500); -coffeeMachine.setWaterAmount(150); - -*!* -coffeeMachine.setOnReady(function() { - var amount = coffeeMachine.getWaterAmount(); - alert( 'Готов кофе: ' + amount + 'мл' ); // Кофе готов: 150 мл -}); -*/!* - -coffeeMachine.run(); -``` - -P.S. Значение `onReady` по умолчанию должно быть таким же, как и раньше. - -P.P.S. Постарайтесь сделать так, чтобы `setOnReady` можно было вызвать не только до, но и *после* запуска кофеварки, то есть чтобы функцию `onReady` можно было изменить в любой момент до её срабатывания. \ No newline at end of file diff --git a/1-js/8-oop/3-getters-setters/5-coffeemachine-add-isrunning/solution.md b/1-js/8-oop/3-getters-setters/5-coffeemachine-add-isrunning/solution.md deleted file mode 100644 index 8f61ff53..00000000 --- a/1-js/8-oop/3-getters-setters/5-coffeemachine-add-isrunning/solution.md +++ /dev/null @@ -1,62 +0,0 @@ -Код решения модифицирует функцию `run` и добавляет приватный идентификатор таймера `timerId`, по наличию которого мы судим о состоянии кофеварки: - -```js -//+ run -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - -*!* - var timerId; - - this.isRunning = function() { - return !!timerId; - }; -*/!* - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - this.setWaterAmount = function(amount) { - // ... проверки пропущены для краткости - waterAmount = amount; - }; - - this.getWaterAmount = function(amount) { - return waterAmount; - }; - - function onReady() { - alert( 'Кофе готов!' ); - } - - this.setOnReady = function(newOnReady) { - onReady = newOnReady; - }; - - this.run = function() { -*!* - timerId = setTimeout(function() { - timerId = null; - onReady(); - }, getTimeToBoil()); - }; -*/!* - -} - -var coffeeMachine = new CoffeeMachine(20000, 500); -coffeeMachine.setWaterAmount(100); - -alert( 'До: ' + coffeeMachine.isRunning() ); // До: false - -coffeeMachine.run(); -alert( 'В процессе: ' + coffeeMachine.isRunning() ); // В процессе: true - -coffeeMachine.setOnReady(function() { - alert( "После: " + coffeeMachine.isRunning() ); // После: false -}); -``` - diff --git a/1-js/8-oop/3-getters-setters/5-coffeemachine-add-isrunning/task.md b/1-js/8-oop/3-getters-setters/5-coffeemachine-add-isrunning/task.md deleted file mode 100644 index 7d8ff96c..00000000 --- a/1-js/8-oop/3-getters-setters/5-coffeemachine-add-isrunning/task.md +++ /dev/null @@ -1,25 +0,0 @@ -# Добавить метод isRunning - -[importance 5] - -Из внешнего кода мы хотели бы иметь возможность понять -- запущена кофеварка или нет. - -Для этого добавьте кофеварке публичный метод `isRunning()`, который будет возвращать `true`, если она запущена и `false`, если нет. - -Нужно, чтобы такой код работал: - -```js -var coffeeMachine = new CoffeeMachine(20000, 500); -coffeeMachine.setWaterAmount(100); - -alert( 'До: ' + coffeeMachine.isRunning() ); // До: false - -coffeeMachine.run(); -alert( 'В процессе: ' + coffeeMachine.isRunning() ); // В процессе: true - -coffeeMachine.setOnReady(function() { - alert( "После: " + coffeeMachine.isRunning() ); // После: false -}); -``` - -Исходный код возьмите из решения [предыдущей задачи](/task/setter-onready). \ No newline at end of file diff --git a/1-js/8-oop/3-getters-setters/article.md b/1-js/8-oop/3-getters-setters/article.md deleted file mode 100644 index e97a6e1b..00000000 --- a/1-js/8-oop/3-getters-setters/article.md +++ /dev/null @@ -1,167 +0,0 @@ -# Геттеры и сеттеры - -Для *управляемого* доступа к состоянию объекта используют специальные функции, так называемые "геттеры" и "сеттеры". -[cut] - -## Геттер и сеттер для воды - -На текущий момент количество воды в кофеварке является публичным свойством `waterAmount`: - -```js -//+ run -function CoffeeMachine(power) { - // количество воды в кофеварке - this.waterAmount = 0; - - ... -} -``` - -Это немного опасно. Ведь в это свойство можно записать произвольное количество воды, хоть весь мировой океан. - -```js -// не помещается в кофеварку! -coffeeMachine.waterAmount = 1000000; -``` - -Это ещё ничего, гораздо хуже, что можно наоборот -- вылить больше, чем есть: - -```js -// и не волнует, было ли там столько воды вообще! -coffeeMachine.waterAmount -= 1000000; -``` - -Так происходит потому, что свойство полностью доступно снаружи. - -Чтобы не было таких казусов, нам нужно ограничить контроль над свойством со стороны внешнего кода. - -**Для лучшего контроля над свойством его делают приватным, а запись значения осуществляется через специальный метод, который называют *"сеттер"* (setter method).** - -Типичное название для сеттера -- `setСвойство`, например, в случае с кофеваркой таким сеттером будет метод `setWaterAmount`: - -```js -//+ run -function CoffeeMachine(power, capacity) { // capacity - ёмкость кофеварки - var waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - -*!* - // "умная" установка свойства - this.setWaterAmount = function(amount) { - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить воды больше, чем " + capacity); - } - - waterAmount = amount; - }; -*/!* - - function onReady() { - alert( 'Кофе готов!' ); - } - - this.run = function() { - setTimeout(onReady, getTimeToBoil()); - }; - -} - -var coffeeMachine = new CoffeeMachine(1000, 500); -coffeeMachine.setWaterAmount(600); // упс, ошибка! -``` - -Теперь `waterAmount` -- внутреннее свойство, его можно записать (через сеттер), но, увы, нельзя прочитать. - -**Для того, чтобы дать возможность внешнему коду узнать его значение, создадим специальную функцию -- "геттер" (getter method).** - -Геттеры обычно имеют название вида `getСвойство`, в данном случае `getWaterAmount`: - -```js -//+ run -function CoffeeMachine(power, capacity) { - //... - this.setWaterAmount = function(amount) { - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить воды больше, чем " + capacity); - } - - waterAmount = amount; - }; - -*!* - this.getWaterAmount = function() { - return waterAmount; - }; -*/!* -} - -var coffeeMachine = new CoffeeMachine(1000, 500); -coffeeMachine.setWaterAmount(450); -alert( coffeeMachine.getWaterAmount() ); // 450 -``` - -## Единый геттер-сеттер - -Для большего удобства иногда делают единый метод, который называется так же, как свойство и отвечает *и за запись и за чтение*. - -При вызове без параметров такой метод возвращает свойство, а при передаче параметра -- назначает его. - -Выглядит это так: - -```js -//+ run -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - -*!* - this.waterAmount = function(amount) { -*/!* - // вызов без параметра, значит режим геттера, возвращаем свойство - if (!arguments.length) return waterAmount; - - // иначе режим сеттера - if (amount < 0) { - throw new Error("Значение должно быть положительным"); - } - if (amount > capacity) { - throw new Error("Нельзя залить воды больше, чем " + capacity); - } - - waterAmount = amount; - }; - -} - -var coffeeMachine = new CoffeeMachine(1000, 500); - -// пример использования -*!* -coffeeMachine.waterAmount(450); -alert( coffeeMachine.waterAmount() ); // 450 -*/!* -``` - -Единый геттер-сеттер используется реже, чем две отдельные функции, но в некоторых JavaScript-библиотеках, например [jQuery](http://jquery.com) и [D3](http://d3js.org) подобный подход принят на уровне концепта. - -## Итого - -
          -
        • Для большего контроля над присвоением и чтением значения, вместо свойства делают "функцию-геттер" и "функцию-сеттер", геттер возвращает значение, сеттер -- устанавливает.
        • -
        • Если свойство предназначено только для чтения, то может быть только геттер, только для записи -- только сеттер.
        • -
        • В качестве альтернативы паре геттер/сеттер применяют единую функцию, которая без аргументов ведёт себя как геттер, а с аргументом -- как сеттер.
        • -
        - -Также можно организовать геттеры/сеттеры для свойства, не меняя структуры кода, через [дескрипторы свойств](/descriptors-getters-setters). - - diff --git a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/solution.md b/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/solution.md deleted file mode 100644 index 9f35452d..00000000 --- a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/solution.md +++ /dev/null @@ -1,15 +0,0 @@ -Изменения в методе `run`: - -```js -this.run = function() { -*!* - if (!this._enabled) { - throw new Error("Кофеварка выключена"); - } -*/!* - - setTimeout(onReady, 1000); -}; -``` - - diff --git a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/solution.view/index.html b/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/solution.view/index.html deleted file mode 100755 index 053c8198..00000000 --- a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/solution.view/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/source.view/index.html b/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/source.view/index.html deleted file mode 100755 index 6d287989..00000000 --- a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/source.view/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/task.md b/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/task.md deleted file mode 100644 index d90fce6a..00000000 --- a/1-js/8-oop/5-functional-inheritance/1-coffeemachine-fix-run/task.md +++ /dev/null @@ -1,21 +0,0 @@ -# Запускать только при включённой кофеварке - -[importance 5] - -В коде `CoffeeMachine` сделайте так, чтобы метод `run` выводил ошибку, если кофеварка выключена. - -В итоге должен работать такой код: - -```js -var coffeeMachine = new CoffeeMachine(10000); -coffeeMachine.run(); // ошибка, кофеварка выключена! -``` - -А вот так -- всё в порядке: - -```js -var coffeeMachine = new CoffeeMachine(10000); -coffeeMachine.enable(); -coffeeMachine.run(); // ...Кофе готов! -``` - diff --git a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/solution.md b/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/solution.md deleted file mode 100644 index e69de29b..00000000 diff --git a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/solution.view/index.html b/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/solution.view/index.html deleted file mode 100755 index 61ce8414..00000000 --- a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/solution.view/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md b/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md deleted file mode 100644 index 298cd301..00000000 --- a/1-js/8-oop/5-functional-inheritance/2-coffeemachine-disable-stop/task.md +++ /dev/null @@ -1,16 +0,0 @@ -# Останавливать кофеварку при выключении - -[importance 5] - -Когда кофеварку выключают -- текущая варка кофе должна останавливаться. - -Например, следующий код кофе не сварит: - -```js -var coffeeMachine = new CoffeeMachine(10000); -coffeeMachine.enable(); -coffeeMachine.run(); -coffeeMachine.disable(); // остановит работу, ничего не выведет -``` - -Реализуйте это на основе решения [предыдущей задачи](/task/coffeemachine-fix-run). diff --git a/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/solution.md b/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/solution.md deleted file mode 100644 index 38957f28..00000000 --- a/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -Решение: - -```js -function Fridge(power) { - // унаследовать - Machine.apply(this, arguments); - - var food = []; // приватное свойство food - - this.addFood = function() { - if (!this._enabled) { - throw new Error("Холодильник выключен"); - } - if (food.length + arguments.length >= this._power / 100) { - throw new Error("Нельзя добавить, не хватает мощности"); - } - for (var i = 0; i < arguments.length; i++) { - food.push(arguments[i]); // добавить всё из arguments - } - }; - - this.getFood = function() { - // копируем еду в новый массив, чтобы манипуляции с ним не меняли food - return food.slice(); - }; - -} -``` - diff --git a/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md b/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md deleted file mode 100644 index 150bdb0f..00000000 --- a/1-js/8-oop/5-functional-inheritance/3-inherit-fridge/task.md +++ /dev/null @@ -1,67 +0,0 @@ -# Унаследуйте холодильник - -[importance 4] - -Создайте класс для холодильника `Fridge(power)`, наследующий от `Machine`, с приватным свойством `food` и методами `addFood(...)`, `getFood()`: -
          -
        • Приватное свойство `food` хранит массив еды.
        • -
        • Публичный метод `addFood(item)` добавляет в массив `food` новую еду, доступен вызов с несколькими аргументами `addFood(item1, item2...)` для добавления нескольких элементов сразу.
        • -
        • Если холодильник выключен, то добавить еду нельзя, будет ошибка.
        • -
        • Максимальное количество еды ограничено `power/100`, где `power` -- мощность холодильника, указывается в конструкторе. При попытке добавить больше -- будет ошибка
        • -
        • Публичный метод `getFood()` возвращает еду в виде массива, добавление или удаление элементов из которого не должно влиять на свойство `food` холодильника.
        • -
        - -Код для проверки: - -```js -var fridge = new Fridge(200); -fridge.addFood("котлета"); // ошибка, холодильник выключен -``` - -Ещё код для проверки: - -```js -// создать холодильник мощностью 500 (не более 5 еды) -var fridge = new Fridge(500); -fridge.enable(); -fridge.addFood("котлета"); -fridge.addFood("сок", "зелень"); -fridge.addFood("варенье", "пирог", "торт"); // ошибка, слишком много еды -``` - -Код использования холодильника без ошибок: - -```js -var fridge = new Fridge(500); -fridge.enable(); -fridge.addFood("котлета"); -fridge.addFood("сок", "варенье"); - -var fridgeFood = fridge.getFood(); -alert( fridgeFood ); // котлета, сок, варенье - -// добавление элементов не влияет на еду в холодильнике -fridgeFood.push("вилка", "ложка"); - -alert( fridge.getFood() ); // внутри по-прежнему: котлета, сок, варенье -``` - -Исходный код класса `Machine`, от которого нужно наследовать: - -```js -function Machine(power) { - this._power = power; - this._enabled = false; - - var self = this; - - this.enable = function() { - self._enabled = true; - }; - - this.disable = function() { - self._enabled = false; - }; -} -``` - diff --git a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md b/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md deleted file mode 100644 index e7752658..00000000 --- a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/solution.md +++ /dev/null @@ -1,89 +0,0 @@ - - -```js -//+ run -function Machine(power) { - this._power = power; - this._enabled = false; - - var self = this; - - this.enable = function() { - self._enabled = true; - }; - - this.disable = function() { - self._enabled = false; - }; -} - -function Fridge(power) { - // унаследовать - Machine.apply(this, arguments); - - var food = []; // приватное свойство food - - this.addFood = function() { - if (!this._enabled) { - throw new Error("Холодильник выключен"); - } - if (food.length + arguments.length >= this._power / 100) { - throw new Error("Нельзя добавить, не хватает мощности"); - } - for (var i = 0; i < arguments.length; i++) { - food.push(arguments[i]); // добавить всё из arguments - } - - }; - - this.getFood = function() { - // копируем еду в новый массив, чтобы манипуляции с ним не меняли food - return food.slice(); - }; - -*!* - this.filterFood = function(filter) { - return food.filter(filter); - }; - - this.removeFood = function(item) { - var idx = food.indexOf(item); - if (idx != -1) food.splice(idx, 1); - }; -*/!* -} - -var fridge = new Fridge(500); -fridge.enable(); -fridge.addFood({ - title: "котлета", - calories: 100 -}); -fridge.addFood({ - title: "сок", - calories: 30 -}); -fridge.addFood({ - title: "зелень", - calories: 10 -}); -fridge.addFood({ - title: "варенье", - calories: 150 -}); - -var dietItems = fridge.filterFood(function(item) { - return item.calories < 50; -}); - -fridge.removeFood("нет такой еды"); // без эффекта -alert( fridge.getFood().length ); // 4 - -dietItems.forEach(function(item) { - alert( item.title ); // сок, зелень - fridge.removeFood(item); -}); - -alert( fridge.getFood().length ); // 2 -``` - diff --git a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md b/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md deleted file mode 100644 index 46a4db21..00000000 --- a/1-js/8-oop/5-functional-inheritance/4-add-methods-fridge/task.md +++ /dev/null @@ -1,48 +0,0 @@ -# Добавьте методы в холодильник - -[importance 5] - -Добавьте в холодильник методы: -
          -
        • Публичный метод `filterFood(func)`, который возвращает всю еду, для которой `func(item) == true`
        • -
        • Публичный метод `removeFood(item)`, который удаляет еду `item` из холодильника.
        • -
        - -Код для проверки: - -```js -var fridge = new Fridge(500); -fridge.enable(); -fridge.addFood({ - title: "котлета", - calories: 100 -}); -fridge.addFood({ - title: "сок", - calories: 30 -}); -fridge.addFood({ - title: "зелень", - calories: 10 -}); -fridge.addFood({ - title: "варенье", - calories: 150 -}); - -fridge.removeFood("нет такой еды"); // без эффекта -alert( fridge.getFood().length ); // 4 - -var dietItems = fridge.filterFood(function(item) { - return item.calories < 50; -}); - -dietItems.forEach(function(item) { - alert( item.title ); // сок, зелень - fridge.removeFood(item); -}); - -alert( fridge.getFood().length ); // 2 -``` - -В качестве исходного кода используйте решение [предыдущей задачи](/task/inherit-fridge). diff --git a/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md b/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md deleted file mode 100644 index ec5fbb18..00000000 --- a/1-js/8-oop/5-functional-inheritance/5-override-disable/solution.md +++ /dev/null @@ -1,68 +0,0 @@ - - -```js -//+ run -function Machine(power) { - this._power = power; - this._enabled = false; - - var self = this; - - this.enable = function() { - self._enabled = true; - }; - - this.disable = function() { - self._enabled = false; - }; -} - -function Fridge(power) { - Machine.apply(this, arguments); - - var food = []; // приватное свойство food - - this.addFood = function() { - if (!this._enabled) { - throw new Error("Холодильник выключен"); - } - if (food.length + arguments.length >= this._power / 100) { - throw new Error("Нельзя добавить, не хватает мощности"); - } - for (var i = 0; i < arguments.length; i++) { - food.push(arguments[i]); // добавить всё из arguments - } - - }; - - this.getFood = function() { - // копируем еду в новый массив, чтобы манипуляции с ним не меняли food - return food.slice(); - }; - - this.filterFood = function(filter) { - return food.filter(filter); - }; - - this.removeFood = function(item) { - var idx = food.indexOf(item); - if (idx != -1) food.splice(idx, 1); - }; - -*!* - var parentDisable = this.disable; - this.disable = function() { - if (food.length) { - throw new Error("Нельзя выключить: внутри еда"); - } - parentDisable(); - }; -*/!* -} - -var fridge = new Fridge(500); -fridge.enable(); -fridge.addFood("кус-кус"); -fridge.disable(); // ошибка, в холодильнике есть еда -``` - diff --git a/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md b/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md deleted file mode 100644 index 482ffaaa..00000000 --- a/1-js/8-oop/5-functional-inheritance/5-override-disable/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Переопределите disable - -[importance 5] - -Переопределите метод `disable` холодильника, чтобы при наличии в нём еды он выдавал ошибку. - - -Код для проверки: - -```js -var fridge = new Fridge(500); -fridge.enable(); -fridge.addFood("кус-кус"); -fridge.disable(); // ошибка, в холодильнике есть еда -``` - -В качестве исходного кода используйте решение [предыдущей задачи](/task/add-methods-fridge). diff --git a/1-js/8-oop/5-functional-inheritance/article.md b/1-js/8-oop/5-functional-inheritance/article.md deleted file mode 100644 index 3afd5a93..00000000 --- a/1-js/8-oop/5-functional-inheritance/article.md +++ /dev/null @@ -1,403 +0,0 @@ -# Функциональное наследование - -Наследование -- это создание новых "классов" на основе существующих. - -В JavaScript его можно реализовать несколькими путями, один из которых -- с использованием наложения конструкторов, мы рассмотрим в этой главе. -[cut] - -## Зачем наследование? - -Ранее мы обсуждали различные реализации кофеварки. Продолжим эту тему далее. - -Хватит ли нам только кофеварки для удобной жизни? Вряд ли... Скорее всего, ещё понадобятся как минимум холодильник, микроволновка, а возможно и другие *машины*. - -В реальной жизни у этих *машин* есть базовые правила пользования. Например, большая кнопка -- включение, шнур с розеткой нужно воткнуть в питание и т.п. - -Можно сказать, что "у всех машин есть общие свойства, а конкретные машины могут их дополнять". - -Именно поэтому, увидев новую технику, мы уже можем что-то с ней сделать, даже не читая инструкцию. - -Механизм наследования позволяет определить базовый класс `Машина`, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: `Кофеварка`, `Холодильник` и т.п. - -[smart header="В веб-разработке всё так же"] -В веб-разработке нам могут понадобиться классы `Меню`, `Табы`, `Диалог` и другие компоненты интерфейса. В них всех обычно есть что-то общее. - -Можно выделить такой общий функционал в класс `Компонент` и наследовать их от него, чтобы не дублировать код. -[/smart] - -## Наследование от Machine - -Базовый класс "машина" `Machine` будет реализовывать общего вида методы "включить" `enable()` и "выключить" `disable()`: - -```js -function Machine() { - var enabled = false; - - this.enable = function() { - enabled = true; - }; - - this.disable = function() { - enabled = false; - }; -} -``` - -Унаследуем от него кофеварку. При этом она получит эти методы автоматически: - -```js -function CoffeeMachine(power) { -*!* - Machine.call(this); // отнаследовать -*/!* - - var waterAmount = 0; - - this.setWaterAmount = function(amount) { - waterAmount = amount; - }; - -} - -var coffeeMachine = new CoffeeMachine(10000); - -*!* -coffeeMachine.enable(); -coffeeMachine.setWaterAmount(100); -coffeeMachine.disable(); -*/!* -``` - -Наследование реализовано вызовом `Machine.call(this)` в начале конструктора `CoffeeMachine`. - -Он вызывает функцию `Machine`, передавая ей в качестве контекста `this` текущий объект. `Machine`, в процессе выполнения, записывает в `this` различные полезные свойства и методы, в нашем случае `this.enable` и `this.disable`. - -Далее конструктор `CoffeeMachine` продолжает выполнение и может добавить свои свойства и методы. - -В результате мы получаем объект `coffeeMachine`, который включает в себя методы из `Machine` и `CoffeeMachine`. - -## Защищённые свойства - -В коде выше есть одна проблема. - -**Наследник не имеет доступа к приватным свойствам родителя.** - -Иначе говоря, если кофеварка захочет обратиться к `enabled`, то её ждёт разочарование: - -```js -//+ run -function Machine() { - var enabled = false; - - this.enable = function() { - enabled = true; - }; - - this.disable = function() { - enabled = false; - }; -} - -function CoffeeMachine(power) { - Machine.call(this); - - this.enable(); - -*!* - // ошибка, переменная не определена! - alert( enabled ); -*/!* -} - -var coffeeMachine = new CoffeeMachine(10000); -``` - -Это естественно, ведь `enabled` -- локальная переменная функции `Machine`. Она находится в другой области видимости. - -**Чтобы наследник имел доступ к свойству, оно должно быть записано в `this`.** - -При этом, чтобы обозначить, что свойство является внутренним, его имя начинают с подчёркивания `_`. - -```js -//+ run -function Machine() { -*!* - this._enabled = false; // вместо var enabled -*/!* - - this.enable = function() { - this._enabled = true; - }; - - this.disable = function() { - this._enabled = false; - }; -} - -function CoffeeMachine(power) { - Machine.call(this); - - this.enable(); - -*!* - alert( this._enabled ); // true -*/!* -} - -var coffeeMachine = new CoffeeMachine(10000); -``` - -Подчёркивание в начале свойства -- общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют *защищёнными*. - -Технически, залезть в него из внешнего кода, конечно, возможно, но приличный программист так делать не будет. - -## Перенос свойства в защищённые - -У `CoffeeMachine` есть приватное свойство `power`. Сейчас мы его тоже сделаем защищённым и перенесём в `Machine`, поскольку "мощность" свойственна всем машинам, а не только кофеварке. - -```js -//+ run -function Machine(power) { -*!* - this._power = power; // (1) -*/!* - - this._enabled = false; - - this.enable = function() { - this._enabled = true; - }; - - this.disable = function() { - this._enabled = false; - }; -} - -function CoffeeMachine(power) { -*!* - Machine.apply(this, arguments); // (2) -*/!* - - alert( this._enabled ); // false - alert( this._power ); // 10000 -} - -var coffeeMachine = new CoffeeMachine(10000); -``` - -Теперь все машины `Machine` имеют мощность `power`. Обратим внимание, что мы из параметра конструктора сразу скопировали её в объект в строке `(1)`. Иначе она была бы недоступна из наследников. - -В строке `(2)` мы теперь вызываем не просто `Machine.call(this)`, а расширенный вариант: `Machine.apply(this, arguments)`, который вызывает `Machine` в текущем контексте вместе с передачей текущих аргументов. - -Можно было бы использовать и более простой вызов `Machine.call(this, power)`, но использование `apply` гарантирует передачу всех аргументов, вдруг их количество увеличится -- не надо будет переписывать. - -## Переопределение методов - -Итак, мы получили класс `CoffeeMachine`, который наследует от `Machine`. - -Аналогичным образом мы можем унаследовать от `Machine` холодильник `Fridge`, микроволновку `MicroOven` и другие классы, которые разделяют общий "машинный" функционал, то есть имеют мощность и их можно включать/выключать. - -Для этого достаточно вызвать `Machine` текущем контексте, а затем добавить свои методы. - -```js -// Fridge может добавить и свои аргументы, -// которые в Machine не будут использованы -function Fridge(power, temperature) { - Machine.apply(this, arguments); - - // ... -} -``` - -Бывает так, что реализация конкретного метода машины в наследнике имеет свои особенности. - -Можно, конечно, объявить в `CoffeeMachine` свой `enable`: - -```js -function CoffeeMachine(power, capacity) { - Machine.apply(this, arguments); - - // переопределить this.enable - this.enable = function() { - /* enable для кофеварки */ - }; -} -``` - -...Однако, как правило, мы хотим не заменить, а *расширить* метод родителя, добавить к нему что-то. Например, сделать так, чтобы при включении кофеварка тут же запускалась. - -Для этого метод родителя предварительно копируют в переменную, и затем вызывают внутри нового `enable` -- там, где считают нужным: - -```js -function CoffeeMachine(power) { - Machine.apply(this, arguments); - -*!* - var parentEnable = this.enable; // (1) - this.enable = function() { // (2) - parentEnable.call(this); // (3) - this.run(); // (4) - } -*/!* - - ... -} -``` - -**Общая схема переопределения метода (по строкам выделенного фрагмента кода):** - -
          -
        1. Копируем доставшийся от родителя метод `this.enable` в переменную, например `parentEnable`.
        2. -
        3. Заменяем `this.enable` на свою функцию...
        4. -
        5. ...Которая по-прежнему реализует старый функционал через вызов `parentEnable`.
        6. -
        7. ...И в дополнение к нему делает что-то своё, например запускает приготовление кофе.
        8. -
        - -Обратим внимание на строку `(3)`. - -В ней родительский метод вызывается так: `parentEnable.call(this)`. Если бы вызов был таким: `parentEnable()`, то ему бы не передался текущий `this` и возникла бы ошибка. - -Технически, можно сделать возможность вызывать его и как `parentEnable()`, но тогда надо гарантировать, что контекст будет правильным, например привязать его при помощи `bind` или при объявлении, в родителе, вообще не использовать `this`, а получать контекст через замыкание, вот так: - -```js -//+ run -function Machine(power) { - this._enabled = false; - -*!* - var self = this; -*/!* - - this.enable = function() { -*!* - // используем внешнюю переменную вместо this - self._enabled = true; -*/!* - }; - - this.disable = function() { - self._enabled = false; - }; - -} - -function CoffeeMachine(power) { - Machine.apply(this, arguments); - - var waterAmount = 0; - - this.setWaterAmount = function(amount) { - waterAmount = amount; - }; - -*!* - var parentEnable = this.enable; - this.enable = function() { - parentEnable(); // теперь можно вызывать как угодно, this не важен - this.run(); - } -*/!* - - function onReady() { - alert( 'Кофе готово!' ); - } - - this.run = function() { - setTimeout(onReady, 1000); - }; - -} - -var coffeeMachine = new CoffeeMachine(10000); -coffeeMachine.setWaterAmount(50); -coffeeMachine.enable(); -``` - -В коде выше родительский метод `parentEnable = this.enable` успешно продолжает работать даже при вызове без контекста. А всё потому, что использует `self` внутри. - -## Итого - -Организация наследования, которая описана в этой главе, называется "функциональным паттерном наследования". - -Её общая схема (кратко): - -
          -
        1. Объявляется конструктор родителя `Machine`. В нём могут быть приватные (private), публичные (public) и защищённые (protected) свойства: - -```js -function Machine(params) { - // локальные переменные и функции доступны только внутри Machine - var privateProperty; - - // публичные доступны снаружи - this.publicProperty = ...; - - // защищённые доступны внутри Machine и для потомков - // мы договариваемся не трогать их снаружи - this._protectedProperty = ... -} - -var machine = new Machine(...) -machine.public(); -``` - -
        2. -
        3. Для наследования конструктор потомка вызывает родителя в своём контексте через `apply`. После чего может добавить свои переменные и методы: - -```js -function CoffeeMachine(params) { - // универсальный вызов с передачей любых аргументов -*!* - Machine.apply(this, arguments); -*/!* - - this.coffeePublicProperty = ... -} - -var coffeeMachine = new CoffeeMachine(...); -coffeeMachine.publicProperty(); -coffeeMachine.coffeePublicProperty(); -``` - -
        4. -
        5. В `CoffeeMachine` свойства, полученные от родителя, можно перезаписать своими. Но обычно требуется не заменить, а расширить метод родителя. Для этого он предварительно копируется в переменную: - -```js -function CoffeeMachine(params) { - Machine.apply(this, arguments); - -*!* - var parentProtected = this._protectedProperty; - this._protectedProperty = function(args) { - parentProtected.apply(this, args); // (*) - // ... - }; -*/!* -} -``` - -Строку `(*)` можно упростить до `parentProtected(args)`, если метод родителя не использует `this`, а, например, привязан к `var self = this`: - -```js -function Machine(params) { - var self = this; - - this._protected = function() { - self.property = "value"; - }; -} -``` - -
        6. -
        - -Надо сказать, что способ наследования, описанный в этой главе, используется нечасто. - -В следующих главах мы будем изучать прототипный подход, который обладаем рядом преимуществ. - -Но знать и понимать его необходимо, поскольку во многих существующих библиотеках классы написаны в функциональном стиле, и расширять/наследовать от них можно только так. - - - - - diff --git a/1-js/8-oop/index.md b/1-js/8-oop/index.md deleted file mode 100644 index 292b2d77..00000000 --- a/1-js/8-oop/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# ООП в функциональном стиле - -Инкапсуляция и наследование в функциональном стиле, а также расширенные возможности объектов JavaScript. \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md b/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md deleted file mode 100644 index f6308c17..00000000 --- a/1-js/9-prototypes/1-prototype/1-property-after-delete/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -
          -
        1. `true`, свойство взято из `rabbit`.
        2. -
        3. `null`, свойство взято из `animal`.
        4. -
        5. `undefined`, свойства больше нет.
        6. -
        \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md b/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md deleted file mode 100644 index 70b4e3ba..00000000 --- a/1-js/9-prototypes/1-prototype/1-property-after-delete/task.md +++ /dev/null @@ -1,28 +0,0 @@ -# Чему равно cвойство после delete? - -[importance 5] - -Какие значения будут выводиться в коде ниже? - -```js -var animal = { - jumps: null -}; -var rabbit = { - jumps: true -}; - -rabbit.__proto__ = animal; - -alert( rabbit.jumps ); // ? (1) - -delete rabbit.jumps; - -alert( rabbit.jumps ); // ? (2) - -delete animal.jumps; - -alert( rabbit.jumps ); // ? (3) -``` - -Итого три вопроса. \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/2-proto-and-this/proto5.png b/1-js/9-prototypes/1-prototype/2-proto-and-this/proto5.png deleted file mode 100755 index 055a4447..00000000 Binary files a/1-js/9-prototypes/1-prototype/2-proto-and-this/proto5.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/2-proto-and-this/proto6.png b/1-js/9-prototypes/1-prototype/2-proto-and-this/proto6.png deleted file mode 100755 index d8a63a58..00000000 Binary files a/1-js/9-prototypes/1-prototype/2-proto-and-this/proto6.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md b/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md deleted file mode 100644 index 4206298f..00000000 --- a/1-js/9-prototypes/1-prototype/2-proto-and-this/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -**Ответ: свойство будет записано в `rabbit`.** - -Если коротко -- то потому что `this` будет указывать на `rabbit`, а прототип при записи не используется. - -Если в деталях -- посмотрим как выполняется `rabbit.eat()`: -
          -
        1. Интерпретатор ищет `rabbit.eat`, чтобы его вызвать. Но свойство `eat` отсутствует в объекте `rabbit`, поэтому он идет по ссылке `rabbit.__proto__` и находит это свойство там. - -
        2. -
        3. Функция `eat` запускается. Контекст ставится равным объекту перед точкой, т.е. `this = rabbit`. - -Итак -- получается, что команда `this.full = true` устанавливает свойство `full` в самом объекте `rabbit`. Итог: - - -
        4. -
        - -Эта задача демонстрирует, что несмотря на то, в каком прототипе находится свойство, это никак не влияет на установку `this`, которая осуществляется по своим, независимым правилам. \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md b/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md deleted file mode 100644 index 346de8e1..00000000 --- a/1-js/9-prototypes/1-prototype/2-proto-and-this/task.md +++ /dev/null @@ -1,24 +0,0 @@ -# Прототип и this - -[importance 5] - -Сработает ли вызов `rabbit.eat()` ? - -Если да, то в какой именно объект он запишет свойство `full`: в `rabbit` или `animal`? - -```js -var animal = { - eat: function() { - this.full = true; - } -}; - -var rabbit = { - __proto__: animal -}; - -*!* -rabbit.eat(); -*/!* -``` - diff --git a/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md b/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md deleted file mode 100644 index 60ba9501..00000000 --- a/1-js/9-prototypes/1-prototype/3-search-algorithm/solution.md +++ /dev/null @@ -1,33 +0,0 @@ -
          -
        1. Расставим `__proto__`: - -```js -//+ run -var head = { - glasses: 1 -}; - -var table = { - pen: 3 -}; -table.__proto__ = head; - -var bed = { - sheet: 1, - pillow: 2 -}; -bed.__proto__ = table; - -var pockets = { - money: 2000 -}; -pockets.__proto__ = bed; - -alert( pockets.pen ); // 3 -alert( bed.glasses ); // 1 -alert( table.money ); // undefined -``` - -
        2. -
        3. **В современных браузерах, с точки зрения производительности, нет разницы, брать свойство из объекта или прототипа.** Они запоминают, где было найдено свойство и в следующий раз при запросе, к примеру, `pockets.glasses` начнут искать сразу в прототипе (`head`).
        4. -
        \ No newline at end of file diff --git a/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md b/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md deleted file mode 100644 index ab5f9f16..00000000 --- a/1-js/9-prototypes/1-prototype/3-search-algorithm/task.md +++ /dev/null @@ -1,32 +0,0 @@ -# Алгоритм для поиска - -[importance 5] - -Есть объекты: - -```js -var head = { - glasses: 1 -}; - -var table = { - pen: 3 -}; - -var bed = { - sheet: 1, - pillow: 2 -}; - -var pockets = { - money: 2000 -}; -``` - -Задание состоит из двух частей: -
          -
        1. Присвойте объектам ссылки `__proto__` так, чтобы любой поиск чего-либо шёл по алгоритму `pockets -> bed -> table -> head`. - -То есть `pockets.pen == 3`, `bed.glasses == 1`, но `table.money == undefined`.
        2. -
        3. После этого ответьте на вопрос, как быстрее искать `glasses`: обращением к `pockets.glasses` или `head.glasses`? Попробуйте протестировать.
        4. -
        diff --git a/1-js/9-prototypes/1-prototype/article.md b/1-js/9-prototypes/1-prototype/article.md deleted file mode 100644 index 3d83be6a..00000000 --- a/1-js/9-prototypes/1-prototype/article.md +++ /dev/null @@ -1,246 +0,0 @@ -# Прототип объекта - -Объекты в JavaScript можно организовать в цепочки так, чтобы свойство, не найденное в одном объекте, автоматически искалось бы в другом. - -Связующим звеном выступает специальное свойство `__proto__`. - -[cut] -## Прототип __proto__ - -Если один объект имеет специальную ссылку `__proto__` на другой объект, то при чтении свойства из него, если свойство отсутствует в самом объекте, оно ищется в объекте `__proto__`. - -Свойство `__proto__` доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее. - -Пример кода (кроме IE10-): - -```js -//+ run -var animal = { - eats: true -}; -var rabbit = { - jumps: true -}; - -*!* -rabbit.__proto__ = animal; -*/!* - -// в rabbit можно найти оба свойства -alert( rabbit.jumps ); // true -alert( rabbit.eats ); // true -``` - -
          -
        1. Первый `alert` здесь работает очевидным образом -- он выводит свойство `jumps` объекта `rabbit`.
        2. -
        3. Второй `alert` хочет вывести `rabbit.eats`, ищет его в самом объекте `rabbit`, не находит -- и продолжает поиск в объекте `rabbit.__proto__`, то есть, в данном случае, в `animal`.
        4. -
        - -Иллюстрация происходящего при чтении `rabbit.eats` (поиск идет снизу вверх): - - - -**Объект, на который указывает ссылка `__proto__`, называется *"прототипом"*. В данном случае получилось, что `animal` является прототипом для `rabbit`.** - -**Также говорят, что объект `rabbit` *"прототипно наследует"* от `animal`.** - -Обратим внимание -- прототип используется исключительно при чтении. Запись значения, например, `rabbit.eats = value` или удаление `delete rabbit.eats` -- работает напрямую с объектом. - -В примере ниже мы записываем свойство в сам `rabbit`, после чего `alert` перестаёт брать его у прототипа, а берёт уже из самого объекта: - -```js -//+ run -var animal = { - eats: true -}; -var rabbit = { - jumps: true, - eats: false -}; - -rabbit.__proto__ = animal; - -*!* -alert( rabbit.eats ); // false, свойство взято из rabbit -*/!* -``` - -**Другими словами, прототип -- это "резервное хранилище свойств и методов" объекта, автоматически используемое при поиске.** - -У объекта, который является `__proto__`, может быть свой `__proto__`, у того -- свой, и так далее. При этом свойства будут искаться по цепочке. - -[smart header="Ссылка __proto__ в спецификации"] -Если вы будете читать спецификацию EcmaScript -- свойство `__proto__` обозначено в ней как `[[Prototype]]`. - -Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется `prototype`, и которое мы рассмотрим позже. -[/smart] - - -## Метод hasOwnProperty - -Обычный цикл `for..in` не делает различия между свойствами объекта и его прототипа. - -Он перебирает всё, например: - -```js -//+ run -var animal = { - eats: true -}; - -var rabbit = { - jumps: true, - __proto__: animal -}; - -*!* -for (var key in rabbit) { - alert( key + " = " + rabbit[key] ); // выводит и "eats" и "jumps" -} -*/!* -``` - -Иногда хочется посмотреть, что находится именно в самом объекте, а не в прототипе. - -**Вызов [obj.hasOwnProperty(prop)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/HasOwnProperty) возвращает `true`, если свойство `prop` принадлежит самому объекту `obj`, иначе `false`.** - -Например: - -```js -//+ run -var animal = { - eats: true -}; - -var rabbit = { - jumps: true, - __proto__: animal -}; - -*!* -alert( rabbit.hasOwnProperty('jumps') ); // true: jumps принадлежит rabbit - -alert( rabbit.hasOwnProperty('eats') ); // false: eats не принадлежит -*/!* -``` - -Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать `key` через `hasOwnProperty`: - -```js -//+ run -var animal = { - eats: true -}; - -var rabbit = { - jumps: true, - __proto__: animal -}; - -for (var key in rabbit) { -*!* - if (!rabbit.hasOwnProperty(key)) continue; // пропустить "не свои" свойства -*/!* - alert( key + " = " + rabbit[key] ); // выводит только "jumps" -} -``` - -## Object.create(null) - -Зачастую объекты используют для хранения произвольных значений по ключу, как коллекцию: - -```js -var data = {}; -data.text = "Привет"; -data.age = 35; -// ... -``` - -При дальнейшем поиске в этой коллекции мы найдём не только `text` и `age`, но и встроенные функции: - -```js -//+ run -var data = {}; -alert(data.toString); // функция, хотя мы её туда не записывали -``` - -Это может быть неприятным сюрпризом и приводить к ошибкам, если названия свойств приходят от посетителя и могут быть произвольными. - -Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту: -```js -//+ run -var data = {}; - -// выведет toString только если оно записано в сам объект -alert(data.hasOwnProperty('toString') ? data.toString : undefined); -``` - -Однако, есть путь и проще: -```js -//+ run -*!* -var data = Object.create(null); -*/!* -data.text = "Привет"; - -alert(data.text); // Привет -*!* -alert(data.toString); // undefined -*/!* -``` - -Объект, создаваемый при помощи `Object.create(null)` не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции -- как раз то, что надо. - - -## Методы для работы с __proto__ - -В современных браузерах есть два дополнительных метода для работы с `__proto__`. Зачем они нужны, если есть `__proto__`? В общем-то, не очень нужны, но по историческим причинам тоже существуют. - -
        -
        Чтение: [Object.getPrototypeOf(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getPrototypeOf)
        -
        Возвращает `obj.__proto__` (кроме IE8-)
        -
        Запись: [Object.setPrototypeOf(obj, proto)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)
        -
        Устанавливает `obj.__proto__ = proto` (кроме IE10-).
        -
        - -Кроме того, есть ещё один вспомогательный метод: -
        Создание объекта с прототипом: [Object.create(proto, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create)
        -
        Создаёт пустой объект с `__proto__`, равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать [дескрипторы свойств](/descriptors-getters-setters).
        -
    - - - -## Итого - - - - -Несколько прототипов одному объекту присвоить нельзя, но можно организовать объекты в цепочку, когда один объект ссылается на другой при помощи `__proto__`, тот ссылается на третий, и так далее. - -В современных браузерах есть методы для работы с прототипом: - - - -Возможно, вас смущает недостаточная поддержка `__proto__` в старых IE. Но это не страшно. В последующих главах мы рассмотрим дополнительные методы работы с `__proto__`, включая те, которые работают везде. - -Также мы рассмотрим, как свойство `__proto__` используется внутри самого языка JavaScript и как организовать классы с его помощью. - - - -[head] - -[/head] diff --git a/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png b/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png deleted file mode 100644 index c2b6ede2..00000000 Binary files a/1-js/9-prototypes/1-prototype/proto-animal-rabbit.png and /dev/null differ diff --git a/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png b/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png deleted file mode 100644 index d0e116d8..00000000 Binary files a/1-js/9-prototypes/1-prototype/proto-animal-rabbit@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/solution.md b/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/solution.md deleted file mode 100644 index 9256a9fb..00000000 --- a/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/solution.md +++ /dev/null @@ -1,9 +0,0 @@ -Результат: `true`, из прототипа - -Результат: `true`. Свойство `prototype` всего лишь задаёт `__proto__` у новых объектов. Так что его изменение не повлияет на `rabbit.__proto__`. Свойство `eats` будет получено из прототипа. - -Результат: `false`. Свойство `Rabbit.prototype` и `rabbit.__proto__` указывают на один и тот же объект. В данном случае изменения вносятся в сам объект. - -Результат: `true`, так как `delete rabbit.eats` попытается удалить `eats` из `rabbit`, где его и так нет. А чтение в `alert` произойдёт из прототипа. - -Результат: `undefined`. Удаление осуществляется из самого прототипа, поэтому свойство `rabbit.eats` больше взять неоткуда. \ No newline at end of file diff --git a/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md b/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md deleted file mode 100644 index f8ea33a7..00000000 --- a/1-js/9-prototypes/2-new-prototype/1-prototype-after-new/task.md +++ /dev/null @@ -1,89 +0,0 @@ -# Прототип после создания - -[importance 5] - -В примерах ниже создаётся объект `new Rabbit`, а затем проводятся различные действия с `prototype`. - -Каковы будут результаты выполнения? Почему? - -Начнём с этого кода. Что он выведет? - -```js -function Rabbit() {} -Rabbit.prototype = { - eats: true -}; - -var rabbit = new Rabbit(); - -alert( rabbit.eats ); -``` - -Добавили строку (выделена), что будет теперь? - -```js -function Rabbit() {} -Rabbit.prototype = { - eats: true -}; - -var rabbit = new Rabbit(); - -*!* -Rabbit.prototype = {}; -*/!* - -alert( rabbit.eats ); -``` - -А если код будет такой? (заменена одна строка): - -```js -function Rabbit(name) {} -Rabbit.prototype = { - eats: true -}; - -var rabbit = new Rabbit(); - -*!* -Rabbit.prototype.eats = false; -*/!* - -alert( rabbit.eats ); -``` - -А такой? (заменена одна строка) - -```js -function Rabbit(name) {} -Rabbit.prototype = { - eats: true -}; - -var rabbit = new Rabbit(); - -*!* -delete rabbit.eats; // (*) -*/!* - -alert( rabbit.eats ); -``` - -И последний вариант: - -```js -function Rabbit(name) {} -Rabbit.prototype = { - eats: true -}; - -var rabbit = new Rabbit(); - -*!* -delete Rabbit.prototype.eats; // (*) -*/!* - -alert( rabbit.eats ); -``` - diff --git a/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md b/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md deleted file mode 100644 index 222e9083..00000000 --- a/1-js/9-prototypes/2-new-prototype/2-default-arguments/solution.md +++ /dev/null @@ -1,15 +0,0 @@ -Можно прототипно унаследовать от `options` и добавлять/менять опции в наследнике: - -```js -//+ run -function Menu(options) { - options = Object.create(options); - options.width = options.width || 300; - - alert( options.width ); // возьмёт width из наследника - alert( options.height ); // возьмёт height из исходного объекта - ... -} -``` - -Все изменения будут происходить не в самом `options`, а в его наследнике, при этом исходный объект останется незатронутым. diff --git a/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md b/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md deleted file mode 100644 index 1f1c84d5..00000000 --- a/1-js/9-prototypes/2-new-prototype/2-default-arguments/task.md +++ /dev/null @@ -1,27 +0,0 @@ -# Аргументы по умолчанию - -[importance 4] - -Есть функция `Menu`, которая получает аргументы в виде объекта `options`: - -```js -/* options содержит настройки меню: width, height и т.п. */ -function Menu(options) { - ... -} -``` - -Ряд опций должны иметь значение по умолчанию. Мы могли бы проставить их напрямую в объекте `options`: - -```js -function Menu(options) { - options.width = options.width || 300; // по умолчанию ширина 300 - ... -} -``` - -...Но такие изменения могут привести к непредвиденным результатам, т.к. объект `options` может быть повторно использован во внешнем коде. Он передается в `Menu` для того, чтобы параметры из него читали, а не писали. - -Один из способов безопасно назначить значения по умолчанию -- скопировать все свойства `options` в локальные переменные и затем уже менять. Другой способ -- клонировать `options` путём копирования всех свойств из него в новый объект, который уже изменяется. - -При помощи наследования и `Object.create` предложите третий способ, который позволяет избежать копирования объекта и не требует новых переменных. diff --git a/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md b/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md deleted file mode 100644 index 1b268b84..00000000 --- a/1-js/9-prototypes/2-new-prototype/3-compare-calls/solution.md +++ /dev/null @@ -1,33 +0,0 @@ -# Разница между вызовами - -Первый вызов ставит `this == rabbit`, остальные ставят `this` равным `Rabbit.prototype`, следуя правилу "`this` -- объект перед точкой". - -Так что только первый вызов выведет `Rabbit`, в остальных он будет `undefined`. - -Код для проверки: - -```js -//+ run -function Rabbit(name) { - this.name = name; -} -Rabbit.prototype.sayHi = function() { - alert( this.name ); -} - -var rabbit = new Rabbit("Rabbit"); - -rabbit.sayHi(); -Rabbit.prototype.sayHi(); -Object.getPrototypeOf(rabbit).sayHi(); -rabbit.__proto__.sayHi(); -``` - -# Совместимость - -
      -
    1. Первый вызов работает везде.
    2. -
    3. Второй вызов работает везде.
    4. -
    5. Третий вызов не будет работать в IE8-, там нет метода `getPrototypeOf`
    6. -
    7. Четвёртый вызов -- самый "несовместимый", он не будет работать в IE10-, ввиду отсутствия свойства `__proto__`.
    8. -
    \ No newline at end of file diff --git a/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md b/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md deleted file mode 100644 index b3773dfa..00000000 --- a/1-js/9-prototypes/2-new-prototype/3-compare-calls/task.md +++ /dev/null @@ -1,27 +0,0 @@ -# Есть ли разница между вызовами? - -[importance 5] - -Создадим новый объект, вот такой: - -```js -function Rabbit(name) { - this.name = name; -} -Rabbit.prototype.sayHi = function() { - alert( this.name ); -} - -var rabbit = new Rabbit("Rabbit"); -``` - -Одинаково ли сработают эти вызовы? - -```js -rabbit.sayHi(); -Rabbit.prototype.sayHi(); -Object.getPrototypeOf(rabbit).sayHi(); -rabbit.__proto__.sayHi(); -``` - -Все ли они являются кросс-браузерными? Если нет -- в каких браузерах сработает каждый? \ No newline at end of file diff --git a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md deleted file mode 100644 index eae8b524..00000000 --- a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/solution.md +++ /dev/null @@ -1,34 +0,0 @@ -Да, можем, но только если уверены, что кто-то позаботился о том, чтобы значение `constructor` было верным. - -В частности, без вмешательства в прототип код точно работает, например: - -```js -//+ run -function User(name) { - this.name = name; -} - -var obj = new User('Вася'); -var obj2 = new obj.constructor('Петя'); - -alert( obj2.name ); // Петя (сработало) -``` - -Сработало, так как `User.prototype.constructor == User`. - -Но если кто-то, к примеру, перезапишет `User.prototype` и забудет указать `constructor`, то такой фокус не пройдёт, например: - -```js -//+ run -function User(name) { - this.name = name; - } -*!* -User.prototype = {}; -*/!* - -var obj = new User('Вася'); -var obj2 = new obj.constructor('Петя'); - -alert( obj2.name ); // undefined -``` \ No newline at end of file diff --git a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md b/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md deleted file mode 100644 index 99cb4e90..00000000 --- a/1-js/9-prototypes/2-new-prototype/4-new-object-same-constructor/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Создать объект тем же конструктором - -[importance 5] - -Пусть у нас есть произвольный объект `obj`, созданный каким-то конструктором, каким -- мы не знаем, но хотели бы создать новый объект с его помощью. - -Сможем ли мы сделать так? - -```js -var obj2 = new obj.constructor(); -``` - -Приведите пример конструкторов для `obj`, при которых такой код будет работать верно -- и неверно. diff --git a/1-js/9-prototypes/2-new-prototype/article.md b/1-js/9-prototypes/2-new-prototype/article.md deleted file mode 100644 index 70120f02..00000000 --- a/1-js/9-prototypes/2-new-prototype/article.md +++ /dev/null @@ -1,229 +0,0 @@ -# Свойство F.prototype и создание объектов через new - -До этого момента мы говорили о наследовании объектов, объявленных через `{...}`. - -Но в реальных проектах объекты обычно создаются функцией-конструктором через `new`. Посмотрим, как указать прототип в этом случае. -[cut] - -## Свойство F.prototype - -Самым очевидным решением является назначение `__proto__` в конструкторе. - -Например, если я хочу, чтобы у всех объектов, которые создаются `new Rabbit`, был прототип `animal`, я могу сделать так: - -```js -//+ run -var animal = { - eats: true -}; - -function Rabbit(name) { - this.name = name; -*!* - this.__proto__ = animal; -*/!* -} - -var rabbit = new Rabbit("Кроль"); - -alert( rabbit.eats ); // true, из прототипа -``` - -Недостаток этого подхода -- он не работает в IE10-. - -К счастью, в JavaScript с древнейших времён существует альтернативный, встроенный в язык и полностью кросс-браузерный способ. - -**Чтобы новым объектам автоматически ставить прототип, конструктору ставится свойство `prototype`.** - -**При создании объекта через `new`, в его прототип `__proto__` записывается ссылка из `prototype` функции-конструктора.** - -Например, код ниже полностью аналогичен предыдущему, но работает всегда и везде: - -```js -//+ run -var animal = { - eats: true -}; - -function Rabbit(name) { - this.name = name; -} - -*!* -Rabbit.prototype = animal; -*/!* - -var rabbit = new Rabbit("Кроль"); // rabbit.__proto__ == animal - -alert( rabbit.eats ); // true -``` - -Установка `Rabbit.prototype = animal` буквально говорит интерпретатору следующее: *"При создании объекта через `new Rabbit` запиши ему `__proto__ = animal`".* - -[smart header="Свойство `prototype` имеет смысл только у конструктора"] -Свойство с именем `prototype` можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору. - -Само по себе, без вызова оператора `new`, оно вообще ничего не делает, его единственное назначение -- указывать `__proto__` для новых объектов. -[/smart] - - - -[warn header="Значением `prototype` может быть только объект"] -Технически, в это свойство можно записать что угодно. - -Однако, при работе `new`, свойство `prototype` будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано. -[/warn] - -## Свойство constructor - -У каждой функции по умолчанию уже есть свойство `prototype`. - -Оно содержит объект такого вида: - -```js -function Rabbit() {} - -Rabbit.prototype = { - constructor: Rabbit -}; -``` - -В коде выше я создал `Rabbit.prototype` вручную, но ровно такой же -- генерируется автоматически. - -Проверим: - -```js -//+ run -function Rabbit() {} - -// в Rabbit.prototype есть одно свойство: constructor -alert( Object.getOwnPropertyNames(Rabbit.prototype) ); // constructor - -// оно равно Rabbit -alert( Rabbit.prototype.constructor == Rabbit ); // true -``` - -Можно его использовать для создания объекта с тем же конструктором, что и данный: - -```js -//+ run -function Rabbit(name) { - this.name = name; - alert( name ); -} - -var rabbit = new Rabbit("Кроль"); - -var rabbit2 = new rabbit.constructor("Крольчиха"); -``` - -Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же. - -[warn header="Свойство `constructor` легко потерять"] -JavaScript никак не использует свойство `constructor`. То есть, оно создаётся автоматически, а что с ним происходит дальше -- это уже наша забота. В стандарте прописано только его создание. - -В частности, при перезаписи `Rabbit.prototype = { jumps: true }` свойства `constructor` больше не будет. - -Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не "сломается". Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие `constructor` вручную: -```js -Rabbit.prototype = { - jumps: true, -*!* - constructor: Rabbit -*/!* -}; -``` - -Либо можно поступить аккуратно и добавить свойства к встроенному `prototype` без его замены: -```js -// сохранится встроенный constructor -Rabbit.prototype.jumps = true -``` -[/warn] - - -## Эмуляция Object.create для IE8- [#inherit] - -Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи `F.prototype`. - -Теперь небольшое "лирическое отступление" в область совместимости. - -Прямые методы работы с прототипом осутствуют в старых IE, но один из них -- `Object.create(proto)` можно эмулировать, как раз при помощи `prototype`. И он будет работать везде, даже в самых устаревших браузерах. - -Кросс-браузерный аналог -- назовём его `inherit`, состоит буквально из нескольких строк: - -```js -function inherit(proto) { - function F() {} - F.prototype = proto; - var object = new F; - return object; -} -``` - -Результат вызова `inherit(animal)` идентичен `Object.create(animal)`. Она создаёт новый пустой объект с прототипом `animal`. - -Например: - -```js -//+ run -var animal = { - eats: true -}; - -var rabbit = inherit(animal); - -alert( rabbit.eats ); // true -``` - -Посмотрите внимательно на функцию `inherit` и вы, наверняка, сами поймёте, как она работает... - -Если где-то неясности, то её построчное описание: - -```js -//+ no-beautify -function inherit(proto) { - function F() {} // (1) - F.prototype = proto // (2) - var object = new F; // (3) - return object; // (4) -} -``` - -
      -
    1. Создана новая функция `F`. Она ничего не делает с `this`, так что если вызвать `new F`, то получим пустой объект.
    2. -
    3. Свойство `F.prototype` устанавливается в будущий прототип `proto`
    4. -
    5. Результатом вызова `new F` будет пустой объект с `__proto__` равным значению `F.prototype`.
    6. -
    7. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
    8. -
    - -Для унификации можно запустить такой код, и метод `Object.create` станет кросс-браузерным: - -```js -if (!Object.create) Object.create = inherit; /* определение inherit - выше */ -``` - -В частности, аналогичным образом работает библиотека [es5-shim](https://github.com/es-shims/es5-shim), при подключении которой `Object.create` станет доступен для всех браузеров. - - -## Итого - -Для произвольной функции -- назовём её `Constructor`, верно следующее: - - - - - -[head] - -[/head] \ No newline at end of file diff --git a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md b/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md deleted file mode 100644 index 46d09410..00000000 --- a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/solution.md +++ /dev/null @@ -1,15 +0,0 @@ - - -```js -//+ run -Function.prototype.defer = function(ms) { - setTimeout(this, ms); -} - -function f() { - alert( "привет" ); -} - -f.defer(1000); // выведет "привет" через 1 секунду -``` - diff --git a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md b/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md deleted file mode 100644 index 000adcc5..00000000 --- a/1-js/9-prototypes/3-native-prototypes/1-defer-to-prototype/task.md +++ /dev/null @@ -1,16 +0,0 @@ -# Добавить функциям defer - -[importance 5] - -Добавьте всем функциям в прототип метод `defer(ms)`, который откладывает вызов функции на `ms` миллисекунд. - -После этого должен работать такой код: - -```js -function f() { - alert( "привет" ); -} - -f.defer(1000); // выведет "привет" через 1 секунду -``` - diff --git a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md deleted file mode 100644 index 6c421265..00000000 --- a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/solution.md +++ /dev/null @@ -1,23 +0,0 @@ - - -```js -//+ run -Function.prototype.defer = function(ms) { - var f = this; - return function() { - var args = arguments, - context = this; - setTimeout(function() { - f.apply(context, args); - }, ms); - } -} - -// проверка -function f(a, b) { - alert( a + b ); -} - -f.defer(1000)(1, 2); // выведет 3 через 1 секунду. -``` - diff --git a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md b/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md deleted file mode 100644 index ddb3d4ba..00000000 --- a/1-js/9-prototypes/3-native-prototypes/2-defer-to-prototype-extended/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# Добавить функциям defer с аргументами - -[importance 4] - -Добавьте всем функциям в прототип метод defer(ms), который возвращает обёртку, откладывающую вызов функции на ms миллисекунд. - -Например, должно работать так: - -```js -function f(a, b) { - alert( a + b ); -} - -f.defer(1000)(1, 2); // выведет 3 через 1 секунду. -``` - -То есть, должны корректно передаваться аргументы. - - diff --git a/1-js/9-prototypes/3-native-prototypes/article.md b/1-js/9-prototypes/3-native-prototypes/article.md deleted file mode 100644 index 1c8ce795..00000000 --- a/1-js/9-prototypes/3-native-prototypes/article.md +++ /dev/null @@ -1,328 +0,0 @@ -# Встроенные "классы" в JavaScript - -В JavaScript есть встроенные объекты: `Date`, `Array`, `Object` и другие. Они используют прототипы и демонстрируют организацию "псевдоклассов" на JavaScript, которую мы вполне можем применить и для себя. - -[cut] - -## Откуда методы у {} ? - -Начнём мы с того, что создадим пустой объект и выведем его. - -```js -//+ run -var obj = {}; -alert( obj ); // "[object Object]" ? -``` - -Где код, который генерирует строковое представление для `alert(obj)`? Объект-то ведь пустой. - -## Object.prototype - -...Конечно же, это сделал метод `toString`, который находится... Конечно, не в самом объекте (он пуст), а в его прототипе `obj.__proto__`, можно его даже вывести: - -```js -//+ run -alert( {}.__proto__.toString ); // function toString -``` - -Откуда новый объект `obj` получает такой `__proto__`? - -
      -
    1. Запись `obj = {}` является краткой формой `obj = new Object`, где `Object` -- встроенная функция-конструктор для объектов.
    2. -
    3. При выполнении `new Object`, создаваемому объекту ставится `__proto__` по `prototype` конструктора, который в данном случае равен встроенному `Object.prototype`.
    4. -
    5. В дальнейшем при обращении к `obj.toString()` -- функция будет взята из `Object.prototype`.
    6. -
    - - - -Это можно легко проверить: - -```js -//+ run -var obj = {}; - -// метод берётся из прототипа? -alert( obj.toString == Object.prototype.toString ); // true, да - -// проверим, правда ли что __proto__ это Object.prototype? -alert( obj.__proto__ == Object.prototype ); // true - -// А есть ли __proto__ у Object.prototype? -alert( obj.__proto__.__proto__ ); // null, нет -``` - -## Встроенные "классы" в JavaScript - -Точно такой же подход используется в массивах `Array`, функциях `Function` и других объектах. Встроенные методы для них находятся в `Array.prototype`, `Function.prototype` и т.п. - - - -Например, когда мы создаём массив, `[1, 2, 3]`, то это альтернативный вариант синтаксиса `new Array`, так что у массивов есть стандартный прототип `Array.prototype`. - -Но в нём есть методы лишь для массивов, а для общих методов всех объектов есть ссылка `Array.prototype.__proto__`, равная `Object.prototype`. - -Аналогично, для функций. - -Лишь для чисел (как и других примитивов) всё немного иначе, но об этом чуть далее. - -Объект `Object.prototype` -- вершина иерархии, единственный, у которого `__proto__` равно `null`. - -**Поэтому говорят, что "все объекты наследуют от `Object`", а если более точно, то от `Object.prototype`.** - -"Псевдоклассом" или, более коротко, "классом", называют функцию-конструктор вместе с её `prototype`. Такой способ объявления классов называют "прототипным стилем ООП". - -При наследовании часть методов переопределяется, например, у массива `Array` есть свой `toString`, который выводит элементы массива через запятую: - -```js -//+ run -var arr = [1, 2, 3] -alert( arr ); // 1,2,3 <-- результат Array.prototype.toString -``` - -Как мы видели раньше, у `Object.prototype` есть свой `toString`, но так как в `Array.prototype` он ищется первым, то берётся именно вариант для массивов: - - - - -[smart header="Вызов методов через `apply` из прототипа"] - -Ранее мы говорили о применении методов массивов к "псевдомассивам", например, можно использовать `[].join` для `arguments`: - -```js -//+ run -function showList() { -*!* - alert( [].join.call(arguments, " - ") ); -*/!* -} - -showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша -``` - -Так как метод `join` находится в `Array.prototype`, то можно вызвать его оттуда напрямую, вот так: - -```js -//+ run -function showList() { -*!* - alert( Array.prototype.join.call(arguments, " - ") ); -*/!* -} - -showList("Вася", "Паша", "Маша"); // Вася - Паша - Маша -``` - -Это эффективнее, потому что не создаётся лишний объект массива `[]`, хотя, с другой стороны -- больше букв писать. -[/smart] - -## Примитивы - -Примитивы не являются объектами, но методы берут из соответствующих прототипов: `Number.prototype`, `Boolean.prototype`, `String.prototype`. - -По стандарту, если обратиться к свойству числа, строки или логического значения, то будет создан объект соответствующего типа, например `new String` для строки, `new Number` для чисел, `new Boolean` -- для логических выражений. - -Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен. - -Именно так работает код ниже: - -```js -//+ run -var user = "Вася"; // создали строку (примитив) - -*!* -alert( user.toUpperCase() ); // ВАСЯ -// был создан временный объект new String -// вызван метод -// new String уничтожен, результат возвращён -*/!* -``` - -Можно даже попробовать записать в этот временный объект свойство: - -```js -//+ run -// попытаемся записать свойство в строку: -var user = "Вася"; -user.age = 30; - -*!* -alert( user.age ); // undefined -*/!* -``` - -Свойство `age` было записано во временный объект, который был тут же уничтожен, так что смысла в такой записи немного. - -[warn header="Конструкторы `String/Number/Boolean` -- только для внутреннего использования"] -Технически, можно создавать объекты для примитивов и вручную, например `new Number`. Но в ряде случаев получится откровенно бредовое поведение. Например: - -```js -//+ run -alert( typeof 1 ); // "number" - -alert( typeof new Number(1) ); // "object" ?!? -``` - -Или, ещё страннее: - -```js -//+ run -var zero = new Number(0); - -if (zero) { // объект - true, так что alert выполнится - alert( "число ноль -- true?!?" ); -} -``` - -Поэтому в явном виде `new String`, `new Number` и `new Boolean` никогда не вызываются. -[/warn] - -[warn header="Значения `null` и `undefined` не имеют свойств"] -Значения `null` и `undefined` стоят особняком. Вышесказанное к ним не относится. - -Для них нет соответствующих классов, в них нельзя записать свойство (будет ошибка), в общем, на конкурсе "самое примитивное значение" они точно разделили бы первое место. -[/warn] - - -## Изменение встроенных прототипов [#native-prototype-change] - -Встроенные прототипы можно изменять. В том числе -- добавлять свои методы. - -Мы можем написать метод для многократного повторения строки, и он тут же станет доступным для всех строк: - -```js -//+ run -String.prototype.repeat = function(times) { - return new Array(times + 1).join(this); -}; - -alert( "ля".repeat(3) ); // ляляля -``` - -Аналогично мы могли бы создать метод `Object.prototype.each(func)`, который будет применять `func` к каждому свойству: - -```js -//+ run -Object.prototype.each = function(f) { - for (var prop in this) { - var value = this[prop]; - f.call(value, prop, value); // вызовет f(prop, value), this=value - } -} - -// Попробуем! (внимание, пока что это работает неверно!) -var user = { - name: 'Вася', - age: 25 -}; - -user.each(function(prop, val) { - alert( prop ); // name -> age -> (!) each -}); -``` - -Обратите внимание -- пример выше работает не совсем корректно. Вместе со свойствами объекта `user` он выводит и наше свойство `each`. Технически, это правильно, так как цикл `for..in` перебирает свойства и в прототипе тоже, но не очень удобно. - -Конечно, это легко поправить добавлением проверки `hasOwnProperty`: - -```js -//+ run -Object.prototype.each = function(f) { - - for (var prop in this) { - -*!* - // пропускать свойства из прототипа - if (!this.hasOwnProperty(prop)) continue; -*/!* - - var value = this[prop]; - f.call(value, prop, value); - - } - -}; - -// Теперь все будет в порядке -var obj = { - name: 'Вася', - age: 25 -}; - -obj.each(function(prop, val) { - alert( prop ); // name -> age -}); -``` - -Здесь это сработало, теперь код работает верно. Но мы же не хотим добавлять `hasOwnProperty` в цикл по любому объекту! Поэтому либо не добавляйте свойства в `Object.prototype`, либо можно использовать [дескриптор свойства](/descriptors-getters-setters) и флаг `enumerable`. - -Это, конечно, не будет работать в IE8-: - -```js -//+ run -Object.prototype.each = function(f) { - - for (var prop in this) { - var value = this[prop]; - f.call(value, prop, value); - } - -}; - -*!* -// поправить объявление свойства, установив флаг enumerable: false -Object.defineProperty(Object.prototype, 'each', { - enumerable: false -}); -*/!* - -// Теперь все будет в порядке -var obj = { - name: 'Вася', - age: 25 -}; - -obj.each(function(prop, val) { - alert( prop ); // name -> age -}); -``` - -Есть несколько "за" и "против" модификации встроенных прототипов: - -[compare] -+Методы в прототипе автоматически доступны везде, их вызов прост и красив. --Новые свойства, добавленные в прототип из разных мест, могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен. --Изменения встроенных прототипов влияют глобально, на все-все скрипты, делать их не очень хорошо с архитектурной точки зрения. -[/compare] - -Как правило, минусы весомее, но есть одно исключение, когда изменения встроенных прототипов не только разрешены, но и приветствуются. - -**Допустимо изменение прототипа встроенных объектов, которое добавляет поддержку метода из современных стандартов в те браузеры, где её пока нет.** - -Например, добавим `Object.create(proto)` в старые браузеры: - -```js -if (!Object.create) { - - Object.create = function(proto) { - function F() {} - F.prototype = proto; - return new F; - }; - -} -``` - -Именно так работает библиотека [es5-shim](https://github.com/kriskowal/es5-shim), которая предоставляет многие функции современного JavaScript для старых браузеров. Они добавляются во встроенные объекты и их прототипы. - -## Итого - - - diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototype-object.png b/1-js/9-prototypes/3-native-prototypes/native-prototype-object.png deleted file mode 100644 index 1353bd9c..00000000 --- a/1-js/9-prototypes/3-native-prototypes/native-prototype-object.png +++ /dev/null @@ -1 +0,0 @@ -native-prototype-objecttoString: function другие методы объектовObject.prototypeobj__proto____proto__null \ No newline at end of file diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png deleted file mode 100644 index 4ed5dbd4..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png deleted file mode 100644 index 757f4041..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-array-tostring@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png deleted file mode 100644 index e1135834..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png deleted file mode 100644 index db1a0b8e..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-classes@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png deleted file mode 100644 index 125f68e2..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object.png and /dev/null differ diff --git a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png b/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png deleted file mode 100644 index 77cdf9c5..00000000 Binary files a/1-js/9-prototypes/3-native-prototypes/native-prototypes-object@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md b/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md deleted file mode 100644 index a43508c8..00000000 --- a/1-js/9-prototypes/4-classes/1-rewrite-by-class/solution.md +++ /dev/null @@ -1,32 +0,0 @@ - - -```js -//+ run -function CoffeeMachine(power) { - // свойства конкретной кофеварки - this._power = power; - this._waterAmount = 0; -} - -// свойства и методы для всех объектов класса -CoffeeMachine.prototype.WATER_HEAT_CAPACITY = 4200; - -CoffeeMachine.prototype._getTimeToBoil = function() { - return this._waterAmount * this.WATER_HEAT_CAPACITY * 80 / this._power; -}; - -CoffeeMachine.prototype.run = function() { - setTimeout(function() { - alert( 'Кофе готов!' ); - }, this._getTimeToBoil()); -}; - -CoffeeMachine.prototype.setWaterAmount = function(amount) { - this._waterAmount = amount; -}; - -var coffeeMachine = new CoffeeMachine(10000); -coffeeMachine.setWaterAmount(50); -coffeeMachine.run(); -``` - diff --git a/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md b/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md deleted file mode 100644 index cfc8aa91..00000000 --- a/1-js/9-prototypes/4-classes/1-rewrite-by-class/task.md +++ /dev/null @@ -1,39 +0,0 @@ -# Перепишите в виде класса - -[importance 5] - -Есть класс `CoffeeMachine`, заданный в функциональном стиле. - -Задача: переписать `CoffeeMachine` в виде класса с использованием прототипа. - -Исходный код: - -```js -//+ run -function CoffeeMachine(power) { - var waterAmount = 0; - - var WATER_HEAT_CAPACITY = 4200; - - function getTimeToBoil() { - return waterAmount * WATER_HEAT_CAPACITY * 80 / power; - } - - this.run = function() { - setTimeout(function() { - alert( 'Кофе готов!' ); - }, getTimeToBoil()); - }; - - this.setWaterAmount = function(amount) { - waterAmount = amount; - }; - -} - -var coffeeMachine = new CoffeeMachine(10000); -coffeeMachine.setWaterAmount(50); -coffeeMachine.run(); -``` - -P.S. При описании через прототипы локальные переменные недоступны методам, поэтому нужно будет переделать их в защищённые свойства. diff --git a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md b/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md deleted file mode 100644 index 113334fd..00000000 --- a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/solution.md +++ /dev/null @@ -1,52 +0,0 @@ -# Почему возникает проблема - -Давайте подробнее разберем происходящее при вызове `speedy.found("яблоко")`: -
      -
    1. Интерпретатор ищет свойство `found` в `speedy`. Но `speedy` -- пустой объект, т.к. `new Hamster` ничего не делает с `this`.
    2. -
    3. Интерпретатор идёт по ссылке `speedy.__proto__ (==Hamster.prototype)` и находят там метод `found`, запускает его.
    4. -
    5. Значение `this` устанавливается в объект перед точкой, т.е. в `speedy`.
    6. -
    7. Для выполнения `this.food.push()` нужно найти свойство `this.food`. Оно отсутствует в `speedy`, но есть в `speedy.__proto__`.
    8. -
    9. Значение `"яблоко"` добавляется в `speedy.__proto__.food`.
    10. -
    - -**У всех хомяков общий живот!** Или, в терминах JavaScript, свойство `food` изменяется в прототипе, который является общим для всех объектов-хомяков. - -Заметим, что этой проблемы не было бы при простом присваивании: - -```js -this.food = something; -``` - -В этом случае значение записалось бы в сам объект, без поиска `found` в прототипе. - -**Проблема возникает только со свойствами-объектами в прототипе.** - -Исправьте её? - -# Исправление - -Для исправления проблемы нужно дать каждому хомяку свой живот. Это можно сделать, присвоив его в конструкторе. - -```js -//+ run -function Hamster() { -*!* - this.food = []; -*/!* -} - -Hamster.prototype.found = function(something) { - this.food.push(something); -}; - -speedy = new Hamster(); -lazy = new Hamster(); - -speedy.found("яблоко"); -speedy.found("орех"); - -alert(speedy.food.length) // 2 -alert(lazy.food.length) // 0(!) -``` - -Теперь всё в порядке. У каждого хомяка -- свой живот. \ No newline at end of file diff --git a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md b/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md deleted file mode 100644 index 48aa3220..00000000 --- a/1-js/9-prototypes/4-classes/2-hamsters-with-proto/task.md +++ /dev/null @@ -1,33 +0,0 @@ -# Хомяки с __proto__ - -[importance 5] - -Вы -- руководитель команды, которая разрабатывает игру, хомяковую ферму. Один из программистов получил задание создать класс "хомяк" (англ - `"Hamster"`). - -Объекты-хомяки должны иметь массив `food` для хранения еды и метод `found`, который добавляет к нему. - -Ниже -- его решение. При создании двух хомяков, если поел один -- почему-то сытым становится и второй тоже. - -В чём дело? Как поправить? - -```js -//+ run -function Hamster() {} - -Hamster.prototype.food = []; // пустой "живот" - -Hamster.prototype.found = function(something) { - this.food.push(something); -}; - -// Создаём двух хомяков и кормим первого -var speedy = new Hamster(); -var lazy = new Hamster(); - -speedy.found("яблоко"); -speedy.found("орех"); - -alert( speedy.food.length ); // 2 -alert( lazy.food.length ); // 2 (!??) -``` - diff --git a/1-js/9-prototypes/4-classes/article.md b/1-js/9-prototypes/4-classes/article.md deleted file mode 100644 index 082c22f2..00000000 --- a/1-js/9-prototypes/4-classes/article.md +++ /dev/null @@ -1,128 +0,0 @@ -# Свои классы на прототипах - -Используем ту же структуру, что JavaScript использует внутри себя, для объявления своих классов. - -[cut] -## Обычный конструктор - -Вспомним, как мы объявляли классы ранее. - -Например, этот код задаёт класс `Animal` в функциональном стиле, без всяких прототипов: - -```js -//+ run -function Animal(name) { - this.speed = 0; - this.name = name; - - this.run = function(speed) { - this.speed += speed; - alert( this.name + ' бежит, скорость ' + this.speed ); - }; - - this.stop = function() { - this.speed = 0; - alert( this.name + ' стоит' ); - }; -}; - -var animal = new Animal('Зверь'); - -alert( animal.speed ); // 0, начальная скорость -animal.run(3); // Зверь бежит, скорость 3 -animal.run(10); // Зверь бежит, скорость 13 -animal.stop(); // Зверь стоит -``` - -## Класс через прототип - -А теперь создадим аналогичный класс, используя прототипы, наподобие того, как сделаны классы `Object`, `Date` и остальные. - -Чтобы объявить свой класс, нужно: - -
      -
    1. Объявить функцию-конструктор.
    2. -
    3. Записать методы и свойства, нужные всем объектам класса, в `prototype`.
    4. -
    - -Опишем класс `Animal`: - -```js -//+ run -// конструктор -function Animal(name) { - this.name = name; - this.speed = 0; -} - -// методы в прототипе -Animal.prototype.run = function(speed) { - this.speed += speed; - alert( this.name + ' бежит, скорость ' + this.speed ); -}; - -Animal.prototype.stop = function() { - this.speed = 0; - alert( this.name + ' стоит' ); -}; - -var animal = new Animal('Зверь'); - -alert( animal.speed ); // 0, свойство взято из прототипа -animal.run(5); // Зверь бежит, скорость 5 -animal.run(5); // Зверь бежит, скорость 10 -animal.stop(); // Зверь стоит -``` - -В объекте `animal` будут хранится свойства конкретного экземпляра: `name` и `speed`, а общие методы -- в прототипе. - -Совершенно такой же подход, как и для встроенных классов в JavaScript. - -## Сравнение - -Чем такое задание класса лучше и хуже функционального стиля? - -[compare] -+Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный -- только свойства. Поэтому прототипный стиль -- быстрее и экономнее по памяти. --При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором. -[/compare] - -Таким образом, прототипный стиль -- быстрее и экономнее, но немного менее удобен. - -К примеру, есть у нас приватное свойство `name` и метод `sayHi` в функциональном стиле ООП: - -```js -//+ run -function Animal(name) { - this.sayHi = function() { -*!* - alert( name ); -*/!* - }; -} - -var animal = new Animal("Зверь"); -animal.sayHi(); // Зверь -``` - -При задании методов в прототипе мы не сможем её так оставить, ведь методы находятся *вне* конструктора, у них нет общей области видимости, поэтому приходится записывать `name` в сам объект, обозначив его как защищённое: - -```js -//+ run -function Animal(name) { -*!* - this._name = name; -*/!* -} - -Animal.prototype.sayHi = function() { -*!* - alert( this._name ); -*/!* -} - -var animal = new Animal("Зверь"); -animal.sayHi(); // Зверь -``` - -Впрочем, недостаток этот -- довольно условный. Ведь при наследовании в функциональном стиле также пришлось бы писать `this._name`, чтобы потомок получил доступ к этому значению. diff --git a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md b/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md deleted file mode 100644 index 8987b2d7..00000000 --- a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/solution.md +++ /dev/null @@ -1,44 +0,0 @@ -Ошибка в строке: - -```js -Rabbit.prototype = Animal.prototype; -``` - -Эта ошибка приведёт к тому, что `Rabbit.prototype` и `Animal.prototype` -- один и тот же объект. В результате методы `Rabbit` будут помещены в него и, при совпадении, перезапишут методы `Animal`. - -Получится, что все животные прыгают, вот пример: - -```js -//+ run no-beautify -function Animal(name) { - this.name = name; -} - -Animal.prototype.walk = function() { - alert("ходит " + this.name); -}; - -function Rabbit(name) { - this.name = name; -} -*!* -Rabbit.prototype = Animal.prototype; -*/!* - -Rabbit.prototype.walk = function() { - alert("прыгает! и ходит: " + this.name); -}; - -*!* -var animal = new Animal("Хрюшка"); -animal.walk(); // прыгает! и ходит Хрюшка -*/!* -``` - -Правильный вариант этой строки: - -```js -Rabbit.prototype = Object.create(Animal.prototype); -``` - -Если так написать, то в `Rabbit.prototype` будет отдельный объект, который прототипно наследует от `Animal.prototype`, но может содержать и свои свойства, специфичные для кроликов. diff --git a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md b/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md deleted file mode 100644 index ddb177e9..00000000 --- a/1-js/9-prototypes/5-class-inheritance/1-inheritance-error-assign/task.md +++ /dev/null @@ -1,25 +0,0 @@ -# Найдите ошибку в наследовании - -[importance 5] - -Найдите ошибку в прототипном наследовании. К чему она приведёт? - -```js -function Animal(name) { - this.name = name; -} - -Animal.prototype.walk = function() { - alert( "ходит " + this.name ); -}; - -function Rabbit(name) { - this.name = name; -} -Rabbit.prototype = Animal.prototype; - -Rabbit.prototype.walk = function() { - alert( "прыгает! и ходит: " + this.name ); -}; -``` - diff --git a/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/solution.md b/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/solution.md deleted file mode 100644 index 19384566..00000000 --- a/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Ошибка -- в том, что метод `walk` присваивается в конструкторе `Animal` самому объекту вместо прототипа. - -Поэтому, если мы решим перезаписать этот метод своим, специфичным для кролика, то он не сработает: - -```js -// ... - -// записывается в прототип -Rabbit.prototype.walk = function() { - alert( "прыгает " + this.name ); -}; -``` - -Метод `this.walk` из `Animal` записывается в сам объект, и поэтому он всегда будет первым, игнорируя цепочку прототипов. - -Правильно было бы определять `walk` как `Animal.prototype.walk`. - -Тем более, что этот метод является общим для всех объектов, тратить память и время на запись его в каждый конструктор определённо ни к чему. \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md b/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md deleted file mode 100644 index b97447f5..00000000 --- a/1-js/9-prototypes/5-class-inheritance/2-inheritance-error-constructor/task.md +++ /dev/null @@ -1,30 +0,0 @@ -# В чём ошибка в наследовании - -[importance 5] - -Найдите ошибку в прототипном наследовании. К чему она приведёт? - -```js -//+ run -function Animal(name) { - this.name = name; - - this.walk = function() { - alert( "ходит " + this.name ); - }; - -} - -function Rabbit(name) { - Animal.apply(this, arguments); -} -Rabbit.prototype = Object.create(Animal.prototype); - -Rabbit.prototype.walk = function() { - alert( "прыгает " + this.name ); -}; - -var rabbit = new Rabbit("Кроль"); -rabbit.walk(); -``` - diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/clock.js b/1-js/9-prototypes/5-class-inheritance/3-clock-class/clock.js deleted file mode 100755 index 34fb026d..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md b/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md deleted file mode 100644 index d1c08b87..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.md +++ /dev/null @@ -1,7 +0,0 @@ - - -```js -//+ src="clock.js" -``` - -[edit src="solution"]Открыть полное решение[/edit] \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.view/clock.js b/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.view/clock.js deleted file mode 100755 index 34fb026d..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.view/index.html b/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.view/index.html deleted file mode 100755 index ec1acda9..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/solution.view/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Часики в консоли - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/source.view/clock.js b/1-js/9-prototypes/5-class-inheritance/3-clock-class/source.view/clock.js deleted file mode 100755 index 87f457c6..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/source.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - - var template = options.template; - var timer; - - function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); - } - - this.stop = function() { - clearInterval(timer); - }; - - this.start = function() { - render(); - timer = setInterval(render, 1000); - } - -} \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/source.view/index.html b/1-js/9-prototypes/5-class-inheritance/3-clock-class/source.view/index.html deleted file mode 100755 index ec1acda9..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/source.view/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Часики в консоли - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md b/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md deleted file mode 100644 index 59355fee..00000000 --- a/1-js/9-prototypes/5-class-inheritance/3-clock-class/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Класс "часы" - -[importance 5] - -Есть реализация часиков, оформленная в виде одной функции-конструктора. У неё есть приватные свойства `timer`, `template` и метод `render`. - -Задача: переписать часы на прототипах. Приватные свойства и методы сделать защищёнными. - - - -P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть). \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/extended-clock.js b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/extended-clock.js deleted file mode 100755 index 3e0bf9bd..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/extended-clock.js +++ /dev/null @@ -1,14 +0,0 @@ -function ExtendedClock(options) { - Clock.apply(this, arguments); - this._precision = +options.precision || 1000; -} - -ExtendedClock.prototype = Object.create(Clock.prototype); - -ExtendedClock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, this._precision); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md deleted file mode 100644 index f58d46d8..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.md +++ /dev/null @@ -1,7 +0,0 @@ -Наследник: - -```js -//+ src="extended-clock.js" -``` - -[edit src="solution"]Открыть полное решение в редакторе[/edit] \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/clock.js b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/clock.js deleted file mode 100755 index 34fb026d..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/extended-clock.js b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/extended-clock.js deleted file mode 100755 index 3e0bf9bd..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/extended-clock.js +++ /dev/null @@ -1,14 +0,0 @@ -function ExtendedClock(options) { - Clock.apply(this, arguments); - this._precision = +options.precision || 1000; -} - -ExtendedClock.prototype = Object.create(Clock.prototype); - -ExtendedClock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, this._precision); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/index.html b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/index.html deleted file mode 100755 index c226468c..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/solution.view/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Часики в консоли - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/clock.js b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/clock.js deleted file mode 100755 index 34fb026d..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/extended-clock.js b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/extended-clock.js deleted file mode 100755 index 3b3efe1e..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/extended-clock.js +++ /dev/null @@ -1,13 +0,0 @@ -function extend(Child, Parent) { - Child.prototype = inherit(Parent.prototype); - Child.prototype.constructor = Child; - Child.parent = Parent.prototype; -} - -function inherit(proto) { - function F() {} - F.prototype = proto; - return new F; -} - -// ваш код \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/index.html b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/index.html deleted file mode 100755 index 4aac9ceb..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/source.view/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - Часики в консоли - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md b/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md deleted file mode 100644 index 94bee22a..00000000 --- a/1-js/9-prototypes/5-class-inheritance/4-clock-class-extended/task.md +++ /dev/null @@ -1,15 +0,0 @@ -# Класс "расширенные часы" - -[importance 5] - -Есть реализация часиков на прототипах. Создайте класс, расширяющий её, добавляющий поддержку параметра `precision`, который будет задавать частоту тика в `setInterval`. Значение по умолчанию: `1000`. - - - - - -P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть). \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md deleted file mode 100644 index 9cb942f7..00000000 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.md +++ /dev/null @@ -1,3 +0,0 @@ -[edit src="solution"]Открыть решение в редакторе[/edit] - -Обратите внимание: константы состояний перенесены в прототип, чтобы `AnimatingMenu` их тоже унаследовал. diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.view/index.html b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.view/index.html deleted file mode 100755 index 705919d8..00000000 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.view/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.view/menu.js b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.view/menu.js deleted file mode 100755 index 9a91f30e..00000000 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/solution.view/menu.js +++ /dev/null @@ -1,28 +0,0 @@ -function Menu(state) { - this._state = state || this.STATE_CLOSED; -}; - -Menu.prototype.STATE_OPEN = 1; -Menu.prototype.STATE_CLOSED = 0; - -Menu.prototype.open = function() { - this._state = this.STATE_OPEN; -}; - -Menu.prototype.close = function() { - this._state = this.STATE_CLOSED; -}; - -Menu.prototype._stateAsString = function() { - switch (this._state) { - case this.STATE_OPEN: - return 'открыто'; - - case this.STATE_CLOSED: - return 'закрыто'; - } -}; - -Menu.prototype.showState = function() { - alert(this._stateAsString()); -} \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/source.view/index.html b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/source.view/index.html deleted file mode 100755 index 20de9758..00000000 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/source.view/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/source.view/menu.js b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/source.view/menu.js deleted file mode 100755 index 996329ad..00000000 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/source.view/menu.js +++ /dev/null @@ -1,28 +0,0 @@ -function Menu(state) { - this._state = state || Menu.STATE_CLOSED; -}; - -Menu.STATE_OPEN = 1; -Menu.STATE_CLOSED = 0; - -Menu.prototype.open = function() { - this._state = Menu.STATE_OPEN; -}; - -Menu.prototype.close = function() { - this._state = Menu.STATE_CLOSED; -}; - -Menu.prototype._stateAsString = function() { - switch (this._state) { - case Menu.STATE_OPEN: - return 'открыто'; - - case Menu.STATE_CLOSED: - return 'закрыто'; - } -}; - -Menu.prototype.showState = function() { - alert(this._stateAsString()); -}; \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md b/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md deleted file mode 100644 index dd080260..00000000 --- a/1-js/9-prototypes/5-class-inheritance/5-menu-timer-animated/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Меню с таймером для анимации - -[importance 5] - -Есть класс `Menu`. У него может быть два состояния: открыто `STATE_OPEN` и закрыто `STATE_CLOSED`. - -Создайте наследника `AnimatingMenu`, который добавляет третье состояние `STATE_ANIMATING`. - - -[edit src="source"]Исходный документ, вместе с тестом[/edit] \ No newline at end of file diff --git a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md deleted file mode 100644 index 4339ffe9..00000000 --- a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/solution.md +++ /dev/null @@ -1,26 +0,0 @@ -**Нет, не распознает, выведет `false`.** - -Свойство `constructor` содержится в `prototype` функции по умолчанию, интерпретатор не поддерживает его корректность. Посмотрим, чему оно равно и откуда оно будет взято в данном случае. - -Порядок поиска свойства `rabbit.constructor`, по цепочке прототипов: -
      -
    1. `rabbit` -- это пустой объект, в нём нет.
    2. -
    3. `Rabbit.prototype` -- в него при помощи `Object.create` записан пустой объект, наследующий от `Animal.prototype`. Поэтому `constructor'а` в нём также нет.
    4. -
    5. `Animal.prototype` -- у функции `Animal` свойство `prototype` никто не менял. Поэтому оно содержит `Animal.prototype.constructor == Animal`.
    6. -
    - -```js -//+ run -function Animal() {} - -function Rabbit() {} -Rabbit.prototype = Object.create(Animal.prototype); - -var rabbit = new Rabbit(); - -*!* -alert( rabbit.constructor == Rabbit ); // false -alert( rabbit.constructor == Animal ); // true -*/!* -``` - diff --git a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md b/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md deleted file mode 100644 index ea7fde62..00000000 --- a/1-js/9-prototypes/5-class-inheritance/6-constructor-inherited/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# Что содержит constructor? - -[importance 5] - -В коде ниже создаётся простейшая иерархия классов: `Animal -> Rabbit`. - -Что содержит свойство `rabbit.constructor`? Распознает ли проверка в `alert` объект как `Rabbit`? - -```js -function Animal() {} - -function Rabbit() {} -Rabbit.prototype = Object.create(Animal.prototype); - -var rabbit = new Rabbit(); - -alert( rabbit.constructor == Rabbit ); // что выведет? -``` - diff --git a/1-js/9-prototypes/5-class-inheritance/article.md b/1-js/9-prototypes/5-class-inheritance/article.md deleted file mode 100644 index 3aa7012c..00000000 --- a/1-js/9-prototypes/5-class-inheritance/article.md +++ /dev/null @@ -1,358 +0,0 @@ -# Наследование классов в JavaScript - -Наследование на уровне объектов в JavaScript, как мы видели, реализуется через ссылку `__proto__`. - -Теперь поговорим о наследовании на уровне классов, то есть когда объекты, создаваемые, к примеру, через `new Admin`, должны иметь все методы, которые есть у объектов, создаваемых через `new User`, и ещё какие-то свои. - -[cut] - -## Наследование Array от Object - -Для реализации наследования в наших классах мы будем использовать тот же подход, который принят внутри JavaScript. - -Взглянем на него ещё раз на примере `Array`, который наследует от `Object`: - - - - - -Поэтому когда экземпляры класса `Array` хотят получить метод массива -- они берут его из своего прототипа, например `Array.prototype.slice`. - -Если же нужен метод объекта, например, `hasOwnProperty`, то его в `Array.prototype` нет, и он берётся из `Object.prototype`. - -Отличный способ "потрогать это руками" -- запустить в консоли команду `console.dir([1,2,3])`. - -Вывод в Chrome будет примерно таким: - - - -Здесь отчётливо видно, что сами данные и `length` находятся в массиве, дальше в `__proto__` идут методы для массивов `concat`, то есть `Array.prototype`, а далее -- `Object.prototype`. - -[smart header="`console.dir` для доступа к свойствам"] -Обратите внимание, я использовал именно `console.dir`, а не `console.log`, поскольку `log` зачастую выводит объект в виде строки, без доступа к свойствам. -[/smart] - -## Наследование в наших классах - -Применим тот же подход для наших классов: объявим класс `Rabbit`, который будет наследовать от `Animal`. - -Вначале создадим два этих класса по отдельности, они пока что будут совершенно независимы. - -`Animal`: - -```js -function Animal(name) { - this.name = name; - this.speed = 0; -} - -Animal.prototype.run = function(speed) { - this.speed += speed; - alert( this.name + ' бежит, скорость ' + this.speed ); -}; - -Animal.prototype.stop = function() { - this.speed = 0; - alert( this.name + ' стоит' ); -}; -``` - -`Rabbit`: - -```js -function Rabbit(name) { - this.name = name; - this.speed = 0; -} - -Rabbit.prototype.jump = function() { - this.speed++; - alert( this.name + ' прыгает' ); -}; - -var rabbit = new Rabbit('Кроль'); -``` - -Для того, чтобы наследование работало, объект `rabbit = new Rabbit` должен использовать свойства и методы из своего прототипа `Rabbit.prototype`, а если их там нет, то -- свойства и метода родителя, которые хранятся в `Animal.prototype`. - -Если ещё короче -- порядок поиска свойств и методов должен быть таким: `rabbit -> Rabbit.prototype -> Animal.prototype`, по аналогии с тем, как это сделано для объектов и массивов. - -Для этого можно поставить ссылку `__proto__` с `Rabbit.prototype` на `Animal.prototype`. - -Можно сделать это так: -```js -Rabbit.prototype.__proto__ = Animal.prototype; -``` - -Однако, прямой доступ к `__proto__` не поддерживается в IE10-, поэтому для поддержки этих браузеров мы используем функцию `Object.create`. Она либо встроена либо легко эмулируется во всех браузерах. - -Класс `Animal` остаётся без изменений, а `Rabbit.prototype` мы будем создавать с нужным прототипом, используя `Object.create`: - -```js -//+ no-beautify -function Rabbit(name) { - this.name = name; - this.speed = 0; -} - -*!* -// задаём наследование -Rabbit.prototype = Object.create(Animal.prototype); -*/!* - -// и добавим свой метод (или методы...) -Rabbit.prototype.jump = function() { ... }; -``` - -Теперь выглядеть иерархия будет так: - - - -В `prototype` по умолчанию всегда находится свойство `constructor`, указывающее на функцию-конструктор. В частности, `Rabbit.prototype.constructor == Rabbit`. Если мы рассчитываем использовать это свойство, то при замене `prototype` через `Object.create` нужно его явно сохранить: - -```js -Rabbit.prototype = Object.create(Animal.prototype); -Rabbit.prototype.constructor = Rabbit; -``` - -## Полный код наследования - -Для наглядности -- вот итоговый код с двумя классами `Animal` и `Rabbit`: - -```js -// 1. Конструктор Animal -function Animal(name) { - this.name = name; - this.speed = 0; -} - -// 1.1. Методы -- в прототип - -Animal.prototype.stop = function() { - this.speed = 0; - alert( this.name + ' стоит' ); -} - -Animal.prototype.run = function(speed) { - this.speed += speed; - alert( this.name + ' бежит, скорость ' + this.speed ); -}; - - -// 2. Конструктор Rabbit -function Rabbit(name) { - this.name = name; - this.speed = 0; -} - -// 2.1. Наследование -Rabbit.prototype = Object.create(Animal.prototype); -Rabbit.prototype.constructor = Rabbit; - -// 2.2. Методы Rabbit -Rabbit.prototype.jump = function() { - this.speed++; - alert( this.name + ' прыгает, скорость ' + this.speed ); -} -``` - -Как видно, наследование задаётся всего одной строчкой, поставленной в правильном месте. - -Обратим внимание: `Rabbit.prototype = Object.create(proto)` присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы. - -[warn header="Неправильный вариант: `Rabbit.prototype = new Animal`"] - -В некоторых устаревших руководствах предлагают вместо `Object.create(Animal.prototype)` записывать в прототип `new Animal`, вот так: - -```js -// вместо Rabbit.prototype = Object.create(Animal.prototype) -Rabbit.prototype = new Animal(); -``` - - -Частично, он рабочий, поскольку иерархия прототипов будет такая же, ведь `new Animal` -- это объект с прототипом `Animal.prototype`, как и `Object.create(Animal.prototype)`. Они в этом плане идентичны. - -Но у этого подхода важный недостаток. Как правило мы не хотим создавать `Animal`, а хотим только унаследовать его методы! - -Более того, на практике создание объекта может требовать обязательных аргументов, влиять на страницу в браузере, делать запросы к серверу и что-то ещё, чего мы хотели бы избежать. Поэтому рекомендуется использовать вариант с `Object.create`. -[/warn] - -## Вызов конструктора родителя - -Посмотрим внимательно на конструкторы `Animal` и `Rabbit` из примеров выше: - -```js -function Animal(name) { - this.name = name; - this.speed = 0; -} - -function Rabbit(name) { - this.name = name; - this.speed = 0; -} -``` - -Как видно, объект `Rabbit` не добавляет никакой особенной логики при создании, которой не было в `Animal`. - -Чтобы упростить поддержку кода, имеет смысл не дублировать код конструктора `Animal`, а напрямую вызвать его: - -```js -function Rabbit(name) { - Animal.apply(this, arguments); -} -``` - -Такой вызов запустит функцию `Animal` в контексте текущего объекта, со всеми аргументами, она выполнится и запишет в `this` всё, что нужно. - -Здесь можно было бы использовать и `Animal.call(this, name)`, но `apply` надёжнее, так как работает с любым количеством аргументов. - -## Переопределение метода - -Итак, `Rabbit` наследует `Animal`. Теперь если какого-то метода нет в `Rabbit.prototype` -- он будет взят из `Animal.prototype`. - -В `Rabbit` может понадобиться задать какие-то методы, которые у родителя уже есть. Например, кролики бегают не так, как остальные животные, поэтому переопределим метод `run()`: - -```js -Rabbit.prototype.run = function(speed) { - this.speed++; - this.jump(); -}; -``` - -Вызов `rabbit.run()` теперь будет брать `run` из своего прототипа: - - - - -### Вызов метода родителя внутри своего - -Более частая ситуация -- когда мы хотим не просто заменить метод на свой, а взять метод родителя и расширить его. Скажем, кролик бежит так же, как и другие звери, но время от времени подпрыгивает. - -Для вызова метода родителя можно обратиться к нему напрямую, взяв из прототипа: - -```js -//+ run - Rabbit.prototype.run = function() { -*!* - // вызвать метод родителя, передав ему текущие аргументы - Animal.prototype.run.apply(this, arguments); -*/!* - this.jump(); - } -``` - -Обратите внимание на вызов через `apply` и явное указание контекста. - -Если вызвать просто `Animal.prototype.run()`, то в качестве `this` функция `run` получит `Animal.prototype`, а это неверно, нужен текущий объект. - - -## Итого - - - -Структура наследования полностью: - -```js -//+ run -*!* -// --------- Класс-Родитель ------------ -*/!* -// Конструктор родителя пишет свойства конкретного объекта -function Animal(name) { - this.name = name; - this.speed = 0; -} - -// Методы хранятся в прототипе -Animal.prototype.run = function() { - alert(this.name + " бежит!") -} - -*!* -// --------- Класс-потомок ----------- -*/!* -// Конструктор потомка -function Rabbit(name) { - Animal.apply(this, arguments); -} - -// Унаследовать -*!* -Rabbit.prototype = Object.create(Animal.prototype); -*/!* - -// Желательно и constructor сохранить -Rabbit.prototype.constructor = Rabbit; - -// Методы потомка -Rabbit.prototype.run = function() { - // Вызов метода родителя внутри своего - Animal.prototype.run.apply(this); - alert( this.name + " подпрыгивает!" ); -}; - -// Готово, можно создавать объекты -var rabbit = new Rabbit('Кроль'); -rabbit.run(); -``` - -Такое наследование лучше функционального стиля, так как не дублирует методы в каждом объекте. - -Кроме того, есть ещё неявное, но очень важное архитектурное отличие. - -Зачастую вызов конструктора имеет какие-то побочные эффекты, например влияет на документ. Если конструктор родителя имеет какое-то поведение, которое нужно переопределить в потомке, то в функциональном стиле это невозможно. - -Иначе говоря, в функциональном стиле в процессе создания `Rabbit` нужно обязательно вызывать `Animal.apply(this, arguments)`, чтобы получить методы родителя -- и если этот `Animal.apply` кроме добавления методов говорит: "Му-у-у!", то это проблема: - -```js -function Animal() { - this.walk = function() { - alert('walk') - }; - alert( 'Му-у-у!' ); -} - -function Rabbit() { - Animal.apply(this, arguments); // как избавиться от мычания, но получить walk? -} -``` - -...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе. - -Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса -- она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор [babeljs](https://babeljs.io/). - diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png deleted file mode 100644 index 5de99e36..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png deleted file mode 100644 index 31e5da8e..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-array-object@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png deleted file mode 100644 index d242a125..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png deleted file mode 100644 index 64906600..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-animal@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png deleted file mode 100644 index 3f479cec..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png b/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png deleted file mode 100644 index 1bd04c3a..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/class-inheritance-rabbit-run-animal@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/console_dir_array.png b/1-js/9-prototypes/5-class-inheritance/console_dir_array.png deleted file mode 100755 index a58db646..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/console_dir_array.png and /dev/null differ diff --git a/1-js/9-prototypes/5-class-inheritance/console_dir_array@2x.png b/1-js/9-prototypes/5-class-inheritance/console_dir_array@2x.png deleted file mode 100755 index cd8506f4..00000000 Binary files a/1-js/9-prototypes/5-class-inheritance/console_dir_array@2x.png and /dev/null differ diff --git a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md deleted file mode 100644 index 6bf55d43..00000000 --- a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/solution.md +++ /dev/null @@ -1,7 +0,0 @@ -Да, это выглядит достаточно странно, поскольку объект `a` не создавался функцией `B`. - -Но методу `instanceof` на самом деле вообще не важна функция. Он смотрит на её `prototype` и сверяет его с цепочкой `__proto__` объекта. - -В данном случае `a.__proto__ == B.prototype`, поэтому `instanceof` возвращает `true`. - -По логике `instanceof` именно прототип задаёт "тип объекта", поэтому `instanceof` работает именно так. \ No newline at end of file diff --git a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md b/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md deleted file mode 100644 index a3c73f74..00000000 --- a/1-js/9-prototypes/6-instanceof/1-strange-instanceof/task.md +++ /dev/null @@ -1,21 +0,0 @@ -# Странное поведение instanceof - -[importance 5] - -Почему `instanceof` в коде ниже возвращает `true`, ведь объект `a` явно создан не `B()`? - -```js -//+ run -function A() {} - -function B() {} - -A.prototype = B.prototype = {}; - -var a = new A(); - -*!* -alert( a instanceof B ); // true -*/!* -``` - diff --git a/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md b/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md deleted file mode 100644 index 646e3aa1..00000000 --- a/1-js/9-prototypes/6-instanceof/2-instanceof-result/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Да, распознает. - -Он проверяет наследование с учётом цепочки прототипов. - -```js -//+ run -function Animal() {} - -function Rabbit() {} -Rabbit.prototype = Object.create(Animal.prototype); - -var rabbit = new Rabbit(); - -alert( rabbit instanceof Rabbit ); // true -alert( rabbit instanceof Animal ); // true -alert( rabbit instanceof Object ); // true -``` - diff --git a/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md b/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md deleted file mode 100644 index 56c971e4..00000000 --- a/1-js/9-prototypes/6-instanceof/2-instanceof-result/task.md +++ /dev/null @@ -1,23 +0,0 @@ -# Что выведет instanceof? - -[importance 5] - -В коде ниже создаётся простейшая иерархия классов: `Animal -> Rabbit`. - -Что выведет [instanceof](/instanceof)? - -Распознает ли он `rabbit` как `Animal`, `Rabbit` и к тому же `Object`? - -```js -function Animal() {} - -function Rabbit() {} -Rabbit.prototype = Object.create(Animal.prototype); - -var rabbit = new Rabbit(); - -alert( rabbit instanceof Rabbit ); -alert( rabbit instanceof Animal ); -alert( rabbit instanceof Object ); -``` - diff --git a/1-js/9-prototypes/6-instanceof/article.md b/1-js/9-prototypes/6-instanceof/article.md deleted file mode 100644 index 174333cf..00000000 --- a/1-js/9-prototypes/6-instanceof/article.md +++ /dev/null @@ -1,91 +0,0 @@ -# Проверка класса: "instanceof" - -Оператор `instanceof` позволяет проверить, какому классу принадлежит объект, с учетом прототипного наследования. - -[cut] - -## Алгоритм работы instanceof [#ref-instanceof] - -Вызов `obj instanceof Constructor` возвращает `true`, если объект принадлежит классу `Constructor` или его родителям. - -Пример использования: - -```js -//+ run -function Rabbit() {} - -*!* -// создаём объект -*/!* -var rabbit = new Rabbit(); - -// проверяем -- этот объект создан Rabbit? -*!* -alert( rabbit instanceof Rabbit ); // true, верно -*/!* -``` - -Массив `arr` принадлежит классу `Array`, но также и является объектом `Object`. Это верно, так как массивы наследуют от объектов: - -```js -//+ run -var arr = []; -alert( arr instanceof Array ); // true -alert( arr instanceof Object ); // true -``` - -Как это часто бывает в JavaScript, здесь есть ряд тонкостей. В некоторых ситуациях, проверка может даже ошибаться! - -**Алгоритм проверки `obj instanceof Constructor`:** - -
      -
    1. Получить `obj.__proto__`
    2. -
    3. Сравнить `obj.__proto__` с `Constructor.prototype`
    4. -
    5. Если не совпадает, тогда заменить `obj` на `obj.__proto__` и повторить проверку на шаге 2 до тех пор, пока либо не найдется совпадение (результат `true`), либо цепочка прототипов не закончится (результат `false`).
    6. -
    - -В проверке `rabbit instanceof Rabbit` совпадение происходит на первом же шаге этого алгоритма, так как: `rabbit.__proto__ == Rabbit.prototype`. - -А если рассмотреть `arr instanceof Object`, то совпадение будет найдено на следующем шаге, так как `arr.__proto__.__proto__ == Object.prototype`. - -Забавно, что сама функция-констуктор не участвует в процессе проверки! Важна только цепочка прототипов для проверяемого объекта. - -Это может приводить к забавному результату и даже ошибкам в проверке при изменении `prototype`, например: - -```js -//+ run -// Создаём объект rabbit, как обычно -function Rabbit() {} -var rabbit = new Rabbit(); - -// изменили prototype... -Rabbit.prototype = {}; - -// ...instanceof перестал работать! -*!* -alert( rabbit instanceof Rabbit ); // false -*/!* -``` - -Стоит ли говорить, что это один из доводов для того, чтобы никогда не менять `prototype`? Так сказать, во избежание. - -[warn header="Не друзья: `instanceof` и фреймы"] - -Оператор `instanceof` не срабатывает, когда значение приходит из другого окна или фрейма. - -Например, массив, который создан в ифрейме и передан родительскому окну -- будет массивом *в том ифрейме*, но не в родительском окне. Проверка `instanceof Array` в родительском окне вернёт `false`. - -Вообще, у каждого окна и фрейма -- своя иерархия объектов и свой `window` . - -Как правило, эта проблема возникает со встроенными объектами, в этом случае используется проверка внутреннего свойства `[[Class]]`, которое подробнее описано в главе [](/class-instanceof). -[/warn] - - -## Итого - - - -Оператор `instanceof` особенно востребован в случаях, когда мы работаем с иерархиями классов. Это наилучший способ проверить принадлежность тому или иному классу с учётом наследования. diff --git a/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md b/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md deleted file mode 100644 index 1532bd3c..00000000 --- a/1-js/9-prototypes/7-oop-errors/1-format-error/solution.md +++ /dev/null @@ -1,28 +0,0 @@ -```js -//+ run -function FormatError(message) { - this.name = "FormatError"; - - this.message = message; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } else { - this.stack = (new Error()).stack; - } - -} - -FormatError.prototype = Object.create(SyntaxError.prototype); -FormatError.prototype.constructor = FormatError; - -// Использование - -var err = new FormatError("ошибка форматирования"); - -alert( err.message ); // ошибка форматирования -alert( err.name ); // FormatError -alert( err.stack ); // стек на момент генерации ошибки - -alert( err instanceof SyntaxError ); // true -``` \ No newline at end of file diff --git a/1-js/9-prototypes/7-oop-errors/1-format-error/task.md b/1-js/9-prototypes/7-oop-errors/1-format-error/task.md deleted file mode 100644 index bd78ad35..00000000 --- a/1-js/9-prototypes/7-oop-errors/1-format-error/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Унаследуйте от SyntaxError - -[importance 5] - -Создайте ошибку `FormatError`, которая будет наследовать от встроенного класса `SyntaxError`. - -Синтаксис для её создания -- такой же, как обычно: - -```js -var err = new FormatError("ошибка форматирования"); - -alert( err.message ); // ошибка форматирования -alert( err.name ); // FormatError -alert( err.stack ); // стек на момент генерации ошибки - -alert( err instanceof SyntaxError ); // true -``` diff --git a/1-js/9-prototypes/7-oop-errors/article.md b/1-js/9-prototypes/7-oop-errors/article.md deleted file mode 100644 index 13fd9715..00000000 --- a/1-js/9-prototypes/7-oop-errors/article.md +++ /dev/null @@ -1,285 +0,0 @@ -# Свои ошибки, наследование от Error - -Когда мы работаем с внешними данными, возможны самые разные ошибки. - -Если приложение сложное, то ошибки естественным образом укладываются в иерархию, разобраться в которой помогает `instanceof`. - -## Свой объект ошибки - -Для примера создадим функцию `readUser(json)`, которая будет разбирать JSON с данными посетителя. Мы его получаем с сервера -- может, нашего, а может -- чужого, в общем -- желательно проверить на ошибки. А может, это даже и не JSON, а какие-то другие данные -- не важно, для наглядности поработаем с JSON. - -Пример `json` на входе в функцию: `{ "name": "Вася", "age": 30 }`. - -В процессе работы `readUser` возможны различные ошибки. Одна -- очевидно, `SyntaxError` -- если передан некорректный JSON. - -Но могут быть и другие, например `PropertyError` -- эта ошибка будет возникать, если в прочитанном объекте нет свойства `name` или `age`. - -Реализуем класс `PropertyError`: - -```js -function PropertyError(property) { - Error.call(this, property) ; - this.name = "PropertyError"; - - this.property = property; - this.message = "Ошибка в свойстве " + property; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, PropertyError); - } else { - this.stack = (new Error()).stack; - } - -} - -PropertyError.prototype = Object.create(Error.prototype); -``` - -В этом коде вы можете видеть ряд важных деталей, важных именно для ошибок: - -
    -
    `name` -- имя ошибки.
    -
    Должно совпадать с именем функции.
    -
    `message` -- сообщение об ошибке.
    -
    Несмотря на то, что `PropertyError` наследует от `Error` (последняя строка), конструктор у неё немного другой. Он принимает не сообщение об ошибке, а название свойства `property`, ну а сообщение генерируется из него. - -В результате в объекте ошибки есть как стандартное свойство `message`, так и более точное `property`. - -Это частая практика -- добавлять в объект ошибки свойства, которых нет в базовых объектах `Error`, более подробно описывающие ситуацию для данного класса ошибок.
    -
    `stack` -- стек вызовов, которые в итоге привели к ошибке.
    -
    У встроенных объектов `Error` это свойство есть автоматически, вот к примеру: -```js -//+ run -function f() { - alert( new Error().stack ); -} - -f(); // выведет список вложенных вызовов, с номерами строк, где они были сделаны -``` - -Если же объект ошибки делаем мы, то "по умолчанию" у него такого свойства у него не будет. Нам нужно как-то самим узнавать последовательность вложенных вызовов на текущий момент. Однако удобного способа сделать это в JavaScript нет, поэтому мы поступаем хитро и копируем его из нового объекта `new Error`, который генерируем тут же. - -В V8 (Chrome, Opera, Node.JS) есть нестандартное расширение [Error.captureStackTrace](https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi), которое позволяет стек получать. - -Это делает строка из кода выше: -```js -Error.captureStackTrace(this, PropertyError); -``` - -Такой вызов записывает в объект `this` (текущий объект ошибки) стек вызовов, ну а второй аргумент -- вообще не обязателен, но если есть, то говорит, что при генерации стека нужно на этой функции остановиться. В результате в стеке будет информация о цепочке вложенных вызовов вплоть до вызова `PropertyError`. - -То есть, будет последовательность вызовов до генерации ошибки, но не включая код самого конструктора ошибки, который, как правило, не интересен. Такое поведение максимально соответствует встроенным ошибкам JavaScript. -
    -
    - -[smart header="Конструктор родителя здесь не обязателен"] -Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядит как `Error.call(this, message)`. - -Строго говоря, этот вызов здесь не обязателен. Встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Единственный возможный смысл его вызова -- он ставит специальное внутреннее свойство `[[ErrorData]]`, которое выводится в `toString` и позволяет увидить, что это ошибка. Поэтому по стандарту вызывать конструктор `Error` при наследовании в таких случаях рекомендовано. -[/smart] - - -## instanceof + try..catch = ♡ - -Давайте теперь используем наш новый класс для `readUser`: - -```js -//+ run -*!* -// Объявление -*/!* -function PropertyError(property) { - this.name = "PropertyError"; - - this.property = property; - this.message = "Ошибка в свойстве " + property; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, PropertyError); - } else { - this.stack = (new Error()).stack; - } - -} - -PropertyError.prototype = Object.create(Error.prototype); - -*!* -// Генерация ошибки -*/!* -function readUser(data) { - - var user = JSON.parse(data); - - if (!user.age) { - throw new PropertyError("age"); - } - - if (!user.name) { - throw new PropertyError("name"); - } - - return user; -} - -*!* -// Запуск и try..catch -*/!* - -try { - var user = readUser('{ "age": 25 }'); -} catch (err) { - if (err instanceof PropertyError) { - if (err.property == 'name') { - // если в данном месте кода возможны анонимы, то всё нормально -*!* - alert( "Здравствуйте, Аноним!" ); -*/!* - } else { - alert( err.message ); // Ошибка в свойстве ... - } - } else if (err instanceof SyntaxError) { - alert( "Ошибка в синтаксисе данных: " + err.message ); - } else { - throw err; // неизвестная ошибка, не знаю что с ней делать - } -} -``` - -Всё работает -- и наша ошибка `PropertyError` и встроенная `SyntaxError` корректно генерируются, перехватываются, обрабатываются. - -Обратим внимание на проверку типа ошибки в `try..catch`. Оператор `instanceof` проверяет класс с учётом наследования. Это значит, что если мы в дальнейшем решим создать новый тип ошибки, наследующий от `PropertyError`, то проверка `err instanceof PropertyError` для класса-наследника тоже будет работать. Код получился расширяемым, это очень важно. - -## Дальнейшее наследование - -`PropertyError` -- это просто общего вида ошибка в свойстве. Создадим ошибку `PropertyRequiredError`, которая означает, что свойства нет. - -Эт подвид `PropertyError`, так что унаследуем он неё. Общий вид конструктора-наследника -- стандартный: - -```js -function PropertyRequiredError(property) { - // вызываем конструктор родителя и передаём текущие аргументы - PropertyError.apply(this, arguments); - ... -} -``` - -Достаточно ли в наследнике просто вызвать конструктор родителя? Увы, нет. - -Если так поступить, то свойство `this.name` будет некорректным, да и `Error.captureStackTrace` тоже получит неправильную функцию вторым параметром. - -Можно ли как-то поправить конструктор родителя, чтобы от него было проще наследовать? - -Для этого нужно убрать из него упоминания о конкретном классе `PropertyError`, чтобы сделать код универсальным. Частично -- это возможно. Как мы помним, существует свойство `constructor`, которое есть в `prototype` по умолчанию, и которое мы можем намеренно сохранить при наследовании. - -Исправим родителя `PropertyError` для более удобного наследования от него: - -```js -function PropertyError(property) { - this.name = "PropertyError"; - - this.property = property; - this.message = "Ошибка в свойстве " + property; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, *!*this.constructor*/!*); // (*) - } else { - this.stack = (new Error()).stack; - } - -} - -PropertyError.prototype = Object.create(Error.prototype); -*!* -PropertyError.prototype.constructor = PropertyError; -*/!* -``` - -В строке `(*)` вместо ссылки на `PropertyError` используем `constructor` чтобы получить именно конструктор для текущего объекта. В наследнике там будет `PropertyRequiredError`, как и задумано. - -Мы убрали одну жёсткую привязку к `PropertyError`, но со второй (`this.name`), увы, сложности. Оно должно содержать имя ошибки, то есть, имя её функции-конструктора. Его можно получить через `this.name = this.constructor.name`, но в IE11- это работать не будет. - -Если подерживать IE11-, то тут уж придётся в наследнике его записывать вручную. - -Полный код для наследника: - -```js -function PropertyRequiredError(property) { - PropertyError.apply(this, arguments); - this.name = 'PropertyRequiredError'; - this.message = 'Отсутствует свойство ' + property; -} - -PropertyRequiredError.prototype = Object.create(PropertyError.prototype); -PropertyRequiredError.prototype.constructor = PropertyRequiredError; - -var err = new PropertyRequiredError("age"); -// пройдёт проверку -alert( err instanceof PropertyError ); // true -``` - -Здесь заодно и `message` в наследнике было перезаписано на более точное. Если хочется избежать записи и перезаписи, то можно оформить его в виде геттера через `Object.defineProperty`. - -## Итого - - - -Чтобы создавать наследники от `Error` было проще, можно создать класс `CustomError`, записать в него универсальный код, наподобие `PropertyError` и далее наследовать уже от него: - -```js -*!* -// общего вида "наша" ошибка -*/!* -function CustomError(message) { - this.name = "CustomError"; - this.message = message; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } else { - this.stack = (new Error()).stack; - } - -} - -CustomError.prototype = Object.create(Error.prototype); -CustomError.prototype.constructor = CustomError; - -*!* -// наследник -*/!* -function PropertyError(property) { - CustomError.call(this, "Отсутствует свойство " + property) - this.name = "PropertyError"; - - this.property = property; -} - -PropertyError.prototype = Object.create(CustomError.prototype); -PropertyError.prototype.constructor = PropertyError; - -*!* -// и ещё уровень -*/!* -function PropertyRequiredError(property) { - PropertyError.call(this, property); - this.name = 'PropertyRequiredError'; - this.message = 'Отсутствует свойство ' + property; -} - -PropertyRequiredError.prototype = Object.create(PropertyError.prototype); -PropertyRequiredError.prototype.constructor = PropertyRequiredError; - -*!* -// использование -*/!* -var err = new PropertyRequiredError("age"); -// пройдёт проверку -alert( err instanceof PropertyRequiredError ); // true -alert( err instanceof PropertyError ); // true -alert( err isntanceof CustomError ); // true -alert( err isntanceof Error ); // true -``` diff --git a/1-js/9-prototypes/8-mixins/article.md b/1-js/9-prototypes/8-mixins/article.md deleted file mode 100644 index a87d1bf3..00000000 --- a/1-js/9-prototypes/8-mixins/article.md +++ /dev/null @@ -1,170 +0,0 @@ -# Примеси - -В JavaScript невозможно унаследовать от двух и более объектов. Ссылка `__proto__` -- только одна. - -Но потребность такая существует -- к примеру, мы написали код, релизующий методы работы с шаблонизатором или методы по обмену событиями, и хочется легко и непринуждённо добавлять эти возможности к любому классу. - -Обычно это делают через примеси. - -Примесь (англ. mixin) -- класс или объект, реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для самостоятельного использования. - - - -## Пример примеси - -Самый простой вариант примеси -- это объект с полезными методами, которые мы просто копируем в нужный прототип. - -Например: - -```js -//+ run -*!* -// примесь -*/!* -var sayHiMixin = { - sayHi: function() { - alert("Привет " + this.name); - }, - sayBye: function() { - alert("Пока " + this.name); - } -}; - -*!* -// использование: -*/!* -function User(name) { - this.name = name; -} - -// передать методы примеси -for(var key in sayHiMixin) User.prototype[key] = sayHiMixin[key]; - -// User "умеет" sayHi -new User("Вася").sayHi(); // Привет Вася -``` - -Как видно из примера, методы примеси активно используют `this` и предназначены именно для запуска в контексте "объекта-носителя примеси". - -Если какие-то из методов примеси не нужны -- их можно перезаписать своими после копирования. - - -## Примесь для событий - -Теперь пример из реальной жизни. - -Важный аспект, который может понадобиться объектам -- это умение работать с событиями. - -То есть, чтобы объект мог специальным вызовом генерировать "уведомление о событии", а на эти уведомления другие объекты могли "подписываться", чтобы их получать. - -Например, объект "Пользователь" при входе на сайт может генерировать событие `"login"`, а другие объекты, например "Календарь" может такие уведомления получать и подгружать информацию о пользователе. - -Или объект "Меню" может при выборе пункта меню генерировать событие `"select"` с информацией о выбранном пункте меню, а другие объекты -- подписавшись на это событие, будут узнавать об этом. - -События -- это средство "поделиться информацией" с неопределённым кругом заинтересованных лиц. А там уже кому надо -- тот среагирует. - -Примесь `eventMixin`, реализующая события: - -```js -var eventMixin = { - - /** - * Подписка на событие - * Использование: - * menu.on('select', function(item) { ... } - */ - on: function(eventName, handler) { - if (!this._eventHandlers) this._eventHandlers = {}; - if (!this._eventHandlers[eventName]) { - this._eventHandlers[eventName] = []; - } - this._eventHandlers[eventName].push(handler); - }, - - /** - * Прекращение подписки - * menu.off('select', handler) - */ - off: function(eventName, handler) { - var handlers = this._eventHandlers && this._eventHandlers[eventName]; - if (!handlers) return; - for(var i=0; i -
  • `.on(имя события, функция)` -- назначает функцию к выполнению при наступлении события с данным именем. Такие функции хранятся в защищённом свойстве объекта `_eventHandlers`.
  • -
  • `.off(имя события, функция)` -- удаляет функцию из списка предназначенных к выполнению.
  • -
  • `.trigger(имя события, аргументы)` -- генерирует событие, при этом вызываются все назначенные на него функции, и им передаются аргументы.
  • - - -Использование: - -```js -// Класс Menu с примесью eventMixin -function Menu() { - // ... -} - -for(var key in eventMixin) { - Menu.prototype[key] = eventMixin[key]; -} - -// Генерирует событие select при выборе значения -Menu.prototype.choose = function(value) { -*!* - this.trigger("select", value); -*/!* -} - -// Создадим меню -var menu = new Menu(); - -// При наступлении события select вызвать эту функцию -*!* -menu.on("select", function(value) { - alert("Выбрано значение " + value); -}); -*/!* - -// Запускаем выбор (событие select вызовет обработчики) -menu.choose("123"); -``` - -...То есть, смысл событий -- обычно в том, что объект, в процессе своей деятельности, внутри себя (`this.trigger`) генерирует уведомления, на которые внешний код через `menu.on(...)` может быть подписан. И узнавать из них ценную информцию о происходящем, например -- что выбран некий пункт меню. - -Один раз написав методы `on/off/trigger` в примеси, мы затем можем использовать их во множестве прототипов. - -## Итого - - - - diff --git a/1-js/9-prototypes/index.md b/1-js/9-prototypes/index.md deleted file mode 100644 index 3b67318e..00000000 --- a/1-js/9-prototypes/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# ООП в прототипном стиле - -В этом разделе мы изучим прототипы и классы на них -- де-факто стандарт объектно-ориентированной разработки в JavaScript. \ No newline at end of file diff --git a/10-regular-expressions-javascript/1-regexp-introduction/article.md b/10-regular-expressions-javascript/1-regexp-introduction/article.md deleted file mode 100644 index 3fb0195a..00000000 --- a/10-regular-expressions-javascript/1-regexp-introduction/article.md +++ /dev/null @@ -1,104 +0,0 @@ -# Паттерны и флаги - -Регулярные выражения –- мощное средство поиска и замены в строке. - -В JavaScript регулярные выражения реализованы отдельным объектом `RegExp` и интегрированы в методы строк. -[cut] - -## Регэкспы - -Регулярное выражение (оно же "регэксп", "регулярка" или просто "рег"), состоит из *паттерна* (он же "шаблон") и необязательных *флагов*. - -Синтаксис создания регулярного выражения: - -```js -var regexp = new RegExp("шаблон", "флаги"); -``` - -Как правило, используют более короткую запись: шаблон внутри слешей `"/"`: - -```js -var regexp = /шаблон/; // без флагов -var regexp = /шаблон/gmi; // с флагами gmi (изучим их дальше) -``` - -Слэши `"/"` говорят JavaScript о том, что это регулярное выражение. Они играют здесь ту же роль, что и кавычки для обозначения строк. - -## Использование - -Основа регулярного выражения -- паттерн. Это строка, которую можно расширить специальными символами, которые делают поиск намного мощнее. - -В простейшем случае, если флагов и специальных символов нет, поиск по паттерну -- то же самое, что и обычный поиск подстроки: - -```js -//+ run -var str = "Я люблю JavaScript!"; // будем искать в этой строке - -var regexp = /лю/; -alert( str.search(regexp) ); // 2 -``` - -Сравните с обычным поиском: - -```js -//+ run -var str = "Я люблю JavaScript!"; - -var substr = "лю"; -alert( str.indexOf(substr) ); // 2 -``` - -Как видим, то же самое, разве что для регэкспа использован метод [search](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/search) -- он как раз работает с регулярными выражениями, а для подстроки -- [indexOf](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf). - -Но это соответствие лишь кажущееся. Очень скоро мы усложним регулярные выражения, и тогда увидим, что они гораздо мощнее. - -[smart header="Цветовые обозначения"] -Здесь и далее в тексте используется следующая цветовая схема: - -[/smart] - -## Флаги - -Регулярные выражения могут иметь флаги, которые влияют на поиск. - -В JavaScript их всего три: - -
    -
    `i`
    -
    Если этот флаг есть, то регэксп ищет независимо от регистра, то есть не различает между `А` и `а`.
    -
    `g`
    -
    Если этот флаг есть, то регэксп ищет все совпадения, иначе -- только первое.
    -
    `m`
    -
    Многострочный режим.
    -
    - -Самый простой для понимания из этих флагов -- безусловно, `i`. - -Пример его использования: - -```js -//+ run -var str = "Я люблю JavaScript!"; // будем искать в этой строке - -alert( str.search( *!*/ЛЮ/*/!* ) ); // -1 -alert( str.search( *!*/ЛЮ/i*/!* ) ); // 2 -``` - -
      -
    1. С регом /ЛЮ/ вызов вернул `-1`, что означает "не найдено" (как и в `indexOf`),
    2. -
    3. С регом /ЛЮ/i вызов нашёл совпадение на позиции 2, так как стоит флаг `i`, а значит "лю" тоже подходит.
    4. -
    - -Другие флаги мы рассмотрим в последующих главах. - -## Итого - - diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md b/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md deleted file mode 100644 index e5118b47..00000000 --- a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/solution.md +++ /dev/null @@ -1,6 +0,0 @@ - -Нам нужна строка, которая начинается -- и тут же кончается. То есть, пустая. - -Или, если быть ближе к механике регэкспов, то движок сначала будет искать в тексте начальную позицию ``pattern`^`, а как только найдёт её -- будет ожидать конечной ``pattern`$`. - -Заметим, что и ``pattern`^` и ``pattern`$` не требуют наличия символов. Это -- проверки. В пустой строке движок сначала проверит первую, а потом -- вторую -- и зафиксирует совпадение. \ No newline at end of file diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md b/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md deleted file mode 100644 index f5df587e..00000000 --- a/10-regular-expressions-javascript/10-regexp-ahchors/1-start-end/task.md +++ /dev/null @@ -1,4 +0,0 @@ -# Регэксп ^$ - -Предложите строку, которая подойдёт под регулярное выражение ``pattern`^$`. - diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md b/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md deleted file mode 100644 index 5a91a896..00000000 --- a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/solution.md +++ /dev/null @@ -1,21 +0,0 @@ -Двузначное шестнадцатиричное число -- это ``pattern`[0-9a-f]{2}` (с учётом флага ``pattern`/i`). - -Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: ``pattern`[0-9a-f]{2}(:[0-9a-f]{2}){5}` - -И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в ``pattern`^...$`. - -Итого, в действии: - - -```js -//+ run -var re = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i; - -alert( re.test('01:32:54:67:89:AB') ); // true - -alert( re.test('0132546789AB') ); // false (нет двоеточий) - -alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6) - -alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце) -``` diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md b/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md deleted file mode 100644 index fecf7dd0..00000000 --- a/10-regular-expressions-javascript/10-regexp-ahchors/2-test-mac/task.md +++ /dev/null @@ -1,20 +0,0 @@ -# Проверьте MAC-адрес - -MAC-адрес сетевого интерфейса состоит из шести двузначиных шестандцатиричных чисел, разделённых двоеточием. - -Например: ``subject`'01:32:54:67:89:AB'`. - -Напишите регулярное выражение, которое по строке проверяет, является ли она корректным MAC-адресом. - -Использование: -```js -var re = ваш регэксп - -alert( re.test('01:32:54:67:89:AB') ); // true - -alert( re.test('0132546789AB') ); // false (нет двоеточий) - -alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6) - -alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце) -``` diff --git a/10-regular-expressions-javascript/10-regexp-ahchors/article.md b/10-regular-expressions-javascript/10-regexp-ahchors/article.md deleted file mode 100644 index 16abf01c..00000000 --- a/10-regular-expressions-javascript/10-regexp-ahchors/article.md +++ /dev/null @@ -1,68 +0,0 @@ -# Начало строки ^ и конец $ - -Знак каретки '^' и доллара '$' имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.). -[cut] - -Каретка ^ совпадает в начале текста, а доллар $ -- в конце. - -**Якоря являются не символами, а проверками.** - -До этого мы говорили о регулярных выражениях, которые ищут один или несколько символов. Если совпадение есть -- эти символы включаются в результат. - -А якоря -- не такие. Когда поиск ходит до якоря -- он проверяет, есть ли соответствие, если есть -- продолжает идти по шаблону, не прибавляя ничего к результату. - -Каретку ^ обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста. - -Например, без каретки найдёт все числа: - -```js -//+ run -var str = '100500 попугаев съели 500100 бананов!'; -alert( str.match(/\d+/ig) ); // 100500, 500100 (нашло все числа) -``` - -А с кареткой -- только первое: - -```js -//+ run -var str = '100500 попугаев съели 500100 бананов!'; -alert( str.match(/^\d+/ig) ); // 100500 (только в начале строки)*!* -``` - -Знак доллара $ используют, чтобы указать, что паттерн должен заканчиваться в конце текста. - -Аналогичный пример с долларом для поиска числа в конце: - -```js -//+ run -var str = '100500 попугаев съели 500100'; -alert( str.match(/\d+$/ig) ); // 500100 -``` - -Оба якоря используют одновременно, если требуется, чтобы шаблон охватывал текст с начала и до конца. Обычно это требуется при валидации. - -Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь. - -Ей соответствует регэксп \d+\.\d+. Но простой поиск найдёт дробь в любом тексте: - -```js -//+ run -var num = "ля-ля 12.34"; -alert( num.match(/\d+\.\d+/ig) ); // 12.34 -``` - -Наша же задача -- проверить, что `num` *целиком* соответствует паттерну \d+\.\d+. - -Для этого обернём шаблон в якоря ^...$: - -```js -//+ run -var num = "ля-ля 12.34"; -alert( num.match(/^\d+\.\d+$/ig) ); // null, не дробь - -var num = "12.34"; -alert( num.match(/^\d+\.\d+$/ig) ); // 12.34, дробь! -``` - -Теперь поиск ищет начало текста, за которым идёт число, затем точка, ещё число и конец текста. Это как раз то, что нужно. - diff --git a/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md b/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md deleted file mode 100644 index 918a85eb..00000000 --- a/10-regular-expressions-javascript/11-regexp-multiline-mode/article.md +++ /dev/null @@ -1,89 +0,0 @@ -# Многострочный режим, флаг "m" - -Многострочный режим включается, если у регэкспа есть флаг /m. -[cut] - -В этом случае изменяется поведение ^ и $. - -В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки. - -## Начало строки ^ - -В примере ниже текст состоит из нескольких строк. Паттерн /^\d+/gm берёт число с начала каждой строки: - -```js -//+ run -var str = '1е место: Винни\n' + - '2е место: Пятачок\n' + - '33е место: Слонопотам'; - -*!* -alert( str.match(/^\d+/gm) ); // 1, 2, 33 -*/!* -``` - -Обратим внимание -- без флага /m было бы найдено только первое число: - -```js -//+ run -var str = '1е место: Винни\n' + - '2е место: Пятачок\n' + - '33е место: Слонопотам'; - -alert( str.match(/^\d+/g) ); // 1 -``` - -Это потому что в обычном режиме каретка ^ -- это только начало текста, а в многострочном -- начало любой строки. - -Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там \d+. - -## Конец строки $ - -Символ доллара $ ведёт себя аналогично. - -Регулярное выражение [а-я]+$ в следующем примере находит последнее слово в каждой строке: - -```js -//+ run -var str = '1е место: Винни\n' + - '2е место: Пятачок\n' + - '33е место: Слонопотам'; - -alert( str.match(/[а-я]+$/gim) ); // Винни,Пятачок,Слонопотам -``` - -Без флага m якорь $ обозначал бы конец всего текста, и было бы найдено только последнее слово. - -[smart header="Якорь `$` против `\n`"] -Для того, чтобы найти конец строки, можно использовать не только `$`, но и символ `\n`. - -Но, в отличие от `$`, символ `\n` во-первых берёт символ в результат, а во-вторых -- не совпадает в конце текста (если, конечно, последний символ -- не конец строки). - -Посмотрим, что будет с примером выше, если вместо [а-я]+$ использовать [а-я]+\n: - -```js -//+ run -var str = '1е место: Винни\n' + - '2е место: Пятачок\n' + - '33е место: Слонопотам'; - -alert( str.match(/[а-я]+\n/gim) ); -/* -Винни -,Пятачок -*/ -``` - -Всего два результата: Винни\n (с символом перевода строки) и Пятачок\n. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки. -[/smart] - -## Итого - -В мультистрочном режиме: - - -Оба символа являются проверками, они не добавляют ничего к результату. Про них также говорят, что "они имеют нулевую длину". - diff --git a/10-regular-expressions-javascript/12-regexp-lookahead/article.md b/10-regular-expressions-javascript/12-regexp-lookahead/article.md deleted file mode 100644 index 2cd4438c..00000000 --- a/10-regular-expressions-javascript/12-regexp-lookahead/article.md +++ /dev/null @@ -1,4 +0,0 @@ -# Предпросмотр (неготово) - -Требуется добавить главу про предпросмотр lookahead. - diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md deleted file mode 100644 index 582c1066..00000000 --- a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/article.md +++ /dev/null @@ -1,305 +0,0 @@ -# Чёрная дыра бэктрекинга - -Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript. - -Рано или поздно, с этим сталкивается любой разработчик, потому что нечаянно создать такое регулярное выражение -- легче лёгкого. - -Типична ситуация, когда регулярное выражение до поры до времени работает нормально, и вдруг на каком-то тексте как начнёт "подвешивать" интерпретатор и есть 100% процессора. - -Это может стать уязвимостью. Например, если JavaScript выполняется на сервере, то при разборе данных, присланных посетителем, он может зависнуть, если использует подобный регэксп. На клиенте тоже возможно подобное, при использовании регэкспа для подсветки синтаксиса. - -Такие уязвимости "убивали" почтовые сервера и системы обмена сообщениями и до появления JavaScript, и наверно будут "убивать" и после его исчезновения. Так что мы просто обязаны с ними разобраться. - -[cut] - -## Пример - -План изложения у нас будет таким: - -
      -
    1. Сначала посмотрим на проблему в реальной ситуации.
    2. -
    3. Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся.
    4. -
    - -Рассмотрим, например, поиск по HTML. - -Мы хотим найти теги с атрибутами, то есть совпадения вида <a href="..." class=doc ...>. - -Самый простой способ это сделать -- <[^>]*>. Но он же и не совсем корректный, так как тег может выглядеть так: <a test="<>" href="#">. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт <a test="<>. - -Соответствие: -``` -<[^>]*....> - -``` - -А нам нужен весь тег. - -Для того, чтобы правильно обрабатывать такие ситуации, нужно учесть их в регулярном выражении. Оно будет иметь вид <тег (ключ=значение)*>. - -Если перевести на язык регэкспов, то: <\w+(\s*\w+=(\w+|"[^"]*")\s*)*>: -
      -
    1. <\w+ -- начало тега
    2. -
    3. (\s*\w+=(\w+|"[^"]*")\s*)* -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом \w+, либо строкой в кавычках "[^"]*".
    4. -
    - - -Мы пока не учитываем все детали грамматики HTML, ведь строки возможны и в 'одинарных' кавычках, но на данный момент этого достаточно. Главное, что регулярное выражение получилось в меру простым и понятным. - - -Испытаем полученный регэксп в действии: - -```js -//+ run -var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g; - -var str='...
    ... ...'; - -alert( str.match(reg) ); // , -``` - -Отлично, всё работает! Нашло как длинный тег <a test="<>" href="#">, так и одинокий <b>. - -А теперь -- демонстрация проблемы. - -Если запустить пример ниже, то он может подвесить браузер: - -```js -//+ run -var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g; - -var str = "/g; - -var str = "(\d+)*$. - -В большинстве движков регэкспов, например в Chrome или IE, этот поиск выполняется очень долго (осторожно, может "подвесить" браузер): - -```js -//+ run -alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) ); -``` - -В чём же дело, что не так с регэкспом? - -Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор * здесь выглядит лишним. - -Если хочется найти число, то с тем же успехом можно искать \d+$. - -Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова. - -В целом, с регэкспом "всё так", синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему. - -Посмотрим, что происходит при поиске в строке 123456789z: - -
      -
    1. Первым делом, движок регэкспов пытается найти \d+. Плюс + является жадным по умолчанию, так что он хватает все цифры, какие может: - -``` -\d+....... -(123456789)z -``` -
    2. -
    3. Затем движок пытается применить звёздочку вокруг скобок (\d+)*, но больше цифр нет, так что звёздочка не даёт повторений. - -Затем в шаблоне идёт символ конца строки $, а в тексте -- символ z. - -``` - X -\d+........$ -(123456789)z -``` -Соответствия нет. -
    4. -
    5. Так как соответствие не найдено, то "жадный" плюс + отступает на один символ (бэктрекинг). - -Теперь `\d+` -- это все цифры, за исключением последней: -``` -\d+....... -(12345678)9z -``` -
    6. -
    7. После бэктрекинга, \d+ содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`). - -Звёздочка (\d+)* теперь может быть применена -- она даёт число 9: - -``` - -\d+.......\d+ -(12345678)(9)z -``` -Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`: - -``` - X -\d+.......\d+ -(12345678)(9)z -``` - -Так как совпадения нет, то поисковой движок отступает назад ещё раз. -
    8. -
    9. Теперь первое число \d+ будет содержать 7 цифр, а остаток строки 89 становится вторым \d+: - - -``` - X -\d+......\d+ -(1234567)(89)z -``` - -Увы, всё ещё нет соответствия для $. - -Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй \d+, до одного символа 8, и звёздочка забирает следующий 9. - - -``` - X -\d+......\d+\d+ -(1234567)(8)(9)z -``` -
    10. -
    11. ...И снова неудача. Второе и третье \d+ отступили по-максимуму, так что сокращается снова первое число, до 123456, а звёздочка берёт оставшееся: - -``` - X -\d+.......\d+ -(123456)(789)z -``` - -Снова нет совпадения. Процесс повторяется, последний жадный квантификатор + отпускает один символ (`9`): - -``` - X -\d+.....\d+ \d+ -(123456)(78)(9)z -``` -
    12. -
    13. -...И так далее. -
    14. -
    - -Получается, что движок регулярных выражений перебирает все комбинации из `123456789` и их подпоследовательности. А таких комбинаций очень много. - -На этом месте умный читатель может воскликнуть: "Во всём виноват бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!" - -Что ж, заменим \d+ на \d+? и посмотрим (аккуратно, может подвесить браузер): - -```js -//+ run -alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) ); -``` - -Не помогло! - -**Ленивые регулярные выражения делают то же самое, но в обратном порядке.** - -Просто подумайте о том, как будет в этом случае работать поисковой движок. - -Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда. - -Возвращаясь к примеру выше -- при поиске <(\s*\w+=\w+\s*)*> в строке <a=b a=b a=b a=b происходит то же самое. - -Поиск успешно начинается, выбирается некая комбинация из \s*\w+=\w+\s*, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее. - -## Что делать? - -Проблема -- в сверхмноговариантном переборе. - -Движок регулярных выражений перебирает кучу возможных вариантов скобок там, где это не нужно. - -Например, в регэкспе (\d+)*$ нам (людям) очевидно, что в (\d+) откатываться не нужно. От того, что вместо одного \d+ у нас два независимых \d+\d+, ничего не изменится. - -Без разницы: - -``` -\d+........ -(123456789)z - -\d+...\d+.... -(1234)(56789)z -``` - -Если вернуться к более реальному примеру <(\s*\w+=\w+\s*)*> то -cам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится). - -Никакого "отката" здесь не нужно. - -В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг. - -То есть, они даже проще, чем "жадные" -- берут максимальное количество символов и всё. Поиск продолжается дальше. При несовпадении никакого возврата не происходит. - -Это, c стороны уменьшает количество возможных результатов, но с другой стороны -- в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше. - -Есть и другое средство -- "атомарные скобочные группы", которые запрещают перебор внутри скобок, по сути позволяя добиваться того же, что и сверхжадные квантификаторы, - -К сожалению, в JavaScript они не поддерживаются. - -Однако, можно получить подобный эффект при помощи предпросмотра. Подробное описание соответствия с учётом синтаксиса сверхжадных квантификаторов и атомарных групп есть в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups), здесь же мы останемся в рамках синтаксиса JavaScript. - -Взятие максимального количества повторений `a+` без отката выглядит так: (?=(a+))\1. - -То есть, иными словами, предпросмотр ?= ищет максимальное количество повторений a+, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой \1. Дальнейший поиск -- после найденных повторений. - -Откат в этой логике принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук a+, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна. - -Исправим регэксп для поиска тега с атрибутами <\w+(\s*\w+=(\w+|"[^"]*")\s*)*>, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`: - -```js -//+ run -// регэксп для пары атрибут=значение -var attr = /(\s*\w+=(\w+|"[^"]*")\s*)/ - -// используем его внутри регэкспа для тега -var reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g'); - -var good = '...
    ... ...'; - -var bad = ", -alert( bad.match(reg) ); // null (нет результатов, быстро) -``` - -Отлично, всё работает! Нашло как длинный тег <a test="<>" href="#">, так и одинокий <b>. - - - - - - - diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png deleted file mode 100644 index 8f207c4d..00000000 Binary files a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png and /dev/null differ diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png deleted file mode 100644 index 713532ae..00000000 Binary files a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png and /dev/null differ diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png deleted file mode 100644 index 0de64148..00000000 Binary files a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png and /dev/null differ diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png deleted file mode 100644 index 8d0cd522..00000000 Binary files a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png and /dev/null differ diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png deleted file mode 100644 index c693a42e..00000000 Binary files a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png and /dev/null differ diff --git a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png b/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png deleted file mode 100644 index 63f0e9e4..00000000 Binary files a/10-regular-expressions-javascript/13-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png and /dev/null differ diff --git a/10-regular-expressions-javascript/2-regexp-methods/article.md b/10-regular-expressions-javascript/2-regexp-methods/article.md deleted file mode 100644 index fdf66de8..00000000 --- a/10-regular-expressions-javascript/2-regexp-methods/article.md +++ /dev/null @@ -1,392 +0,0 @@ -# Методы RegExp и String - -Регулярные выражения в JavaScript являются объектами класса [RegExp](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp). - -Кроме того, методы для поиска по регулярным выражениям встроены прямо в обычные строки `String`. - -К сожалению, общая структура встроенных методов слегка запутана, поэтому мы сначала рассмотрим их по отдельности, а затем -- рецепты по решению стандартных задач с ними. - -[cut] - -## str.search(reg) - -Этот метод мы уже видели. - -Он возвращает позицию первого совпадения или `-1`, если ничего не найдено. - -```js -//+ run -var str = "Люблю регэкспы я, но странною любовью"; - -alert( str.search( *!*/лю/i*/!* ) ); // 0 -``` - -**Ограничение метода `search` -- он всегда ищет только первое совпадение.** - -Нельзя заставить `search` искать дальше первого совпадения, такой синтаксис попросту не предусмотрен. Но есть другие методы, которые это умеют. - -## str.match(reg) без флага g - -Метод `str.match` работает по-разному, в зависимости от наличия или отсутствия флага `g`, поэтому сначала мы разберём вариант, когда его нет. - -В этом случае `str.match(reg)` находит только одно, первое совпадение. - -Результат вызова -- это массив, состоящий из этого совпадения, с дополнительными свойствами `index` -- позиция, на которой оно обнаружено и `input` -- строка, в которой был поиск. - -Например: - -```js -//+ run -var str = "ОЙ-Ой-ой"; - -var result = str.match( *!*/ой/i*/!* ); - -alert( result[0] ); // ОЙ (совпадение) -alert( result.index ); // 0 (позиция) -alert( result.input ); // ОЙ-Ой-ой (вся поисковая строка) -``` - -У этого массива не всегда только один элемент. - -**Если часть шаблона обозначена скобками, то она станет отдельным элементом массива.** - -Например: - -```js -//+ run -var str = "javascript - это такой язык"; - -var result = str.match( *!*/JAVA(SCRIPT)/i*/!* ); - -alert( result[0] ); // javascript (всё совпадение полностью) -alert( result[1] ); // script (часть совпадения, соответствующая скобкам) -alert( result.index ); // 0 -alert( result.input ); // javascript - это такой язык -``` - -Благодаря флагу `i` поиск не обращает внимание на регистр буквы, поэтому находит javascript. При этом часть строки, соответствующая SCRIPT, выделена в отдельный элемент массива. - -Позже мы ещё вернёмся к скобочным выражениям, они особенно удобны для поиска с заменой. - -## str.match(reg) с флагом g - -При наличии флага `g`, вызов `match` возвращает обычный массив из всех совпадений. - -Никаких дополнительных свойств у массива в этом случае нет, скобки дополнительных элементов не порождают. - -Например: - -```js -//+ run -var str = "ОЙ-Ой-ой"; - -var result = str.match( *!*/ой/ig*/!* ); - -alert( result ); // ОЙ, Ой, ой -``` - -Пример со скобками: - -```js -//+ run -var str = "javascript - это такой язык"; - -var result = str.match( *!*/JAVA(SCRIPT)/gi*/!* ); - -alert( result[0] ); // javascript -alert( result.length ); // 1 -alert( result.index ); // undefined -``` - -Из последнего примера видно, что элемент в массиве ровно один, и свойства `index` также нет. Такова особенность глобального поиска при помощи `match` -- он просто возвращает все совпадения. - -Для расширенного глобального поиска, который позволит получить все позиции и, при желании, скобки, нужно использовать метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec), которые будет рассмотрен далее. - -[warn header="В случае, если совпадений не было, `match` возвращает `null`"] -Обратите внимание, это важно -- если `match` не нашёл совпадений, он возвращает не пустой массив, а именно `null`. - -Это важно иметь в виду, чтобы не попасть в такую ловушку: - -```js -//+ run -var str = "Ой-йой-йой"; - -// результат match не всегда массив! -alert(str.match(/лю/gi).length) // ошибка! нет свойства length у null -``` -[/warn] - -## str.split(reg|substr, limit) - -Разбивает строку в массив по разделителю -- регулярному выражению `regexp` или подстроке `substr`. - -Обычно мы используем метод `split` со строками, вот так: - -```js -//+ run -alert('12-34-56'.split('-')) // [12, 34, 56] -``` - -Можно передать в него и регулярное выражение, тогда он разобьёт строку по всем совпадениям. - -Тот же пример с регэкспом: - -```js -//+ run -alert('12-34-56'.split(/-/)) // [12, 34, 56] -``` - -## str.replace(reg, str|func) - -Швейцарский нож для работы со строками, поиска и замены любого уровня сложности. - -Его простейшее применение -- поиск и замена подстроки в строке, вот так: - -```js -//+ run -// заменить дефис на двоеточие -alert('12-34-56'.replace("-", ":")) // 12:34-56 -``` - -**При вызове со строкой замены `replace` всегда заменяет только первое совпадение.** - -Чтобы заменить *все* совпадения, нужно использовать для поиска не строку `"-"`, а регулярное выражение /-/g, причём обязательно с флагом `g`: - -```js -//+ run -// заменить дефис на двоеточие -alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 -``` - - -В строке для замены можно использовать специальные символы: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    СпецсимволыДействие в строке замены
    `$$`Вставляет `"$"`.
    `$&`Вставляет всё найденное совпадение.
    $`Вставляет часть строки до совпадения.
    - $' - Вставляет часть строки после совпадения.
    - $*n* - где `n` -- цифра или двузначное число, обозначает `n-ю` по счёту скобку, если считать слева-направо.
    - -Пример использования скобок и `$1`, `$2`: - -```js -//+ run -var str = "Василий Пупкин"; - -alert(str.replace(/(Василий) (Пупкин)/, '$2, $1')) // Пупкин, Василий -``` - -Ещё пример, с использованием `$&`: - -```js -//+ run -var str = "Василий Пупкин"; - -alert(str.replace(/Василий Пупкин/, 'Великий $&!')) // Великий Василий Пупкин! -``` - -**Для ситуаций, который требуют максимально "умной" замены, в качестве второго аргумента предусмотрена функция.** - -Она будет вызвана для каждого совпадения, и её результат будет вставлен как замена. - -Например: - -```js -//+ run -var i = 0; - -// заменить каждое вхождение "ой" на результат вызова функции -alert("ОЙ-Ой-ой".replace(/ой/gi, function() { - return ++i; -})); // 1-2-3 -``` - -В примере выше функция просто возвращала числа по очереди, но обычно она основывается на поисковых данных. - -Эта функция получает следующие аргументы: - -
      -
    1. `str` -- найденное совпадение,
    2. -
    3. `p1, p2, ..., pn` -- содержимое скобок (если есть),
    4. -
    5. `offset` -- позиция, на которой найдено совпадение,
    6. -
    7. `s` -- исходная строка.
    8. -
    - -Если скобок в регулярном выражении нет, то у функции всегда будет ровно 3 аргумента: `replacer(str, offset, s)`. - -Используем это, чтобы вывести полную информацию о совпадениях: - -```js -//+ run -// вывести и заменить все совпадения -function replacer(str, offset, s) { - alert( "Найдено: " + str + " на позиции: " + offset + " в строке: " + s ); - return str.toLowerCase(); -} - -var result = "ОЙ-Ой-ой".replace(/ой/gi, replacer); -alert( 'Результат: ' + result ); // Результат: ой-ой-ой -``` - -С двумя скобочными выражениями -- аргументов уже 5: - -```js -//+ run -function replacer(str, name, surname, offset, s) { - return surname + ", " + name; -} - -alert(str.replace(/(Василий) (Пупкин)/, replacer)) // Пупкин, Василий -``` - -Функция -- это самый мощный инструмент для замены, какой только может быть. Она владеет всей информацией о совпадении и имеет доступ к замыканию, поэтому может всё. - -## regexp.test(str) - -Теперь переходим к методам класса `RegExp`. - -Метод `test` проверяет, есть ли хоть одно совпадение в строке `str`. Возвращает `true/false`. - -Работает, по сути, так же, как и проверка `str.search(reg) != -1`, например: - -```js -//+ run -var str = "Люблю регэкспы я, но странною любовью"; - -// эти две проверки идентичны -alert( *!*/лю/i*/!*.test(str) ) // true -alert( str.search(*!*/лю/i*/!*) != -1 ) // true -``` - -Пример с отрицательным результатом: - -```js -//+ run -var str = "Ой, цветёт калина..."; - -alert( *!*/javascript/i*/!*.test(str) ) // false -alert( str.search(*!*/javascript/i*/!*) != -1 ) // false -``` - -## regexp.exec(str) - -Для поиска мы уже видели методы: -
      -
    • `search` -- ищет индекс
    • -
    • `match` -- если регэксп без флага `g` -- ищет совпадение с подрезультатами в скобках
    • -
    • `match` -- если регэксп с флагом `g` -- ищет все совпадения, но без скобочных групп.
    • -
    - -Метод `regexp.exec` дополняет их. Он позволяет искать и все совпадения и скобочные группы в них. - -Он ведёт себя по-разному, в зависимости от того, есть ли у регэкспа флаг `g`. - -
      -
    • Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(reg)`.
    • -
    • Если флаг `g` есть, то вызов `regexp.exec` возвращает первое совпадение и *запоминает* его позицию в свойстве `regexp.lastIndex`. Последующий поиск он начнёт уже с этой позиции. Если совпадений не найдено, то сбрасывает `regexp.lastIndex` в ноль.
    • -
    - -Это используют для поиска всех совпадений в цикле: - -```js -//+ run -var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru'; - -var regexp = /javascript/ig; - -alert( "Начальное значение lastIndex: " + regexp.lastIndex ); - -while (result = regexp.exec(str)) { - alert( 'Найдено: ' + result[0] + ' на позиции:' + result.index ); - alert( 'Свойство lastIndex: ' + regexp.lastIndex ); -} - -alert( 'Конечное значение lastIndex: ' + regexp.lastIndex ); -``` - -Здесь цикл продолжается до тех пор, пока `regexp.exec` не вернёт `null`, что означает "совпадений больше нет". - -Найденные результаты последовательно помещаются в `result`, причём находятся там в том же формате, что и `match` -- с учётом скобок, со свойствами `result.index` и `result.input`. - -[smart header="Поиск с нужной позиции"] -Можно заставить `regexp.exec` искать сразу с нужной позиции, если поставить `lastIndex` вручную: - -```js -//+ run -var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru'; - -var regexp = /javascript/ig; -regexp.lastIndex = 40; - -alert( regexp.exec(str).index ); // 49, поиск начат с 40й позиции -``` -[/smart] - -## Итого, рецепты - -Методы становятся гораздо понятнее, если разбить их использование по задачам, которые нужны в реальной жизни. - -
    -
    Для поиска только одного совпадения:
    -
    -
      -
    • Найти позицию первого совпадения -- `str.search(reg)`.
    • -
    • Найти само совпадение -- `str.match(reg)`.
    • -
    • Проверить, есть ли хоть одно совпадение -- `regexp.test(str)` или `str.search(reg) != -1`.
    • -
    • Найти совпадение с нужной позиции -- `regexp.exec(str)`, начальную позицию поиска задать в `regexp.lastIndex`.
    • -
    -
    -
    Для поиска всех совпадений:
    -
    -
      -
    • Найти массив совпадений -- `str.match(reg)`, с флагом `g`.
    • -
    • Получить все совпадения, с подробной информацией о каждом -- `regexp.exec(str)` с флагом `g`, в цикле.
    • -
    -
    - -
    Для поиска-и-замены:
    -
    -
      -
    • Замена на другую строку или функцией -- `str.replace(reg, str|func)`
    • -
    -
    -
    Для разбивки строки на части:
    -
    -
      -
    • `str.split(str|reg)`
    • -
    -
    -
    - -Зная эти методы, мы уже можем использовать регулярные выражения. - -Конечно, для этого желательно хорошо понимать их синтаксис и возможности, так что переходим к ним дальше. diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md b/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md deleted file mode 100644 index 1fd6b26a..00000000 --- a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/solution.md +++ /dev/null @@ -1,8 +0,0 @@ - -Ответ: \d\d:\d\d. - -```js -//+ run -alert( "Завтрак в 09:00.".match( /\d\d:\d\d/ ) ); // 09:00 -``` - diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md b/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md deleted file mode 100644 index f92b52a5..00000000 --- a/10-regular-expressions-javascript/3-regexp-character-classes/1-find-time-hh-mm/task.md +++ /dev/null @@ -1,8 +0,0 @@ -# Найдите время - -Время имеет формат `часы:минуты`. И часы и минуты состоят из двух цифр, например: `09:00`. - -Напишите регулярное выражение для поиска времени в строке: Завтрак в 09:00. - -P.S. В этой задаче выражению позволительно найти и некорректное время, например `25:99`. - diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/article.md b/10-regular-expressions-javascript/3-regexp-character-classes/article.md deleted file mode 100644 index 26fd9277..00000000 --- a/10-regular-expressions-javascript/3-regexp-character-classes/article.md +++ /dev/null @@ -1,271 +0,0 @@ -# Классы и спецсимволы - -Рассмотрим практическую задачу -- есть телефонный номер `"+7(903)-123-45-67"`, и нам нужно найти в этой строке цифры. А остальные символы нас не интересуют. - -Для поиска символов определённого вида в регулярных выражениях предусмотрены "классы символов". - -[cut] - -Класс символов -- это специальное обозначение, под которое подходит любой символ из определённого набора. - -Например, есть класс "любая цифра". Он обозначается `\d`. Это обозначение вставляется в шаблон, и при поиске под него подходит любая цифра. - -То есть, регулярное выражение /\d/ ищет ровно одну цифру: - -```js -//+ run -var str = "+7(903)-123-45-67"; - -var reg = /\d/; - -// не глобальный регэксп, поэтому ищет только первую цифру -alert( str.match(reg) ); // 7 -``` - -...Ну а для поиска всех цифр достаточно добавить к регэкспу флаг `g`: - -```js -//+ run -var str = "+7(903)-123-45-67"; - -var reg = /\d/g; - -alert( str.match(reg) ); // массив всех совпадений: 7,9,0,3,1,2,3,4,5,6,7 -``` - -## Важнейшие классы: \d \s \w - -Это был класс для цифр. - -Конечно же, есть и другие. - -Наиболее часто используются: -
    -
    `\d` (от английского "digit" -- "цифра")
    -
    Цифра, символ от `0` до `9`.
    -
    `\s` (от английского "space" -- "пробел")
    -
    Пробельный символ, включая табы, переводы строки и т.п.
    -
    `\w` (от английского "word" -- "слово")
    -
    Символ "слова", а точнее -- буква латинского алфавита или цифра или подчёркивание `'_'`. Не-английские буквы не являются `\w`, то есть русская буква не подходит.
    -
    - -Например, \d\s\w обозначает цифру, за которой идёт пробельный символ, а затем символ слова. - -Регулярное выражение может содержать одновременно и обычные символы и классы. - -Например, CSS\d найдёт строку CSS, с любой цифрой после неё: - -```js -//+ run -var str = "Стандарт CSS4 - это здорово"; -var reg = /CSS\d/ - -alert( str.match(reg) ); // CSS4 -``` - -И много классов подряд: - -```js -//+ run -alert( "Я люблю HTML5!".match(/\s\w\w\w\w\d/) ); // 'HTML5' -``` - -Совпадение (каждому классу в регэкспе соответствует один символ результата): - - - -## Граница слова \b - -Граница слова \b -- это особый класс. - -Он интересен тем, что обозначает не символ, а границу между символами. - -Например, \bJava\b найдёт слово Java в строке Hello, Java!, но не в строке Hello, Javascript!. - - -```js -//+ run - -alert( "Hello, Java!".match(/\bJava\b/) ); // Java -alert( "Hello, Javascript!".match(/\bJava\b/) ); // null -``` - -Граница имеет "нулевую ширину" в том смысле, что обычно символам регулярного выражения соответствуют символы строки, но не в этом случае. - -Граница -- это проверка. - -При поиске движок регулярных выражений идёт по шаблону и одновременно по строке, пытаясь построить соответствие. Когда он видит \b, то проверяет, что текущая позиция в строке подходит под одно из условий: -
      -
    • Начало текста, если первый символ `\w`.
    • -
    • Конец текста, если последний символ `\w`.
    • -
    • Внутри текста, если с одной стороны `\w`, а с другой -- не `\w`.
    • -
    - -Например, в строке Hello, Java! под `\b` подходят следующие позиции: - - - -Как правило, `\b` используется, чтобы искать отдельно стоящее слово. Не на русском конечно, хотя подобную проверку, как мы увидим далее, можно легко сделать для любого языка. А вот на английском, как в примере выше или для чисел, которые являются частным случаем `\w` -- легко. - -Например, регэксп \b\d\d\b ищет отдельно двузначные числа. Иными словами, он требует, чтобы до и после \d\d был символ, отличный от `\w` (или начало/конец текста). - - -## Обратные классы - -Для каждого класса существует "обратный ему", представленный такой же, но заглавной буквой. - -"Обратный" -- означает, что ему соответствуют все остальные символы, например: - -
    -
    `\D`
    -
    Не-цифра, то есть любой символ кроме `\d`, например буква.
    -
    `\S`
    -
    Не-пробел, то есть любой символ кроме `\s`, например буква.
    -
    `\W`
    -
    Любой символ, кроме `\w`, то есть не латинница, не подчёркивание, не цифра. В частности, русские буквы принадлежат этому классу.
    -
    `\B`
    -
    Проверка, обратная `\b`.
    -
    - -В начале этой главы мы видели, как получить из телефона +7(903)-123-45-67 все цифры. - -Первый способ -- найти все цифры через `match(/\d/g)`. - -Обратные классы помогут реализовать альтернативный -- найти все НЕцифры и удалить их из строки: - -```js -//+ run -var str = "+7(903)-123-45-67"; - -alert( str.replace(/\D/g, "") ); // 79031234567 -``` - -## Пробелы -- обычные символы - -Заметим, что в регулярных выражениях пробел -- такой же символ, как и другие. - -Обычно мы не обращаем внимание на пробелы. Для нашего взгляда строки 1-5 и 1 - 5 почти идентичны. - -Однако, если регэксп не учитывает пробелов, то он не сработает. - -Попытаемся найти цифры, разделённые дефисом: - -```js -//+ run -alert( "1 - 5".match(/\d-\d/) ); // null, нет совпадений! -``` - -Поправим это, добавив в регэксп пробелы: - -```js -//+ run -alert( "1 - 5".match(/\d - \d/) ); // работает, пробелы вокруг дефиса -``` - -Конечно же, пробелы в регэкспе нужны лишь тогда, когда мы их ищем. Лишние пробелы (как и любые лишние символы) могут навредить: - -```js -//+ run -alert( "1-5".match(/\d - \d/) ); // null, так как в строке 1-5 нет пробелов -``` - -Короче говоря, в регулярном выражении все символы имеют значение. Даже (и тем более) -- пробелы. - -## Точка -- любой символ - -Особым классом символов является точка `"."`. - -В регулярном выражении, точка "." обозначает *любой символ*, кроме перевода строки: - -```js -//+ run -alert( "Z".match(/./) ); // найдено Z -``` - -Посередине регулярного выражения: - -```js -//+ run -var re = /CS.4/; - -alert( "CSS4".match(re) ); // найдено "CSS4" -alert( "CS-4".match(re) ); // найдено "CS-4" -alert( "CS 4".match(re) ); // найдено "CS 4" (пробел тоже символ) -``` - -Обратим внимание -- точка означает именно "произвольный символ". - -То есть какой-то символ на этом месте в строке должен быть: - -```js -//+ run -alert( "CS4".match(/CS.4/) ); // нет совпадений, так как для точки нет символа -``` - -## Экранирование специальных символов - -В регулярных выражениях есть и другие символы, имеющие особый смысл. - -Они используются, чтобы расширить возможности поиска. - -Вот их полный список: [ \ ^ $ . | ? * + ( ). - -Не пытайтесь запомнить его -- когда мы разберёмся с каждым из них по отдельности, он запомнится сам собой. - -**Чтобы использовать специальный символ в качестве обычного, он должен быть *экранирован*.** - -Или, другими словами, перед символом должен быть обратный слэш `'\'`. - -Например, нам нужно найти точку '.'. В регулярном выражении она означает "любой символ, кроме новой строки", поэтому чтобы найти именно сам символ "точка" -- её нужно экранировать: \.. - -```js -//+ run -alert( "Глава 5.1".match(/\d\.\d/) ); // 5.1 -``` - -Круглые скобки также являются специальными символами, так что для поиска именно скобки нужно использовать `\(`. Пример ниже ищет строку `"g()"`: - -```js -//+ run -alert( "function g()".match(/g\(\)/) ); // "g()" -``` - -Сам символ слэш `'/'`, хотя и не является специальными символом в регулярных выражениях, но открывает-закрывает регэксп в синтаксисе /...pattern.../, поэтому его тоже нужно экранировать. - -Так выглядит поиск слэша `'/'`: - -```js -//+ run -alert( "/".match(/\//) ); // '/' -``` - -Ну и, наконец, если нам нужно найти сам обратный слэш `\`, то его нужно просто задублировать. - -Так выглядит поиск обратного слэша `"\"`: - -```js -//+ run -alert( "1\2".match(/\\/) ); // '\' -``` - - -## Итого - -Мы рассмотрели классы для поиска типов символов: - -
      -
    • `\d` -- цифры.
    • -
    • `\D` -- не-цифры.
    • -
    • `\s` -- пробельные символы, переводы строки.
    • -
    • `\S` -- всё, кроме `\s`.
    • -
    • `\w` -- латинница, цифры, подчёркивание `'_'`.
    • -
    • `'.'` -- точка обозначает любой символ, кроме перевода строки.
    • -
    - -Если хочется поискать именно сочетание `"\d"` или символ "точка", то его экранируют обратным слэшем, вот так: \. - -Заметим, что регулярное выражение может также содержать перевод строки `\n`, табуляцию `\t` и прочие спецсимволы для строк. Конфликта с классами не происходит, так как для них зарезервированы другие буквы. - - - - diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png b/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png deleted file mode 100644 index 3a47f13c..00000000 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries.png and /dev/null differ diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png b/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png deleted file mode 100644 index e8f14d87..00000000 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/hello-java-boundaries@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png b/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png deleted file mode 100644 index 387fac9d..00000000 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes.png and /dev/null differ diff --git a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png b/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png deleted file mode 100644 index f5b3e380..00000000 Binary files a/10-regular-expressions-javascript/3-regexp-character-classes/love-html5-classes@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md deleted file mode 100644 index 6177c8e6..00000000 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Ответы: **нет, да**. - -
      -
    • В строке Java он ничего не найдёт, так как исключающие квадратные скобки в `Java[^...]` означают "один символ, кроме указанных". А после "Java" -- конец строки, символов больше нет. - -```js -//+ run -alert( "Java".match(/Java[^script]/) ); // нет совпадений -``` -
    • -
    • Да, найдёт. Поскольку регэксп регистрозависим, то под `[^script]` вполне подходит символ `"S"`. - -```js -//+ run -alert( "JavaScript".match(/Java[^script]/) ); // "JavaS" -``` -
    • -
    diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md deleted file mode 100644 index 6f0b0e37..00000000 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/1-find-range-1/task.md +++ /dev/null @@ -1,5 +0,0 @@ -# Java[^script] - -Найдет ли регэксп /Java[^script]/ что-нибудь в строке Java? - -А в строке JavaScript? diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md deleted file mode 100644 index 969966a2..00000000 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md +++ /dev/null @@ -1,9 +0,0 @@ -Ответ: \d\d[-:]\d\d. - -```js -//+ run -var re = /\d\d[-:]\d\d/g; -alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); -``` - -Обратим внимание, что дефис '-' не экранирован, поскольку в начале скобок он не может иметь специального смысла. diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md deleted file mode 100644 index dd03af58..00000000 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Найдите время в одном из форматов - -Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`. - -Напишите регулярное выражение для поиска времени: - -```js -var re = /ваше выражение/; -alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); // 09:00, 21-30 -``` - diff --git a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md b/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md deleted file mode 100644 index c8f2041f..00000000 --- a/10-regular-expressions-javascript/4-regexp-character-sets-and-ranges/article.md +++ /dev/null @@ -1,170 +0,0 @@ -# Наборы и диапазоны [...] - -Если в регулярном выражении несколько символов или символьных классов заключены в квадратные скобки `[…]`, то это означает "искать любой символ из указанных в `[…]`". - -[cut] - -## Набор - -Например, [еао] означает любой символ из этих трёх: `'а'`, `'е'`, или `'о'`. - -Такое обозначение называют *набором*. Наборы используются в регулярном выражении наравне с обычными символами: - -```js -//+ run -// найти [г или т], а затем "оп" -alert( "Гоп-стоп".match(/[гт]оп/gi) ); // "Гоп", "топ" -``` - -Обратим внимание: несмотря на то, что в наборе указано несколько символов, в совпадении должен присутствовать *ровно один* из них. - -Поэтому в примере ниже нет результатов: - -```js -//+ run -// найти "В", затем [у или а], затем "ля" -alert( "Вуаля".match(/В[уа]ля/) ); // совпадений нет -``` - -Поиск подразумевает: -
      -
    • В,
    • -
    • затем *одну* из букв набора [уа],
    • -
    • а затем ля
    • -
    - -Таким образом, совпадение было бы для строки Вуля или Валя. - -## Диапазоны - -Квадратные скобки могут также содержать *диапазоны символов*. - -Например, [a-z] -- произвольный символ от `a` до `z`, [0-5] -- цифра от `0` до `5`. - -В примере ниже мы будем искать `"x"`, после которого идёт два раза любая цифра или буква от A до F: - -```js -//+ run -// найдёт "xAF" -alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); -``` - -Обратим внимание, в слове Exception есть сочетание xce, но оно не подошло, потому что буквы в нём маленькие, а в диапазоне [0-9A-F] -- большие. - -Если хочется искать и его тоже, можно добавить в скобки диапазон `a-f`: [0-9A-Fa-f]. Или же просто указать у всего регулярного выражения флаг `i`. - -**Символьные классы -- всего лишь более короткие записи для диапазонов, в частности:** - -
      -
    • **\d** -- то же самое, что [0-9],
    • -
    • **\w** -- то же самое, что [a-zA-Z0-9_],
    • -
    • **\s** -- то же самое, что [\t\n\v\f\r ] плюс несколько юникодных пробельных символов.
    • -
    - -В квадратных скобках можно использовать и диапазоны и символьные классы -- вместе. - -Например, нам нужно найти все слова в тексте. Если они на английском -- это достаточно просто: - -```js -//+ run -var str = "The sun is rising!"; - -alert( str.match(/\w+/g) ); // The, sun, is, rising*!* -``` - -А если есть слова и на русском? - -```js -//+ run -var str = "Солнце встаёт!"; - -alert( str.match(/\w+/g) ); // null*!* -``` - -Ничего не найдено! Это можно понять, ведь \w -- это именно английская букво-цифра, как можно видеть из аналога [a-zA-Z0-9_]. - -Чтобы находило слово на русском -- нужно использовать диапазон, например /[а-я]/. - -А чтобы на обоих языках -- и то и другое вместе: - -```js -//+ run -var str = "Солнце (the sun) встаёт!"; - -alert( str.match(/[\wа-я]+/gi) ); // Солнце, the, sun, вста, т*!* -``` - -...Присмотритесь внимательно к предыдущему примеру! Вы видите странность? Оно не находит букву ё, более того -- считает её разрывом в слове. Причина -- в кодировке юникод, она подробно раскрыта в главе [](/string). - -Буква `ё` лежит в стороне от основной кириллицы и её следует добавить в диапазон дополнительно, вот так: - -```js -//+ run -var str = "Солнце (the sun) встаёт!"; - -alert( str.match(/[\wа-яё]+/gi) ); // Солнце, the, sun, встаёт*!* -``` - -Теперь всё в порядке. - -## Диапазоны "кроме" - -**Кроме обычных, существуют также *исключающие* диапазоны: [^…].** - -Квадратные скобки, начинающиеся со знака каретки: [^…] находят любой символ, *кроме указанных*. - -Например: - -
      -
    • [^аеуо] -- любой символ, кроме `'a'`, `'e'`, `'y'`, `'o'`.
    • -
    • [^0-9] -- любой символ, кроме цифры, то же что `\D`.
    • -
    • [^\s] -- любой не-пробельный символ, то же что `\S`.
    • -
    - -Пример ниже ищет любые символы, кроме букв, цифр и пробелов: - -```js -//+ run -alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // "@", "." -``` - -## Не нужно экранирование - -Обычно, если мы хотим искать именно точку, а не любой символ, или именно символ `\`, то мы используем экранирование: указываем `\.` или `\\`. - -В квадратных скобках большинство специальных символов можно использовать без экранирования, если конечно они не имеют какой-то особый смысл именно внутри квадратных скобок. - -То есть, "как есть", без экранирования можно использовать символы: -
      -
    • Точка '.'.
    • -
    • Плюс '+'.
    • -
    • Круглые скобки '( )'.
    • -
    • Дефис '-', если он находится в начале или конце квадратных скобок, то есть не выделяет диапазон.
    • -
    • Символ каретки '^', если не находится в начале квадратных скобок.
    • -
    • А также открывающая квадратная скобка '['.
    • -
    - -То есть, точка `"."` в квадратных скобках означает не "любой символ", а обычную точку. - -Регэксп [.,] ищет один из символов "точка" или "запятая". - -В примере ниже регэксп [-().^+] ищет один из символов `-().^`. Они не экранированы: - -```js -//+ run -// Без экранирования -var re = /[-().^+]/g; - -alert( "1 + 2 - 3".match(re) ); // найдёт +, - -``` - -...Впрочем, даже если вы решите "на всякий случай" заэкранировать эти символы, поставив перед ними обратный слэш `\` -- вреда не будет: - -```js -//+ run -// Всё заэкранировали -var re = /[\-\(\)\.\^\+]/g; - -alert( "1 + 2 - 3".match(re) ); // тоже работает: +, - -``` - diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md deleted file mode 100644 index 5108d628..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/solution.md +++ /dev/null @@ -1,11 +0,0 @@ - -Решение: - -```js -//+ run -var reg = /\.{3,}/g; -alert( "Привет!... Как дела?.....".match(reg) ); // ..., ..... -``` - -Заметим, что символ `.` является специальным, значит его надо экранировать, то есть вставлять как `\.`. - diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md deleted file mode 100644 index 4e94ba20..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/1-find-text-manydots/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Как найти многоточие... ? - -[importance 5] - -Напишите регулярное выражения для поиска многоточий: трёх или более точек подряд. - -Проверьте его: - -```js -var reg = /ваше выражение/g; -alert( "Привет!... Как дела?.....".match(reg) ); // ..., ..... -``` - diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md deleted file mode 100644 index 09576d99..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/solution.md +++ /dev/null @@ -1,34 +0,0 @@ -Итак, нужно написать выражение для описания цвета, который начинается с "#", за которым следуют 6 шестнадцатеричных символов. - -Шестнадцатеричный символ можно описать с помощью [0-9a-fA-F]. Мы можем сократить выражение, используя не чувствительный к регистру шаблон [0-9a-f]. - -Для его шестикратного повторения мы будем использовать квантификатор {6}. - -В итоге, получаем выражение вида /#[a-f0-9]{6}/gi. - -```js -//+ run -var re = /#[a-f0-9]{6}/gi; - -var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"; - -alert( str.match(re) ); // #121212,#AA00ef -``` - -Проблема этого выражения в том, что оно находит цвет и в более длинных последовательностях: - -```js -//+ run -alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678 -``` - -Чтобы такого не было, можно добавить в конец `\b`: - -```js -//+ run -// цвет -alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456 - -// не цвет -alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null -``` diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/task.md deleted file mode 100644 index ea2dbf18..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/2-find-html-colors-6hex/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Регулярное выражение для цвета - -Напишите регулярное выражение для поиска HTML-цвета, заданного как `#ABCDEF`, то есть `#` и содержит затем 6 шестнадцатеричных символов. - -Пример использования: - -``` -var re = /*...ваше регулярное выражение...*/ - -var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2" - -alert( str.match(re) ) // #121212,#AA00ef -``` - diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md deleted file mode 100644 index 41f71b50..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/solution.md +++ /dev/null @@ -1,18 +0,0 @@ - - -Целое число -- это \d+. - -Десятичная точка с дробной частью -- \.\d+. - -Она не обязательна, так что обернём её в скобки с квантификатором '?'. - -Итого, получилось регулярное выражение \d+(\.\d+)?: - -```js -//+ run -var re = /\d+(\.\d+)?/g - -var str = "1.5 0 12. 123.4."; - -alert( str.match(re) ); // 1.5, 0, 12, 123.4 -``` diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md deleted file mode 100644 index 17e4d836..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/3-find-decimal-positive-numbers/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Найдите положительные числа - -Создайте регэксп, который ищет все положительные числа, в том числе и с десятичной точкой. - -Пример использования: - -```js -var re = /* ваш регэксп */ - -var str = "1.5 0 12. 123.4."; - -alert( str.match(re) ); // 1.5, 0, 12, 123.4 -``` \ No newline at end of file diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md b/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md deleted file mode 100644 index 2abd91c0..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/solution.md +++ /dev/null @@ -1,13 +0,0 @@ -Целое число с необязательной дробной частью -- это \d+(\.\d+)?. - -К этому нужно добавить необязательный `-` в начале: - - -```js -//+ run -var re = /-?\d+(\.\d+)?/g - -var str = "-1.5 0 2 -123.4."; - -alert( str.match(re) ); // -1.5, 0, 2, -123.4 -``` diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md b/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md deleted file mode 100644 index 5c5d4d09..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/4-find-decimal-numbers/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Найдите десятичные числа - -Создайте регэксп, который ищет все числа, в том числе и с десятичной точкой, в том числе и отрицательные. - -Пример использования: - -```js -var re = /* ваш регэксп */ - -var str = "-1.5 0 2 -123.4."; - -alert( str.match(re) ); // -1.5, 0, 2, -123.4 -``` \ No newline at end of file diff --git a/10-regular-expressions-javascript/5-regexp-quantifiers/article.md b/10-regular-expressions-javascript/5-regexp-quantifiers/article.md deleted file mode 100644 index 17605289..00000000 --- a/10-regular-expressions-javascript/5-regexp-quantifiers/article.md +++ /dev/null @@ -1,168 +0,0 @@ -# Квантификаторы +, *, ? и {n} - -Рассмотрим ту же задачу, что и ранее -- взять телефон вида `+7(903)-123-45-67` и найти все числа в нём. Но теперь нас интересуют не цифры по отдельности, а именно числа, то есть результат вида `7, 903, 123, 45, 67`. - -Для поиска цифр по отдельности нам было достаточно класса `\d`. Но здесь нужно искать *числа* -- последовательности из 1 или более цифр. - -## Количество {n} - -Количество повторений символа можно указать с помощью числа в фигурных скобках: `{n}`. - -Такое указание называют *квантификатором* (от англ. quantifier). - -У него есть несколько подформ записи: - -
    -
    Точное количество: `{5}`
    -
    Регэксп \d{5} обозначает ровно 5 цифр, в точности как \d\d\d\d\d. - -Следующий пример находит пятизначное число. - -```js -//+ run -alert( "Мне 12345 лет".match(/\d{5}/) ); // "12345" -``` - -
    -
    Количество от-до: `{3,5}`
    -
    Для того, чтобы найти, например, числа размером от трёх до пяти знаков, нужно указать границы в фигурных скобках: \d{3,5} - -```js -//+ run -alert( "Мне не 12, а 1234 года".match(/\d{3,5}/) ); // "1234" -``` - -Последнее значение можно и не указывать. Тогда выражение \d{3,} найдет числа, длиной от трех цифр: - -```js -//+ run -alert( "Мне не 12, а 345678 лет".match(/\d{3,5}/) ); // "345678" -``` -
    -
    - -В случае с телефоном нам нужны числа -- одна или более цифр подряд. Этой задаче соответствует регулярное выражение \d{1,}: - -```js -//+ run -var str = "+7(903)-123-45-67"; - -alert( str.match(/\d{1,}/g) ); // 7,903,123,45,67 -``` - - -## Короткие обозначения - -Для самые часто востребованных квантификаторов есть специальные короткие обозначения. - -
    -
    `+`
    -
    Означает "один или более", то же что `{1,}`. - -Например, \d+ находит числа -- последовательности из 1 или более цифр: - -```js -//+ run -var str = "+7(903)-123-45-67"; - -alert( str.match(/\d+/g) ); // 7,903,123,45,67 -``` - -
    -
    `?`
    -
    Означает "ноль или один", то же что и `{0,1}`. По сути, делает символ необязательным. - -Например, регэксп ou?r найдёт o, после которого, возможно, следует u, а затем r. - -Этот регэксп найдёт or в слове color и our в colour: - -```js -//+ run -var str = "Можно писать color или colour (британский вариант)"; - -alert( str.match(/colou?r/g) ); // color, colour -``` - -
    -
    `*`
    -
    Означает "ноль или более", то же что `{0,}`. То есть, символ может повторяться много раз или вообще отсутствовать. - -Пример ниже находит цифру, после которой идёт один или более нулей: - -```js -//+ run -alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1 -``` - -Сравните это с `'+'` (один или более): - -```js -//+ run -alert( "100 10 1".match(/\d0+/g) ); // 100, 10 -``` - -
    -
    - -## Ещё примеры - -Эти квантификаторы принадлежат к числу самых важных "строительных блоков" для сложных регулярных выражений, поэтому мы рассмотрим ещё примеры. - -
    -
    Регэксп "десятичная дробь" (число с точкой внутри): \d+\.\d+
    -
    - -В действии: -```js -//+ run -alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 -``` - -
    -
    Регэксп "открывающий HTML-тег без атрибутов", такой как `` или `

    `: /<[a-z]+>/i

    -
    Пример: - -```js -//+ run -alert( " ... ".match(/<[a-z]+>/gi) ); // -``` - -Это регулярное выражение ищет символ '<', за которым идут одна или более букв английского алфавита, и затем '>'. -
    -
    Регэксп "открывающий HTML-тег без атрибутов" (лучше): /<[a-z][a-z0-9]*>/i
    -
    -Здесь регулярное выражение расширено: в соответствие со стандартом, HTML-тег может иметь символ на любой позиции, кроме первой, например `

    `. - -```js -//+ run -alert( "

    Привет!

    ".match(/<[a-z][a-z0-9]*>/gi) ); //

    -``` - -

    -
    Регэксп "открывающий или закрывающий HTML-тег без атрибутов": /<\/?[a-z][a-z0-9]*>/i
    -
    В предыдущий паттерн добавили необязательный слэш /? перед тегом. Его понадобилось заэкранировать, чтобы JavaScript не принял его за конец шаблона. - -```js -//+ run -alert( "

    Привет!

    ".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

    ,

    -``` - -
    -
    - - -[smart header="Точнее -- значит сложнее"] -В этих примерах мы видим общее правило, которое повторяется из раза в раз: чем точнее регулярное выражение, тем оно длиннее и сложнее. - -Например, для HTML-тегов, скорее всего, подошло бы и более короткое регулярное выражение <\w+>. - -Так как класс `\w` означает "любая цифра или английская буква или `'_'`, то под такой регэксп подойдут и не теги, например <_>. Однако он гораздо проще, чем более точный регэксп <[a-z][a-z0-9]*>. - -Подойдёт ли нам <\w+> или нужно использовать именно <[a-z][a-z0-9]*>? - -В реальной жизни допустимы оба варианта. Ответ на подобные вопросы зависит от того, насколько реально важна точность и насколько сложно потом будет отфильтровать лишние совпадения (если появятся). -[/smart] - - - - diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md deleted file mode 100644 index 083b3044..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/solution.md +++ /dev/null @@ -1,6 +0,0 @@ - -Результат: `123 456`. - -Ленивый `\d+?` будет брать цифры до пробела, то есть `123`. После каждой цифры он будет останавливаться, проверять -- не пробел ли дальше? Если нет -- брать ещё цифру, в итоге возьмёт `123`. - -З в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`. \ No newline at end of file diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md deleted file mode 100644 index 7076396f..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/1-lazy-greedy/task.md +++ /dev/null @@ -1,8 +0,0 @@ -# Совпадение для /d+? d+/ - -Что будет при таком поиске, когда сначало стоит ленивый, а потом жадный квантификаторы? - -```js -"123 456".match(/\d+? \d+/g) ); // какой результат? -``` - diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md deleted file mode 100644 index b47d89b3..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/solution.md +++ /dev/null @@ -1,16 +0,0 @@ -Они очень похожи и, да, *почти* одинаковы. Оба ищут от одной кавычки до другой. - -Различие здесь в символе точка '.'. Как мы помним, точка '.' обозначает *любой символ, кроме перевода строки*. - -А [^"] -- это *любой символ, кроме кавычки '"'. - -Получатся, что первый регэксп "[^"]*" найдёт закавыченные строки с `\n` внутри, а второй регэксп ".*?" -- нет. - -Вот пример: -```js -//+ run -alert( '"многострочный \n текст"'.match(/"[^"]*"/) ); // найдёт - -alert( '"многострочный \n текст"'.match(/".*?"/) ); // null (нет совпадений) -``` - diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md deleted file mode 100644 index 5e9dcf17..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/2-difference-find-quote/task.md +++ /dev/null @@ -1,5 +0,0 @@ -# Различие между "[^"]*" и ".*?" - -Регулярные выражения "[^"]*" и ".*?" -- при выполнении одинаковы? - -Иначе говоря, существует ли такая строка, на которой они дадут разные результаты? Если да -- дайте такую строку. diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md deleted file mode 100644 index 17bf43eb..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Нужно найти начало комментария <!--, затем всё до конца -->. - -С первого взгляда кажется, что это сделает регулярное выражение <!--.*?--> -- квантификатор сделан ленивым, чтобы остановился, достигнув -->. - -Однако, точка в JavaScript -- любой символ, *кроме* конца строки. Поэтому такой регэксп не найдёт многострочный комментарий. - -Всё получится, если вместо точки использовать полностю "всеядный" [\s\S]. - -Итого: - -```js -//+ run -var re = //g; - -var str = '.. .. .. '; - -alert( str.match(re) ); // '', '' -``` \ No newline at end of file diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md deleted file mode 100644 index af0b1b2a..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/3-find-html-comments/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Найти HTML-комментарии - -Найдите все HTML-комментарии в тексте: - -```js -var re = ..ваш регэксп.. - -var str = '.. .. .. '; - -alert( str.match(re) ); // '', '' -``` diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md deleted file mode 100644 index 6a87a597..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md +++ /dev/null @@ -1,38 +0,0 @@ -Начнём поиск с <, затем один или более произвольный символ, но до закрывающего "уголка": .+?>. - -Проверим, как работает этот регэксп: - -```js -//+ run -var re = /<.+?>/g; - -var str = '<>
    '; - -alert( str.match(re) ); // <> , , -``` - -Результат неверен! В качестве первого тега регэксп нашёл подстроку <> <a href="/">, но это явно не тег. - -Всё потому, что .+? -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)". - -Поэтому он находит первый `<`, затем есть "всё подряд" до следующего `>`. - -Первое совпадение получается как раз таким: - -``` -<.............> -<> -``` - -Правильным решением будет использовать <[^>]+>: - -```js -//+ run -var re = /<[^>]+>/g - -var str = '<> '; - -alert( str.match(re) ); // , , -``` - -Это же решение автоматически позволяет находится внутри тегу символу `\n`, который в класс точка `.` не входит. \ No newline at end of file diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md deleted file mode 100644 index 43935130..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Найти HTML-теги - -Создайте регулярное выражение для поиска всех (открывающихся и закрывающихся) HTML-тегов вместе с атрибутами. - -Пример использования: -```js -//+ run -var re = /* ваш регэксп */ - -var str = '<> '; - -alert( str.match(re) ); // '', '', '' -``` - -В этой задаче можно считать, что тег начинается с <, заканчивается > и может содержать внутри любые символы, кроме < и >. - -Но хотя бы один символ внутри тега должен быть: <> -- не тег. - diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md deleted file mode 100644 index 09758bab..00000000 --- a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/article.md +++ /dev/null @@ -1,335 +0,0 @@ -# Жадные и ленивые квантификаторы - -Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука. - -Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем /\d+/. - -[cut] - -Для примера рассмотрим задачу, которая часто возникает в типографике -- заменить в тексте кавычки вида `"..."` (их называют "английские кавычки") на "кавычки-ёлочки": `«...»`. - -Для этого нужно сначала найти все слова в таких кавычках. - -Соотверствующее регулярное выражение может выглядеть так: /".+"/g, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка. - -Однако, если попробовать применить его на практике, даже на таком простом случае... - -```js -//+ run -var reg = /".+"/g; - -var str = 'a "witch" and her "broom" is one'; - -alert( str.match(reg) ); // "witch" and her "broom" -``` - -...Мы увидим, что оно работает совсем не так, как задумано! - -Вместо того, чтобы найти два совпадения "witch" и "broom", оно находит одно: "witch" and her "broom". - -Это как раз тот случай, когда *жадность* -- причина всех зол. - -## Жадный поиск - -Чтобы найти совпадение, движок регулярных выражений обычно использует следующий алгоритм: - -
      -
    • Для каждой позиции в поисковой строке -
        -
      • Проверить совпадение на данной позиции -
        • Посимвольно, с учётом классов и квантификаторов сопоставив с ней регулярное выражение.
        -
      • -
      -
    • -
    - -Это общие слова, гораздо понятнее будет, если мы проследим, что именно он делает для регэкспа ".+". - -
      -
    1. Первый символ шаблона -- это кавычка ". - -Движок регулярных выражений пытается сопоставить её на 0й позиции в строке, но символ `a`, поэтому на 0й позиции соответствия явно нет. - -Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3й позиции: - -
    2. -
    3. Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна. - -В данном случае следующий символ шаблона: . (точка). Она обозначает "любой символ", так что следующая буква строки 'w' вполне подходит: - -
    4. -
    5. Далее "любой символ" повторяется, так как стоит квантификатор .+. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается. - -В данном случае это означает "до конца строки": - -
    6. -
    7. Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для .+ и переходит к следующему символу шаблона. - -Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился! - -Движок регулярных выражений понимает, что, наверное, взял многовато .+ и начинает отступать обратно. - -Иными словами, он сокращает текущее совпадение на один символ: - - - -Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.). - -Теперь .+ соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки. - -Если бы последним символом строки была кавычка '"', то на этом бы всё и закончилось. Но последний символ 'e', так что совпадения нет.
    8. -
    9. ...Поэтому движок уменьшает число повторений .+ ещё на один символ: - - - -Кавычка '"' не совпадает с 'n'. Опять неудача.
    10. -
    11. Движок продолжает отступать, он уменьшает количество повторений точки '.' до тех пор, пока остаток паттерна, то есть в данном случае кавычка '"', не совпадёт: - - -
    12. -
    13. Совпадение получено. Дальнейший поиск по оставшейся части строки is one новых совпадений не даст.
    14. -
    - -Возможно, это не совсем то, что мы ожидали. - -**В жадном режиме (по умолчанию) регэксп повторяет квантификатор настолько много раз, насколько это возможно, чтобы найти соответствие.** - -То есть, любой символ .+ повторился максимальное количество раз, что и привело к такой длинной строке. - -А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее. - -## Ленивый режим - -Ленивый режим работы квантификаторов -- противоположность жадному, он означает "повторять минимальное количество раз". - -Его можно включить, если поставить знак вопроса '?' после квантификатора, так что он станет таким: *? или +? или даже ?? для '?'. - -Чтобы не возникло путаницы -- важно понимать: обычно `?` сам является квантификатором (ноль или один). Но если он стоит *после другого квантификатора (или даже после себя)*, то обретает другой смысл -- в этом случае он меняет режим его работы на ленивый. - -Регэксп /".+?"/g работает, как задумано -- находит отдельно witch и broom: - -```js -//+ run -var reg = /".+?"/g; - -var str = 'a "witch" and her "broom" is one'; - -alert( str.match(reg) ); // witch, broom -``` - -Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам. - -
      -
    1. Первый шаг -- тот же, кавычка '"' найдена на 3й позиции: - -
    2. - -
    3. Второй шаг -- тот же, находим произвольный символ '.': - -
    4. - -
    5. А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторет точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть '"': - - -Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случе -- нет, символ `'i'` не равен '"'. -
    6. -
    7. Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз: - - -Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё... -
    8. -
    9. Только на 5м шаге поисковой движок наконец находит соответствие для остатка паттерна: - - -
    10. -
    11. Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат: - - -
    12. -
    - -В примере выше продемонстрирована работа ленивого режима для +?. Квантификаторы +? и ?? ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия. - -**Ленивость распространяется только на тот квантификатор, после которого стоит `?`.** - -Прочие квантификаторы остаются жадными. - -Например: - -```js -//+ run -alert( "123 456".match(/\d+ \d+?/g) ); // 123 4 -``` - -
      -
    1. Подшаблон \d+ пытается найти столько цифр, сколько возможно (работает жадно), так что он находит 123 и останавливается, поскольку символ пробела ' ' не подходит под \d.
    2. -
    3. Далее в шаблоне пробел, он совпадает.
    4. -
    5. Далее в шаблоне идёт \d+?. - -Квантификатор указан в ленивом режиме, поэтому он находит одну цифру 4 и пытается проверить, есть ли совпадение с остатком шаблона. - -Но после \d+? в шаблоне ничего нет. - -**Ленивый режим без необходимости лишний раз квантификатор не повторит.** - -Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение 123 4.
    6. -
    7. Следующий поиск продолжится с `5`, но ничего не найдёт.
    8. -
    - -[smart header="Конечные автоматы и не только"] -Современные движки регулярных выражений могут иметь более хитрую реализацию внутренних алгоритмов, чтобы искать быстрее. - -Однако, чтобы понять, как работает регулярное выражение, и строить регулярные выражения самому, знание этих хитрых алгоритмов ни к чему. Они служат лишь внутренней оптимизации способа поиска, описанного выше. - -Кроме того, сложные регулярные выражения плохо поддаются всяким оптимизациям, так что поиск вполне может работать и в точности как здесь описано. -[/smart] - -## Альтернативный подход - -В данном конкретном случае, возможно искать строки в кавычках, оставаясь в жадном режиме, с использованием регулярного выражения "[^"]+": - -```js -//+ run -var reg = /"[^"]+"/g; - -var str = 'a "witch" and her "broom" is one'; - -alert( str.match(reg) ); // witch, broom -``` - -Регэксп "[^"]+" даст правильные результаты, поскольку ищет кавычку '"', за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно. - -Так что вторая кавычка автоматически прекращает повторения [^"]+ и позволяет найти остаток шаблона ". - -**Эта логика ни в коей мере не заменяет ленивые квантификаторы!** - - -Она просто другая. И то и другое бывает полезно. - -Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут. - -Например, мы хотим найти в тексте ссылки вида `
    `, с любым содержанием `href`. - -Какое регулярное выражение для этого подойдёт? - -Первый вариант может выглядеть так: /<a href=".*" class="doc">/g. - -Проверим его: -```js -//+ run -var str = '......'; -var reg = //g; - -// Сработало! -alert( str.match(reg) ); // -``` - -А если в тексте несколько ссылок? - -```js -//+ run -var str = '...... ...'; -var reg = //g; - -// Упс! Сразу две ссылки! -alert( str.match(reg) ); // ... -``` - -На этот раз результат неверен. - -Жадный .* взял слишком много символов. - -Соответствие получилось таким: -``` - -... -``` - -Модифицируем шаблон -- добавим ленивость квантификатору .*?: - -```js -//+ run -var str = '...... ...'; -var reg = //g; - -// Сработало! -alert( str.match(reg) ); // , -``` - -Теперь всё верно, два результата: - -``` - -... -``` - -Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно. - -Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример: - -```js -//+ run -var str = '......

    ...'; -var reg = //g; - -// Неправильное совпадение! -alert( str.match(reg) ); // ...

    -``` - -Совпадение -- не ссылка, а более длинный текст. - -Получилось следующее: -

      -
    1. Найдено совпадение <a href=".
    2. -
    3. Лениво ищем .*?, после каждого символа проверяя, есть ли совпадение остальной части шаблона. - -Подшаблон .*? будет брать символы до тех пор, пока не найдёт class="doc">. - -В данном случае этот поиск закончится уже за пределами ссылки, в теге `

      `, вообще не имеющем отношения к ``. -

    4. -
    5. Получившееся совпадение: - -``` - -...

      -``` -

    6. -
    - -Итак, ленивость нам не помогла. - -Необходимо как-то прекратить поиск .*, чтобы он не вышел за пределы кавычек. - -Для этого мы используем более точное указание, какие символы нам подходят, а какие нет. - -Правильный вариант: [^"]*. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется. - -Рабочий пример: - -```js -//+ run -var str1 = '......

    ...'; -var str2 = '...... ...'; -var reg = //g; - -// Работает! -alert( str1.match(reg) ); // null, совпадений нет, и это верно -alert( str2.match(reg) ); // , -``` - -## Итого - -Квантификаторы имеют два режима работы: -

    -
    Жадный
    -
    Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений.
    -
    Ленивый
    -
    При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции.
    -
    - -Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны. - - - - - diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png deleted file mode 100644 index a4fc2c4f..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png deleted file mode 100644 index e1b2ca0a..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy1@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png deleted file mode 100644 index b7b84c1b..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png deleted file mode 100644 index ce87d89e..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy2@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png deleted file mode 100644 index 0e5b07fd..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png deleted file mode 100644 index 975fc49c..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy3@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png deleted file mode 100644 index 0ca3df76..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png deleted file mode 100644 index fe514d4d..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy4@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png deleted file mode 100644 index 43b01a9a..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png deleted file mode 100644 index 353f69e2..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy5@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png deleted file mode 100644 index 2ae63d20..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png deleted file mode 100644 index 5b09faa0..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_greedy6@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png deleted file mode 100644 index af0482bb..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png deleted file mode 100644 index 7d5fd2c6..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy3@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png deleted file mode 100644 index 920530bd..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png deleted file mode 100644 index 1e14c11c..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy4@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png deleted file mode 100644 index 0a07eeae..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png deleted file mode 100644 index 8c9ff19f..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy5@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png deleted file mode 100644 index 67f0d27a..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6.png and /dev/null differ diff --git a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png b/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png deleted file mode 100644 index e7eb1aa0..00000000 Binary files a/10-regular-expressions-javascript/6-regexp-greedy-and-lazy/witch_lazy6@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md b/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md deleted file mode 100644 index ece24fb8..00000000 --- a/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/solution.md +++ /dev/null @@ -1,30 +0,0 @@ -Регулярное выражение для поиска 3-значного цвета вида `#abc`: /#[a-f0-9]{3}/i. - -Нужно добавить ещё три символа, причём нужны именно три, четыре или семь символов не нужны. Эти три символа либо есть, либо нет. - -Самый простой способ добавить -- просто дописать в конец регэкспа: /#[a-f0-9]{3}([a-f0-9]{3})?/i - -Можно поступить и хитрее: /#([a-f0-9]{3}){1,2}/i. - -Здесь регэксп [a-f0-9]{3} заключён в скобки, чтобы квантификатор {1,2} применялся целиком ко всей этой структуре. - -В действии: -```js -//+ run -var re = /#([a-f0-9]{3}){1,2}/gi; - -var str = "color: #3f3; background-color: #AA00ef; and: #abcd"; - -alert( str.match(re) ); // #3f3 #AA0ef #abc -``` - -В последнем выражении #abcd было найдено совпадение #abc. Чтобы этого не происходило, добавим в конец \b: - -```js -//+ run -var re = /#([a-f0-9]{3}){1,2}\b/gi; - -var str = "color: #3f3; background-color: #AA00ef; and: #abcd"; - -alert( str.match(re) ); // #3f3 #AA0ef -``` diff --git a/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/task.md b/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/task.md deleted file mode 100644 index 419c0476..00000000 --- a/10-regular-expressions-javascript/7-regexp-groups/1-find-webcolor-3-or-6/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Найдите цвет в формате #abc или #abcdef - -Напишите регулярное выражение, которое находит цвет в формате `#abc` или `#abcdef`. То есть, символ `#`, после которого идут 3 или 6 шестнадцатиричных символа. - -Пример использования: -```js -var re = /* ваш регэксп */ - -var str = "color: #3f3; background-color: #AA00ef; and: #abcd"; - -alert( str.match(re) ); // #3f3 #AA0ef -``` - -P.S. Значения из любого другого количества букв, кроме 3 и 6, такие как `#abcd`, не должны подходить под регэксп. \ No newline at end of file diff --git a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md b/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md deleted file mode 100644 index 05fedcd8..00000000 --- a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/solution.md +++ /dev/null @@ -1,53 +0,0 @@ -Регулярное выражение для числа, возможно, дробного и отрицательного: -?\d+(\.\d+)?. Мы уже разбирали его в предыдущих задачах. - -Оператор -- это [-+*/]. Заметим, что дефис - идёт в списке первым, так как на любой позиции, кроме первой и последней, он имеет специальный смысл внутри [...], и его понадобилось бы экранировать. - -Кроме того, когда мы оформим это в JavaScript-синтаксис /.../ -- понадобится заэкранировать слэш /. - -Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними. - -Полное регулярное выражение будет таким: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?. - -Чтобы получить результат в виде массива, добавим скобки вокруг тех данных, которые нам интересны, то есть -- вокруг чисел и оператора: (-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?). - -Посмотрим в действии: -```js -//+ run -var re = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/; - -alert( "1.2 + 12".match(re) ); -``` - -Итоговый массив будет включать в себя компоненты: - -
      -
    • `result[0] == "1.2 + 12"` (вначале всегда полное совпадение)
    • -
    • `result[1] == "1"` (первая скобка)
    • -
    • `result[2] == "2"` (вторая скобка -- дробная часть `(\.\d+)?`)
    • -
    • `result[3] == "+"` (...)
    • -
    • `result[4] == "12"` (...)
    • -
    • `result[5] == undefined` (последняя скобка, но у второго числа дробная часть отсутствует)
    • -
    - -Нам из этого массива нужны только числа и оператор. А, скажем, дробная часть сама по себе -- не нужна. - -Уберём её из запоминания, добавив в начало скобки ?:, то есть: (?:\.\d+)?. - -Итого, решение: - -```js -//+ run -function parse(expr) { - var re = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/; - - var result = expr.match(re); - - if (!result) return; - result.shift(); - - return result; -} - -alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45 -``` - diff --git a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md b/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md deleted file mode 100644 index c48e5a0c..00000000 --- a/10-regular-expressions-javascript/7-regexp-groups/2-parse-expression/task.md +++ /dev/null @@ -1,20 +0,0 @@ -# Разобрать выражение - -Арифметическое выражение состоит из двух чисел и операции между ними, например: -
      -
    • `1 + 2`
    • -
    • `1.2 * 3.4`
    • -
    • `-3 / -6`
    • -
    • `-2 - 2`
    • -
    - -Список операций: `"+"`, `"-"`, `"*"` и `"/"`. - -Также могут присутсововать пробелы вокруг оператора и чисел. - -Напишите функцию, которая будет получать выражение и возвращать массив из трёх аргументов: -
      -
    1. Первое число.
    2. -
    3. Оператор.
    4. -
    5. Второе число.
    6. -
    diff --git a/10-regular-expressions-javascript/7-regexp-groups/article.md b/10-regular-expressions-javascript/7-regexp-groups/article.md deleted file mode 100644 index 515c6175..00000000 --- a/10-regular-expressions-javascript/7-regexp-groups/article.md +++ /dev/null @@ -1,150 +0,0 @@ -# Скобочные группы - -Часть шаблона может быть заключена в скобки (...). Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами". - -У такого выделения есть два эффекта: -
      -
    1. Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) или [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).
    2. -
    3. Если поставить квантификатор после скобки, то он применится *ко всей скобке*, а не всего лишь к одному символу.
    4. -
    - -[cut] - -## Пример - -В примере ниже, шаблон (go)+ находит один или более повторяющихся 'go': - -```js -//+ run -alert( 'Gogogo now!'.match(/(go)+/i ); // "Gogogo" -``` - -Без скобок, шаблон /go+/ означал бы g, после которого идёт одна или более o, например: goooo. А скобки "группируют" (go) вместе. - - -## Содержимое группы - -Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему -- в шаблоне и строке замены и, конечно же, в результатах. - -Например, найти HTML-тег можно шаблоном <.*?>. - -После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: <(.*?)>. Тогда оно будет доступно отдельно. - -При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне <(.*?)> скобочная группа только одна: - -```js -//+ run -var str = '

    Привет, мир!

    '; -var reg = /<(.*?)>/; - -alert( str.match(reg) ); // массив:

    , h1 -``` - -Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение <h1>, а закрывающий </h1> не нашёл, поскольку без флага `/.../g` ищется только первое совпадение. - -Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec): - -```js -//+ run -var str = '

    Привет, мир!

    '; -var reg = /<(.*?)>/g; - -var match; - -while ((match = reg.exec(str)) !== null) { - // сначала выведет первое совпадение:

    ,h1 - // затем выведет второе совпадение:

    ,/h1 - alert(match); -} -``` - -Теперь найдено оба совпадения <(.*?)>, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае). - -## Вложенные группы -Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо. - -Например, при поиске тега в <span class="my"> нас может интересовать: - -
      -
    1. Содержимое тега целиком: `span class="my"`.
    2. -
    3. В отдельную переменную для удобства хотелось бы поместить тег: `span`.
    4. -
    5. Также может быть удобно отдельно выделить атрибуты `class="my"`.
    6. -
    - -Добавим скобки в регулярное выражение: - -```js -//+ run -var str = ''; - -reg = /<(([a-z]+)\s*([^>]*))>/; - -alert( str.match(reg) ); // , span, s -``` - -Вот так выглядят скобочные группы: - - - -На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке. - -В данном случае получилось, что группа 1 включает в себя содержимое групп 2 и 3. Это совершенно нормальная ситуация, которая возникает, когда нужно выделить что-то отдельное внутри большей группы. - -**Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `undefined`).** - -Например, рассмотрим регэксп a(z)?(c)?. Он ищет `"a"`, за которой не обязательно идёт буква `"z"`, за которой необязательно идёт буква `"c"`. - -Если напустить его на строку из одной буквы `"a"`, то результат будет таков: - -```js -//+ run -match = 'a'.match(/a(z)?(c)?/) - -alert( match.length ); // 3 -alert( match[0] ); // a -alert( match[1] ); // undefined -alert( match[2] ); // undefined -``` - -Массив получился длины `3`, но все скобочные группы -- `undefined`. - -А теперь более сложная ситуация, строка ack: - -```js -//+ run -match = 'ack'.match(/a(z)?(c)?/) - -alert( match.length ); // 3 -alert( match[0] ); // ac, всё совпадение -alert( match[1] ); // undefined, для (z)? ничего нет -alert( match[2] ); // c -``` - -Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы (z)? в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`. - -## Исключение из запоминания через ?: - -Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно. - -Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало ?:. - -Например, мы хотим найти (go)+, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим. - -Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: (?:go)+. - -Например: - -```js -//+ run -var str = "Gogo John!"; -*!* -var reg = /(?:go)+ (\w+)/i; -*/!* - -var result = str.match(reg); - -alert( result.length ); // 2 -alert( result[1] ); // John -``` - -В примере выше массив результатов имеет длину `2` и содержит только полное совпадение и результат (\w+). Это удобно в тех случаях, когда содержимое скобок нас не интересует. diff --git a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png b/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png deleted file mode 100644 index 9103ea28..00000000 Binary files a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups.png and /dev/null differ diff --git a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png b/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png deleted file mode 100644 index 4f5ee487..00000000 Binary files a/10-regular-expressions-javascript/7-regexp-groups/regexp-nested-groups@2x.png and /dev/null differ diff --git a/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md b/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md deleted file mode 100644 index 234032d4..00000000 --- a/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/solution.md +++ /dev/null @@ -1,21 +0,0 @@ - -Открывающий тег -- это \[(b|url|quote)\]. - -Для того, чтобы найти всё до закрывающего -- используем ленивый поиск [\s\S]*? и обратную ссылку на открывающий тег. - -Итого, получится: \[(b|url|quote)\][\s\S]*?\[/\1\]. - -В действии: - -```js -//+ run -var re = /\[(b|url|quote)\][\s\S]*?\[\/\1\]/g; - -var str1 = "..[url]http://ya.ru[/url].."; -var str2 = "..[url][b]http://ya.ru[/b][/url].."; - -alert( str1.match(re) ); // [url]http://ya.ru[/url] -alert( str2.match(re) ); // [url][b]http://ya.ru[/b][/url] -``` - -Для закрывающего тега `[/1]` понадобилось дополнительно экранировать слеш: `\[\/1\]`. diff --git a/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/task.md b/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/task.md deleted file mode 100644 index 8596bed5..00000000 --- a/10-regular-expressions-javascript/8-regexp-backreferences/1-find-matching-bbtags/task.md +++ /dev/null @@ -1,41 +0,0 @@ -# Найдите пары тегов - -ББ-тег имеет вид `[имя]...[/имя]`, где имя -- слово, одно из: `b`, `url`, `quote`. - -Например: -``` -[b]текст[/b] -[url]http://ya.ru[/url] -``` - -ББ-теги могут быть вложенными, но сам в себя тег быть вложен не может, например: - -``` -Допустимо: -[url] [b]http://ya.ru[/b] [/url] -[quote] [b]текст[/b] [/quote] - -Нельзя: -[b][b]текст[/b][/b] -``` - -Создайте регулярное выражение для поиска ББ-тегов и их содержимого. - -Например: - -```js -var re = /* регулярка */ - -var str = "..[url]http://ya.ru[/url].."; -alert( str.match(re) ); // [url]http://ya.ru[/url] -``` - -Если теги вложены, то нужно искать самый внешний тег (при желании можно будет продолжить поиск в его содержимом): - -```js -var re = /* регулярка */ - -var str = "..[url][b]http://ya.ru[/b][/url].."; -alert( str.match(re) ); // [url][b]http://ya.ru[/b][/url] -``` - diff --git a/10-regular-expressions-javascript/8-regexp-backreferences/article.md b/10-regular-expressions-javascript/8-regexp-backreferences/article.md deleted file mode 100644 index 3814da12..00000000 --- a/10-regular-expressions-javascript/8-regexp-backreferences/article.md +++ /dev/null @@ -1,68 +0,0 @@ -# Обратные ссылки: \n и $n - -Скобочные группы можно не только получать в результате. - -Движок регулярных выражений запоминает их содержимое, и затем его можно использовать как в самом паттерне, так и в строке замены. - -[cut] - -## Группа в строке замены - -Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы. - -Вместо `$n` подставляется содержимое соответствующей скобки: - -```js -//+ run -var name = "Александр Пушкин"; - -name = name.replace(/([а-яё]+) ([а-яё]+)/i, *!*"$2, $1"*/!*); -alert( name ); // Пушкин, Александр -``` - -В примере выше вместо $2 подставляется второе найденное слово, а вместо $1 -- первое. - -## Группа в шаблоне - -Выше был пример использования содержимого групп в строке замены. Это удобно, когда нужно реорганизовать содержимое или создать новое с использованием старого. - -Но к скобочной группе можно также обратиться в самом поисковом шаблоне, ссылкой вида `\номер`. - -Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными '...' или двойными "..." -- и то и другое должно искаться корректно. - -Как такие строки искать? - -Можно в регэкспе предусмотреть произвольные кавычки: `['"](.*?)['"]`. Такой регэксп найдёт строки вида "...", '...', но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке "She's the one!": - -```js -//+ run -str = "He said: \"She's the one!\"."; - -reg = /['"](.*?)['"]/g; - -// Результат не соответствует замыслу -alert( str.match(reg) ); // "She' -``` - -Как видно, регэксп нашёл открывающую кавычку ", затем текст, вплоть до новой кавычки ', которая закрывает соответствие. - -Для того, чтобы попросить регэксп искать закрывающую кавычку -- такую же, как открывающую, мы обернём её в скобочную группу и используем обратную ссылку на неё: - -```js -//+ run -str = "He said: \"She's the one!\"."; - -reg = /(['"])(.*?)\1/g; - -alert( str.match(reg) ); // "She's the one!" -``` - -Теперь работает верно! Движок регулярных выражений, найдя первое скобочное выражение -- кавычку (['"]), запоминает его и далее \1 означает "найти то же самое, что в первой скобочной группе". - -Обратим внимание на два нюанса: - -
      -
    • Чтобы использовать скобочную группу в строке замены -- нужно использовать ссылку вида `$1`, а в шаблоне -- обратный слэш: `\1`.
    • -
    • Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации.
    • -
    - diff --git a/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md b/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md deleted file mode 100644 index 483f818e..00000000 --- a/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/solution.md +++ /dev/null @@ -1,36 +0,0 @@ -Сначала неправильный способ. - -Если перечислить языки один за другим через `|`, то получится совсем не то: - -```js -//+ run -var reg = /Java|JavaScript|PHP|C|C\+\+/g; - -var str = "Java, JavaScript, PHP, C, C++"; - -alert( str.match(reg) ); // Java,Java,PHP,C,C -``` - -Как видно, движок регулярных выражений ищет альтернации в порядке их перечисления. То есть, он сначала смотрит, есть ли Java, а если нет -- ищет JavaScript. - -Естественно, при этом JavaScript не будет найдено никогда. - -То же самое -- с языками C и C++. - -Есть два решения проблемы: - -
      -
    1. Поменять порядок, чтобы более длинное совпадение проверялось первым: JavaScript|Java|C\+\+|C|PHP.
    2. -
    3. Соединить длинный вариант с коротким: Java(Script)?|C(\+\+)?|PHP.
    4. -
    - -В действии: - -```js -//+ run -var reg = /Java(Script)?|C(\+\+)?|PHP/g; - -var str = "Java, JavaScript, PHP, C, C++"; - -alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++ -``` diff --git a/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/task.md b/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/task.md deleted file mode 100644 index b93570f3..00000000 --- a/10-regular-expressions-javascript/9-regexp-alternation/1-find-programming-language/task.md +++ /dev/null @@ -1,6 +0,0 @@ -# Найдите языки программирования - -Существует много языков программирования, например Java, JavaScript, PHP, C, C++. - -Напишите регулярное выражение, которое найдёт их все в строке "Java JavaScript PHP C++ C" - diff --git a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md b/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md deleted file mode 100644 index c959b6fa..00000000 --- a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/solution.md +++ /dev/null @@ -1,19 +0,0 @@ -Решение задачи: /"(\\.|[^"\\])*"/g. - -То есть: -
      -
    • Сначала ищем кавычку "
    • -
    • Затем, если далее слэш \\ (удвоение слэша -- техническое, для вставки в регэксп, на самом деле там один слэш), то после него также подойдёт любой символ (точка).
    • -
    • Если не слэш, то берём любой символ, кроме кавычек (которые будут означать конец строки) и слэша (чтобы предотвратить одинокие слэши, сам по себе единственный слэш не нужен, он должен экранировать какой-то символ) [^"\\]
    • -
    • ...И так жадно, до закрывающей кавычки.
    • -
    - -В действии: - -```js -//+ run -var re = /"(\\.|[^"\\])*"/g; -var str = '.. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" ..'; - -alert( str.match(re) ); // "test me","Скажи \"Привет\"!","\r\n\\" -``` \ No newline at end of file diff --git a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md b/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md deleted file mode 100644 index 4db27891..00000000 --- a/10-regular-expressions-javascript/9-regexp-alternation/2-match-quoted-string/task.md +++ /dev/null @@ -1,26 +0,0 @@ -# Найдите строки в кавычках - -Найдите в тексте при помощи регэкспа строки в двойных кавычках "...". - -В строке поддерживается экранирование при помощи слеша -- примерно в таком же виде, как в обычных строках JavaScript. То есть, строка может содержать любые символы, экранированные слэшем, в частности: \", \n, и даже сам слэш в экранированном виде: \\. - -Здесь особо важно, что двойная кавычка после слэша не оканчивает строку, а считается её частью. В этом и состоит основная сложность задачи, которая без этого условия была бы элементарной. - -Пример совпадающих строк: -```js -.. *!*"test me"*/!* .. (обычная строка) -.. *!*"Скажи \"Привет\"!"*/!* ... (строка с кавычками внутри) -.. *!*"\r\n\\"*/!* .. (строка со спец. символами и слэшем внутри) -``` - -Заметим, что в JavaScript такие строки удобнее всего задавать в одинарных кавычках, и слеши придётся удвоить (в одинарных кавычках они являются экранирующими символами): - -Пример задания тестовой строки в JavaScript: -```js -//+ run -var str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" .. '; - -// эта строка будет такой: -alert(str); // .. "test me" .. "Скажи \"Привет\"!" .. "\r\n\\" .. -``` - diff --git a/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md b/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md deleted file mode 100644 index 0a422af1..00000000 --- a/10-regular-expressions-javascript/9-regexp-alternation/3-match-exact-tag/solution.md +++ /dev/null @@ -1,18 +0,0 @@ - -Начало шаблона очевидно: ``pattern``, так как ``match`` удовлетворяет этому регэкспу. - -Нужно уточнить его. После ``match`|\s.*?>)`. - -В действии: - -```js -//+ run -var re = /|\s.*?>)/g; - -alert( " - - - - -
    - hh:mm:ss -
    - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/10-clock-setinterval/source.view/index.html b/2-ui/1-document/11-modifying-document/10-clock-setinterval/source.view/index.html deleted file mode 100644 index 9120728f..00000000 --- a/2-ui/1-document/11-modifying-document/10-clock-setinterval/source.view/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/10-clock-setinterval/task.md b/2-ui/1-document/11-modifying-document/10-clock-setinterval/task.md deleted file mode 100644 index e422846b..00000000 --- a/2-ui/1-document/11-modifying-document/10-clock-setinterval/task.md +++ /dev/null @@ -1,8 +0,0 @@ -# Часики с использованием "setInterval" - -[importance 4] - -Создайте цветные часики как в примере ниже: - -[iframe src="solution" height=100] - diff --git a/2-ui/1-document/11-modifying-document/2-remove-polyfill/solution.md b/2-ui/1-document/11-modifying-document/2-remove-polyfill/solution.md deleted file mode 100644 index bd99772f..00000000 --- a/2-ui/1-document/11-modifying-document/2-remove-polyfill/solution.md +++ /dev/null @@ -1,23 +0,0 @@ -Родителя `parentNode` можно получить из `elem`. - -Вот так выглядит решение: -```html -//+ run -
    Это
    -
    Все
    -
    Элементы DOM
    - - -``` \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/2-remove-polyfill/task.md b/2-ui/1-document/11-modifying-document/2-remove-polyfill/task.md deleted file mode 100644 index a212fdc0..00000000 --- a/2-ui/1-document/11-modifying-document/2-remove-polyfill/task.md +++ /dev/null @@ -1,28 +0,0 @@ -# Удаление элементов - -[importance 5] - -Напишите полифилл для метода `remove` для старых браузеров. - -Вызов `elem.remove()`: -
      -
    • Если у `elem` нет родителя -- ничего не делает.
    • -
    • Если есть -- удаляет элемент из родителя.
    • -
    - -```html -
    Это
    -
    Все
    -
    Элементы DOM
    - - -``` - diff --git a/2-ui/1-document/11-modifying-document/3-insert-after/solution.md b/2-ui/1-document/11-modifying-document/3-insert-after/solution.md deleted file mode 100644 index 4436bdcb..00000000 --- a/2-ui/1-document/11-modifying-document/3-insert-after/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -Для того, чтобы добавить элемент *после* `refElem`, мы можем, используя `insertBefore`, вставить его *перед* `refElem.nextSibling`. - -Но что если `nextSibling` нет? Это означает, что `refElem` является последним потомком своего родителя и можем использовать `appendChild`. - -Код: - -```js -function insertAfter(elem, refElem) { - var parent = refElem.parentNode; - var next = refElem.nextSibling; - if (next) { - return parent.insertBefore(elem, next); - } else { - return parent.appendChild(elem); - } -} -``` - -Но код может быть гораздо короче, если вспомнить, что `insertBefore` со вторым аргументом null работает как `appendChild`: - -```js -function insertAfter(elem, refElem) { - return refElem.parentNode.insertBefore(elem, refElem.nextSibling); -} -``` - -Если нет `nextSibling`, то второй аргумент `insertBefore` становится `null` и тогда `insertBefore(elem, null)` осуществит вставку в конец, как и требуется. - -В решении нет проверки на существование `refElem.parentNode`, поскольку вставка после элемента без родителя -- уже ошибка, пусть она возникнет в функции, это нормально. \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/3-insert-after/task.md b/2-ui/1-document/11-modifying-document/3-insert-after/task.md deleted file mode 100644 index d5fa5329..00000000 --- a/2-ui/1-document/11-modifying-document/3-insert-after/task.md +++ /dev/null @@ -1,26 +0,0 @@ -# insertAfter - -[importance 5] - -Напишите функцию `insertAfter(elem, refElem)`, которая добавит `elem` после узла `refElem`. - -```html -
    Это
    -
    Элементы
    - - -``` - diff --git a/2-ui/1-document/11-modifying-document/4-remove-children/solution.md b/2-ui/1-document/11-modifying-document/4-remove-children/solution.md deleted file mode 100644 index 3ca743c4..00000000 --- a/2-ui/1-document/11-modifying-document/4-remove-children/solution.md +++ /dev/null @@ -1,54 +0,0 @@ -# Неправильное решение - -Для начала рассмотрим забавный пример того, как делать *не надо*: - -```js -function removeChildren(elem) { - for (var k = 0; k < elem.childNodes.length; k++) { - elem.removeChild(elem.childNodes[k]); - } -} -``` - -Если вы попробуете это на практике, то увидите, то это не сработает. - -Не сработает потому, что коллекция `childNodes` всегда начинается с индекса 0 и автоматически обновляется, когда первый потомок удален(т.е. тот, что был вторым, станет первым). А переменная `k` в цикле всё время увеличивается, поэтому такой цикл пропустит половину узлов. - -# Решение через DOM - -Правильное решение: - -```js -function removeChildren(elem) { - while (elem.lastChild) { - elem.removeChild(elem.lastChild); - } -} -``` - -# Альтернатива через innerHTML - -Можно и просто обнулить содержимое через `innerHTML`: - -```js -function removeChildren(elem) { - elem.innerHTML = ''; -} -``` - -Это не будет работать в IE8- для таблиц, так как на большинстве табличных элементов (кроме ячеек `TH/TD`) в старых IE запрещено менять `innerHTML`. - -Впрочем, можно завернуть `innerHTML` в `try/catch`: - -```js -function removeChildren(elem) { - try { - elem.innerHTML = ''; - } catch (e) { - while (elem.firstChild) { - elem.removeChild(elem.firstChild); - } - } -} -``` - diff --git a/2-ui/1-document/11-modifying-document/4-remove-children/task.md b/2-ui/1-document/11-modifying-document/4-remove-children/task.md deleted file mode 100644 index e6b7cb74..00000000 --- a/2-ui/1-document/11-modifying-document/4-remove-children/task.md +++ /dev/null @@ -1,29 +0,0 @@ -# removeChildren - -[importance 5] - -Напишите функцию `removeChildren`, которая удаляет всех потомков элемента. - -```html - - - - - - -
    ЭтоВсеЭлементы DOM
    - -
      -
    1. Вася
    2. -
    3. Петя
    4. -
    5. Маша
    6. -
    7. Даша
    8. -
    - - -``` diff --git a/2-ui/1-document/11-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/11-modifying-document/5-why-aaa/solution.md deleted file mode 100644 index 452540b9..00000000 --- a/2-ui/1-document/11-modifying-document/5-why-aaa/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -HTML в задаче некорректен. В этом всё дело. И вопрос легко решится, если открыть отладчик. - -В нём видно, что браузер поместил текст `aaa` *перед* таблицей. Поэтому он и остался в документе. - -Вообще, в стандарте HTML5 описано, как браузеру обрабатывать некорректный HTML, так что такое действие браузера является правильным. \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/5-why-aaa/task.md b/2-ui/1-document/11-modifying-document/5-why-aaa/task.md deleted file mode 100644 index d0d6d4df..00000000 --- a/2-ui/1-document/11-modifying-document/5-why-aaa/task.md +++ /dev/null @@ -1,25 +0,0 @@ -# Почему остаётся "ааа" ? - -[importance 1] - -Запустите этот пример. Почему вызов `removeChild` не удалил текст `"aaa"`? - -```html - - - aaa - - - -
    Test
    - - -``` - diff --git a/2-ui/1-document/11-modifying-document/6-create-list/solution.md b/2-ui/1-document/11-modifying-document/6-create-list/solution.md deleted file mode 100644 index f3689eb4..00000000 --- a/2-ui/1-document/11-modifying-document/6-create-list/solution.md +++ /dev/null @@ -1,4 +0,0 @@ - -Делаем цикл, пока посетитель что-то вводит -- добавляет `
  • `. - -Содержимое в `
  • ` присваиваем через `document.createTextNode`, чтобы правильно работали <, > и т.д. diff --git a/2-ui/1-document/11-modifying-document/6-create-list/solution.view/index.html b/2-ui/1-document/11-modifying-document/6-create-list/solution.view/index.html deleted file mode 100755 index 254844e3..00000000 --- a/2-ui/1-document/11-modifying-document/6-create-list/solution.view/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - -

    Создание списка

    - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/6-create-list/task.md b/2-ui/1-document/11-modifying-document/6-create-list/task.md deleted file mode 100644 index f703837f..00000000 --- a/2-ui/1-document/11-modifying-document/6-create-list/task.md +++ /dev/null @@ -1,18 +0,0 @@ -# Создать список - -[importance 4] - -Напишите интерфейс для создания списка. - -Для каждого пункта: -
      -
    1. Запрашивайте содержимое пункта у пользователя с помощью `prompt`.
    2. -
    3. Создавайте пункт и добавляйте его к `UL`.
    4. -
    5. Процесс прерывается, когда пользователь нажимает ESC или вводит пустую строку.
    6. -
    - -**Все элементы должны создаваться динамически.** - -Если посетитель вводит теги -- пусть в списке они показываются как обычный текст. - -[demo src="solution"] diff --git a/2-ui/1-document/11-modifying-document/7-create-object-tree/build-tree-dom.view/index.html b/2-ui/1-document/11-modifying-document/7-create-object-tree/build-tree-dom.view/index.html deleted file mode 100755 index 50428719..00000000 --- a/2-ui/1-document/11-modifying-document/7-create-object-tree/build-tree-dom.view/index.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - -
    - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/7-create-object-tree/solution.md b/2-ui/1-document/11-modifying-document/7-create-object-tree/solution.md deleted file mode 100644 index ff334e70..00000000 --- a/2-ui/1-document/11-modifying-document/7-create-object-tree/solution.md +++ /dev/null @@ -1,6 +0,0 @@ -Решения через рекурсию. - -
      -
    1. [edit src="solution"]Через innerHTML[/edit].
    2. -
    3. [edit src="build-tree-dom"]Через DOM[/edit].
    4. -
    \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/7-create-object-tree/solution.view/index.html b/2-ui/1-document/11-modifying-document/7-create-object-tree/solution.view/index.html deleted file mode 100755 index fc4cae62..00000000 --- a/2-ui/1-document/11-modifying-document/7-create-object-tree/solution.view/index.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - -
    - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/7-create-object-tree/source.view/index.html b/2-ui/1-document/11-modifying-document/7-create-object-tree/source.view/index.html deleted file mode 100755 index 9a64f587..00000000 --- a/2-ui/1-document/11-modifying-document/7-create-object-tree/source.view/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - -
    - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/7-create-object-tree/task.md b/2-ui/1-document/11-modifying-document/7-create-object-tree/task.md deleted file mode 100644 index 25eb80be..00000000 --- a/2-ui/1-document/11-modifying-document/7-create-object-tree/task.md +++ /dev/null @@ -1,50 +0,0 @@ -# Создайте дерево из объекта - -[importance 5] - -Напишите функцию, которая создаёт вложенный список `UL/LI` (дерево) из объекта. - -Например: - -```js -var data = { - "Рыбы": { - "Форель": {}, - "Щука": {} - }, - - "Деревья": { - "Хвойные": { - "Лиственница": {}, - "Ель": {} - }, - "Цветковые": { - "Берёза": {}, - "Тополь": {} - } - } -}; -``` - -Синтаксис: - -```js -var container = document.getElementById('container'); -*!* -createTree(container, data); // создаёт -*/!* -``` - -Результат (дерево): - -[iframe border=1 src="solution"] - -Выберите один из двух способов решения этой задачи: -
      -
    1. Создать строку, а затем присвоить через `container.innerHTML`.
    2. -
    3. Создавать узлы через методы DOM.
    4. -
    - -Если получится -- сделайте оба. - -P.S. Желательно, чтобы в дереве не было лишних элементов, в частности -- пустых `
      ` на нижнем уровне. \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/8-tree-count/solution.md b/2-ui/1-document/11-modifying-document/8-tree-count/solution.md deleted file mode 100644 index 539da988..00000000 --- a/2-ui/1-document/11-modifying-document/8-tree-count/solution.md +++ /dev/null @@ -1,8 +0,0 @@ -# Подсказки - -
        -
      1. Получить количество вложенных узлов можно через `elem.getElementsByTagName('*').length`.
      2. -
      3. Текст в начале `
      4. ` доступен как `li.firstChild`, его содержимое -- `li.firstChild.data`.
      5. -
      - -# Решение \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/8-tree-count/solution.view/index.html b/2-ui/1-document/11-modifying-document/8-tree-count/solution.view/index.html deleted file mode 100644 index 736bfa9f..00000000 --- a/2-ui/1-document/11-modifying-document/8-tree-count/solution.view/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - -
        -
      • Животные -
          -
        • Млекопитающие -
            -
          • Коровы
          • -
          • Ослы
          • -
          • Собаки
          • -
          • Тигры
          • -
          -
        • -
        • Другие -
            -
          • Змеи
          • -
          • Птицы
          • -
          • Ящерицы
          • -
          -
        • -
        -
      • -
      • Рыбы -
          -
        • Аквариумные -
            -
          • Гуппи
          • -
          • Скалярии
          • -
          - -
        • -
        • Морские -
            -
          • Морская форель
          • -
          -
        • -
        -
      • -
      - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/8-tree-count/source.view/index.html b/2-ui/1-document/11-modifying-document/8-tree-count/source.view/index.html deleted file mode 100644 index 2f45460c..00000000 --- a/2-ui/1-document/11-modifying-document/8-tree-count/source.view/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - -
        -
      • Животные -
          -
        • Млекопитающие -
            -
          • Коровы
          • -
          • Ослы
          • -
          • Собаки
          • -
          • Тигры
          • -
          -
        • -
        • Другие -
            -
          • Змеи
          • -
          • Птицы
          • -
          • Ящерицы
          • -
          -
        • -
        -
      • -
      • Рыбы -
          -
        • Аквариумные -
            -
          • Гуппи
          • -
          • Скалярии
          • -
          - -
        • -
        • Морские -
            -
          • Морская форель
          • -
          -
        • -
        -
      • -
      - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/8-tree-count/task.md b/2-ui/1-document/11-modifying-document/8-tree-count/task.md deleted file mode 100644 index ac6993b7..00000000 --- a/2-ui/1-document/11-modifying-document/8-tree-count/task.md +++ /dev/null @@ -1,10 +0,0 @@ -# Дерево - -[importance 5] - -Есть дерево [edit src="source"]в песочнице[/edit]. - -Напишите код, который добавит каждому элементу списка `
    • ` количество вложенных в него элементов. Узлы нижнего уровня, без детей -- пропускайте. - -Результат: -[iframe border=1 src="solution"] diff --git a/2-ui/1-document/11-modifying-document/9-calendar-table/solution.md b/2-ui/1-document/11-modifying-document/9-calendar-table/solution.md deleted file mode 100644 index a4e1a7f3..00000000 --- a/2-ui/1-document/11-modifying-document/9-calendar-table/solution.md +++ /dev/null @@ -1,11 +0,0 @@ -Для решения задачи сгенерируем таблицу в виде строки: `"...
      "`, а затем присвоим в `innerHTML`. - -Алгоритм: -
        -
      1. Создать объект даты `d = new Date(year, month-1)`. Это первый день месяца `month` (с учетом того, что месяцы в JS начинаются от 0, а не от 1).
      2. -
      3. Ячейки первого ряда пустые от начала и до дня недели `d.getDay()`, с которого начинается месяц. Создадим их.
      4. -
      5. Увеличиваем день в `d` на единицу: `d.setDate(d.getDate()+1)`, и добавляем в календарь очередную ячейку, пока не достигли следующего месяца. При этом последний день недели означает вставку перевода строки "</tr><tr>".
      6. -
      7. При необходимости, если календарь окончился не на воскресенье - добавить пустые `TD` в таблицу, чтобы было все ровно.
      8. -
      - -[edit src="solution"]Открыть полное решение[/edit] diff --git a/2-ui/1-document/11-modifying-document/9-calendar-table/solution.view/index.html b/2-ui/1-document/11-modifying-document/9-calendar-table/solution.view/index.html deleted file mode 100644 index e0a7443c..00000000 --- a/2-ui/1-document/11-modifying-document/9-calendar-table/solution.view/index.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - -
      - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/9-calendar-table/source.view/index.html b/2-ui/1-document/11-modifying-document/9-calendar-table/source.view/index.html deleted file mode 100644 index 2eedf93e..00000000 --- a/2-ui/1-document/11-modifying-document/9-calendar-table/source.view/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - -
      - - - - - \ No newline at end of file diff --git a/2-ui/1-document/11-modifying-document/9-calendar-table/task.md b/2-ui/1-document/11-modifying-document/9-calendar-table/task.md deleted file mode 100644 index 264fcf6c..00000000 --- a/2-ui/1-document/11-modifying-document/9-calendar-table/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# Создать календарь в виде таблицы - -[importance 4] - -Напишите функцию, которая умеет генерировать календарь для заданной пары (месяц, год). - -Календарь должен быть таблицей, где каждый день -- это `TD`. У таблицы должен быть заголовок с названиями дней недели, каждый день -- `TH`. - -Синтаксис: `createCalendar(id, year, month)`. - -Такой вызов должен генерировать текст для календаря месяца `month` в году `year`, а затем помещать его внутрь элемента с указанным `id`. - -Например: `createCalendar("cal", 2012, 9)` сгенерирует в <div id='cal'></div> следующий календарь: - -[iframe height=210 src="solution"] - -P.S. Достаточно сгенерировать календарь, кликабельным его делать не нужно. - - diff --git a/2-ui/1-document/11-modifying-document/article.md b/2-ui/1-document/11-modifying-document/article.md deleted file mode 100644 index d1cf7a43..00000000 --- a/2-ui/1-document/11-modifying-document/article.md +++ /dev/null @@ -1,412 +0,0 @@ -# Добавление и удаление узлов - -Изменение DOM -- ключ к созданию "живых" страниц. - -В этой главе мы рассмотрим, как создавать новые элементы "на лету" и заполнять их данными. - -[cut] - -## Пример: показ сообщения - -В качестве примера рассмотрим добавление сообщения на страницу, чтобы оно было оформленно красивее чем обычный `alert`. - -HTML-код для сообщения: - -```html - - - -*!* -
      - Ура! Вы прочитали это важное сообщение. -
      -*/!* -``` - -## Создание элемента - -Для создания элементов используются следующие методы: - -
      -
      `document.createElement(tag)`
      -
      Создает новый элемент с указанным тегом: - -```js -var div = document.createElement('div'); -``` - -
      -
      `document.createTextNode(text)`
      -
      Создает новый *текстовый* узел с данным текстом: - -```js -var textElem = document.createTextNode('Тут был я'); -``` - -
      - -### Создание сообщения - -В нашем случае мы хотим сделать DOM-элемент `div`, дать ему классы и заполнить текстом: - -```js -var div = document.createElement('div'); -div.className = "alert alert-success"; -div.innerHTML = "Ура! Вы прочитали это важное сообщение."; -``` - -После этого кода у нас есть готовый DOM-элемент. Пока что он присвоен в переменную `div`, но не виден, так как никак не связан со страницей. - -## Добавление элемента: appendChild, insertBefore - -Чтобы DOM-узел был показан на странице, его необходимо вставить в `document`. - -Для этого первым делом нужно решить, куда мы будем его вставлять. Предположим, что мы решили, что вставлять будем в некий элемент `parentElem`, например `var parentElem = document.body`. - -Для вставки внутрь `parentElem` есть следующие методы: - -
      -
      `parentElem.appendChild(elem)`
      -
      Добавляет `elem` в конец дочерних элементов `parentElem`. - -Следующий пример добавляет новый элемент в конец `
        `: - -```html - -
          -
        1. 0
        2. -
        3. 1
        4. -
        5. 2
        6. -
        - - -``` - -
      -
      `parentElem.insertBefore(elem, nextSibling)`
      -
      Вставляет `elem` в коллекцию детей `parentElem`, перед элементом `nextSibling`. - -Следующий код вставляет новый элемент перед вторым `
    • `: - -```html - -
        -
      1. 0
      2. -
      3. 1
      4. -
      5. 2
      6. -
      - -``` - -Для вставки элемента в начало достаточно указать, что вставлять будем перед первым потомком: - -```js -list.insertBefore(newLi, list.firstChild); -``` - -У читателя, который посмотрит на этот код внимательно, наверняка возникнет вопрос: "А что, если `list` вообще пустой, в этом случае ведь `list.firstChild = null`, произойдёт ли вставка?" - -Ответ -- да, произойдёт. - -**Дело в том, что если вторым аргументом указать `null`, то `insertBefore` сработает как `appendChild`:** - -```js -parentElem.insertBefore(elem, null); -// то же, что и: -parentElem.appendChild(elem) -``` - -Так что `insertBefore` универсален. -
    • -
      - -[smart] -Все методы вставки возвращают вставленный узел. - -Например, `parentElem.appendChild(elem)` возвращает `elem`. -[/smart] - - -### Пример использования - -Добавим сообщение в конец ``: - -```html - - - - -

      Моя страница

      - - - -``` - -...А теперь -- в начало ``: - -```html - - - - -

      Моя страница

      - - - -``` - -## Клонирование узлов: cloneNode - -А как бы вставить второе похожее сообщение? - -Конечно, можно сделать функцию для генерации сообщений и поместить туда этот код, но в ряде случаев гораздо эффективнее -- *клонировать* существующий `div`, а потом изменить текст внутри. В частности, если элемент большой, то клонировать его будет гораздо быстрее, чем пересоздавать. - -Вызов `elem.cloneNode(true)` создаст "глубокую" копию элемента -- вместе с атрибутами, включая подэлементы. Если же вызвать с аргументом `false`, то копия будет сделана без дочерних элементов. Это нужно гораздо реже. - -Пример со вставкой копии сообщения: - -```html - - - - -

      Моя страница

      - - - -``` - -Обратите внимание на последнюю строку, которая вставляет `div2` после `div`: - -```js -div.parentNode.insertBefore(div2, div.nextSibling); -``` - -
        -
      1. Для вставки нам нужен будущий родитель. Мы, возможно, не знаем, где точно находится `div` (или не хотим зависеть от того, где он), но если нужно вставить рядом с `div`, то родителем определённо будет `div.parentNode`.
      2. -
      3. Мы хотели бы вставить *после* `div`, но метода `insertAfter` нет, есть только `insertBefore`, поэтому вставляем *перед* его правым соседом `div.nextSibling`.
      4. -
      - - -## Удаление узлов: removeChild - -Для удаления узла есть два метода: - -
      -
      `parentElem.removeChild(elem)`
      -
      Удаляет `elem` из списка детей `parentElem`.
      -
      `parentElem.replaceChild(newElem, elem)`
      -
      Среди детей `parentElem` удаляет `elem` и вставляет на его место `newElem`.
      -
      - -Оба этих метода возвращают удаленный узел, то есть `elem`. Если нужно, его можно вставить в другое место DOM тут же или в будущем. - -[smart] -Если вы хотите *переместить* элемент на новое место -- не нужно его удалять со старого. - -**Все методы вставки автоматически удаляют вставляемый элемент со старого места.** - -Конечно же, это очень удобно. - -Например, поменяем элементы местами: - -```html - -
      Первый
      -
      Второй
      - -``` - -[/smart] - - -[smart header="Метод `remove`"] - -В современном стандарте есть также метод [elem.remove()](https://dom.spec.whatwg.org/#dom-childnode-remove), который удаляет элемент напрямую, не требуя ссылки на родителя. Это зачастую удобнее, чем `removeChild`. - -Он поддерживается во всех современных браузерах, кроме IE11-. Впрочем, легко подключить или даже сделать полифилл. -[/smart] - -### Удаление сообщения - -Сделаем так, что через секунду сообщение пропадёт: - -```html - - - - -

      Сообщение пропадёт через секунду

      - - - -``` - -## Текстовые узлы для вставки текста - -При работе с сообщением мы использовали только узлы-элементы и `innerHTML`. - -Но и текстовые узлы тоже имеют интересную область применения! - -Если текст для сообщения нужно показать именно как текст, а не как HTML, то можно обернуть его в текстовый узел. - -Например: - -```html - - - - -``` - -В современных браузерах (кроме IE8-) в качестве альтернативы можно использовать присвоение `textContent`. - - -## Итого - -Методы для создания узлов: - -
        -
      • `document.createElement(tag)` -- создает элемент
      • -
      • `document.createTextNode(value)` -- создает текстовый узел
      • -
      • `elem.cloneNode(deep)` -- клонирует элемент, если `deep == true`, то со всеми потомками, если `false` -- без потомков.
      • -
      - -Вставка и удаление узлов: -
        -
      • `parent.appendChild(elem)`
      • -
      • `parent.insertBefore(elem, nextSibling)`
      • -
      • `parent.removeChild(elem)`
      • -
      • `parent.replaceChild(newElem, elem)`
      • -
      - -Все эти методы возвращают `elem`. - -Методы для изменения DOM также описаны в спецификации
      DOM Level 1. - - - - - - - diff --git a/2-ui/1-document/12-multi-insert/1-append-to-list/solution.md b/2-ui/1-document/12-multi-insert/1-append-to-list/solution.md deleted file mode 100644 index f8202293..00000000 --- a/2-ui/1-document/12-multi-insert/1-append-to-list/solution.md +++ /dev/null @@ -1,8 +0,0 @@ -Решение: - -```js -var ul = document.body.children[0]; - -ul.insertAdjacentHTML("beforeEnd", "
    • 3
    • 4
    • 5
    • "); -``` - diff --git a/2-ui/1-document/12-multi-insert/1-append-to-list/task.md b/2-ui/1-document/12-multi-insert/1-append-to-list/task.md deleted file mode 100644 index 88c334f6..00000000 --- a/2-ui/1-document/12-multi-insert/1-append-to-list/task.md +++ /dev/null @@ -1,15 +0,0 @@ -# Вставьте элементы в конец списка - -[importance 5] - -Напишите код для вставки текста `html` в конец списка `ul` с использованием метода `insertAdjacentHTML`. Такая вставка, в отличие от присвоения `innerHTML+=`, не будет перезаписывать текущее содержимое. - -Добавьте к списку ниже элементы `
    • 3
    • 4
    • 5
    • `: - -```html -
        -
      • 1
      • -
      • 2
      • -
      -``` - diff --git a/2-ui/1-document/12-multi-insert/2-sort-table-performance/solution.md b/2-ui/1-document/12-multi-insert/2-sort-table-performance/solution.md deleted file mode 100644 index bd110879..00000000 --- a/2-ui/1-document/12-multi-insert/2-sort-table-performance/solution.md +++ /dev/null @@ -1,11 +0,0 @@ -Для сортировки нам поможет функция `sort` массива. - -Общая идея лежит на поверхности: сделать массив из строк и отсортировать его. Тонкости кроются в деталях. - -В ифрейме ниже загружен документ, описывающий и реализующий разные алгоритмы. Обратите внимание: разница в производительности может достигать нескольких раз! - -[iframe height=800 border=1 src="solution" link edit] - -P.S. Создавать `DocumentFragment` здесь ни к чему. Можно вытащить из документа `TBODY` и иметь дело с ним в отрыве от DOM (алгоритм 4). - -P.P.S. Если нужно сделать много узлов, то обычно `innerHTML` работает быстрее, чем удаление и вставка элементов через DOM-вызовы. То есть, сгенерировать таблицу заново эффективнее. diff --git a/2-ui/1-document/12-multi-insert/2-sort-table-performance/solution.view/index.html b/2-ui/1-document/12-multi-insert/2-sort-table-performance/solution.view/index.html deleted file mode 100644 index 9f249c03..00000000 --- a/2-ui/1-document/12-multi-insert/2-sort-table-performance/solution.view/index.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - -
      - Алгоритм 1. -
        -
      1. Все TR удалить из таблицы, при этом собрав их в JavaScript-массив.
      2. -
      3. Отсортировать этот массив, используя свою функцию в sort(...) для сравнения TR
      4. -
      5. Добавить TR из массива в таблицу в нужном порядке
      6. -
      - -
      - -
      - Алгоритм 2. -
        -
      1. Скопировать TR в JavaScript-массив.
      2. -
      3. Отсортировать этот массив, используя свою функцию в sort(...) для сравнения TR
      4. -
      5. Добавить TR из массива в таблицу в нужном порядке. При добавлении каждый TR сам удалится с предыдущего места.
      6. -
      - -
      - - -
      - Алгоритм 3. -
        -
      1. Создать массив из объектов вида {elem: ссылка на TR, value: содержимое TR}.
      2. -
      3. Отсортировать массив по value. Функция сравнения во время сортировки теперь будет обращаться не к innerHTML, а к свойству объекта, это быстрее. Сортировка может потребовать многократных сравнений одного и того же элемента, - отсюда выигрыш.
      4. -
      5. Добавить TR в таблицу в нужном порядке (автоудалятся с предыдущего места).
      6. -
      - -
      - - -
      - Алгоритм 4. -
        -
      1. Выполнить алгоритм 3, но перед этим удалить таблицу из документа, а после - вставить обратно.
      2. -
      - -
      - -
      - Алгоритм 5. -
        -
      1. Замерить время генерации таблицы (создаётся строка и пишется в innerHTML).
      2. -
      - -
      - -
      - - - - - - -

      Содержимое документа для придания "реалистичности"

      - -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      -
      01234567890123456789012345678901234567890123456789
      - - - - - \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/2-sort-table-performance/task.md b/2-ui/1-document/12-multi-insert/2-sort-table-performance/task.md deleted file mode 100644 index 600836e0..00000000 --- a/2-ui/1-document/12-multi-insert/2-sort-table-performance/task.md +++ /dev/null @@ -1,48 +0,0 @@ -# Отсортировать таблицу - -[importance 5] - -Есть таблица: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      ИмяФамилияОтчествоВозраст
      ВасяПетровАлександрович10
      ПетяИвановПетрович15
      ВладимирЛенинИльич9
      ............
      - -Строк в таблице много: может быть 20, 50, 100.. Есть и другие элементы в документе. - -Как бы вы предложили отсортировать содержимое таблицы по полю `Возраст`? Обдумайте алгоритм, реализуйте его. - -Как сделать, чтобы сортировка работала как можно быстрее? А если в таблице 10000 строк (бывает и такое)? - -P.S. Может ли здесь помочь `DocumentFragment`? - -P.P.S. Если предположить, что у нас заранее есть массив данных для таблицы в JavaScript -- что быстрее: отсортировать эту таблицу или сгенерировать новую? \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/article.md b/2-ui/1-document/12-multi-insert/article.md deleted file mode 100644 index 80c7576e..00000000 --- a/2-ui/1-document/12-multi-insert/article.md +++ /dev/null @@ -1,362 +0,0 @@ -# Мультивставка: insertAdjacentHTML и DocumentFragment - -Обычные методы вставки работают с одним узлом. Но есть и способы вставлять множество узлов одновременно. -[cut] -## Оптимизация вставки в документ - -Рассмотрим задачу: сгенерировать список `UL/LI`. - -Есть две возможных последовательности: - -
        -
      1. Сначала вставить `UL` в документ, а потом добавить к нему `LI`: - -```js -var ul = document.createElement('ul'); -document.body.appendChild(ul); // сначала в документ -for (...) ul.appendChild(li); // потом узлы -``` - -
      2. -
      3. Полностью создать список "вне DOM", а потом -- вставить в документ: - -```js -//+ no-beautify -var ul = document.createElement('ul'); -for(...) ul.appendChild(li); // сначала вставить узлы -document.body.appendChild(ul); // затем в документ -``` - -
      4. -
      - -Как ни странно, между этими последовательностями есть разница. В большинстве браузеров, второй вариант -- быстрее. - -Почему же? Иногда говорят: "потому что браузер перерисовывает каждый раз при добавлении элемента". Это не так. Дело вовсе не в перерисовке. - -Браузер достаточно "умён", чтобы ничего не перерисовывать понапрасну. В большинстве случаев процессы перерисовки и сопутствующие вычисления будут отложены до окончания работы скрипта, и на тот момент уже совершенно без разницы, в какой последовательности были изменены узлы. - -**Тем не менее, при вставке узла происходят разные внутренние события и обновления внутренних структур данных, скрытые от наших глаз.** - -Что именно происходит -- зависит от конкретной, внутренней браузерной реализации DOM, но это отнимает время. Конечно, браузеры развиваются и стараются свести лишние действия к минимуму. - -[online] -### Бенчмарк [#insert-bench-tbody] - -Чтобы легко проверить текущее состояние дел -- вот два бенчмарка. - -Оба они создают таблицу 20x20, наполняя TBODY элементами TR/TD. - -При этом первый вставляет все в документ тут же, второй -- задерживает вставку TBODY в документ до конца процесса. - -Кликните, чтобы запустить. - - - -
      - -Код для тестов находится в файле [insert-bench.js](insert-bench.js). - -[/online] -## Добавление множества узлов - -Продолжим работать со вставкой узлов. - -Рассмотрим случай, когда в документе *уже есть* большой список `UL`. И тут понадобилось срочно добавить еще 20 элементов `LI`. - -Как это сделать? - -Если новые элементы пришли в виде строки, то можно попробовать добавить их так: - -```js -ul.innerHTML += "
    • 1
    • 2
    • ..."; -``` - -Но операцию `ul.innerHTML += "..."` можно по-другому переписать как `ul.innerHTML = ul.innerHTML + "..."`. Иначе говоря, она *не прибавляет, а заменяет* всё содержимое списка на дополненную строку. Это и нехорошо с точки зрения производительности, но и будут побочные эффекты. В частности, все внешние ресурсы (картинки) внутри перезаписываемого `innerHTML` будут загружены заново. Если в каких-то переменных были ссылки на элементы списка -- они станут неверны, так как содержимое полностью заменяется.В общем, так лучше не делать. - -А если нужно вставить в середину списка? Здесь `innerHTML` вообще не поможет. - -Можно, конечно, вставить строку во временный DOM-элемент и перенести оттуда элементы, но есть и гораздо лучший вариант: метод `insertAdjacentHTML`! - -## insertAdjacent* - -Метод [insertAdjacentHTML](https://developer.mozilla.org/en/DOM/element.insertAdjacentHTML) позволяет вставлять произвольный HTML в любое место документа, в том числе *и между узлами*! - -Он поддерживается всеми браузерами, кроме Firefox меньше версии 8, ну а там его можно эмулировать. - -Синтаксис: - -```js -elem.insertAdjacentHTML(where, html); -``` - -
      -
      `html`
      -
      Строка HTML, которую нужно вставить
      -
      `where`
      -
      Куда по отношению к `elem` вставлять строку. Всего четыре варианта: -
        -
      1. `beforeBegin` -- перед `elem`.
      2. -
      3. `afterBegin` -- внутрь `elem`, в самое начало.
      4. -
      5. `beforeEnd` -- внутрь `elem`, в конец.
      6. -
      7. `afterEnd` -- после `elem`.
      8. -
      -
      - - - -Например, вставим пропущенные элементы списка *перед* `
    • 5
    • `: - -```html - -
        -
      • 1
      • -
      • 2
      • -
      • 5
      • -
      - - -``` - -Единственный недостаток этого метода -- он не работает в Firefox до версии 8. Но его можно легко добавить, используя [полифилл insertAdjacentHTML для Firefox](insertAdjacentFF.js). - -У этого метода есть "близнецы-братья", которые поддерживаются везде, кроме Firefox, но в него они добавляются тем же полифиллом: - -
        -
      • [elem.insertAdjacentElement(where, newElem)](http://help.dottoro.com/ljbreokf.php) -- вставляет в произвольное место не строку HTML, а элемент `newElem`.
      • -
      • [elem.insertAdjacentText(where, text)](http://help.dottoro.com/ljrsluxu.php) -- создаёт текстовый узел из строки `text` и вставляет его в указанное место относительно `elem`.
      • -
      - -Синтаксис этих методов, за исключением последнего параметра, полностью совпадает с `insertAdjacentHTML`. Вместе они образуют "универсальный швейцарский нож" для вставки чего угодно куда угодно. - -## DocumentFragment - -[warn header="Важно для старых браузеров"] -Оптимизация, о которой здесь идёт речь, важна в первую очередь для старых браузеров, включая IE9-. В современных браузерах эффект от нее, как правило, небольшой, а иногда может быть и отрицательным. -[/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`, и потом вызвать `ul.appendChild(fragment)`, то фрагмент растворится, и в DOM вставятся именно `LI`, причём в том же порядке, в котором были во фрагменте. - -Псевдокод: - -```js -// хотим вставить в список UL много LI - -// делаем вспомогательный DocumentFragment -var fragment = document.createDocumentFragment(); - -for (цикл по li) { - fragment.appendChild(list[i]); // вставить каждый LI в DocumentFragment -} - -ul.appendChild(fragment); // вместо фрагмента вставятся элементы списка -``` - -В современных браузерах эффект от такой оптимизации может быть различным, а на небольших документах иногда и отрицательным. - -Понять текущее положение вещей вы можете, запустив следующий [edit src="benchmark"]небольшой бенчмарк[/edit]. - -## append/prepend, before/after, replaceWith - -Сравнительно недавно в [стандарте](https://dom.spec.whatwg.org/) появились методы, которые позволяют вставить что угодно и куда угодно. - -Синтаксис: - -
        -
      • `node.append(...nodes)` -- вставляет `nodes` в конец `node`,
      • -
      • `node.prepend(...nodes)` -- вставляет `nodes` в начало `node`,
      • -
      • `node.after(...nodes)` -- вставляет `nodes` после узла `node`,
      • -
      • `node.before(...nodes)` -- вставляет `nodes` перед узлом `node`,
      • -
      • `node.replaceWith(...nodes)` -- вставляет `nodes` вместо `node`.
      • -
      - -Эти методы ничего не возвращают. - -Во всех этих методах `nodes` -- DOM-узлы или строки, в любом сочетании и количестве. Причём строки вставляются именно как текстовые узлы, в отличие от `insertAdjacentHTML`. - - -Пример (с полифиллом): -```html - - - - - - - - - - - - - - -``` - -## Итого - -
        -
      • Манипуляции, меняющие структуру DOM (вставка, удаление элементов), как правило, быстрее с отдельным маленьким узлом, чем с большим DOM, который находится в документе. - -Конкретная разница зависит от внутренней реализации DOM в браузере.
      • -
      • Семейство методов для вставки HTML/элемента/текста в произвольное место документа: -
          -
        • `elem.insertAdjacentHTML(where, html)`
        • -
        • `elem.insertAdjacentElement(where, node)`
        • -
        • `elem.insertAdjacentText(where, text)`
        • -
        - -Два последних метода не поддерживаются в Firefox, на момент написания текста, но есть небольшой полифилл [insertAdjacentFF.js](insertAdjacentFF.js), который добавляет их. Конечно, он нужен только для Firefox. -
      • -
      • `DocumentFragment` позволяет минимизировать количество вставок в большой живой DOM. Эта оптимизация особо эффективна в старых браузерах, в новых эффект от неё меньше или наоборот отрицательный. - -Элементы сначала вставляются в него, а потом -- он вставляется в DOM. При вставке `DocumentFragment` "растворяется", и вместо него вставляются содержащиеся в нём узлы. - -`DocumentFragment`, в отличие от `insertAdjacent*`, работает с коллекцией DOM-узлов. -
      • -
      • Современные методы, работают с любым количеством узлов и текста, желателен полифилл: -
          -
        • `append/prepend` -- вставка в конец/начало.
        • -
        • `before/after` -- вставка после/перед.
        • -
        • `replaceWith` -- замена.
        • -
        -
      • - -
      - - -[head] - - - -[/head] \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/benchmark.view/bench.js b/2-ui/1-document/12-multi-insert/benchmark.view/bench.js deleted file mode 100644 index e06acecb..00000000 --- a/2-ui/1-document/12-multi-insert/benchmark.view/bench.js +++ /dev/null @@ -1,11 +0,0 @@ -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; -} \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/benchmark.view/documentfragment-bench.js b/2-ui/1-document/12-multi-insert/benchmark.view/documentfragment-bench.js deleted file mode 100644 index 8e875cf1..00000000 --- a/2-ui/1-document/12-multi-insert/benchmark.view/documentfragment-bench.js +++ /dev/null @@ -1,45 +0,0 @@ -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); - } - - }; -} \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/benchmark.view/index.html b/2-ui/1-document/12-multi-insert/benchmark.view/index.html deleted file mode 100644 index 12fecd91..00000000 --- a/2-ui/1-document/12-multi-insert/benchmark.view/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - Вставляются 100 элементов LI в пустой UL. - - - - - -
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/documentfragment-bench.js b/2-ui/1-document/12-multi-insert/documentfragment-bench.js deleted file mode 100755 index 8e875cf1..00000000 --- a/2-ui/1-document/12-multi-insert/documentfragment-bench.js +++ /dev/null @@ -1,45 +0,0 @@ -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); - } - - }; -} \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/insert-bench.js b/2-ui/1-document/12-multi-insert/insert-bench.js deleted file mode 100755 index 95a9c2a6..00000000 --- a/2-ui/1-document/12-multi-insert/insert-bench.js +++ /dev/null @@ -1,59 +0,0 @@ -/* 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); - } - -} \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/insertAdjacentFF.js b/2-ui/1-document/12-multi-insert/insertAdjacentFF.js deleted file mode 100644 index 5d619e44..00000000 --- a/2-ui/1-document/12-multi-insert/insertAdjacentFF.js +++ /dev/null @@ -1,35 +0,0 @@ -// http://learn.javascript.ru/files/tutorial/browser/dom/insertAdjacentFF.js -// Добавляет поддержку insertAdjacent* в Firefox - -if (typeof HTMLElement != "undefined" && !HTMLElement.prototype.insertAdjacentElement) { - HTMLElement.prototype.insertAdjacentElement = function(where, parsedNode) { - switch (where) { - case 'beforeBegin': - this.parentNode.insertBefore(parsedNode, this) - break; - case 'afterBegin': - this.insertBefore(parsedNode, this.firstChild); - break; - case 'beforeEnd': - this.appendChild(parsedNode); - break; - case 'afterEnd': - if (this.nextSibling) this.parentNode.insertBefore(parsedNode, this.nextSibling); - else this.parentNode.appendChild(parsedNode); - break; - } - } - - HTMLElement.prototype.insertAdjacentHTML = function(where, htmlStr) { - var r = this.ownerDocument.createRange(); - r.setStartBefore(this); - var parsedHTML = r.createContextualFragment(htmlStr); - this.insertAdjacentElement(where, parsedHTML) - } - - - HTMLElement.prototype.insertAdjacentText = function(where, txtStr) { - var parsedText = document.createTextNode(txtStr) - this.insertAdjacentElement(where, parsedText) - } -} \ No newline at end of file diff --git a/2-ui/1-document/12-multi-insert/insertAdjacentHTML.png b/2-ui/1-document/12-multi-insert/insertAdjacentHTML.png deleted file mode 100755 index e9ac590b..00000000 Binary files a/2-ui/1-document/12-multi-insert/insertAdjacentHTML.png and /dev/null differ diff --git a/2-ui/1-document/13-document-write/article.md b/2-ui/1-document/13-document-write/article.md deleted file mode 100644 index ae83994f..00000000 --- a/2-ui/1-document/13-document-write/article.md +++ /dev/null @@ -1,149 +0,0 @@ -# Метод document.write - -Метод `document.write` -- один из наиболее древних методов добавления текста к документу. - -У него есть существенные ограничения, поэтому он используется редко, но по своей сути он совершенно уникален и иногда, хоть и редко, может быть полезен. -[cut] -## Как работает document.write - -Метод `document.write(str)` работает только пока HTML-страница находится в процессе загрузки. Он дописывает текст в текущее место HTML ещё до того, как браузер построит из него DOM. - -HTML-документ ниже будет содержать `1 2 3`. - -```html - - - 1 - - 3 - -``` - -**Нет никаких ограничений на содержимое `document.write`**. - -Строка просто пишется в HTML-документ без проверки структуры тегов, как будто она всегда там была. - -Например: - -```html - - - - - - Текст внутри TD. - - -
        -``` - -Также существует метод `document.writeln(str)` -- не менее древний, который добавляет после `str` символ перевода строки `"\n"`. - -## Только до конца загрузки - -Во время загрузки браузер читает документ и тут же строит из него DOM, по мере получения информации достраивая новые и новые узлы, и тут же отображая их. Этот процесс идет непрерывным потоком. Вы наверняка видели это, когда заходили на сайты в качестве посетителя -- браузер зачастую отображает неполный документ, добавляя его новыми узлами по мере их получения. - -**Методы `document.write` и `document.writeln` пишут напрямую в текст документа, до того как браузер построит из него DOM, поэтому они могут записать в документ все, что угодно, любые стили и незакрытые теги.** - -Браузер учтёт их при построении DOM, точно так же, как учитывает очередную порцию HTML-текста. - -Технически, вызвать `document.write` можно в любое время, однако, когда HTML загрузился, и браузер полностью построил DOM, документ становится *"закрытым"*. Попытка дописать что-то в закрытый документ открывает его заново. При этом все текущее содержимое удаляется. - -Текущая страница, скорее всего, уже загрузилась, поэтому если вы нажмёте на эту кнопку -- её содержимое удалится: - -[pre no-typography] - -[/pre] - -Из-за этой особенности `document.write` для загруженных документов не используют. - -[warn header="XHTML и `document.write`"] -В некоторых современных браузерах при получении страницы с заголовком `Content-Type: text/xml` или `Content-Type: text/xhtml+xml` включается "XML-режим" чтения документа. Метод `document.write` при этом не работает. - -Это лишь одна из причин, по которой XML-режим обычно не используют. -[/warn] - - -## Преимущества перед innerHTML - -Метод `document.write` -- динозавр, он существовал десятки миллионов лет назад. С тех пор, как появился и стал стандартным метод `innerHTML`, нужда в нём возникает редко, но некоторые преимущества, всё же, есть. - -
          -
        • Метод `document.write` работает быстрее, фактически это самый быстрый способ добавить на страницу текст, сгенерированный скриптом. - -Это естественно, ведь он не модифицирует существующий DOM, а пишет в текст страницы до его генерации.
        • -
        • Метод `document.write` вставляет любой текст на страницу "как есть", в то время как `innerHTML` может вписать лишь валидный HTML (при попытке подсунуть невалидный -- браузер скорректирует его).
        • -
        - -Эти преимущества являются скорее средством оптимизации, которое нужно использовать именно там, где подобная оптимизация нужна или уместна. - -Однако, `document.write` по своей природе уникален: он добавляет текст "в текущее место документа", без всяких хитроумных DOM. Поэтому он бывает просто-напросто удобен, из-за чего его нередко используют не по назначению. - -## Антипример: реклама - -Например, `document.write` используют для вставки рекламных скриптов и различных счетчиков, когда URL скрипта необходимо генерировать динамически, добавляя в него параметры из JavaScript, например: - -```html - -``` - -[smart] -Закрывающий тег </script> в строке разделён, чтобы браузер не увидел `` и не посчитал его концом скрипта. - -Также используют запись: - -```js -document.write('`: обратный слеш `\` обычно используется для вставки спецсимволов типа `\n`, а если такого спецсимвола нет, в данном случае `\/` не является спецсимволом, то будет проигнорирован. Так что получается такой альтернативный способ безопасно вставить строку ``. -[/smart] - -Сервер, получив запрос с такими параметрами, обрабатывает его и, учитывая переданную информацию, генерирует текст скрипта, в котором обычно есть какой-то другой `document.write`, рисующий на этом месте баннер. - -**Проблема здесь в том, что загрузка такого скрипта блокирует отрисовку всей страницы.** - -То есть, дело даже не в самом `document.write`, а в том, что в страницу вставляется сторонний скрипт, а браузер устроен так, что пока он его не загрузит и не выполнит -- он не будет дальше строить DOM и показывать документ. - -Представим на минуту, что сервер `ads.com`, с которого грузится скрипт, работает медленно или вообще завис -- зависнет и наша страница. - -Что делать? - -В современных браузерах у скриптов есть атрибуты `async` и `defer`, которые разрешают браузеру продолжать обработку страницы, но применить их здесь нельзя, так как рекламный скрипт захочет вызвать `document.write` именно на этом месте, и браузер не должен двигаться вперёд по документу. - -Альтернатива -- использовать другие техники вставки рекламы и счётчиков. Примеры вы можете увидеть в коде Google Analytics, Яндекс.Метрики и других. - -Если это невозможно -- применяют всякие хитрые оптимизации, например заменяют метод `document.write` своей функцией, и она уже разбирается со скриптами и баннерами. - -## Итого - -Метод `document.write` (или `writeln`) пишет текст прямо в HTML, как будто он там всегда был. - -
          -
        • Этот метод редко используется, так как работает только из скриптов, выполняемых в процессе загрузки страницы. - -Запуск после загрузки приведёт к очистке документа.
        • -
        • Метод `document.write` очень быстр. - -В отличие от установки `innerHTML` и DOM-методов, он не изменяет существующий документ, а работает на стадии текста, до того как DOM-структура сформирована.
        • -
        • Иногда `document.write` используют для добавления скриптов с динамическим URL. - -Рекомендуется избегать этого, так как браузер остановится на месте добавления скрипта и будет ждать его загрузки. Если скрипт будет тормозить, то и страница -- тоже. - -Поэтому желательно подключать внешние скрипты, используя вставку скрипта через DOM или `async/defer`. Современные системы рекламы и статистики так и делают. -
        • -
        diff --git a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/solution.md b/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/solution.md deleted file mode 100644 index c78f0136..00000000 --- a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/solution.md +++ /dev/null @@ -1,43 +0,0 @@ -Есть два варианта. - -
          -
        1. Можно использовать свойство `elem.style.cssText` и присвоить стиль в текстовом виде. При этом все присвоенные ранее свойства `elem.style` будут удалены.
        2. -
        3. Можно назначить подсвойства `elem.style` одно за другим. Этот способ более безопасен, т.к. меняет только явно присваемые свойства.
        4. -
        - -Мы выберем второй путь. - -[edit src="solution"]Открыть решение[/edit] - -**Описание CSS-свойств:** - -```css -.button { - -moz-border-radius: 8px; - -webkit-border-radius: 8px; - border-radius: 8px; - border: 2px groove green; - display: block; - height: 30px; - line-height: 30px; - width: 100px; - text-decoration: none; - text-align: center; - color: red; - font-weight: bold; -} -``` - -
        -
        `*-border-radius`
        -
        Добавляет скругленные углы. Свойство присваивается в вариантах для Firefox `-moz-...`, Chrome/Safari `-webkit-...` и стандартное CSS3-свойство для тех, кто его поддерживает (Opera).
        -
        `display`
        -
        По умолчанию, у `A` это свойство имеет значение `display: inline`.
        -
        `height`, `line-height`
        -
        Устанавливает высоту и делает текст вертикально центрированным путем установки `line-height` в значение, равное высоте. Такой способ центрирования текста работает, если он состоит из одной строки.
        -
        `text-align`
        -
        Центрирует текст горизонтально.
        -
        `color`, `font-weight`
        -
        Делает текст красным и жирным.
        -
        - diff --git a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/solution.view/index.html b/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/solution.view/index.html deleted file mode 100755 index bf487e59..00000000 --- a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/solution.view/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - -
        - Кнопка: - -
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/source.view/index.html b/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/source.view/index.html deleted file mode 100755 index 3492198b..00000000 --- a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/source.view/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - -
        - Кнопка: - -
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/task.md b/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/task.md deleted file mode 100644 index 78da0a79..00000000 --- a/2-ui/1-document/14-styles-and-classes/1-round-button-javascript/task.md +++ /dev/null @@ -1,33 +0,0 @@ -# Скругленая кнопка со стилями из JavaScript - -[importance 3] - -Создайте кнопку в виде элемента `` с заданным стилем, используя JavaScript. - -В примере ниже такая кнопка создана при помощи HTML/CSS. В вашем решении кнопка должна создаваться, настраиваться и добавляться в документ при помощи *только JavaScript*, без тегов ` - -Нажми меня -``` - -**Проверьте себя: вспомните, что означает каждое свойство. В чём состоит эффект его появления здесь?** - - diff --git a/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.md b/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.md deleted file mode 100644 index e69de29b..00000000 diff --git a/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.view/index.css b/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.view/index.css deleted file mode 100755 index b21a80be..00000000 --- a/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.view/index.css +++ /dev/null @@ -1,14 +0,0 @@ -.notification { - position: fixed; - z-index: 1000; - padding: 5px; - border: 1px solid black; - font: normal 20px Georgia; - background: white; - text-align: center; -} - -.welcome { - background: red; - color: yellow; -} \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.view/index.html b/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.view/index.html deleted file mode 100755 index 07ba7a03..00000000 --- a/2-ui/1-document/14-styles-and-classes/2-create-notification/solution.view/index.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - -

        Уведомление

        - -

        - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum - magnam consequatur sit laboriosam velit excepturi laborum sequi eos placeat et quia deleniti? Corrupti velit impedit autem et obcaecati fuga debitis nemo ratione iste veniam amet dicta hic ipsam unde cupiditate incidunt aut iure ipsum officiis soluta - temporibus. Tempore dicta ullam delectus numquam consectetur quisquam explicabo culpa excepturi placeat quo sequi molestias reprehenderit hic at nemo cumque voluptates quidem repellendus maiores unde earum molestiae ad. -

        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/2-create-notification/source.view/index.css b/2-ui/1-document/14-styles-and-classes/2-create-notification/source.view/index.css deleted file mode 100755 index b21a80be..00000000 --- a/2-ui/1-document/14-styles-and-classes/2-create-notification/source.view/index.css +++ /dev/null @@ -1,14 +0,0 @@ -.notification { - position: fixed; - z-index: 1000; - padding: 5px; - border: 1px solid black; - font: normal 20px Georgia; - background: white; - text-align: center; -} - -.welcome { - background: red; - color: yellow; -} \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/2-create-notification/source.view/index.html b/2-ui/1-document/14-styles-and-classes/2-create-notification/source.view/index.html deleted file mode 100755 index dd184fce..00000000 --- a/2-ui/1-document/14-styles-and-classes/2-create-notification/source.view/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - -

        Уведомление

        - -

        - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum aspernatur quam ex eaque inventore quod voluptatem adipisci omnis nemo nulla fugit iste numquam ducimus cumque minima porro ea quidem maxime necessitatibus beatae labore soluta voluptatum - magnam consequatur sit laboriosam velit excepturi laborum sequi eos placeat et quia deleniti? Corrupti velit impedit autem et obcaecati fuga debitis nemo ratione iste veniam amet dicta hic ipsam unde cupiditate incidunt aut iure ipsum officiis soluta - temporibus. Tempore dicta ullam delectus numquam consectetur quisquam explicabo culpa excepturi placeat quo sequi molestias reprehenderit hic at nemo cumque voluptates quidem repellendus maiores unde earum molestiae ad. -

        - -

        В CSS есть готовый класс notification, который можно ставить уведомлению.

        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/2-create-notification/task.md b/2-ui/1-document/14-styles-and-classes/2-create-notification/task.md deleted file mode 100644 index bc89ddcc..00000000 --- a/2-ui/1-document/14-styles-and-classes/2-create-notification/task.md +++ /dev/null @@ -1,41 +0,0 @@ -# Создать уведомление - -[importance 5] - -Напишите функцию `showNotification(options)`, которая показывает уведомление, пропадающее через 1.5 сек. - -Описание функции: - -```js -/** - * Показывает уведомление, пропадающее через 1.5 сек - * - * @param options.top {number} вертикальный отступ, в px - * @param options.right {number} правый отступ, в px - * @param options.cssText {string} строка стиля - * @param options.className {string} CSS-класс - * @param options.html {string} HTML-текст для показа - */ -function showNotification(options) { - // ваш код -} -``` - -Пример использования: - -```js -// покажет элемент с текстом "Привет" и классом welcome справа-сверху окна -showNotification({ - top: 10, - right: 10, - html: "Привет", - className: "welcome" -}); -``` - -[demo src="solution"] - -Элемент уведомления должен иметь CSS-класс `notification`, к которому добавляется класс из `options.className`, если есть. Исходный документ содержит готовые стили. - - - diff --git a/2-ui/1-document/14-styles-and-classes/article.md b/2-ui/1-document/14-styles-and-classes/article.md deleted file mode 100644 index ad9b8893..00000000 --- a/2-ui/1-document/14-styles-and-classes/article.md +++ /dev/null @@ -1,319 +0,0 @@ -# Стили, getComputedStyle - -Эта глава -- о свойствах стиля, получении о них информации и изменении при помощи JavaScript. - -Перед прочтением убедитесь, что хорошо знакомы с [блочной моделью CSS](http://www.w3.org/TR/CSS2/box.html) и понимаете, что такое `padding`, `margin`, `border`. - -[cut] - -## Стили элемента: свойство style - -Объект `element.style` дает доступ к стилю элемента на чтение и запись. - -С его помощью можно изменять большинство CSS-свойств, например `element.style.width="100px"` работает так, как будто у элемента в атрибуте прописано `style="width:100px"`. - -[warn header="Единицы измерения обязательны в `style`"] -Об этом иногда забывают, но в `style` так же, как и в CSS, нужно указывать единицы измерения, например `px`. - -Ни в коем случае не просто `elem.style.width = 100` -- работать не будет. -[/warn] - -**Для свойств, названия которых состоят из нескольких слов, используется вотТакаяЗапись:** - -```js -//+ no-beautify -background-color => elem.style.backgroundColor -z-index => elem.style.zIndex -border-left-width => elem.style.borderLeftWidth -``` - -Пример использования `style`: - -```js -//+ run -document.body.style.backgroundColor = prompt('background color?', 'green'); -``` - -[warn header="`style.cssFloat` вместо `style.float`"] -Исключением является свойство `float`. В старом стандарте JavaScript слово `"float"` было зарезервировано и недоступно для использования в качестве свойства объекта. Поэтому используется не `elem.style.float`, а `elem.style.cssFloat`. -[/warn] - -[smart header="Свойства с префиксами"] -Специфические свойства браузеров, типа `-moz-border-radius`, `-webkit-border-radius`, записываются следующим способом: - -```js -button.style.MozBorderRadius = '5px'; -button.style.WebkitBorderRadius = '5px'; -``` - -То есть, каждый дефис даёт большую букву. -[/smart] - -**Чтобы сбросить поставленный стиль, присваивают в `style` пустую строку: `elem.style.width=""`.** - -При сбросе свойства `style` стиль будет взят из CSS. - -Например, для того, чтобы спрятать элемент, можно присвоить: `elem.style.display = "none"`. - -А вот чтобы показать его обратно -- не обязательно явно указывать другой `display`, наподобие `elem.style.display = "block"`. Можно просто снять поставленный стиль: `elem.style.display = ""`. - -```js -//+ run -// если запустить этот код, то "мигнёт" -document.body.style.display = "none"; - -setTimeout(function() { - document.body.style.display = ""; -}, 1000); -``` - -**Стиль в `style` находится в формате браузера, а не в том, в котором его присвоили.** - -Например: - -```html - - - - -``` - -Обратите внимание на то, как браузер "распаковал" свойство `style.margin`, предоставив для чтения `style.marginTop`. То же самое произойдет и для `border`, `background` и т.д. - - -[warn header="Свойство `style` мы используем лишь там, где не работают классы"] -В большинстве случаев внешний вид элементов задаётся классами. А JavaScript добавляет или удаляет их. Такой код красив и гибок, дизайн можно легко изменять. - -Свойство `style` нужно использовать лишь там, где классы не подходят, например если точное значение цвета/отступа/высоты вычисляется в JavaScript. -[/warn] - - -### Строка стилей style.cssText - -Свойство `style` является специальным объектом, ему нельзя присваивать строку. - -Запись `div.style="color:blue"` работать не будет. Но как же, всё-таки, поставить свойство стиля, если хочется задать его строкой? - -Можно попробовать использовать атрибут: `elem.setAttribute("style", ...)`, но самым правильным и, главное, кросс-браузерным (с учётом старых IE) решением такой задачи будет использование свойства `style.cssText`. - -**Свойство `style.cssText` позволяет поставить стиль целиком в виде строки.** - -Например: - -```html - -
        Button
        - - -``` - -Браузер разбирает строку `style.cssText` и применяет известные ему свойства. Неизвестные, наподобие `blabla`, большинство браузеров просто проигнорируют. - -**При установке `style.cssText` все предыдущие свойства `style` удаляются.** - -Итак, `style.cssText` осуществляет полную перезапись `style`. Если же нужно заменить какое-то конкретно свойство стиля, то обращаются именно к нему: `style.color`, `style.width` и т.п, чтобы не затереть что-то важное по ошибке. - -Свойство `style.cssText` используют, например, для новосозданных элементов, когда старых стилей точно нет. - -### Чтение стиля из style - -Записать в стиль очень просто. А как прочитать? - -Например, мы хотим узнать размер, отступы элемента, его цвет... Как это сделать? - -**Свойство `style` содержит лишь тот стиль, который указан в атрибуте элемента, без учёта каскада CSS.** - -Вот так `style` уже ничего не увидит: - -```html - - - - - - - Красный текст - - -``` - -## Полный стиль из getComputedStyle - -Итак, свойство `style` дает доступ только к той информации, которая хранится в `elem.style`. - -Он не скажет ничего об отступе, если он появился в результате наложения CSS или встроенных стилей браузера: - -А если мы хотим, например, сделать анимацию и плавно увеличивать `marginTop` от текущего значения? Как нам сделать это? Ведь для начала нам надо это текущее значение получить. - -**Для того, чтобы получить текущее используемое значение свойства, используется метод `window.getComputedStyle`, описанный в стандарте DOM Level 2.** - -Его синтаксис таков: - -```js -getComputedStyle(element[, pseudo]) -``` - -
        -
        element
        -
        Элемент, значения для которого нужно получить
        -
        pseudo
        -
        Указывается, если нужен стиль псевдо-элемента, например `"::before"`. Пустая строка или отсутствие аргумента означают сам элемент.
        -
        - -Поддерживается всеми браузерами, кроме IE8-. Следующий код будет работать во всех не-IE браузерах и в IE9+: - -```html - - - - - - - - -``` - -[smart header="Вычисленное (computed) и окончательное (resolved) значения"] -В CSS есть две концепции: -
          -
        1. *Вычисленное* (computed) значение -- это то, которое получено после применения всех правил CSS и CSS-наследования. Например, `width: auto` или `font-size: 125%`.
        2. -
        3. *Окончательное* ([resolved](http://dev.w3.org/csswg/cssom/#resolved-values)) значение -- непосредственно применяемое к элементу. При этом все размеры приводятся к пикселям, например `width: 212px` или `font-size: 16px`. В некоторых браузерах пиксели могут быть дробными.
        4. -
        -Когда-то `getComputedStyle` задумывалось для возврата вычисленного значения, но со временем оказалось, что окончательное гораздо удобнее. - -Поэтому сейчас в целом все значения возвращаются именно окончательные, кроме некоторых небольших глюков в браузерах, которые постепенно вычищаются. -[/smart] - -[warn header="`getComputedStyle` требует полное свойство!"] -Для правильного получения значения нужно указать точное свойство. Например: `paddingLeft`, `marginTop`, `borderLeftWidth`. - -**При обращении к сокращенному: `padding`, `margin`, `border` -- правильный результат не гарантируется.** - -Действительно, допустим свойства `paddingLeft/paddingTop` взяты из разных классов CSS. Браузер не обязан объединять их в одно свойство `padding`. Иногда, в простейших случаях, когда свойство задано сразу целиком, `getComputedStyle` сработает для сокращённого свойства, но не во всех браузерах. - -Например, некоторые браузеры (Chrome) выведут `10px` в документе ниже, а некоторые (Firefox) -- нет: - -```html - - - -``` - -[/warn] - - -[smart header="Стили посещенных ссылок -- тайна!"] -У посещенных ссылок может быть другой цвет, фон, чем у обычных. Это можно поставить в CSS с помощью псевдокласса `:visited`. - -Но `getComputedStyle` не дает доступ к этой информации, чтобы произвольная страница не могла определить, посещал ли пользователь ту или иную ссылку. - -Кроме того, большинство браузеров запрещают применять к `:visited` CSS-стили, которые могут изменить геометрию элемента, чтобы даже окольным путем нельзя было это понять. В целях безопасности. -[/smart] - -## currentStyle для IE8- - -В IE8- нет `getComputedStyle`, но у элементов есть свойство currentStyle, которое возвращает вычисленное (computed) значение: уже с учётом CSS-каскада, но не всегда в окончательном формате. - -Чтобы код работал и в старых и новых браузерах, обычно пишут кросс-браузерный код, наподобие такого: - -```js -function getStyle(elem) { - return window.getComputedStyle ? getComputedStyle(elem, "") : elem.currentStyle; -} -``` - -Если вы откроете такой документ в IE8-, то размеры будут в процентах, а в современных браузерах -- в пикселях. - -```html - - - - - - -``` - -[smart header="IE8-: перевод `pt,em,%` из `currentStyle` в пиксели"] -Эта информация -- дополнительная, она не обязательна для освоения. - -В IE для того, чтобы получить из процентов реальное значение в пикселях существует метод "runtimeStyle+pixel", [описанный Дином Эдвардсом](http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291). - -Он основан на свойствах `runtimeStyle` и `pixelLeft`, работающих только в IE. - -В [edit src="getiecomputedstyle"]песочнице[/edit] вы можете найти функцию `getIEComputedStyle(elem, prop)`, которая получает значение в пикселях для свойства `prop`, используя `elem.currentStyle` и метод Дина Эдвардса, и пример её применения. - -Если вам интересно, как он работает, ознакомьтесь со свойствами с runtimeStyle и pixelLeft в MSDN и раскройте код. - -Конечно, это актуально только для IE8- и полифиллов. -[/smart] - - -## Итого - -Все DOM-элементы предоставляют следующие свойства. - -
          -
        • Свойство `style` -- это объект, в котором CSS-свойства пишутся `вотТакВот`. Чтение и изменение его свойств -- это, по сути, работа с компонентами атрибута `style`.
        • -
        • `style.cssText` -- строка стилей для чтения или записи. Аналог полного атрибута `style`.
        • - -
        • Свойство `currentStyle`(IE8-) и метод `getComputedStyle` (IE9+, стандарт) позволяют получить реальное, применённое сейчас к элементу свойство стиля с учётом CSS-каскада и браузерных стилей по умолчанию. - -При этом `currentStyle` возвращает значение из CSS, до окончательных вычислений, а `getComputedStyle` -- окончательное, непосредственно применённое к элементу (как правило).
        • -
        - -Более полная информация о свойстве `style`, включающая другие, реже используемые методы работы с ним, доступна [в документации](https://developer.mozilla.org/en-US/docs/DOM/CSSStyleDeclaration). - diff --git a/2-ui/1-document/14-styles-and-classes/getiecomputedstyle.view/getiecomputedstyle.js b/2-ui/1-document/14-styles-and-classes/getiecomputedstyle.view/getiecomputedstyle.js deleted file mode 100644 index 12a7e04c..00000000 --- a/2-ui/1-document/14-styles-and-classes/getiecomputedstyle.view/getiecomputedstyle.js +++ /dev/null @@ -1,18 +0,0 @@ -function getIEComputedStyle(elem, prop) { - var value = elem.currentStyle[prop] || 0; - - // we use 'left' property as a place holder so backup values - var leftCopy = elem.style.left; - var runtimeLeftCopy = elem.runtimeStyle.left; - - // assign to runtimeStyle and get pixel value - elem.runtimeStyle.left = elem.currentStyle.left; - elem.style.left = (prop === "fontSize") ? "1em" : value; - value = elem.style.pixelLeft + "px"; - - // restore values for left - elem.style.left = leftCopy; - elem.runtimeStyle.left = runtimeLeftCopy; - - return value; -} \ No newline at end of file diff --git a/2-ui/1-document/14-styles-and-classes/getiecomputedstyle.view/index.html b/2-ui/1-document/14-styles-and-classes/getiecomputedstyle.view/index.html deleted file mode 100644 index a757f39f..00000000 --- a/2-ui/1-document/14-styles-and-classes/getiecomputedstyle.view/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - -
        Тестовый элемент с margin 1%
        - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/1-get-scroll-height-bottom/solution.md b/2-ui/1-document/15-metrics/1-get-scroll-height-bottom/solution.md deleted file mode 100644 index 4d84cf8f..00000000 --- a/2-ui/1-document/15-metrics/1-get-scroll-height-bottom/solution.md +++ /dev/null @@ -1 +0,0 @@ -Решение: `elem.scrollHeight - elem.scrollTop - elem.clientHeight`. \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/1-get-scroll-height-bottom/task.md b/2-ui/1-document/15-metrics/1-get-scroll-height-bottom/task.md deleted file mode 100644 index d2684e18..00000000 --- a/2-ui/1-document/15-metrics/1-get-scroll-height-bottom/task.md +++ /dev/null @@ -1,9 +0,0 @@ -# Найти размер прокрутки снизу - -[importance 5] - -Свойство `elem.scrollTop` содержит размер прокрученной области при отсчете сверху. А как подсчитать размер прокрутки снизу? - -Напишите соответствующее выражение для произвольного элемента `elem`. - -Проверьте: если прокрутки нет вообще или элемент полностью прокручен -- оно должно давать ноль. \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/2-scrollbar-width/solution.md b/2-ui/1-document/15-metrics/2-scrollbar-width/solution.md deleted file mode 100644 index 9cb7a82d..00000000 --- a/2-ui/1-document/15-metrics/2-scrollbar-width/solution.md +++ /dev/null @@ -1,23 +0,0 @@ -Создадим элемент с прокруткой, но без `border` и `padding`. Тогда разница между его полной шириной `offsetWidth` и внутренней `clientWidth` будет равна как раз прокрутке: - -```js -//+ run -// создадим элемент с прокруткой -var div = document.createElement('div'); - -div.style.overflowY = 'scroll'; -div.style.width = '50px'; -div.style.height = '50px'; - -// при display:none размеры нельзя узнать -// нужно, чтобы элемент был видим, -// visibility:hidden - можно, т.к. сохраняет геометрию -div.style.visibility = 'hidden'; - -document.body.appendChild(div); -var scrollWidth = div.offsetWidth - div.clientWidth; -document.body.removeChild(div); - -alert( scrollWidth ); -``` - diff --git a/2-ui/1-document/15-metrics/2-scrollbar-width/task.md b/2-ui/1-document/15-metrics/2-scrollbar-width/task.md deleted file mode 100644 index 77867ac8..00000000 --- a/2-ui/1-document/15-metrics/2-scrollbar-width/task.md +++ /dev/null @@ -1,7 +0,0 @@ -# Узнать ширину полосы прокрутки - -[importance 3] - -Напишите код, который возвращает ширину стандартной полосы прокрутки. Именно самой полосы, где ползунок. Обычно она равна `16px`, в редких и мобильных браузерах может колебаться от `14px` до `18px`, а кое-где даже равна `0px`. - -P.S. Ваш код должен работать на любом HTML-документе, независимо от его содержимого. \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/3-div-placeholder/solution.md b/2-ui/1-document/15-metrics/3-div-placeholder/solution.md deleted file mode 100644 index aa685293..00000000 --- a/2-ui/1-document/15-metrics/3-div-placeholder/solution.md +++ /dev/null @@ -1,29 +0,0 @@ -Нам нужно создать `div` с такими же размерами и вставить его на место "переезжающего". - -Один из вариантов -- это просто клонировать элемент. - -Если делать это при помощи `div.cloneNode(true)`, то склонируется все содержимое, которого может быть много. Обычно нам это не нужно, поэтому можно использовать `div.cloneNode(false)` для клонирования элемента со стилями, и потом поправить его `width/height`. - -Можно и просто создать новый `div` и поставить ему нужные размеры. - -**Всё, кроме `margin`, можно получить из свойств DOM-элемента, а `margin` -- только через `getComputedStyle`.** - -Причём `margin` мы обязаны поставить, так как иначе наш элемент при вставке будет вести себя иначе, чем исходный. - -Код: - -```js -var div = document.getElementById('moving-div'); - -var placeHolder = document.createElement('div'); -placeHolder.style.height = div.offsetHeight + 'px'; -// можно и width, но в этом примере это не обязательно - -// IE || другой браузер -var computedStyle = div.currentStyle || getComputedStyle(div, ''); - -placeHolder.style.marginTop = computedStyle.marginTop; // (1) -placeHolder.style.marginBottom = computedStyle.marginBottom; -``` - -В строке `(1)` использование полного название свойства `"marginTop"` гарантирует, что полученное значение будет корректным. \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/3-div-placeholder/solution.view/index.html b/2-ui/1-document/15-metrics/3-div-placeholder/solution.view/index.html deleted file mode 100755 index fd8f91f0..00000000 --- a/2-ui/1-document/15-metrics/3-div-placeholder/solution.view/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - Before Before Before - -
        - Text Text Text -
        Text Text Text -
        -
        - - After After After - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/3-div-placeholder/source.view/index.html b/2-ui/1-document/15-metrics/3-div-placeholder/source.view/index.html deleted file mode 100755 index 69e3e504..00000000 --- a/2-ui/1-document/15-metrics/3-div-placeholder/source.view/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - Before Before Before - -
        - Text Text Text -
        Text Text Text -
        -
        - - After After After - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/3-div-placeholder/task.md b/2-ui/1-document/15-metrics/3-div-placeholder/task.md deleted file mode 100644 index 3fa0528f..00000000 --- a/2-ui/1-document/15-metrics/3-div-placeholder/task.md +++ /dev/null @@ -1,52 +0,0 @@ -# Подменить div на другой с таким же размером - -[importance 3] - -Посмотрим следующий случай из жизни. Был текст, который, в частности, содержал `div` с зелеными границами: - -```html - - - -Before Before Before - -
        -Text Text Text
        -Text Text Text
        -
        - -After After After -``` - -Программист Валера из вашей команды написал код, который позиционирует его абсолютно и смещает в правый верхний угол. Вот этот код: - -```js -var div = document.getElementById('moving-div'); -div.style.position = 'absolute'; -div.style.right = div.style.top = 0; -``` - -Побочным результатом явилось смещение текста, который раньше шел после `DIV`. Теперь он поднялся вверх: -[iframe height=90 src="source"] - -**Допишите код Валеры, сделав так, чтобы текст оставался на своем месте после того, как `DIV` будет смещен.** - -Сделайте это путем создания вспомогательного `DIV` с теми же `width`, `height`, `border`, `margin`, `padding`, что и у желтого `DIV`. - -Используйте только JavaScript, без CSS. - -Должно быть так (новому блоку задан фоновый цвет для демонстрации): - -[iframe height=140 src="solution"] - - - - - diff --git a/2-ui/1-document/15-metrics/4-put-ball-in-center/ball-half/index.html b/2-ui/1-document/15-metrics/4-put-ball-in-center/ball-half/index.html deleted file mode 100755 index 23b8d7b2..00000000 --- a/2-ui/1-document/15-metrics/4-put-ball-in-center/ball-half/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - -
        - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -
        - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/4-put-ball-in-center/field.png b/2-ui/1-document/15-metrics/4-put-ball-in-center/field.png deleted file mode 100755 index eea8c171..00000000 Binary files a/2-ui/1-document/15-metrics/4-put-ball-in-center/field.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/4-put-ball-in-center/solution.md b/2-ui/1-document/15-metrics/4-put-ball-in-center/solution.md deleted file mode 100644 index 07ccdf60..00000000 --- a/2-ui/1-document/15-metrics/4-put-ball-in-center/solution.md +++ /dev/null @@ -1,51 +0,0 @@ -При абсолютном позиционировании мяча внутри поля его координаты `left/top` отсчитываются от **внутреннего** угла поля, например верхнего-левого: - - - -Метрики для внутренней зоны поля -- это `clientWidth/Height`. - -Центр - это `(clientWidth/2, clientHeight/2)`. - -Но если мы установим мячу такие значения `ball.style.left/top`, то в центре будет не сам мяч, а его левый верхний угол: - -```js -var ball = document.getElementById('ball'); -var field = document.getElementById('field'); - -ball.style.left = Math.round(field.clientWidth / 2) + 'px'; -ball.style.top = Math.round(field.clientHeight / 2) + 'px'; -``` - -[iframe hide="Нажмите, чтобы посмотреть текущий результат" height=180 src="ball-half"] - -Для того, чтобы центр мяча находился в центре поля, нам нужно сместить мяч на половину его ширины влево и на половину его высоты вверх. - -```js -var ball = document.getElementById('ball'); -var field = document.getElementById('field'); - -ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'; -ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'; -``` - -**Внимание, подводный камень!** - -Код выше стабильно работать не будет, потому что `IMG` идет без ширины/высоты: - -```html - -``` - -**Высота и ширина изображения неизвестны браузеру до тех пор, пока оно не загрузится, если размер не указан явно.** - -После первой загрузки изображение уже будет в кеше браузера, и его размеры будут известны. Но когда браузер впервые видит документ -- он ничего не знает о картинке, поэтому значение `ball.offsetWidth` равно `0`. Вычислить координаты невозможно. - -Чтобы это исправить, добавим `width/height` к картинке: - -```html - -``` - -Теперь браузер всегда знает ширину и высоту, так что все работает. Тот же эффект дало бы указание размеров в CSS. - -[edit src="solution"]Полный код решения[/edit] \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/4-put-ball-in-center/solution.view/index.html b/2-ui/1-document/15-metrics/4-put-ball-in-center/solution.view/index.html deleted file mode 100755 index 47694b5a..00000000 --- a/2-ui/1-document/15-metrics/4-put-ball-in-center/solution.view/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - -
        - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -
        - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/4-put-ball-in-center/source.view/index.html b/2-ui/1-document/15-metrics/4-put-ball-in-center/source.view/index.html deleted file mode 100755 index 8b54beed..00000000 --- a/2-ui/1-document/15-metrics/4-put-ball-in-center/source.view/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - -
        - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -
        - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/4-put-ball-in-center/task.md b/2-ui/1-document/15-metrics/4-put-ball-in-center/task.md deleted file mode 100644 index 305acfb8..00000000 --- a/2-ui/1-document/15-metrics/4-put-ball-in-center/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# Поместите мяч в центр поля - -[importance 5] - -Поместите мяч в центр поля. - -Исходный документ выглядит так: -[iframe src="source" edit link height=180] - -**Используйте JavaScript, чтобы поместить мяч в центр:** -[iframe src="solution" height=180] - -
          -
        • Менять CSS нельзя, мяч должен переносить в центр ваш скрипт, через установку нужных стилей элемента.
        • -
        • JavaScript-код должен работать при разных размерах мяча (`10`, `20`, `30` пикселей) без изменений.
        • -
        • JavaScript-код должен работать при различных размерах и местоположениях поля на странице без изменений. Также он не должен зависеть от ширины рамки поля `border`.
        • -
        - -P.S. Да, центрирование можно сделать при помощи чистого CSS, но задача именно на JavaScript. Далее будут другие темы и более сложные ситуации, когда JavaScript будет уже точно необходим, это -- своего рода "разминка". diff --git a/2-ui/1-document/15-metrics/5-expand-element/solution.md b/2-ui/1-document/15-metrics/5-expand-element/solution.md deleted file mode 100644 index a8b2e2ec..00000000 --- a/2-ui/1-document/15-metrics/5-expand-element/solution.md +++ /dev/null @@ -1,43 +0,0 @@ -# Решение через width: auto - -Вначале рассмотрим решение через "умную" установку CSS-свойства. - -Они могут быть разными. Самое простое выглядит так: - -```js -elem.style.width = 'auto'; -``` - -Такой способ работает, так как `
        ` по умолчанию распахивается на всю ширину. - -Конечно, такое решение не будет работать для элементов, которые сами по себе не растягиваются, например в случае со `` или при наличии `position: absolute`. - -Обратим внимание, такой вариант был бы неверен: -```js -elem.style.width = '100%'; -``` - -По умолчанию в CSS ширина `width` -- это то, что *внутри `padding`*, а проценты отсчитываются от ширины родителя. То есть, ставя ширину в `100%`, мы говорим: "внутренняя область должна занимать `100%` ширины родителя". А в элементе есть ещё `padding`, которые в итоге вылезут наружу. - -Можно бы поменять блочную модель, указав `box-sizing` через свойство `elem.style.boxSizing`, но такое изменение потенциально может затронуть много других свойств, поэтому нежелательно. - -# Точное вычисление - -Альтернатива -- вычислить ширину родителя через `clientWidth`. - -Доступную внутреннюю ширину родителя можно получить, вычитая из `clientWidth` размеры `paddingLeft/paddingRight`, и затем присвоить её элементу: - -```js -var bodyClientWidth = document.body.clientWidth; - -var style = getComputedStyle(elem); - -*!* -var bodyInnerWidth = bodyClientWidth - parseInt(style.paddingLeft) - parseInt(style.paddingRight); -*/!* - -elem.style.width = bodyInnerWidth + 'px'; -``` - -Такое решение будет работать всегда, вне зависимости от типа элемента. Конечно, при изменении размеров окна браузера ширина не адаптируется к новому размеру автоматически, как с `width:auto`. Это недостаток. Его, конечно, тоже можно обойти при помощи событий (изучим далее), но как общий рецепт -- если CSS может решить задачу -- лучше использовать CSS. - diff --git a/2-ui/1-document/15-metrics/5-expand-element/solution.view/index.html b/2-ui/1-document/15-metrics/5-expand-element/solution.view/index.html deleted file mode 100755 index 6c51d6ad..00000000 --- a/2-ui/1-document/15-metrics/5-expand-element/solution.view/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - -
        - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст -
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/5-expand-element/source.view/index.html b/2-ui/1-document/15-metrics/5-expand-element/source.view/index.html deleted file mode 100755 index 4bfe7fee..00000000 --- a/2-ui/1-document/15-metrics/5-expand-element/source.view/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - -
        - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст -
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/5-expand-element/task.md b/2-ui/1-document/15-metrics/5-expand-element/task.md deleted file mode 100644 index 88d72e2f..00000000 --- a/2-ui/1-document/15-metrics/5-expand-element/task.md +++ /dev/null @@ -1,15 +0,0 @@ -# Расширить элемент - -[importance 4] - -В `` есть элемент `
        ` с заданной шириной `width`. - -Задача -- написать код, который "распахнет" `
        ` по ширине на всю страницу. - -Исходный документ (`
        ` содержит текст и прокрутку): -[iframe height=220 src="source"] - -P.S. Пользоваться следует исключительно средствами JS, CSS в этой задаче менять нельзя. Также ваш код должен быть универсален и не ломаться, если цифры в CSS станут другими. - -P.P.S. При расширении элемент `
        ` не должен вылезти за границу ``. - diff --git a/2-ui/1-document/15-metrics/6-width-vs-clientwidth/solution.md b/2-ui/1-document/15-metrics/6-width-vs-clientwidth/solution.md deleted file mode 100644 index c1e83eff..00000000 --- a/2-ui/1-document/15-metrics/6-width-vs-clientwidth/solution.md +++ /dev/null @@ -1,11 +0,0 @@ -Отличия: - -
          -
        1. `getComputedStyle` не работает в IE8-.
        2. -
        3. `clientWidth` возвращает число, а `getComputedStyle(...).width` -- строку, на конце `px`.
        4. -
        5. `getComputedStyle` не всегда даст ширину, он может вернуть, к примеру, `"auto"` для инлайнового элемента.
        6. -
        7. `clientWidth` соответствует внутренней видимой области элемента, *включая `padding`, а CSS-ширина `width` при стандартном значении `box-sizing` соответствует зоне *внутри `padding`*.
        8. -
        9. Если есть полоса прокрутки, то некоторые браузеры включают её ширину в `width`, а некоторые -- нет. - -Свойство `clientWidth`, с другой стороны, полностью кросс-браузерно. Оно всегда обозначает размер *за вычетом прокрутки*, т.е. реально доступный для содержимого.
        10. -
        diff --git a/2-ui/1-document/15-metrics/6-width-vs-clientwidth/task.md b/2-ui/1-document/15-metrics/6-width-vs-clientwidth/task.md deleted file mode 100644 index 582a2473..00000000 --- a/2-ui/1-document/15-metrics/6-width-vs-clientwidth/task.md +++ /dev/null @@ -1,7 +0,0 @@ -# В чём отличие "width" и "clientWidth" ? - -[importance 5] - -В чём отличия между `getComputedStyle(elem).width` и `elem.clientWidth`? - -Укажите хотя бы три отличия, лучше -- больше. \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/article.md b/2-ui/1-document/15-metrics/article.md deleted file mode 100644 index 39f78ae3..00000000 --- a/2-ui/1-document/15-metrics/article.md +++ /dev/null @@ -1,317 +0,0 @@ -# Размеры и прокрутка элементов - -Для того, чтобы показывать элементы на произвольных местах страницы, необходимо во-первых, знать CSS-позиционирование, а во-вторых -- уметь работать с "геометрией элементов" из JavaScript. - -В этой главе мы поговорим о размерах элементов DOM, способах их вычисления и *метриках* -- различных свойствах, которые содержат эту информацию. - -[cut] - -## Образец документа - -Мы будем использовать для примера вот такой элемент, у которого есть рамка (border), поля (padding), и прокрутка: - -```html - -
        - ...Текст... -
        - -``` - -У него нет отступов `margin`, в этой главе они не важны, так как метрики касаются именно размеров самого элемента, отступы в них не учитываются. - -Результат выглядит так: - - - -Вы можете открыть [edit src="metric"]этот документ в песочнице[/edit]. - -[smart header="Внимание, полоса прокрутки!"] -В иллюстрации выше намеренно продемонстрирован самый сложный и полный случай, когда у элемента есть ещё и полоса прокрутки. - -В этом случае полоса прокрутки "отодвигает" содержимое вместе с `padding` влево, отбирая у него место. - -Именно поэтому ширина содержимого обозначена как `content width` и равна `284px`, а не `300px`, как в CSS. - -Точное значение получено в предположении, что ширина полосы прокрутки равна `16px`, то есть после её вычитания на содержимое остаётся `300 - 16 = 284px`. Конечно, она сильно зависит от браузера, устройства, ОС. - -Мы должны в точности понимать, что происходит с размерами элемента при наличии полосы прокрутки, поэтому на картинке выше это отражено. -[/smart] - -[smart header="Поле `padding` заполнено текстом"] -Обычно поля `padding` изображают пустыми, но так как текста много, то он заполняет нижнее поле `padding-bottom` в примере выше. - -Во избежание путаницы заметим, что `padding` там, всё же, есть. Поля `padding` по CSS в элементе выше одинаковы со всех сторон. А такое заполнение -- нормальное поведение браузера. -[/smart] - -## Метрики - -У элементов существует ряд свойств, содержащих их внешние и внутренние размеры. Мы будем называть их "метриками". - -Метрики, в отличие от свойств CSS, содержат числа, всегда в пикселях и без единиц измерения на конце. - -Вот общая картина: - - - - -На картинке все они с трудом помещаются, но, как мы увидим далее, их значения просты и понятны. - -Будем исследовать их снаружи элемента и вовнутрь. - - -## offsetParent, offsetLeft/Top - -Ситуации, когда эти свойства нужны, можно перечислить по пальцам. Они возникают действительно редко. Как правило, эти свойства используют, потому что не знают средств правильной работы с координатами, о которых мы поговорим позже. - -Несмотря на то, что эти свойства нужны реже всего, они -- самые "внешние", поэтому начнём с них. - -**В `offsetParent` находится ссылка на родительский элемент в смысле отображения на странице.** - -Уточним, что имеется в виду. - -Когда браузер рисует страницу, то он высчитывает дерево расположения элементов, иначе говоря "дерево геометрии" или "дерево рендеринга", которое содержит всю информацию о размерах. - -При этом одни элементы естественным образом рисуются внутри других. Но, к примеру, если у элемента стоит `position:absolute`, то его расположение вычисляется уже не относительно непосредственного родителя `parentNode`, а относительно ближайшего позиционированного элемента (т.е. свойство `position` которого не равно `static`), или `BODY`, если такой отсутствует. - -Получается, что элемент имеет в дополнение к обычному родителю в DOM -- ещё одного "родителя по позиционированию", то есть относительно которого он рисуется. Этот элемент и будет в свойстве `offsetParent`. - -**Свойства `offsetLeft/Top` задают смещение относительно `offsetParent`.** - -В примере ниже внутренний `
        ` имеет DOM-родителя `
        `, но `offsetParent` у него `
        `, и сдвиги относительно его верхнего-левого угла будут в `offsetLeft/Top`: - -```html -
        - -
        ...
        - -
        -``` - - - -## offsetWidth/Height - -Теперь переходим к самому элементу. - -Эти два свойства -- самые простые. Они содержат "внешнюю" ширину/высоту элемента, то есть его полный размер, включая рамки `border`. - - - -Для нашего элемента: -
          -
        • `offsetWidth = 390` -- внешняя ширина блока, её можно получить сложением CSS-ширины (`300px`, но её часть на рисунке выше отнимает прокрутка, поэтому `284 + 16`), полей(`2*20px`) и рамок (`2*25px`).
        • -
        • `offsetHeight = 290` -- внешняя высота блока.
        • -
        - - -[smart header="Метрики для невидимых элементов равны нулю."] - -Координаты и размеры в JavaScript устанавливаются только для *видимых* элементов. - -Для элементов с `display:none` или находящихся вне документа дерево рендеринга не строится. Для них метрики равны нулю. Кстати, и `offsetParent` для таких элементов тоже `null`. - -**Это дает нам замечательный способ для проверки, виден ли элемент**: - -```js -function isHidden(elem) { - return !elem.offsetWidth && !elem.offsetHeight; -} -``` - -
          -
        • Работает, даже если родителю элемента установлено свойство `display:none`.
        • -
        • Работает для всех элементов, кроме `TR`, с которым возникают некоторые проблемы в разных браузерах. Обычно, проверяются не `TR`, поэтому всё ок.
        • -
        • Считает элемент видимым, даже если позиционирован за пределами экрана или имеет свойство `visibility:hidden`.
        • -
        • "Схлопнутый" элемент, например пустой `div` без высоты и ширины, будет считаться невидимым.
        • -
        -[/smart] - - - -## clientTop/Left - -Далее внутри элемента у нас рамки `border`. - -Для них есть свойства-метрики `clientTop` и `clientLeft`. - -В нашем примере: -
          -
        • `clientLeft = 25` -- ширина левой рамки
        • -
        • `clientTop = 25` -- ширина верхней рамки
        • -
        - - - -...Но на самом деле они -- вовсе не рамки, а отступ внутренней части элемента от внешней. - -В чём же разница? - -Она возникает тогда, когда документ располагается *справа налево* (операционная система на арабском языке или иврите). Полоса прокрутки в этом случае находится слева, и тогда свойство `clientLeft` включает в себя еще и ширину полосы прокрутки. - -Получится так: - - - - -## clientWidth/Height - -Эти свойства -- размер элемента внутри рамок `border`. - -Они включают в себя ширину содержимого `width` вместе с полями `padding`, но без прокрутки. - - - -На рисунке выше посмотрим вначале на `clientHeight`, её посчитать проще всего. Прокрутки нет, так что это в точности то, что внутри рамок: CSS-высота `200px` плюс верхнее и нижнее поля `padding` (по `20px`), итого `240px`. - -На рисунке нижний `padding` заполнен текстом, но это неважно: по правилам он всегда входит в `clientHeight`. - -Теперь `clientWidth` -- ширина содержимого здесь не равна CSS-ширине, её часть "съедает" полоса прокрутки. -Поэтому в `clientWidth` входит не CSS-ширина, а реальная ширина содержимого `284px` плюс левое и правое поля `padding` (по `20px`), итого `324px`. - - -**Если `padding` нет, то `clientWidth/Height` в точности равны размеру области содержимого, внутри рамок и полосы прокрутки.** - - - -Поэтому в тех случаях, когда мы точно знаем, что `padding` нет, их используют для определения внутренних размеров элемента. - -## scrollWidth/Height - -Эти свойства -- аналоги `clientWidth/clientHeight`, но с учетом прокрутки. - -Свойства `clientWidth/clientHeight` относятся только к видимой области элемента, а `scrollWidth/scrollHeight` добавляют к ней прокрученную (которую не видно) по горизонтали/вертикали. - - - -На рисунке выше: -
          -
        • `scrollHeight = 723` -- полная внутренняя высота, включая прокрученную область.
        • -
        • `scrollWidth = 324` -- полная внутренняя ширина, в данном случае прокрутки нет, поэтому она равна `clientWidth`.
        • -
        - -Эти свойства можно использовать, чтобы "распахнуть" элемент на всю ширину/высоту, таким кодом: - -```js -element.style.height = element.scrollHeight + 'px'; -``` - -[online] -[pre no-typography] -Нажмите на кнопку, чтобы распахнуть элемент: - -
        текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст
        - - -[/pre] -[/online] - -## scrollLeft/scrollTop - -Свойства `scrollLeft/scrollTop` -- ширина/высота невидимой, прокрученной в данный момент, части элемента слева и сверху. - -Следующее иллюстрация показывает значения `scrollHeight` и `scrollTop` для блока с вертикальной прокруткой. - - - -[smart header="`scrollLeft/scrollTop` можно изменять"] -В отличие от большинства свойств, которые доступны только для чтения, значения `scrollLeft/scrollTop` можно изменить, и браузер выполнит прокрутку элемента. - -[online] -При клике на следующий элемент будет выполняться код `elem.scrollTop += 10`. Поэтому он будет прокручиваться на `10px` вниз: - -
        Кликни
        Меня
        1
        2
        3
        4
        5
        6
        7
        8
        9
        -[/online] -[/smart] - - -## Не стоит брать width/height из CSS - -Мы рассмотрели метрики -- свойства, которые есть у DOM-элементов. Их обычно используют для получения их различных высот, ширин и прочих расстояний. - -Теперь несколько слов о том, как *не* надо делать. - -Как мы знаем, CSS-высоту и ширину можно установить с помощью `elem.style` и извлечь, используя `getComputedStyle`, которые в подробностях обсуждаются в главе [](/styles-and-classes). - -Получение ширины элемента может быть таким: - -```js -//+ run -var elem = document.body; - -alert( getComputedStyle(elem).width ); // вывести CSS-ширину для elem -``` - -Не лучше ли получать ширину так, вместо метрик? Вовсе нет! - -
          -
        1. Во-первых, CSS-свойства `width/height` зависят от другого свойства -- `box-sizing`, которое определяет, что такое, собственно, эти ширина и высота. Получается, что изменение `box-sizing`, к примеру, для более удобной вёрстки, сломает такой JavaScript.
        2. -
        3. Во-вторых, в CSS свойства `width/height` могут быть равны `auto`, например, для инлайн-элемента: - -```html - -Привет! - - -``` - -Конечно, с точки зрения CSS размер `auto` -- совершенно нормально, но нам-то в JavaScript нужен конкретный размер в пикселях, который мы могли бы использовать для вычислений. Получается, что в данном случае ширина `width` из CSS вообще бесполезна. -
        4. -
        - -Есть и ещё одна причина. - -Полоса прокрутки -- причина многих проблем и недопониманий. Как говорится, "дьявол кроется в деталях". Недопустимо, чтобы наш код работал на элементах без прокрутки и начинал "глючить" с ней. - -Как мы говорили ранее, при наличии вертикальной полосы прокрутки, в зависимости от браузера, устройства и операционной системы, она может сдвинуть содержимое. - -Получается, что реальная ширина содержимого меньше CSS-ширины. И это учитывают свойства `clientWidth/clientHeight`. - -...Но при этом некоторые браузеры также учитывают это в результате `getComputedStyle(elem).width`, то есть возвращают реальную внутреннюю ширину, а некоторые -- именно CSS-свойство. Эти кросс-браузерные отличия -- ещё один повод не использовать такой подход, а использовать свойства-метрики. - -[online] -Если ваш браузер показывает полосу прокрутки (например, под Windows почти все браузеры так делают), то вы можете протестировать это сами, нажав на кнопку в ифрейме ниже. - -[iframe src="cssWidthScroll" link border=1] - -У элемента с текстом в стилях указано `width:300px`. - -На момент написания этой главы при тестировании в Chrome под Windows `alert` выводил `283px`, а в Firefox -- `300px`. При этом оба браузера показывали прокрутку. Это из-за того, что Firefox возвращал именно CSS-ширину, а Chrome -- реальную ширину, за вычетом прокрутки. -[/online] - -Описанные разночтения касаются только чтения свойства `getComputedStyle(...).width` из JavaScript, визуальное отображение корректно в обоих случаях. - -## Итого - -У элементов есть следующие метрики: -
          -
        • `offsetParent` -- "родитель по дереву рендеринга" -- ближайшая ячейка таблицы, body для статического позиционирования или ближайший позиционированный элемент для других типов позиционирования.
        • -
        • `offsetLeft/offsetTop` -- позиция в пикселях левого верхнего угла блока, относительно его `offsetParent`.
        • -
        • `offsetWidth/offsetHeight` -- "внешняя" ширина/высота блока, включая рамки.
        • -
        • `clientLeft/clientTop` -- отступ области содержимого от левого-верхнего угла элемента. Если операционная система располагает вертикальную прокрутку справа, то равны ширинам левой/верхней рамки, если же слева (ОС на иврите, арабском), то `clientLeft` включает в себя прокрутку. -
        • -
        • `clientWidth/clientHeight` -- ширина/высота содержимого вместе с полями `padding`, но без полосы прокрутки.
        • -
        • `scrollWidth/scrollHeight` -- ширина/высота содержимого, включая прокручиваемую область. Включает в себя `padding` и не включает полосы прокрутки.
        • -
        • `scrollLeft/scrollTop` -- ширина/высота прокрученной части документа, считается от верхнего левого угла.
        • -
        - -Все свойства, доступны только для чтения, кроме `scrollLeft/scrollTop`. Изменение этих свойств заставляет браузер прокручивать элемент. - -В этой главе мы считали, что страница находится в режиме соответствия стандартам. В режиме совместимости -- некоторые старые браузеры требуют `document.body` вместо `documentElement`, в остальном всё так же. Конечно, по возможности, стоит использовать только режим соответствия стандарту. - diff --git a/2-ui/1-document/15-metrics/cssWidthScroll.view/index.html b/2-ui/1-document/15-metrics/cssWidthScroll.view/index.html deleted file mode 100755 index a29f8a3a..00000000 --- a/2-ui/1-document/15-metrics/cssWidthScroll.view/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - -
        - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст - текст текст текст текст текст текст текст текст текст текст текст текст текст текст -
        - - - - У элемента стоит style="width:300px" -
        - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/15-metrics/metric-all.png b/2-ui/1-document/15-metrics/metric-all.png deleted file mode 100644 index fb7c3b7b..00000000 Binary files a/2-ui/1-document/15-metrics/metric-all.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-all@2x.png b/2-ui/1-document/15-metrics/metric-all@2x.png deleted file mode 100644 index bb10d86e..00000000 Binary files a/2-ui/1-document/15-metrics/metric-all@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-left-top-rtl.png b/2-ui/1-document/15-metrics/metric-client-left-top-rtl.png deleted file mode 100644 index c934fb23..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-left-top-rtl.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-left-top-rtl@2x.png b/2-ui/1-document/15-metrics/metric-client-left-top-rtl@2x.png deleted file mode 100644 index 4cc5b4a1..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-left-top-rtl@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-left-top.png b/2-ui/1-document/15-metrics/metric-client-left-top.png deleted file mode 100644 index b821043c..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-left-top.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-left-top@2x.png b/2-ui/1-document/15-metrics/metric-client-left-top@2x.png deleted file mode 100644 index afcdcdf8..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-left-top@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-width-height.png b/2-ui/1-document/15-metrics/metric-client-width-height.png deleted file mode 100644 index e2112da6..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-width-height.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-width-height@2x.png b/2-ui/1-document/15-metrics/metric-client-width-height@2x.png deleted file mode 100644 index 55168738..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-width-nopadding.png b/2-ui/1-document/15-metrics/metric-client-width-nopadding.png deleted file mode 100644 index f715c8ff..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-width-nopadding.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-client-width-nopadding@2x.png b/2-ui/1-document/15-metrics/metric-client-width-nopadding@2x.png deleted file mode 100644 index 316bec7b..00000000 Binary files a/2-ui/1-document/15-metrics/metric-client-width-nopadding@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-css.png b/2-ui/1-document/15-metrics/metric-css.png deleted file mode 100644 index d1a219ee..00000000 Binary files a/2-ui/1-document/15-metrics/metric-css.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-css@2x.png b/2-ui/1-document/15-metrics/metric-css@2x.png deleted file mode 100644 index 28cde46d..00000000 Binary files a/2-ui/1-document/15-metrics/metric-css@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-offset-parent.png b/2-ui/1-document/15-metrics/metric-offset-parent.png deleted file mode 100644 index a9711549..00000000 Binary files a/2-ui/1-document/15-metrics/metric-offset-parent.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-offset-parent@2x.png b/2-ui/1-document/15-metrics/metric-offset-parent@2x.png deleted file mode 100644 index df6799ef..00000000 Binary files a/2-ui/1-document/15-metrics/metric-offset-parent@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-offset-width-height.png b/2-ui/1-document/15-metrics/metric-offset-width-height.png deleted file mode 100644 index 84c331e2..00000000 Binary files a/2-ui/1-document/15-metrics/metric-offset-width-height.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-offset-width-height@2x.png b/2-ui/1-document/15-metrics/metric-offset-width-height@2x.png deleted file mode 100644 index 6f3c9c53..00000000 Binary files a/2-ui/1-document/15-metrics/metric-offset-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-scroll-top.png b/2-ui/1-document/15-metrics/metric-scroll-top.png deleted file mode 100644 index b65a6406..00000000 Binary files a/2-ui/1-document/15-metrics/metric-scroll-top.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-scroll-top@2x.png b/2-ui/1-document/15-metrics/metric-scroll-top@2x.png deleted file mode 100644 index 539cd979..00000000 Binary files a/2-ui/1-document/15-metrics/metric-scroll-top@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-scroll-width-height.png b/2-ui/1-document/15-metrics/metric-scroll-width-height.png deleted file mode 100644 index cdec4be7..00000000 Binary files a/2-ui/1-document/15-metrics/metric-scroll-width-height.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric-scroll-width-height@2x.png b/2-ui/1-document/15-metrics/metric-scroll-width-height@2x.png deleted file mode 100644 index b4b9cb43..00000000 Binary files a/2-ui/1-document/15-metrics/metric-scroll-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/15-metrics/metric.view/index.html b/2-ui/1-document/15-metrics/metric.view/index.html deleted file mode 100755 index 88392ce7..00000000 --- a/2-ui/1-document/15-metrics/metric.view/index.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - -
        -

        Introduction

        -

        This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company's Navigator 2.0 browser. - It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the - Ecma General Assembly of June 1997.

        - -

        That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep - it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.

        - -

        The third edition of the Standard introduced powerful regular expressions, better string handling, new control statements, try/catch exception handling, tighter definition of errors, formatting for numeric output and minor changes in anticipation - of forthcoming internationalisation facilities and future language growth. The third edition of the ECMAScript standard was adopted by the Ecma General Assembly of December 1999 and published as ISO/IEC 16262:2002 in June 2002.

        - -
        - - -
        Координаты мыши: ...
        -
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/16-metrics-window/1-pageyoffset-polyfill/solution.md b/2-ui/1-document/16-metrics-window/1-pageyoffset-polyfill/solution.md deleted file mode 100644 index 9ed5a847..00000000 --- a/2-ui/1-document/16-metrics-window/1-pageyoffset-polyfill/solution.md +++ /dev/null @@ -1,39 +0,0 @@ -В стандартном режиме IE8 можно получить текущую прокрутку так: - -```js -//+ run -alert( document.documentElement.scrollTop ); -``` - -Самым простым, но неверным было бы такое решение: -```js -//+ run -// "полифилл" -window.pageYOffset = document.documentElement.scrollTop; - -// использование "полифилла" -alert( window.pageYOffset ); -``` - -Код выше не учитывает текущую прокрутку. Он присваивает `window.pageYOffset` текущую прокрутку, но при её изменении -- не обновляет это свойство автоматически, а поэтому -- бесполезен. - -Более правильное решение -- сделать это свойство геттером. При этом в IE8 для DOM-объектов работает `Object.defineProperty`: - -```js -//+ run -// полифилл -Object.defineProperty(window, 'pageYOffset', { - get: function() { - return document.documentElement.scrollTop; - } -}); - -// использование полифилла -alert( window.pageYOffset ); -``` - - - - - - diff --git a/2-ui/1-document/16-metrics-window/1-pageyoffset-polyfill/task.md b/2-ui/1-document/16-metrics-window/1-pageyoffset-polyfill/task.md deleted file mode 100644 index 7fe0ca65..00000000 --- a/2-ui/1-document/16-metrics-window/1-pageyoffset-polyfill/task.md +++ /dev/null @@ -1,12 +0,0 @@ -# Полифилл для pageYOffset в IE8 - -[importance 3] - -Обычно в IE8 не поддерживается свойство `pageYOffset`. Напишите полифилл для него. - -При подключённом полифилле такой код должен работать в IE8: - -```js -// текущая прокрутка страницы в IE8 -alert( window.pageYOffset ); -``` diff --git a/2-ui/1-document/16-metrics-window/article.md b/2-ui/1-document/16-metrics-window/article.md deleted file mode 100644 index 3236343f..00000000 --- a/2-ui/1-document/16-metrics-window/article.md +++ /dev/null @@ -1,213 +0,0 @@ -# Размеры и прокрутка страницы - -Как найти ширину окна браузера? Как узнать всю высоту страницы, с учётом прокрутки? -Как прокрутить её из JavaScript? - - -С точки зрения HTML, документ -- это `document.documentElement`. У этого элемента, соответствующего тегу ``, есть все стандартные свойства и метрики и, в теории, они и должны нам помочь. Однако, на практике есть ряд нюансов, именно их мы рассмотрим в этой главе. - -[cut] - -## Ширина/высота видимой части окна - -Свойства `clientWidth/Height` для элемента `document.documentElement` -- это как раз ширина/высота видимой области окна. - - - -[online] -Например, кнопка ниже выведет размер такой области для этой страницы: - - -[/online] - -[warn header="Не `window.innerWidth/Height`"] -Все браузеры, кроме IE8-, также поддерживают свойства `window.innerWidth/innerHeight`. Они хранят текущий размер *окна браузера*. - -В чём отличие? Оно небольшое, но чрезвычайно важное. - -Свойства `clientWidth/Height`, если есть полоса прокрутки, возвращают именно ширину/высоту внутри неё, доступную для документа, а `window.innerWidth/Height` -- игнорируют её наличие. - -Если справа часть страницы занимает полоса прокрутки, то эти строки выведут разное: -```js -//+ run -alert( window.innerWidth ); // вся ширина окна -alert( document.documentElement.clientWidth ); // ширина минус прокрутка -``` - -Обычно нам нужна именно *доступная* ширина окна, например, чтобы нарисовать что-либо, то есть за вычетом полосы прокрутки. Поэтому используем `documentElement.clientWidth`. -[/warn] - -## Ширина/высота страницы с учётом прокрутки - -Теоретически, видимая часть страницы -- это `documentElement.clientWidth/Height`, а полный размер с учётом прокрутки -- по аналогии, `documentElement.scrollWidth/scrollHeight`. - -Это верно для обычных элементов. - -А вот для страницы с этими свойствами возникает проблема, когда *прокрутка то есть, то нет*. В этом случае они работают некорректно. В браузерах Chrome/Safari и Opera при отсутствии прокрутки значение `documentElement.scrollHeight` в этом случае может быть даже меньше, чем `documentElement.clientHeight`, что, конечно же, выглядит как совершеннейшая чепуха и нонсенс. - -Эта проблема возникает именно для `documentElement`, то есть для всей страницы. - -Надёжно определить размер страницы с учетом прокрутки можно, взяв максимум из нескольких свойств: - -```js -//+ run -var scrollHeight = Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight -); - -alert( 'Высота с учетом прокрутки: ' + scrollHeight ); -``` - -Почему так? Лучше и не спрашивайте, это одно из редких мест, где просто ошибки в браузерах. Глубокой логики здесь нет. - -## Получение текущей прокрутки [#page-scroll] - -У обычного элемента текущую прокрутку можно получить в `scrollLeft/scrollTop`. - -Что же со страницей? - -Большинство браузеров корректно обработает запрос к `documentElement.scrollLeft/Top`, однако Safari/Chrome/Opera есть ошибки (к примеру [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)), из-за которых следует использовать `document.body`. - -Чтобы вообще обойти проблему, можно использовать специальные свойства `window.pageXOffset/pageYOffset`: - -```js -//+ run -alert( 'Текущая прокрутка сверху: ' + window.pageYOffset ); -alert( 'Текущая прокрутка слева: ' + window.pageXOffset ); -``` - -Эти свойства: -
          -
        • Не поддерживаются IE8-
        • -
        • Их можно только читать, а менять нельзя.
        • -
        - -Если IE8- не волнует, то просто используем эти свойства. - -Кросс-браузерный вариант с учётом IE8 предусматривает откат на `documentElement`: - -```js -//+ run -var scrollTop = window.pageYOffset || document.documentElement.scrollTop; - -alert( "Текущая прокрутка: " + scrollTop ); -``` - -## Изменение прокрутки: scrollTo, scrollBy, scrollIntoView [#window-scroll] - -[warn] -Чтобы прокрутить страницу при помощи JavaScript, её DOM должен быть полностью загружен. -[/warn] - -На обычных элементах свойства `scrollTop/scrollLeft` можно изменять, и при этом элемент будет прокручиваться. - -Никто не мешает точно так же поступать и со страницей. Во всех браузерах, кроме Chrome/Safari/Opera можно осуществить прокрутку установкой `document.documentElement.scrollTop`, а в указанных -- использовать для этого `document.body.scrollTop`. И будет работать. Можно попробовать прокручивать и так и эдак и проверять, подействовала ли прокрутка, будет кросс-браузерно. - -Но есть и другое, простое и универсальное решение -- специальные методы прокрутки страницы [window.scrollBy(x,y)](https://developer.mozilla.org/en/Window.scrollBy) и [window.scrollTo(pageX,pageY)](https://developer.mozilla.org/en/Window.scrollTo). - -
          -
        • Метод `scrollBy(x,y)` прокручивает страницу относительно текущих координат. -[online] -Например, кнопка ниже прокрутит страницу на `10px` вниз: - - -[/online] -
        • -
        • Метод `scrollTo(pageX,pageY)` прокручивает страницу к указанным координатам относительно документа. - -Он эквивалентен установке свойств `scrollLeft/scrollTop`. - -Чтобы прокрутить в начало документа, достаточно указать координаты `(0,0)`. -[online] - -[/online] -
        • -
        - -## scrollIntoView - -Для полноты картины рассмотрим также метод [elem.scrollIntoView(top)](https://developer.mozilla.org/en/DOM/element.scrollIntoView). - -Метод `elem.scrollIntoView(top)` вызывается на элементе и прокручивает страницу так, чтобы элемент оказался вверху, если параметр `top` равен `true`, и внизу, если `top` равен `false`. Причем, если параметр `top` не указан, то он считается равным `true`. - -Кнопка ниже прокрутит страницу так, чтобы кнопка оказалась вверху: - - - -А следующая кнопка прокрутит страницу так, чтобы кнопка оказалась внизу: - - - -## Запрет прокрутки - -Иногда бывает нужно временно сделать документ "непрокручиваемым". Например, при показе большого диалогового окна над документом -- чтобы посетитель мог прокручивать это окно, но не документ. - -**Чтобы запретить прокрутку страницы, достаточно поставить `document.body.style.overflow = "hidden"`.** - -При этом страница замрёт в текущем положении. - -[online] -Попробуйте сами: - - - - - -При нажатии на верхнюю кнопку страница замрёт на текущем положении прокрутки. После нажатия на нижнюю -- прокрутка возобновится. -[/online] - -Вместо `document.body` может быть любой элемент, прокрутку которого необходимо запретить. - -Недостатком этого способа является то, что сама полоса прокрутки исчезает. Если она занимала некоторую ширину, то теперь эта ширина освободится, и содержимое страницы расширится, текст "прыгнет", заняв освободившееся место. - -Это может быть не очень красиво, но легко обходится, если вычислить размер прокрутки и добавить такой же по размеру `padding`. - -## Итого - -Размеры: - -
          -
        • Для получения размеров видимой части окна: `document.documentElement.clientWidth/Height` -
        • -
        • Для получения размеров страницы с учётом прокрутки: - -```js -var scrollHeight = Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight -); -``` - -
        • -
        - -**Прокрутка окна:** - -
          -
        • Прокрутку окна можно *получить* как `window.pageYOffset` (для горизонтальной -- `window.pageXOffset`) везде, кроме IE8-. - -На всякий случай -- вот самый кросс-браузерный способ, учитывающий IE7- в том числе: - -```js -//+ run -var html = document.documentElement; -var body = document.body; - -var scrollTop = html.scrollTop || body && body.scrollTop || 0; -scrollTop -= html.clientTop; // в IE7- смещён относительно (0,0) - -alert( "Текущая прокрутка: " + scrollTop ); -``` - -
        • -
        • Установить прокрутку можно при помощи специальных методов: -
            -
          • `window.scrollTo(pageX,pageY)` -- абсолютные координаты,
          • -
          • `window.scrollBy(x,y)` -- прокрутить относительно текущего места.
          • `elem.scrollIntoView(top)` -- прокрутить, чтобы элемент `elem` стал виден.
          • -
          -
        • -
        - diff --git a/2-ui/1-document/16-metrics-window/document-client-width-height.png b/2-ui/1-document/16-metrics-window/document-client-width-height.png deleted file mode 100644 index 49360367..00000000 Binary files a/2-ui/1-document/16-metrics-window/document-client-width-height.png and /dev/null differ diff --git a/2-ui/1-document/16-metrics-window/document-client-width-height@2x.png b/2-ui/1-document/16-metrics-window/document-client-width-height@2x.png deleted file mode 100644 index 7f3584f2..00000000 Binary files a/2-ui/1-document/16-metrics-window/document-client-width-height@2x.png and /dev/null differ diff --git a/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.md b/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.md deleted file mode 100644 index 1d911709..00000000 --- a/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.md +++ /dev/null @@ -1,42 +0,0 @@ -# Координаты внешних углов - -Координаты элемента возвращаются функцией [elem.getBoundingClientRect](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect). Она возвращает все координаты относительно окна в виде объекта со свойствами `left`, `top`, `right`, `bottom`. Некоторые браузеры также добавляют `width`, `height`. - -Так что координаты верхнего-левого `coords1` и правого-нижнего `coords4` внешних углов: - -```js -var coords = elem.getBoundingClientRect(); - -var coords1 = [coords.left, coords.top]; -var coords2 = [coords.right, coords.bottom]; -``` - -# Левый-верхний угол внутри - -Этот угол отстоит от наружных границ на размер рамки, который доступен через `clientLeft/clientTop`: - -```js -var coords3 = [coords.left + field.clientLeft, coords.top + field.clientTop]; -``` - -# Правый-нижний угол внутри - -Этот угол отстоит от правой-нижней наружной границы на размер рамки. Так как нужная рамка находится справа-внизу, то специальных свойств для нее нет, но мы можем получить этот размер из CSS: - -```js -var coords4 = [ - coords.right - parseInt(getComputedStyle(field).borderRightWidth), - coords.bottom - parseInt(getComputedStyle(field).borderBottomWidth) -] -``` - -Можно получить их альтернативным путем, прибавив `clientWidth/clientHeight` к координатам левого-верхнего внутреннего угла. Получится то же самое, пожалуй даже быстрее и изящнее. - -```js -var coords4 = [ - coords.left + elem.clientLeft + elem.clientWidth, - coords.top + elem.clientTop + elem.clientHeight -] -``` - -[edit src="solution"]Полный код решения[/edit] \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.view/index.css b/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.view/index.css deleted file mode 100755 index 74a22c20..00000000 --- a/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.view/index.css +++ /dev/null @@ -1,27 +0,0 @@ -body { - padding: 20px 0 0 20px; - cursor: pointer; -} - -#field { - overflow: hidden; - width: 200px; - height: 150px; - border-top: 10px solid black; - border-right: 10px solid gray; - border-bottom: 10px solid gray; - border-left: 10px solid black; - background-color: #00FF00; - font: 10px/1.2 monospace; -} - -.triangle-right { - position: relative; - width: 0; - height: 0; - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - border-left: 20px solid red; - text-indent: -20px; - font: 12px/1 monospace; -} \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.view/index.html b/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.view/index.html deleted file mode 100755 index b0daa0ef..00000000 --- a/2-ui/1-document/17-coordinates/1-find-point-coordinates/solution.view/index.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - Кликните на любое место, чтобы получить координаты относительно окна. -
        Это для удобства тестирования, чтобы проверить результат, который вы получите из DOM. -
        -
        (координаты появятся тут)
        - - -
        - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -
        - - -
        1
        -
        3
        -
        4
        -
        2
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/1-find-point-coordinates/source.view/index.css b/2-ui/1-document/17-coordinates/1-find-point-coordinates/source.view/index.css deleted file mode 100755 index 74a22c20..00000000 --- a/2-ui/1-document/17-coordinates/1-find-point-coordinates/source.view/index.css +++ /dev/null @@ -1,27 +0,0 @@ -body { - padding: 20px 0 0 20px; - cursor: pointer; -} - -#field { - overflow: hidden; - width: 200px; - height: 150px; - border-top: 10px solid black; - border-right: 10px solid gray; - border-bottom: 10px solid gray; - border-left: 10px solid black; - background-color: #00FF00; - font: 10px/1.2 monospace; -} - -.triangle-right { - position: relative; - width: 0; - height: 0; - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - border-left: 20px solid red; - text-indent: -20px; - font: 12px/1 monospace; -} \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/1-find-point-coordinates/source.view/index.html b/2-ui/1-document/17-coordinates/1-find-point-coordinates/source.view/index.html deleted file mode 100755 index bcd35954..00000000 --- a/2-ui/1-document/17-coordinates/1-find-point-coordinates/source.view/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - Кликните на любое место, чтобы получить координаты относительно окна. -
        Это для удобства тестирования, чтобы проверить результат, который вы получите из DOM. -
        -
        (координаты появятся тут)
        - - -
        - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -
        - - -
        1
        -
        3
        -
        4
        -
        2
        - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/1-find-point-coordinates/task.md b/2-ui/1-document/17-coordinates/1-find-point-coordinates/task.md deleted file mode 100644 index a653c27f..00000000 --- a/2-ui/1-document/17-coordinates/1-find-point-coordinates/task.md +++ /dev/null @@ -1,24 +0,0 @@ -# Найдите координаты точки в документе - -[importance 5] - -В ифрейме ниже вы видите документ с зеленым "полем". - -При помощи JavaScript найдите координаты указанных стрелками углов относительно окна браузера. - -Для тестирования в документ добавлено удобство: клик в любом месте отображает координаты мыши относительно окна. - -[iframe border=1 height=360 src="source" link edit] - -Ваш код должен при помощи DOM получить четыре пары координат: -
          -
        1. Левый-верхний угол снаружи, это просто.
        2. -
        3. Правый-нижний угол снаружи, это тоже просто.
        4. -
        5. Левый-верхний угол внутри, это чуть сложнее.
        6. -
        7. Правый-нижний угол внутри, это ещё сложнее, но можно сделать даже несколькими способами.
        8. -
        - -Они должны совпадать с координатами, которые вы получите кликом по полю. - -P.S. Код не должен быть как-то привязан к конкретным размерам элемента, стилям, наличию или отсутствию рамки. - diff --git a/2-ui/1-document/17-coordinates/2-position-at/solution.md b/2-ui/1-document/17-coordinates/2-position-at/solution.md deleted file mode 100644 index e69de29b..00000000 diff --git a/2-ui/1-document/17-coordinates/2-position-at/solution.view/index.css b/2-ui/1-document/17-coordinates/2-position-at/solution.view/index.css deleted file mode 100755 index a82c7d9c..00000000 --- a/2-ui/1-document/17-coordinates/2-position-at/solution.view/index.css +++ /dev/null @@ -1,28 +0,0 @@ -.note { - position: fixed; - z-index: 1000; - padding: 5px; - border: 1px solid black; - background: white; - text-align: center; - font: italic 14px Georgia; -} - -blockquote { - background: #f9f9f9; - border-left: 10px solid #ccc; - margin: 0 0 0 100px; - padding: .5em 10px; - quotes: "\201C""\201D""\2018""\2019"; - display: inline-block; - white-space: pre; -} - -blockquote:before { - color: #ccc; - content: open-quote; - font-size: 4em; - line-height: .1em; - margin-right: .25em; - vertical-align: -.4em; -} \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/2-position-at/solution.view/index.html b/2-ui/1-document/17-coordinates/2-position-at/solution.view/index.html deleted file mode 100755 index 82d38dde..00000000 --- a/2-ui/1-document/17-coordinates/2-position-at/solution.view/index.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - -
        - - Что на завтрак, Бэрримор? - Овсянка, сэр. - А на обед? - Овсянка, сэр. - Ну а на ужин? - Котлеты, сэр. - Уррра!!! - Из овсянки, сэр!!! -
        - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/2-position-at/source.view/index.css b/2-ui/1-document/17-coordinates/2-position-at/source.view/index.css deleted file mode 100755 index a82c7d9c..00000000 --- a/2-ui/1-document/17-coordinates/2-position-at/source.view/index.css +++ /dev/null @@ -1,28 +0,0 @@ -.note { - position: fixed; - z-index: 1000; - padding: 5px; - border: 1px solid black; - background: white; - text-align: center; - font: italic 14px Georgia; -} - -blockquote { - background: #f9f9f9; - border-left: 10px solid #ccc; - margin: 0 0 0 100px; - padding: .5em 10px; - quotes: "\201C""\201D""\2018""\2019"; - display: inline-block; - white-space: pre; -} - -blockquote:before { - color: #ccc; - content: open-quote; - font-size: 4em; - line-height: .1em; - margin-right: .25em; - vertical-align: -.4em; -} \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/2-position-at/source.view/index.html b/2-ui/1-document/17-coordinates/2-position-at/source.view/index.html deleted file mode 100755 index 2439a7e2..00000000 --- a/2-ui/1-document/17-coordinates/2-position-at/source.view/index.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - -
        - - Что на завтрак, Бэрримор? - Овсянка, сэр. - А на обед? - Овсянка, сэр. - Ну а на ужин? - Котлеты, сэр. - Уррра!!! - Из овсянки, сэр!!! -
        - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/2-position-at/task.md b/2-ui/1-document/17-coordinates/2-position-at/task.md deleted file mode 100644 index d580742f..00000000 --- a/2-ui/1-document/17-coordinates/2-position-at/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Разместить заметку рядом с элементом - -[importance 5] - -Создайте функцию `positionAt(anchor, position, elem)`, которая позиционирует элемент `elem`, в зависимости от `position`, сверху (`"top"`), справа (`"right"`) или снизу (`"bottom"`) от элемента `anchor`. - -Используйте её, чтобы сделать функцию `showNote(anchor, position, html)`, которая показывает элемент с классом `note` и текстом `html` на позиции `position` рядом с элементом `anchor`. - -Выведите заметки как здесь: - -[iframe src="solution" height="450" border="1" link] - - diff --git a/2-ui/1-document/17-coordinates/article.md b/2-ui/1-document/17-coordinates/article.md deleted file mode 100644 index 777421a2..00000000 --- a/2-ui/1-document/17-coordinates/article.md +++ /dev/null @@ -1,201 +0,0 @@ -# Координаты в окне - -Для того, чтобы поместить один элемент рядом с другим на странице, а также двигать его произвольным образом, к примеру, рядом с указателем мыши -- используются координаты. - -*Координатная система относительно окна браузера* начинается в левом-верхнем углу текущей видимой области окна. - -Мы будем называть координаты в ней `clientX/clientY`. - - -## getBoundingClientRect() - -Метод `elem.getBoundingClientRect()` возвращает координаты элемента, под которыми понимаются размеры "воображаемого прямоугольника", который охватывает весь элемент. - -Координаты возвращаются в виде объекта со свойствами: -
          -
        • `top` -- Y-координата верхней границы элемента,
        • -
        • `left` -- X-координата левой границы,
        • -
        • `right` -- X-координата правой границы,
        • -
        • `bottom` -- Y-координата нижней границы.
        • -
        - -Например: - - - -**Координаты относительно окна не учитывают прокрутку, они высчитываются от границ текущей видимой области.** - -Иначе говоря, если страницу прокрутить, то элемент поднимется выше или опустится ниже -- его координаты относительно окна изменятся. - -[online] -Например, кликните на кнопку, чтобы увидеть её координаты: - - - - - -Если вы прокрутите эту страницу, то положение кнопки в окне изменится, и её координаты, соответственно, тоже. -[/online] - -
          -
        • Координаты могут быть дробными -- это нормально, так как они возвращаются из внутренних структур браузера.
        • -
        • Координаты могут быть и отрицательными, например если прокрутить страницу так, что верх элемента будет выходить за верхнуюю границу окна, то его `top`-координата будет меньше нуля.
        • -
        • Некоторые современные браузеры также добавляют к результату `getBoundingClientRect` свойства для ширины и высоты: `width/height`, но их можно получить и простым вычитанием: `height = bottom - top`, `width = right - left`.
        • -
        - -[warn header="Координаты right/bottom отличаются от CSS-свойств"] -Если рассмотреть позиционирование элементов при помощи CSS-свойства `position`, то там тоже указываются `left`, `right`, `top`, `bottom`. - -Однако, по CSS свойство `right` задаёт расстояние от правой границы, а `bottom` -- от нижней. - -Если вы взглянете на иллюстрацию выше, то увидите, что в JavaScript это не так. Все координаты отсчитываются слева/сверху, в том числе и эти. -[/warn] - - -[smart header="Метод `elem.getBoundingClientRect()` изнутри"] - -Браузер отображает любое содержимое, используя прямоугольники. - -В случае с блочным элементом, таким как `DIV`, элемент сам по себе образует прямоугольник. Но если элемент строчный и содержит в себе длинный текст, то каждая строка будет отдельным прямоугольником, с одинаковой высотой но разной длиной (у каждой строки -- своя длина). - -Более подробно это описано в: спецификации. - -Если обобщить, содержимое элемента может отображаться в одном прямоугольнике или в нескольких. - -Все эти прямоугольники можно получить с помощью [elem.getClientRects()](https://developer.mozilla.org/en/DOM/element.getClientRects). А метод [elem.getBoundingClientRect()](https://developer.mozilla.org/en/DOM/element.getBoundingClientRect) возвращает один охватывающий прямоугольник для всех `getClientRects()`. -[/smart] - - -## elementFromPoint(x, y) [#elementFromPoint] - -Возвращает элемент, который находится на координатах `(x, y)` относительно окна. - -Синтаксис: - -```js -var elem = document.elementFromPoint(x, y); -``` - -Например, код ниже ниже выделяет и выводит тег у элемента, который сейчас в середине окна: - -```js -//+ run -var centerX = document.documentElement.clientWidth / 2; -var centerY = document.documentElement.clientHeight / 2; - -var elem = document.elementFromPoint(centerX, centerY); - -elem.style.background = "red"; -alert( elem.tagName ); -elem.style.background = ""; -``` - -Аналогично предыдущему методу, используются координаты относительно окна, так что, в зависимости от прокрутки страницы, в центре может быть разный элемент. - -[warn header="Для координат вне окна `elementFromPoint` возвращает `null`"] -Метод `document.elementFromPoint(x,y)` работает только если координаты `(x,y)` находятся в пределах окна. - -Если одна из них отрицательна или больше чем ширина/высота окна -- он возвращает `null`. - -В большинстве сценариев использования это не является проблемой, но нужно проверять, что результат -- не `null`. -[/warn] - - -## Координаты для position:fixed - -Координаты обычно требуются не просто так, а, например, чтобы переместить элемент на них. - -В CSS для позиционирования элемента относительно окна используется свойство `position:fixed`. Как правило, вместе с ним идут и координаты, например `left/top`. - -Например, функция `createMessageUnder` из кода ниже покажет сообщение под элементом `elem`: - -```js -var elem = document.getElementById("coords-show-mark"); - -function createMessageUnder(elem, text) { - // получить координаты - var coords = elem.getBoundingClientRect(); - - // создать элемент для сообщения - var message = document.createElement('div'); - // стиль лучше задавать классом - message.style.cssText = "position:fixed; color: red"; - -*!* - // к координатам обязательно добавляем "px"! - message.style.left = coords.left + "px"; - message.style.top = coords.bottom + "px"; -*/!* - - message.innerHTML = text; - - return message; -} - -// Использование -// добавить на 5 сек в документ -var message = createMessageUnder(elem, 'Привет, мир!'); -document.body.appendChild(message); -setTimeout(function() { - document.body.removeChild(message); -}, 5000); -``` - -[online] -Нажмите на кнопку, чтобы запустить его: - - -[/online] - -Этот код можно модифицировать, чтобы показывать сообщение слева, справа, сверху, делать это вместе с CSS-анимацией и так далее. Для этого нужно всего лишь понимать, как получить координаты. - -**Заметим, однако, важную деталь: при прокрутке страницы сообщение будет визуально отдаляться от кнопки.** - -Причина очевидна, ведь оно использует `position: fixed`, так что при прокрутке остаётся на месте, а страница скроллируется. - -Как сделать, чтобы сообщение было именно на конкретном месте документа, а не окна, мы рассмотрим в следующей главе. - - -[head] - -[/head] \ No newline at end of file diff --git a/2-ui/1-document/17-coordinates/coords.png b/2-ui/1-document/17-coordinates/coords.png deleted file mode 100644 index 2f7fb46d..00000000 Binary files a/2-ui/1-document/17-coordinates/coords.png and /dev/null differ diff --git a/2-ui/1-document/17-coordinates/coords@2x.png b/2-ui/1-document/17-coordinates/coords@2x.png deleted file mode 100644 index a832cc33..00000000 Binary files a/2-ui/1-document/17-coordinates/coords@2x.png and /dev/null differ diff --git a/2-ui/1-document/18-coordinates-document/1-get-document-scrolls/solution.md b/2-ui/1-document/18-coordinates-document/1-get-document-scrolls/solution.md deleted file mode 100644 index d5d0d41c..00000000 --- a/2-ui/1-document/18-coordinates-document/1-get-document-scrolls/solution.md +++ /dev/null @@ -1,24 +0,0 @@ -
          -
        • `top` -- это `pageYOffset`.
        • -
        • `bottom` -- это `pageYOffset` плюс высота видимой части `documentElement.clientHeight`.
        • -
        • `height` -- полная высота документа, её вычисление дано в главе [](/metrics-window).
        • -
        - -Итого: - -```js -function getDocumentScroll() { - var scrollHeight = Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight - ); - - return { - top: pageYOffset, - bottom: pageYOffset + document.documentElement.clientHeight, - height: scrollHeight - }; -} -``` - diff --git a/2-ui/1-document/18-coordinates-document/1-get-document-scrolls/task.md b/2-ui/1-document/18-coordinates-document/1-get-document-scrolls/task.md deleted file mode 100644 index d82bc669..00000000 --- a/2-ui/1-document/18-coordinates-document/1-get-document-scrolls/task.md +++ /dev/null @@ -1,15 +0,0 @@ -# Область видимости для документа - -[importance 5] - -Напишите функцию `getDocumentScroll()`, которая возвращает объект с информацией о текущей прокрутке и области видимости. - -Свойства объекта-результата: - -
          -
        • `top` -- координата верхней границы видимой части (относительно документа).
        • -
        • `bottom` -- координата нижней границы видимой части (относительно документа).
        • -
        • `height` -- полная высота документа, включая прокрутку.
        • -
        - -В этой задаче учитываем только вертикальную прокрутку: горизонтальная делается аналогично, а нужна сильно реже. \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.md b/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.md deleted file mode 100644 index e69de29b..00000000 diff --git a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.view/index.css b/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.view/index.css deleted file mode 100644 index 700bd8c2..00000000 --- a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.view/index.css +++ /dev/null @@ -1,28 +0,0 @@ -.note { - position: absolute; - z-index: 1000; - padding: 5px; - border: 1px solid black; - background: white; - text-align: center; - font: italic 14px Georgia; -} - -blockquote { - background: #f9f9f9; - border-left: 10px solid #ccc; - margin: 0 0 0 100px; - padding: .5em 10px; - quotes: "\201C""\201D""\2018""\2019"; - display: inline-block; - white-space: pre; -} - -blockquote:before { - color: #ccc; - content: open-quote; - font-size: 4em; - line-height: .1em; - margin-right: .25em; - vertical-align: -.4em; -} \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.view/index.html b/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.view/index.html deleted file mode 100644 index 6b28fea1..00000000 --- a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/solution.view/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - -

        Исправления два:

        - -
          -
        1. Использование функции getCoords() из учебника для получения абсолютных координат.
        2. -
        3. Изменение position:fixed на position:absolute в стилях.
        4. -
        - -
        - - Что на завтрак, Бэрримор? - Овсянка, сэр. - А на обед? - Овсянка, сэр. - Ну а на ужин? - Котлеты, сэр. - Уррра!!! - Из овсянки, сэр!!! -
        - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/task.md b/2-ui/1-document/18-coordinates-document/2-position-at-absolute/task.md deleted file mode 100644 index 5e229a89..00000000 --- a/2-ui/1-document/18-coordinates-document/2-position-at-absolute/task.md +++ /dev/null @@ -1,9 +0,0 @@ -# Разместить заметку рядом с элементом (absolute) - -[importance 5] - -Модифицируйте решение задачи [](/task/position-at), чтобы при прокрутке страницы заметка не убегала от элемента. - -Используйте для этого координаты относительно документа и `position:absolute` вместо `position:fixed`. - -В качестве исходного документа используйте решение задачи [](/task/position-at), для тестирования прокрутки добавьте стиль ``. \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.md b/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.md deleted file mode 100644 index e69de29b..00000000 diff --git a/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.view/index.css b/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.view/index.css deleted file mode 100644 index d2493f6e..00000000 --- a/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.view/index.css +++ /dev/null @@ -1,29 +0,0 @@ -.note { - position: absolute; - z-index: 1000; - padding: 5px; - border: 1px solid black; - background: white; - text-align: center; - font: italic 14px Georgia; - opacity: .8; -} - -blockquote { - background: #f9f9f9; - border-left: 10px solid #ccc; - margin: 0 0 0 100px; - padding: .5em 10px; - quotes: "\201C""\201D""\2018""\2019"; - display: inline-block; - white-space: pre; -} - -blockquote:before { - color: #ccc; - content: open-quote; - font-size: 4em; - line-height: .1em; - margin-right: .25em; - vertical-align: -.4em; -} \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.view/index.html b/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.view/index.html deleted file mode 100644 index df1b2820..00000000 --- a/2-ui/1-document/18-coordinates-document/3-position-at-2/solution.view/index.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - -
        - - Что на завтрак, Бэрримор? - Овсянка, сэр. - А на обед? - Овсянка, сэр. - Ну а на ужин? - Котлеты, сэр. - Уррра!!! - Из овсянки, сэр!!! -
        - -

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reprehenderit sint atque dolorum fuga ad incidunt voluptatum error fugiat animi amet! Odio temporibus nulla id unde quaerat dignissimos enim nisi rem provident molestias sit tempore omnis recusandae - esse sequi officia sapiente.

        - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/3-position-at-2/task.md b/2-ui/1-document/18-coordinates-document/3-position-at-2/task.md deleted file mode 100644 index f81c127f..00000000 --- a/2-ui/1-document/18-coordinates-document/3-position-at-2/task.md +++ /dev/null @@ -1,27 +0,0 @@ -# Разместить заметку внутри элемента - -[importance 5] - -Расширьте предыдущую задачу [](/task/position-at-absolute): научите функцию `positionAt(anchor, position, elem)` вставлять `elem` внутрь `anchor`. - -Новые значения `position`: -
          -
        • `top-out`, `right-out`, `bottom-out` -- работают так же, как раньше, то есть вставляют `elem` над/справа/под `anchor`.
        • -
        • `top-in`, `right-in`, `bottom-in` -- вставляют `elem` внутрь `anchor`: к верхней границе/правой/нижней.
        • -
        - -Например: - -```js -// покажет note сверху blockquote -positionAt(blockquote, "top-out", note); - -// покажет note сверху-внутри blockquote -positionAt(blockquote, "top-in", note); -``` - -Пример результата: - -[iframe src="solution" height="500" border="1" link] - -В качестве исходного документа возьмите решение задачи [](/task/position-at-absolute). \ No newline at end of file diff --git a/2-ui/1-document/18-coordinates-document/article.md b/2-ui/1-document/18-coordinates-document/article.md deleted file mode 100644 index 9e25fc76..00000000 --- a/2-ui/1-document/18-coordinates-document/article.md +++ /dev/null @@ -1,226 +0,0 @@ -# Координаты в документе - -*Система координат относительно страницы* или, иначе говоря, *относительно документа*, начинается в левом-верхнем углу, но не окна, а именно страницы. - -И координаты в ней означают позицию по отношению не к окну браузера, а к документу в целом. - -Если провести аналогию с CSS, то координаты относительно окна -- это `position:fixed`, а относительно документа -- `position:absolute` (при позиционировании вне других элементов, естественно). - -Мы будем называть координаты в ней `pageX/pageY`. - -[cut] - -Они нужны в первую очередь для того, чтобы показывать элемент в определённом месте страницы, а не окна. - -## Сравнение систем координат - -Когда страница не прокручена, точки начала координат относительно окна `(clientX,clientY)` и документа `(pageX,pageY)` совпадают: - - - -Например, координаты элемента с надписью "STANDARDS" равны расстоянию от верхней/левой границы окна: - - - -**Прокрутим страницу, чтобы элемент был на самом верху:** - -Посмотрите на рисунок ниже, на нём -- та же страница, только прокрученная, и тот же элемент "STANDARDS". - -
          -
        • Координата `clientY` изменилась. Она была `175`, а стала `0`, так как элемент находится вверху окна.
        • -
        • Координата `pageY` осталась такой же, так как отсчитывается от левого-верхнего угла *документа*.
        • -
        - - - -Итак, координаты `pageX/pageY` не меняются при прокрутке, в отличие от `clientX/clientY`. - -## Получение координат [#getCoords] - -К сожалению, готовой функции для получения координат элемента относительно страницы нет. Но её можно легко написать самим. - -Эти две системы координат жёстко связаны: `pageY = clientY + текущая вертикальная прокрутка`. - -Наша функция `getCoords(elem)` будет брать результат `elem.getBoundingClientRect()` и прибавлять текущую прокрутку документа. - -Результат `getCoords`: объект с координатами `{left: .., top: ..}` - -```js -function getCoords(elem) { // кроме IE8- - var box = elem.getBoundingClientRect(); - - return { - top: box.top + pageYOffset, - left: box.left + pageXOffset - }; - -} -``` - -Если нужно поддерживать более старые IE, то вот альтернативный, самый кросс-браузерный вариант: - -```js -//+ autorun -function getCoords(elem) { - // (1) - var box = elem.getBoundingClientRect(); - - var body = document.body; - var docEl = document.documentElement; - - // (2) - var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; - var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - // (3) - var clientTop = docEl.clientTop || body.clientTop || 0; - var clientLeft = docEl.clientLeft || body.clientLeft || 0; - - // (4) - var top = box.top + scrollTop - clientTop; - var left = box.left + scrollLeft - clientLeft; - - return { - top: top, - left: left - }; -} -``` - -Разберем что и зачем, по шагам: - -
          -
        1. Получаем прямоугольник.
        2. -
        3. Считаем прокрутку страницы. Все браузеры, кроме IE8- поддерживают свойство `pageXOffset/pageYOffset`. В более старых IE, когда установлен DOCTYPE, прокрутку можно получить из `documentElement`, ну и наконец если DOCTYPE некорректен -- использовать `body`.
        4. -
        5. В IE документ может быть смещен относительно левого верхнего угла. Получим это смещение.
        6. -
        7. Добавим прокрутку к координатам окна и вычтем смещение `html/body`, чтобы получить координаты всего документа.
        8. -
        - -### Устаревший метод: offset* - -Есть альтернативный способ нахождения координат -- это пройти всю цепочку `offsetParent` от элемента вверх и сложить отступы `offsetLeft/offsetTop`. - -Мы разбираем его здесь с учебной целью, так как он используется лишь в старых браузерах. - -Вот функция, реализующая такой подход. - -```js -//+ autorun -function getOffsetSum(elem) { - var top = 0, - left = 0; - - while (elem) { - top = top + parseInt(elem.offsetTop); - left = left + parseInt(elem.offsetLeft); - elem = elem.offsetParent; - } - - return { - top: top, - left: left - }; -} -``` - -Казалось бы, код нормальный. И он как-то работает, но разные браузеры преподносят "сюрпризы", включая или выключая размер рамок и прокруток из `offsetTop/Left`, некорректно учитывая позиционирование. В итоге результат не всегда верен. Можно, конечно, разобрать эти проблемы и посчитать действительно аккуратно и правильно этим способом, но зачем? Ведь есть `getBoundingClientRect`. - -[online] -Вы можете увидеть разницу между вычислением координат через `offset*` и `getBoundingClientRect` на примере. - -В прямоугольнике ниже есть 3 вложенных `DIV`. Все они имеют `border`, кое-кто из них имеет `position/margin/padding`. - -Кликните по внутреннему (жёлтому) элементу, чтобы увидеть результаты обоих методов: `getOffsetSum` и `getCoords`, а также реальные координаты курсора -- `event.pageX/pageY` (мы обсудим их позже в статье [](/fixevent)). - -[pre] -
        -
        -
        Кликните, чтобы получить координаты getOffsetSum и getCoords
        -
        -
        -
        -
        getOffsetSum:значение getOffsetSum()
        -
        getCoords:значение getCoords()
        -
        mouse:координаты мыши
        -
        - - -[/pre] - -**При клике на любом месте желтого блока вы легко увидите разницу между `getOffsetSum(elem)` и `getCoords(elem)`.** - -Для того, чтобы узнать, какой же результат верный, кликните в левом-верхнем углу жёлтого блока, причём обратите внимание -- кликать нужно не на жёлтом, а на чёрном, это рамка, она тоже входит в элемент. Будут видны точные координаты мыши, так что вы можете сравнить их с `getOffsetSum/getCoords`. - -Пример клика в правильном месте (обратите внимание на разницу координат): - - - -Именно `getCoords` всегда возвращает верное значение. -[/online] - -## Координаты на экране screenX/screenY - -Есть ещё одна система координат, которая используется очень редко, но для полноты картины необходимо её упомянуть. - -*Координаты относительно экрана* `screenX/screenY` отсчитываются от его левого-верхнего угла. Имеется в виду именно *весь экран*, а не окно браузера. - - - -Такие координаты могут быть полезны, например, при работе с мобильными устройствами или для открытия нового окна посередине экрана вызовом [window.open](https://developer.mozilla.org/en-US/docs/DOM/window.open). - -
          -
        • Размеры экрана хранятся в глобальной переменной [screen](https://developer.mozilla.org/en/DOM/window.screen): - -```js -//+ run -// общая ширина/высота -alert( screen.width + ' x ' + screen.height ); - -// доступная ширина/высота (за вычетом таскбара и т.п.) -alert( screen.availWidth + ' x ' + screen.availHeight ); - -// есть и ряд других свойств screen (см. документацию) -``` - -
        • -
        • Координаты левого-верхнего угла браузера на экране хранятся в `window.screenX,` `window.screenY` (не поддерживаются IE8-): - -```js -//+ run -alert( "Браузер находится на " + window.screenX + "," + window.screenY ); -``` - -Они могут быть и меньше нуля, если окно частично вне экрана.
        • -
        - -Заметим, что общую информацию об экране и браузере получить можно, а вот координаты конкретного элемента на экране -- нельзя, нет аналога `getBoundingClientRect` или иного метода для этого. - -## Итого - -У любой точки в браузере есть координаты: -
          -
        1. Относительно окна `window` -- `elem.getBoundingClientRect()`.
        2. -
        3. Относительно документа `document` -- добавляем прокрутку, во всех фреймворках есть готовая функция.
        4. -
        5. Относительно экрана `screen` -- можно узнать координаты браузера, но не элемента.
        6. -
        - -Иногда в старом коде можно встретить использование `offsetTop/Left` для подсчёта координат. Это очень старый и неправильный способ, не стоит его использовать. - -Координаты будут нужны нам далее, при работе с событиями мыши (координаты клика) и элементами (перемещение). - diff --git a/2-ui/1-document/18-coordinates-document/getcoords-compare.png b/2-ui/1-document/18-coordinates-document/getcoords-compare.png deleted file mode 100755 index bc0890a2..00000000 Binary files a/2-ui/1-document/18-coordinates-document/getcoords-compare.png and /dev/null differ diff --git a/2-ui/1-document/18-coordinates-document/getcoords-compare@2x.png b/2-ui/1-document/18-coordinates-document/getcoords-compare@2x.png deleted file mode 100755 index 94d9b4a9..00000000 Binary files a/2-ui/1-document/18-coordinates-document/getcoords-compare@2x.png and /dev/null differ diff --git a/2-ui/1-document/18-coordinates-document/pagewindow0.png b/2-ui/1-document/18-coordinates-document/pagewindow0.png deleted file mode 100755 index bb44df35..00000000 Binary files a/2-ui/1-document/18-coordinates-document/pagewindow0.png and /dev/null differ diff --git a/2-ui/1-document/18-coordinates-document/screen.png b/2-ui/1-document/18-coordinates-document/screen.png deleted file mode 100755 index a92cf655..00000000 Binary files a/2-ui/1-document/18-coordinates-document/screen.png and /dev/null differ diff --git a/2-ui/1-document/18-coordinates-document/standards-scroll.png b/2-ui/1-document/18-coordinates-document/standards-scroll.png deleted file mode 100755 index e212238b..00000000 Binary files a/2-ui/1-document/18-coordinates-document/standards-scroll.png and /dev/null differ diff --git a/2-ui/1-document/18-coordinates-document/standards.png b/2-ui/1-document/18-coordinates-document/standards.png deleted file mode 100755 index a76de32e..00000000 Binary files a/2-ui/1-document/18-coordinates-document/standards.png and /dev/null differ diff --git a/2-ui/1-document/19-dom-cheatsheet/article.md b/2-ui/1-document/19-dom-cheatsheet/article.md deleted file mode 100644 index 4e5fbf9b..00000000 --- a/2-ui/1-document/19-dom-cheatsheet/article.md +++ /dev/null @@ -1,187 +0,0 @@ -# Итого - -В этой главе кратко перечислены основные свойства и методы DOM, которые мы изучили. Их уже довольно много. - -Используйте её, чтобы по-быстрому вспомнить и прокрутить в голове то, что изучали ранее. Все ли эти свойства вам знакомы? - -Кое-где стоит ограничение на версии IE, но на все свойства можно найти или сделать или найти полифилл, с которым их можно использовать везде. - -[cut] - -## Создание - -
        -
        `document.createElement(tag)`
        Создать элемент с тегом `tag`
        -
        `document.createTextNode(txt)`
        Создать текстовый узел с текстом `txt`
        -
        `node.cloneNode(deep)`
        Клонировать существующий узел, если `deep=false`, то без потомков.
        -
        - -## Свойства узлов - -
        -
        `node.nodeType`
        Тип узла: 1(элемент) / 3(текст) / другие.
        -
        `elem.tagName`
        Тег элемента.
        -
        `elem.innerHTML`
        HTML внутри элемента.
        -
        `elem.outerHTML`
        Весь HTML элемента, включая сам тег. На запись использовать с осторожностью, так как не модифицирует элемент, а вставляет новый вместо него.
        -
        `node.data` / `node.nodeValue`
        Содержимое узла любого типа, кроме элемента.
        -
        `node.textContent`
        Текстовое содержимое узла, для элементов содержит текст с вырезанными тегами (IE9+).
        -
        `elem.hidden`
        Если поставить `true`, то элемент будет скрыт (IE10+).
        -
        - -## Атрибуты - -
        -
        `elem.getAttribute(name)`, `elem.hasAttribute(name)`, `elem.setAttribute(name, value)`
        -
        Чтение атрибута, проверка наличия и запись.
        -
        `elem.dataset.*`
        Значения атрибутов вида `data-*` (IE10+).
        -
        - -## Ссылки - -
        -
        `document.documentElement`
        -
        Элемент ``
        -
        `document.body`
        -
        Элемент ``
        -
        `document.head`
        -
        Элемент `` (IE9+)
        -
        - -По всем узлам: -
          -
        • `parentNode`
        • -
        • `nextSibling` `previousSibling`
        • -
        • `childNodes` `firstChild` `lastChild`
        • -
        - -Только по элементам: - -
          -
        • `parentElement`
        • -
        • `nextElementSibling` `previousElementSibling`
        • -
        • `children`, `firstElementChild` `lastElementChild`
        • -
        - -Все они IE9+, кроме `children`, который работает в IE8-, но содержит не только элементы, но и комментарии (ошибка в браузере). - -Дополнительно у некоторых типов элементов могут быть и другие ссылки, свойства, коллекции для навигации, -например для таблиц: - -
        -
        `table.rows[N]`
        -
        строка `TR` номер `N`.
        -
        `tr.cells[N]`
        -
        ячейка `TH/TD` номер `N`.
        -
        `tr.sectionRowIndex`
        -
        номер строки в таблице в секции `THEAD/TBODY`.
        -
        `td.cellIndex`
        -
        номер ячейки в строке.
        -
        - -## Поиск - - -
        -
        `*.querySelector(css)`
        -
        По селектору, только первый элемент
        -
        `*.querySelectorAll(css)`
        -
        По селектору CSS3, в IE8 по CSS 2.1
        -
        `document.getElementById(id)`
        -
        По уникальному `id`
        -
        `document.getElementsByName(name)`
        -
        По атрибуту `name`, в IE9- работает только для элементов, где `name` предусмотрен стандартом.
        -
        `*.getElementsByTagName(tag)`
        -
        По тегу `tag`
        -
        `*.getElementsByClassName(class)`
        -
        По классу, IE9+, корректно работает с элементами, у которых несколько классов.
        -
        - -Вообще, обычно можно использовать только `querySelector/querySelectorAll`. Методы `getElement*` работают быстрее (за счёт более оптимальной внутренней реализации), но в 99% случаев это различие очень небольшое и роли не играет. - -Дополнительно есть методы: -
        -
        `elem.matches(css)`
        -
        Проверяет, подходит ли элемент под CSS-селектор.`elem.closest(css)` -
        Ищет ближайший элемент сверху по иерархии DOM, подходящий под CSS-селектор. Первым проверяется сам `elem`. Этот элемент возвращается.
        -
        `elemA.contains(elemB)`
        -
        Возвращает `true`, если `elemA` является предком (содержит) `elemB`.
        -
        `elemA.compareDocumentPosition(elemB)`
        -
        Возвращает битовую маску, которая включает в себя отношение вложенности между `elemA` и `elemB`, а также -- какой из элементов появляется в DOM первым.
        - -
        - - -## Изменение - -
          -
        • `parent.appendChild(newChild)`
        • -
        • `parent.removeChild(child)`
        • -
        • `parent.insertBefore(newChild, refNode)`
        • -
        • `parent.insertAdjacentHTML("beforeBegin|afterBegin|beforeEnd|afterEnd", html)`
        • -
        • `parent.insertAdjacentElement("beforeBegin|...|afterEnd", text)` (кроме FF)
        • -
        • `parent.insertAdjacentText("beforeBegin|...|afterEnd", text)` (кроме FF)
        • -
        • `document.write(...)`
        • -
        - -Скорее всего, понадобятся полифиллы для: - -
          -
        • `node.append(...nodes)`
        • -
        • `node.prepend(...nodes)`
        • -
        • `node.after(...nodes)`,
        • -
        • `node.before(...nodes)`
        • -
        • `node.replaceWith(...nodes)`
        • -
        - -## Классы и стили - -
        -
        `elem.className`
        -
        Атрибут `class` -
        `elem.classList.add(class) remove(class) toggle(class) contains(class)`
        -
        Управление классами, для IE9- есть [эмуляция](https://github.com/eligrey/classList.js/blob/master/classList.js).
        -
        `elem.style`
        -
        Стили в атрибуте `style` элемента
        -
        `getComputedStyle(elem, "")` -
        Стиль, с учётом всего каскада, вычисленный и применённый (только чтение)
        -
        - -## Размеры и прокрутка элемента - -
        -
        `clientLeft/Top`
        -
        Ширина левой/верхней рамки `border`
        -
        `clientWidth/Height`
        -
        Ширина/высота внутренней части элемента, включая содержимое и `padding`, не включая полосу прокрутки (если есть).
        -
        `scrollWidth/Height`
        -
        Ширина/высота внутренней части элемента, с учетом прокрутки.
        -
        `scrollLeft/Top`
        -
        Ширина/высота прокрученной области.
        -
        `offsetWidth/Height`
        -
        Полный размер элемента: ширина/высота, включая `border`.
        -
        - -## Размеры и прокрутка страницы - -
          -
        • ширина/высота видимой области: `document.documentElement.clientHeight`
        • -
        • прокрутка(чтение): `window.pageYOffset || document.documentElement.scrollTop`
        • -
        • прокрутка(изменение): -
            -
          • `window.scrollBy(x,y)`: на x,y относительно текущей позиции.
          • -
          • `window.scrollTo(pageX, pageY)`: на координаты в документе.
          • -
          • `elem.scrollIntoView(true/false)`: прокрутить, чтобы `elem` стал видимым и оказался вверху окна(`true`) или внизу(`false`)
          • -
          -
        • -
        - -## Координаты - -
          -
        • относительно окна: `elem.getBoundingClientRect()`
        • -
        • относительно документа: `elem.getBoundingClientRect()` + прокрутка страницы
        • -
        • получить элемент по координатам: `document.elementFromPoint(clientX, clientY)`
        • -
        - -Список намеренно сокращён, чтобы было проще найти то, что нужно. diff --git a/2-ui/1-document/2-dom-nodes/1-body-from-head/solution.md b/2-ui/1-document/2-dom-nodes/1-body-from-head/solution.md deleted file mode 100644 index 399d66bc..00000000 --- a/2-ui/1-document/2-dom-nodes/1-body-from-head/solution.md +++ /dev/null @@ -1,21 +0,0 @@ -Выведет `null`, так как на момент выполнения скрипта тег `` ещё не обработан браузером. - -Попробуйте в действии: - -```html - - - - - - - - - Привет, мир! - - - -``` - diff --git a/2-ui/1-document/2-dom-nodes/1-body-from-head/task.md b/2-ui/1-document/2-dom-nodes/1-body-from-head/task.md deleted file mode 100644 index ef89617c..00000000 --- a/2-ui/1-document/2-dom-nodes/1-body-from-head/task.md +++ /dev/null @@ -1,24 +0,0 @@ -# Что выведет этот alert? - -[importance 5] - -Что выведет `alert`? - -```html - - - -*!* - -*/!* - - - - Привет, мир! - - - -``` - diff --git a/2-ui/1-document/2-dom-nodes/article.md b/2-ui/1-document/2-dom-nodes/article.md deleted file mode 100644 index aed95c16..00000000 --- a/2-ui/1-document/2-dom-nodes/article.md +++ /dev/null @@ -1,269 +0,0 @@ -# Дерево DOM - -Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) -- объектная модель, используемая для XML/HTML-документов. - - -[cut] -Согласно DOM-модели, документ является иерархией, деревом. Каждый HTML-тег образует узел дерева с типом "элемент". Вложенные в него теги становятся дочерними узлами. Для представления текста создаются узлы с типом "текст". - -DOM -- это представление документа в виде дерева объектов, доступное для изменения через JavaScript. - -## Пример DOM - -Построим, для начала, дерево DOM для следующего документа. - -```html - - - - - О лосях - - - Правда о лосях - - -``` - -Его вид: - -
        - - -В этом дереве выделено два типа узлов. - -
          -
        1. Теги образуют *узлы-элементы* (element node). Естественным образом одни узлы вложены в другие. Структура дерева образована исключительно за счет них.
        2. -
        3. Текст внутри элементов образует *текстовые узлы* (text node), обозначенные как `#text`. Текстовый узел содержит исключительно строку текста и не может иметь потомков, то есть он всегда на самом нижнем уровне.
        4. -
        - -[online] -**На рисунке выше синие узлы-элементы можно кликать, при этом их дети будут скрываться-раскрываться.** -[/online] - -Обратите внимание на специальные символы в текстовых узлах: -
          -
        • перевод строки: `↵`
        • -
        • пробел: `␣`
        • -
        - -**Пробелы и переводы строки -- это тоже текст, полноправные символы, которые учитываются в DOM.** - -В частности, в примере выше тег `` содержит не только узлы-элементы `` и ``, но и `#text` (пробелы, переводы строки) между ними. - -Впрочем, как раз на самом верхнем уровне из этого правила есть исключения: пробелы до `` по стандарту игнорируются, а любое содержимое после `` не создаёт узла, браузер переносит его внутрь, в конец `body`. - -В остальных случаях всё честно -- если пробелы есть в документе, то они есть и в DOM, а если их убрать, то и в DOM их не будет, получится так: - -```html - - -О лосяхПравда о лосях -``` - -
        - - -## Автоисправление - -При чтении неверного HTML браузер автоматически корректирует его для показа и при построении DOM. - -В частности, всегда будет верхний тег ``. Даже если в тексте нет -- в DOM он будет, браузер создаст его самостоятельно. - -То же самое касается и тега ``. - -Например, если файл состоит из одного слова `"Привет"`, то браузер автоматически обернёт его в `` и ``. - -**При генерации DOM браузер самостоятельно обрабатывает ошибки в документе, закрывает теги и так далее.** - -Такой документ: - -```html - -

        Привет -

      • Мама -
      • и -
      • Папа -``` - -...Превратится вот во вполне респектабельный DOM, браузер сам закроет теги: - -
        - - -[warn header="Таблицы всегда содержат ``"] -Важный "особый случай" при работе с DOM -- таблицы. По стандарту DOM они обязаны иметь ``, однако в HTML их можно написать без него. В этом случае браузер добавляет `` самостоятельно. - -Например, для такого HTML: - -```html - - - -
        1
        -``` - -DOM-структура будет такой: -
        - - -Вы видите? Появился ``, как будто документ был таким: - -```html - - -*!* - -*/!* - -*!* - -*/!* -
        1
        -``` - -Важно знать об этом, иначе при работе с таблицами возможны сюрпризы. -[/warn] - - -## Другие типы узлов - -Дополним страницу новыми тегами и комментарием: - -```html - - - - - Правда о лосях -
          -
        1. Лось — животное хитрое
        2. -*!* - -*/!* -
        3. ...и коварное!
        4. -
        - - - -``` - -
        - - -**В этом примере тегов уже больше, и даже появился узел нового типа -- *комментарий*.** - -Казалось бы, зачем комментарий в DOM? На отображение-то он всё равно не влияет. Но так как он есть в HTML -- обязан присутствовать в DOM-дереве. - -**Всё, что есть в HTML, находится и в DOM.** - -Даже директива ``, которую мы ставим в начале HTML, тоже является DOM-узлом, и находится в дереве DOM непосредственно перед ``. На иллюстрациях выше этот факт скрыт, поскольку мы с этим узлом работать не будем, он никогда не нужен. - -Даже сам объект `document`, формально, является DOM-узлом, самым-самым корневым. - -Всего различают 12 типов узлов, но на практике мы работаем с четырьмя из них: -
          -
        1. Документ -- точка входа в DOM.
        2. -
        3. Элементы -- основные строительные блоки.
        4. -
        5. Текстовые узлы -- содержат, собственно, текст.
        6. -
        7. Комментарии -- иногда в них можно включить информацию, которая не будет показана, но доступна из JS.
        8. -
        - -## Возможности, которые дает DOM - -Зачем, кроме красивых рисунков, нужна иерархическая модель DOM? - -**DOM нужен для того, чтобы манипулировать страницей -- читать информацию из HTML, создавать и изменять элементы.** - -Узел `HTML` можно получить как `document.documentElement`, а `BODY` -- как `document.body`. - -Получив узел, мы можем что-то сделать с ним. - -Например, можно поменять цвет `BODY` и вернуть обратно: - -```js -//+ run -document.body.style.backgroundColor = 'red'; -alert( 'Поменяли цвет BODY' ); - -document.body.style.backgroundColor = ''; -alert( 'Сбросили цвет BODY' ); -``` - -DOM предоставляет возможность делать со страницей всё, что угодно. - -Позже мы более подробно рассмотрим различные свойства и методы DOM-узлов. - - -## Особенности IE8- - -IE8- не генерирует текстовые узлы, если они состоят только из пробелов. - - То есть, такие два документа дадут идентичный DOM: - -```html - - -О лосяхПравда о лосях -``` - -И такой: - -```html - - - - - О лосях - - - - Правда о лосях - - - -``` - -Эта, с позволения сказать, "оптимизация" не соответствует стандарту и IE9+ уже работает как нужно, то есть как описано ранее. - -Но, по большому счёту, для нас это отличие должно быть без разницы, ведь при работе с DOM/HTML мы в любом случае не должны быть завязаны на то, есть пробел между тегами или его нет. Мало ли, сегодня он есть, а завтра решили переформатировать HTML и его не стало. - -К счастью, свойства и методы DOM, которые мы пройдём далее, вполне позволяют писать код, который будет работать корректно во всех версиях браузеров. Так что знать об этом отличии надо, если вы хотите поддерживать старые IE, но проблем оно нам создавать не будет. - -## Итого - -
          -
        • DOM-модель -- это внутреннее представление HTML-страницы в виде дерева.
        • -
        • Все элементы страницы, включая теги, текст, комментарии, являются узлами DOM.
        • -
        • У элементов DOM есть свойства и методы, которые позволяют изменять их.
        • -
        • IE8- не генерирует пробельные узлы.
        • -
        - -Кстати, DOM-модель используется не только в JavaScript, это известный способ представления XML-документов. - -В следующих главах мы познакомимся с DOM более плотно. - -[libs] -d3 -domtree -[/libs] \ No newline at end of file diff --git a/2-ui/1-document/3-dom-console/1.png b/2-ui/1-document/3-dom-console/1.png deleted file mode 100644 index 6ba0e56d..00000000 Binary files a/2-ui/1-document/3-dom-console/1.png and /dev/null differ diff --git a/2-ui/1-document/3-dom-console/1@2x.png b/2-ui/1-document/3-dom-console/1@2x.png deleted file mode 100755 index ef193506..00000000 Binary files a/2-ui/1-document/3-dom-console/1@2x.png and /dev/null differ diff --git a/2-ui/1-document/3-dom-console/2.png b/2-ui/1-document/3-dom-console/2.png deleted file mode 100644 index 260e183c..00000000 Binary files a/2-ui/1-document/3-dom-console/2.png and /dev/null differ diff --git a/2-ui/1-document/3-dom-console/2@2x.png b/2-ui/1-document/3-dom-console/2@2x.png deleted file mode 100755 index 4159c07b..00000000 Binary files a/2-ui/1-document/3-dom-console/2@2x.png and /dev/null differ diff --git a/2-ui/1-document/3-dom-console/3.png b/2-ui/1-document/3-dom-console/3.png deleted file mode 100644 index 2be09128..00000000 Binary files a/2-ui/1-document/3-dom-console/3.png and /dev/null differ diff --git a/2-ui/1-document/3-dom-console/3@2x.png b/2-ui/1-document/3-dom-console/3@2x.png deleted file mode 100755 index 2d9a6bd3..00000000 Binary files a/2-ui/1-document/3-dom-console/3@2x.png and /dev/null differ diff --git a/2-ui/1-document/3-dom-console/article.md b/2-ui/1-document/3-dom-console/article.md deleted file mode 100644 index 5e2322ef..00000000 --- a/2-ui/1-document/3-dom-console/article.md +++ /dev/null @@ -1,80 +0,0 @@ -# Работа с DOM из консоли - -Исследовать и изменять DOM можно с помощью инструментов разработки, встроенных в браузер. Посмотрим средства для этого на примере Google Chrome. -[cut] - -## Доступ к элементу - -Откройте документ [losi.html](losi.html) и, в инструментах разработчика, перейдите во вкладку Elements. - -Чтобы проанализировать любой элемент: -
          -
        • Выберите его во вкладке Elements.
        • -
        • ...Либо внизу вкладки Elements есть лупа, при нажатии на которую можно выбрать элемент кликом.
        • -
        • ...Либо, что обычно удобнее всего, просто кликните на нужном месте на странице правой кнопкой и выберите в меню "Проверить Элемент".
        • -
        - - - -Справа будет различная информация об элементе: -
        -
        Computed Style
        -
        Итоговые свойства CSS элемента, которые он приобрёл в результате применения всего каскада стилей, включая внешние CSS-файлы и атрибут `style`.
        -
        Style
        -
        Каскад стилей, применённый к элементу. Каждое стилевое правило отдельно, здесь же можно менять стили кликом.
        -
        Metrics
        -
        Размеры элемента.
        -
        ...
        -
        И еще некоторые реже используемые вкладки, которые станут понятны по мере изучения DOM.
        -
        - -[warn header="DOM в Elements не совсем соответствует реальному"] -Отображение DOM во вкладке Elements не совсем соответствует реальному. В частности, там не отображаются пробельные узлы. Это сделано для удобства просмотра. Но мы-то знаем, что они есть. - -С другой стороны, в Elements можно увидеть CSS-псевдоэлементы, такие как `::before`, `::after`. Это также сделано для удобства, в DOM их не существует. -[/warn] - - -## Выбранные элементы $0 $1... - -Зачастую бывает нужно выбрать элемент DOM и сделать с ним что-то на JavaScript. - -Находясь во вкладке Elements, откройте консоль нажатием Esc (или перейдите на вкладку Console). - -**Последний элемент, выбранный во вкладке Elements, доступен в консоли как `$0`, предыдущий -- `$1` и так далее.** - -Запустите на элементе команду, которая делает его красным: - -```js -$0.style.backgroundColor = 'red'; -``` - -В браузере это может выглядеть примерно так: - - - -Мы выделили элемент, применили к нему JavaScript в консоли, тут же увидели изменения в браузере. - -Есть и обратная дорожка. Любой элемент из JS-переменной можно посмотреть во вкладке Elements, для этого: - -
          -
        1. Выведите эту переменную в консоли, например при помощи `console.log`.
        2. -
        3. Кликните на выводе в консоли правой кнопкой мыши.
        4. -
        5. Выберите соответствующий пункт меню.
        6. -
        - - - -Таким образом, можно легко перемещаться из Elements в консоль и обратно. - -## Ещё методы консоли - -Для поиска элементов в консоли есть два специальных метода: -
          -
        • `$$("div.my")` -- ищет все элементы в DOM по данному CSS-селектору.
        • -
        • `$("div.my")` -- ищет первый элемент в DOM по данному CSS-селектору.
        • -
        - -Более полная документация по методам консоли доступна на страницах [Console API Reference для Chrome](https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference) и [Command Line API для Firebug](https://getfirebug.com/wiki/index.php/Command_Line_API), а также на [firebug.ru](http://firebug.ru). - -Другие браузеры реализуют похожий функционал, освоив Chrome/Firebug, вы легко с ними разберётесь. diff --git a/2-ui/1-document/3-dom-console/losi.html b/2-ui/1-document/3-dom-console/losi.html deleted file mode 100644 index 06f8da0d..00000000 --- a/2-ui/1-document/3-dom-console/losi.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Правда о лосях -
          -
        1. Лось — животное хитрое
        2. - -
        3. ..И коварное!
        4. -
        - - - \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/1-dom-children/solution.md b/2-ui/1-document/4-traversing-dom/1-dom-children/solution.md deleted file mode 100644 index e620832e..00000000 --- a/2-ui/1-document/4-traversing-dom/1-dom-children/solution.md +++ /dev/null @@ -1,32 +0,0 @@ -# HEAD - -Два способа: - -```js -document.documentElement.children[0] -document.documentElement.firstChild -``` - -Второй способ работает, так как пробелы перед `` игнорируются. - -Также в современных браузерах доступен `document.head`. - -# UL - -Например, так: - -```js -document.body.children[1] -``` - -# LI - -Можно так: - -```js -document.body.children[1].children[1]; // LI -``` - -Может возникнуть проблема с комментарием в IE8-, так как он станет одним из `children`, в результате последний код станет работать некорректно. - -В последующих разделах учебника мы рассмотрим другие методы поиска по DOM, которые позволят эту проблему обойти. \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/1-dom-children/task.md b/2-ui/1-document/4-traversing-dom/1-dom-children/task.md deleted file mode 100644 index 21e85842..00000000 --- a/2-ui/1-document/4-traversing-dom/1-dom-children/task.md +++ /dev/null @@ -1,38 +0,0 @@ -# DOM children - -[importance 5] - -Для страницы: - -```html - - - - - - - - -
        Пользователи:
        -
          -
        • Маша
        • -
        • Вовочка
        • -
        - - - - - - - - -``` - -
          -
        • Напишите код, который получит элемент `HEAD`.
        • -
        • Напишите код, который получит `UL`.
        • -
        • Напишите код, который получит второй `LI`. Будет ли ваш код работать в IE8-, если комментарий переместить *между* элементами `LI`?
        • -
        - diff --git a/2-ui/1-document/4-traversing-dom/2-has-childnodes/solution.md b/2-ui/1-document/4-traversing-dom/2-has-childnodes/solution.md deleted file mode 100644 index 07ee2da5..00000000 --- a/2-ui/1-document/4-traversing-dom/2-has-childnodes/solution.md +++ /dev/null @@ -1,28 +0,0 @@ -Вначале нерабочие способы, которые могут прийти на ум: - -```js -//+ no-beautify -if (!elem) { .. } -``` - -Это не работает, так как `elem` всегда есть, и является объектом. Так что проверка `if (elem)` всегда верна, вне зависимости от того, есть ли у `elem` потомки. - -```js -//+ no-beautify -if (!elem.childNodes) { ... } -``` - -Тоже не работает, так как псевдо-массив `childNodes` всегда существует. Он может быть пуст или непуст, но он всегда является объектом, так что проверка `if (elem.childNodes)` всегда верна. - -Несколько рабочих способов: - -```js -//+ no-beautify -if (!elem.childNodes.length) { ... } - -if (!elem.firstChild) { ... } - -if (!elem.lastChild) { ... } -``` - -Также существует метод [hasChildNodes](https://developer.mozilla.org/en-US/docs/Web/API/Node.hasChildNodes), который позволяет вызовом `elem.hasChildNodes()` определить наличие детей. Он работает так же, как проверка `elem.childNodes.length != 0`. \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/2-has-childnodes/task.md b/2-ui/1-document/4-traversing-dom/2-has-childnodes/task.md deleted file mode 100644 index 7532f1fc..00000000 --- a/2-ui/1-document/4-traversing-dom/2-has-childnodes/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Проверка существования детей - -[importance 5] - -Придумайте самый короткий код для проверки, пуст ли элемент `elem`. - -"Пустой" -- значит нет дочерних узлов, даже текстовых. - -```js -//+ no-beautify -if (/*...ваш код проверки elem... */) { узел elem пуст } -``` - -Что написать в условии `if` ? \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/3-navigation-links-which-null/solution.md b/2-ui/1-document/4-traversing-dom/3-navigation-links-which-null/solution.md deleted file mode 100644 index ca09870b..00000000 --- a/2-ui/1-document/4-traversing-dom/3-navigation-links-which-null/solution.md +++ /dev/null @@ -1,10 +0,0 @@ -
          -
        1. Да, верно, с оговоркой. Элемент `elem.lastChild` последний, у него нет правого соседа. - -**Оговорка:** `elem.lastChild.nextSibling` выдаст ошибку если `elem` не имеет детей. -
        2. -
        3. Нет, неверно, это может быть текстовый узел. Значением `elem.children[0]` является первый узел-элемент, перед ним может быть текст. - -Аналогично предыдущему случаю, если у `elem` нет детей-элементов -- будет ошибка. -
        4. - diff --git a/2-ui/1-document/4-traversing-dom/3-navigation-links-which-null/task.md b/2-ui/1-document/4-traversing-dom/3-navigation-links-which-null/task.md deleted file mode 100644 index 993d7d8b..00000000 --- a/2-ui/1-document/4-traversing-dom/3-navigation-links-which-null/task.md +++ /dev/null @@ -1,9 +0,0 @@ -# Вопрос по навигационным ссылкам - -[importance 5] - -Если `elem` -- это произвольный узел DOM... - -Верно ли, что `elem.lastChild.nextSibling` всегда `null`? - -Верно ли, что `elem.children[0].previousSibling` всегда `null` ? \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/solution.md b/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/solution.md deleted file mode 100644 index 27da4a11..00000000 --- a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/solution.md +++ /dev/null @@ -1 +0,0 @@ -Для удобства работы с таблицей используем специальные свойства `rows` и `cells`. \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/solution.view/index.html b/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/solution.view/index.html deleted file mode 100644 index fc03772e..00000000 --- a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/solution.view/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          1:12:13:14:15:1
          1:22:23:24:25:2
          1:32:33:34:35:3
          1:42:43:44:45:4
          1:52:53:54:55:5
          - - - - \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/source.view/index.html b/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/source.view/index.html deleted file mode 100644 index d9b40920..00000000 --- a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/source.view/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          1:12:13:14:15:1
          1:22:23:24:25:2
          1:32:33:34:35:3
          1:42:43:44:45:4
          1:52:53:54:55:5
          - - - - \ No newline at end of file diff --git a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/task.md b/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/task.md deleted file mode 100644 index 92034c40..00000000 --- a/2-ui/1-document/4-traversing-dom/4-select-diagonal-cells/task.md +++ /dev/null @@ -1,17 +0,0 @@ -# Выделите ячейки по диагонали - -[importance 5] - -Напишите код, который выделит все ячейки в таблице по диагонали. - -Вам нужно будет получить из таблицы `table` все диагональные `td` и выделить их, используя код: - -```js -// в переменной td DOM-элемент для тега -td.style.backgroundColor = 'red'; -``` - -Должно получиться так: -[iframe src="solution" height=180] - - diff --git a/2-ui/1-document/4-traversing-dom/article.md b/2-ui/1-document/4-traversing-dom/article.md deleted file mode 100644 index 13ef881a..00000000 --- a/2-ui/1-document/4-traversing-dom/article.md +++ /dev/null @@ -1,433 +0,0 @@ -# Навигация по DOM-элементам - -DOM позволяет делать что угодно с HTML-элементом и его содержимым, но для этого нужно сначала нужный элемент получить. - -Доступ к DOM начинается с объекта `document`. Из него можно добраться до любых узлов. - -[cut] - -Так выглядят основные ссылки, по которым можно переходить между узлами DOM: - - - -Посмотрим на них повнимательнее. - -## Сверху documentElement и body - -Самые верхние элементы дерева доступны напрямую из `document`. - -
          -
          `` = `document.documentElement`
          -
          Первая точка входа -- `document.documentElement`. Это свойство ссылается на DOM-объект для тега ``.
          -
          `` = `document.body`
          -
          Вторая точка входа -- `document.body`, который соответствует тегу ``.
          -
          - -В современных браузерах (кроме IE8-) также есть `document.head` -- прямая ссылка на `` - -[warn header="Есть одна тонкость: `document.body` может быть равен `null`"] -Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта. - -В частности, если скрипт находится в ``, то в нём недоступен `document.body`. - -Поэтому в следующем примере первый `alert` выведет `null`: - -```html - - - - - - - - - - - - - - - -``` -[/warn] - -[smart header="В DOM активно используется `null`"] -В мире DOM в качестве значения, обозначающего "нет такого элемента" или "узел не найден", используется не `undefined`, а `null`. -[/smart] - - -## Дети: childNodes, firstChild, lastChild - -Здесь и далее мы будем использовать два принципиально разных термина. - -
            -
          • **Дочерние элементы (или дети)** -- элементы, которые лежат *непосредственно* внутри данного. Например, внутри `` обычно лежат `` и ``.
          • -
          • **Потомки** -- все элементы, которые лежат внутри данного, вместе с их детьми, детьми их детей и так далее. То есть, всё поддерево DOM.
          • -
          - -Псевдо-массив `childNodes` хранит все дочерние элементы, включая текстовые. - -Пример ниже последовательно выведет дочерние элементы `document.body`: - -```html - - - - - -
          Начало
          - -
            -
          • Информация
          • -
          - -
          Конец
          - - - ... - - - -``` - -Обратим внимание на маленькую деталь. Если запустить пример выше, то последним будет выведен элемент ` - ... - - - -``` - -Всегда верны равенства: - -```js -elem.firstElementChild === elem.children[0] -elem.lastElementChild === elem.children[elem.children.length - 1] -``` - - -[warn header="В IE8- поддерживается только `children`"] -Других навигационных свойств в этих браузерах нет. Впрочем, как мы увидим далее, можно легко сделать полифилл, и они, всё же, будут. -[/warn] - - -[warn header="В IE8- в `children` присутствуют узлы-комментарии"] -С точки зрения стандарта это ошибка, но IE8- также включает в `children` узлы, соответствующие HTML-комментариям. - -Это может привести к сюрпризам при использовании свойства `children`, поэтому HTML-комментарии либо убирают либо используют фреймворк, к примеру, jQuery, который даёт свои методы перебора и отфильтрует их. -[/warn] - -## Особые ссылки для таблиц [#dom-navigation-tables] - -У конкретных элементов DOM могут быть свои дополнительные ссылки для большего удобства навигации. - -Здесь мы рассмотрим таблицу, так как это важный частный случай и просто для примера. - -В списке ниже выделены наиболее полезные: - -
          -
          `TABLE`
          -
          -
            -
          • **`table.rows`** -- коллекция строк `TR` таблицы.
          • -
          • `table.caption/tHead/tFoot` -- ссылки на элементы таблицы `CAPTION`, `THEAD`, `TFOOT`.
          • -
          • `table.tBodies` -- коллекция элементов таблицы `TBODY`, по спецификации их может быть несколько.
          • -
          -
          `THEAD/TFOOT/TBODY`
          -
          -
            -
          • `tbody.rows` -- коллекция строк `TR` секции.
          • -
          -
          `TR`
          -
          -
            -
          • **`tr.cells`** -- коллекция ячеек `TD/TH`
          • -
          • **`tr.sectionRowIndex`** -- номер строки в текущей секции `THEAD/TBODY`
          • -
          • `tr.rowIndex` -- номер строки в таблице
          • -
          -
          -
          `TD/TH`
          -
          -
            -
          • **`td.cellIndex`** -- номер ячейки в строке
          • -
          -
          -
          - -Пример использования: - -```html - - - - - - - - -
          один два
          три четыре
          - - -``` - -Спецификация: [HTML5: tabular data](http://www.w3.org/TR/html5/tabular-data.html). - -Даже если эти свойства не нужны вам прямо сейчас, имейте их в виду на будущее, когда понадобится пройтись по таблице. - -Конечно же, таблицы -- не исключение. - -Аналогичные полезные свойства есть у HTML-форм, они позволяют из формы получить все её элементы, а из них -- в свою очередь, форму. Мы рассмотрим их позже. - -[online] -# Интерактивное путешествие - -Для того, чтобы убедиться, что вы разобрались с навигацией по DOM-ссылкам -- вашему вниманию предлагается интерактивное путешествие по DOM. - -Ниже вы найдёте документ (в ифрейме), и кнопки для перехода по нему. - -Изначальный элемент -- ``. Попробуйте по ссылкам найти "информацию". Или ещё чего-нибудь. - -Вы также можете открыть документ [в отдельном окне](travel/) и походить по нему в браузерной консоли разработчика, чтобы лучше понять разницу между показанным там DOM и реальным. - -Разметка: - -[html src="travel/index.html"/] - -Документ: - -[iframe samedomain id="travel-dom-iframe" src="travel" height=150] - -
          - -Навигация: -
            -
          • -
              -
            • -
            • Здесь стоите вы -
                -
              • -
              • -
              -
            • -
            • -
            -
          • -
          - -
          - - -
          - - -[/online] - -# Итого - -В DOM доступна навигация по соседним узлам через ссылки: -
            -
          • По любым узлам.
          • -
          • Только по элементам.
          • -
          - -Также некоторые виды элементов предоставляют дополнительные ссылки для большего удобства, например у таблиц есть свойства для доступа к строкам/ячейкам. - -[libs] -d3 -domtree -[/libs] -[head] - - -[/head] - diff --git a/2-ui/1-document/4-traversing-dom/dom-links-elements.png b/2-ui/1-document/4-traversing-dom/dom-links-elements.png deleted file mode 100644 index ee1516d4..00000000 Binary files a/2-ui/1-document/4-traversing-dom/dom-links-elements.png and /dev/null differ diff --git a/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png b/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png deleted file mode 100644 index 42b8549a..00000000 Binary files a/2-ui/1-document/4-traversing-dom/dom-links-elements@2x.png and /dev/null differ diff --git a/2-ui/1-document/4-traversing-dom/dom-links.png b/2-ui/1-document/4-traversing-dom/dom-links.png deleted file mode 100644 index a3371ab0..00000000 Binary files a/2-ui/1-document/4-traversing-dom/dom-links.png and /dev/null differ diff --git a/2-ui/1-document/4-traversing-dom/dom-links@2x.png b/2-ui/1-document/4-traversing-dom/dom-links@2x.png deleted file mode 100644 index b5e3a4e7..00000000 Binary files a/2-ui/1-document/4-traversing-dom/dom-links@2x.png and /dev/null differ diff --git a/2-ui/1-document/4-traversing-dom/travel.view/index.html b/2-ui/1-document/4-traversing-dom/travel.view/index.html deleted file mode 100644 index 80e28414..00000000 --- a/2-ui/1-document/4-traversing-dom/travel.view/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Документ - - - - -
            -
          • - Осторожно -
          • -
          • - -
          - - - - \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md b/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md deleted file mode 100644 index 210933da..00000000 --- a/2-ui/1-document/5-searching-elements-dom/1-find-elements/solution.md +++ /dev/null @@ -1,26 +0,0 @@ -Есть много вариантов решения, вот некоторые из них: - -```js -// 1 -document.getElementById('age-table').getElementsByTagName('label'); - -// 2 -document.getElementById('age-table').getElementsByTagName('td')[0]; -// в современных браузерах можно одним запросом: -var result = document.querySelector('#age-table td'); - -// 3 -document.getElementsByTagName('form')[1]; - -// 4 -document.querySelector('form[name="search"]'); - -// 5 -document.querySelector('form[name="search"] input') - -// 6 -document.getElementsByName("info[0]")[0]; - -// 7 -document.querySelector('form[name="search-person"] [name="info[0]"]'); -``` diff --git a/2-ui/1-document/5-searching-elements-dom/1-find-elements/table.html b/2-ui/1-document/5-searching-elements-dom/1-find-elements/table.html deleted file mode 100644 index 3484bc0c..00000000 --- a/2-ui/1-document/5-searching-elements-dom/1-find-elements/table.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - -
          - - -
          - -
          - -
          - Поиск по посетителям: - - - - - - - - - - - -
          Возраст: - - - -
          Дополнительно: - - - -
          - - -
          - - - \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md b/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md deleted file mode 100644 index fa959a95..00000000 --- a/2-ui/1-document/5-searching-elements-dom/1-find-elements/task.md +++ /dev/null @@ -1,20 +0,0 @@ -# Поиск элементов - -[importance 4] - -Ниже находится документ с таблицей и формой. - -Найдите (получите в переменную) в нём: - -
            -
          1. Все элементы `label` внутри таблицы. Должно быть 3 элемента.
          2. -
          3. Первую ячейку таблицы (со словом `"Возраст"`).
          4. -
          5. Вторую форму в документе.
          6. -
          7. Форму с именем `search`, без использования её позиции в документе.
          8. -
          9. Элемент `input` в форме с именем `search`. Если их несколько, то нужен первый.
          10. -
          11. Элемент с именем `info[0]`, без точного знания его позиции в документе.
          12. -
          13. Элемент с именем `info[0]`, внутри формы с именем `search-person`.
          14. -
          - -Используйте для этого консоль браузера, открыв страницу [table.html](table.html) в отдельном окне. - diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md b/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md deleted file mode 100644 index 55b45737..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Сделаем цикл по узлам `
        5. `: - -```js -var lis = document.getElementsByTagName('li'); - -for (i = 0; i < lis.length; i++) { - ... -} -``` - -В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `
        6. ` является как раз текстовый узел, содержащий текст названия. - -Также можно получить количество потомков, используя `lis[i].getElementsByTagName('li')`. - -Напишите код с этой подсказкой. - -Если уж не выйдет -- тогда откройте решение. - diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.view/index.html b/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.view/index.html deleted file mode 100644 index e9b5d644..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree-info/solution.view/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - -
            -
          • Животные -
              -
            • Млекопитающие -
                -
              • Коровы
              • -
              • Ослы
              • -
              • Собаки
              • -
              • Тигры
              • -
              -
            • -
            • Другие -
                -
              • Змеи
              • -
              • Птицы
              • -
              • Ящерицы
              • -
              -
            • -
            -
          • -
          • Рыбы -
              -
            • Аквариумные -
                -
              • Гуппи
              • -
              • Скалярии
              • -
              - -
            • -
            • Морские -
                -
              • Морская форель
              • -
              -
            • -
            -
          • -
          - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree-info/source.view/index.html b/2-ui/1-document/5-searching-elements-dom/2-tree-info/source.view/index.html deleted file mode 100644 index 2f45460c..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree-info/source.view/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - -
            -
          • Животные -
              -
            • Млекопитающие -
                -
              • Коровы
              • -
              • Ослы
              • -
              • Собаки
              • -
              • Тигры
              • -
              -
            • -
            • Другие -
                -
              • Змеи
              • -
              • Птицы
              • -
              • Ящерицы
              • -
              -
            • -
            -
          • -
          • Рыбы -
              -
            • Аквариумные -
                -
              • Гуппи
              • -
              • Скалярии
              • -
              - -
            • -
            • Морские -
                -
              • Морская форель
              • -
              -
            • -
            -
          • -
          - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md b/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md deleted file mode 100644 index 5ffd808d..00000000 --- a/2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md +++ /dev/null @@ -1,14 +0,0 @@ -# Дерево - -[importance 5] - -Есть дерево из тегов `
            /
          • `. - -Напишите код, который для каждого элемента `
          • ` выведет: -
              -
            1. Текст непосредственно в нём (без подразделов).
            2. -
            3. Количество вложенных в него элементов `
            4. ` -- всех, с учётом вложенных.
            5. -
            - -[demo src="solution"] - diff --git a/2-ui/1-document/5-searching-elements-dom/article.md b/2-ui/1-document/5-searching-elements-dom/article.md deleted file mode 100644 index d9faf1d1..00000000 --- a/2-ui/1-document/5-searching-elements-dom/article.md +++ /dev/null @@ -1,382 +0,0 @@ -# Поиск: getElement* и querySelector* и не только - -Прямая навигация от родителя к потомку удобна, если элементы рядом. А если нет? - -Как достать произвольный элемент откуда-то из глубины документа? - -Для этого в DOM есть дополнительные методы поиска. - -[cut] - -## document.getElementById или просто id - -Если элементу назначен специальный атрибут `id`, то можно получить его прямо по переменной с именем из значения `id`. - -Например: - -```html - -
            -
            Элемент
            -
            - - -``` - -Это поведение соответствует [стандарту](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem). Оно существует, в первую очередь, для совместимости, как осколок далёкого прошлого и не очень приветствуется, поскольку использует глобальные переменные. Браузер пытается помочь нам, смешивая пространства имён JS и DOM, но при этом возможны конфликты. - -**Более правильной и общепринятой практикой является доступ к элементу вызовом `document.getElementById("идентификатор")`.** - -Например: - - -```html - -
            Выделим этот элемент
            - - -``` - -[smart header="Должен остаться только один"] -По стандарту значение `id` должно быть уникально, то есть в документе может быть только один элемент с данным `id`. И именно он будет возвращён. - -Если в документе есть несколько элементов с уникальным `id`, то поведение неопределено. То есть, нет гарантии, что браузер вернёт именно первый или последний -- вернёт случайным образом. - -Поэтому стараются следовать правилу уникальности `id`. -[/smart] - - -Далее в примерах я часто буду использовать прямое обращение через переменную, чтобы было меньше букв и проще было понять происходящее. Но предпочтительным методом является `document.getElementById`. - - -## getElementsByTagName - -Метод `elem.getElementsByTagName(tag)` ищет все элементы с заданным тегом `tag` внутри элемента `elem` и возвращает их в виде списка. - -Регистр тега не имеет значения. - -Например: -```js -// получить все div-элементы -var elements = document.getElementsByTagName('div'); -``` - -**Обратим внимание: в отличие от `getElementById`, который существует только в контексте `document`, метод `getElementsByTagName` может искать внутри любого элемента.** - -Например, найдём все элементы `input` внутри таблицы: - -```html - - - - - - - - -
            Ваш возраст: - - - -
            - - -``` - -**Можно получить всех потомков, передав звездочку `'*'` вместо тега:** - -```js -// получить все элементы документа -document.getElementsByTagName('*'); - -// получить всех потомков элемента elem: -elem.getElementsByTagName('*'); -``` - -[warn header="Не забываем про букву `\"s\"`!"] -Одна из самых частых ошибок начинающих (впрочем, иногда и не только) -- это забыть букву `"s"`, то есть пробовать вызывать метод `getElementByTagName` вместо getElementsByTagName. - -Буква `"s"` не нужна там, где элемент только один, то есть в `getElementById`, в остальных методах она обязательна. -[/warn] - -[warn header="Возвращается коллекция, а не элемент"] -Другая частая ошибка -- это код вида: - -```js -// не работает -document.getElementsByTagName('input').value = 5; -``` - -То есть, вместо элемента присваивают значение коллекции. Работать такое не будет. - -Коллекцию нужно или перебрать в цикле или получить элемент по номеру и уже ему присваивать `value`, например так: - -```js -// работает -document.getElementsByTagName('input')[0].value = 5; -``` - -[/warn] - -## getElementsByName - -Вызов `document.getElementsByName(name)` позволяет получить все элементы с данным атрибутом `name`. - -Например, все элементы с именем `age`: - -```js -var elems = document.getElementsByName('age'); -``` - -До появления стандарта HTML5 этот метод возвращал только те элементы, в которых предусмотрена поддержка атрибута `name`, в частности: `iframe`, `a`, `input` и другими. В современных браузерах (IE10+) тег не имеет значения. - -Используется этот метод весьма редко. - -## getElementsByClassName - -Вызов `elem.getElementsByClassName(className)` возвращает коллекцию элементов с классом `className`. Находит элемент и в том случае, если у него несколько классов, а искомый - один из них. - -Поддерживается всеми современными браузерами, кроме IE8-. - -Например: - -```html - -
            Статья
            -
            Длинная статья
            - - -``` - -Как и `getElementsByTagName`, этот метод может быть вызван и в контексте DOM-элемента и в контексте документа. - - -## querySelectorAll [#querySelectorAll] - -Вызов `elem.querySelectorAll(css)` возвращает все элементы внутри `elem`, удовлетворяющие CSS-селектору `css`. - -Это один из самых часто используемых и полезных методов при работе с DOM. - -Он есть во всех современных браузерах, включая IE8+ (в режиме соответствия стандарту). - -Следующий запрос получает все элементы `LI`, которые являются последними потомками в `UL`: - -```html - -
              -
            • Этот
            • -
            • тест
            • -
            -
              -
            • полностью
            • -
            • пройден
            • -
            - -``` - -## querySelector [#querySelector] - -Вызов `elem.querySelector(css)` возвращает не все, а только первый элемент, соответствующий CSS-селектору `css`. - -Иначе говоря, результат -- такой же, как и при `elem.querySelectorAll(css)[0]`, но в последнем вызове сначала ищутся все элементы, а потом берётся первый, а в `elem.querySelector(css)` ищется только первый, то есть он эффективнее. - -## matches - -Предыдущие методы искали по DOM. - -Метод [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) ничего не ищет, а проверяет, удовлетворяет ли `elem` селектору `css`. Он возвращает `true` либо `false`. - -Не поддерживается в IE8-. - -Этот метод бывает полезным, когда мы перебираем элементы (в массиве или по обычным навигационным ссылкам) и пытаемся отфильтровать те из них, которые нам интересны. - -Ранее в спецификации он назывался `matchesSelector`, и большинство браузеров поддерживают его под этим старым именем, либо с префиксами `ms/moz/webkit`. - -Например: - -```html - -... -... - - -``` - -## closest - -Метод `elem.closest(css)` ищет ближайший элемент выше по иерархии DOM, подходящий под CSS-селектор `css`. Сам элемент тоже включается в поиск. - -Иначе говоря, метод `closest` бежит от текущего элемента вверх по цепочке родителей и проверяет, подходит ли элемент под указанный CSS-селектор. Если подходит -- останавливается и возвращает его. - -Он самый новый из методов, рассмотренных в этой главе, поэтому старые браузеры его слабо поддерживают. Это, конечно, легко поправимо, как мы увидим позже в главе [](/dom-polyfill). - -Пример использования (браузер должен поддерживать `closest`): - -```html - -
              -
            • Глава I -
                -
              • Глава 1.1
              • -
              • Глава 1.2
              • -
              -
            • -
            - - - -``` - -## XPath в современных браузерах - -Для полноты картины рассмотрим ещё один способ поиска, который обычно используется в XML. Это язык запросов XPath. - -Он очень мощный, во многом мощнее CSS, но сложнее. Например, запрос для поиска элементов `H2`, содержащих текст `"XPath"`, будет выглядеть так: `//h2[contains(., "XPath")]`. - -Все современные браузеры, кроме IE, поддерживают XPath с синтаксисом, близким к [описанному в MDN](https://developer.mozilla.org/en/XPath). - -Найдем заголовки с текстом `XPath` в текущем документе: - -```js -//+ run no-beautify -var result = document.evaluate("//h2[contains(., 'XPath')]", document.documentElement, null, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - -for (var i = 0; i < result.snapshotLength; i++) { - alert( result.snapshotItem(i).outerHTML ); -} -``` - -IE тоже поддерживает XPath, но эта поддержка не соответствует стандарту и работает только для XML-документов, например, полученных с помощью `XMLHTTPRequest` (AJAX). Для обычных же HTML-документов XPath в IE не поддерживается. - -Так как XPath сложнее и длиннее CSS, то используют его очень редко. - -## Итого - -Есть 6 основных методов поиска элементов DOM: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            МетодИщет по...Ищет внутри элемента?Поддержка
            `getElementById``id`-везде
            `getElementsByName``name`-везде
            `getElementsByTagName`тег или `'*'`везде
            `getElementsByClassName`классукроме IE8-
            `querySelector`CSS-селекторвезде
            `querySelectorAll`CSS-селекторвезде
            - -Практика показывает, что в 95% ситуаций достаточно `querySelector/querySelectorAll`. Хотя более специализированные методы `getElement*` работают чуть быстрее, но разница в миллисекунду-другую редко играет роль. - -Кроме того: -
              -
            • Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (`ms`, `moz`, `webkit`).
            • -
            • Язык запросов XPath поддерживается большинством браузеров, кроме IE, даже 9й версии, но `querySelector` удобнее. Поэтому XPath используется редко.
            • -
            - - - - - - diff --git a/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md b/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md deleted file mode 100644 index 90507daa..00000000 --- a/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/solution.md +++ /dev/null @@ -1,47 +0,0 @@ -# Ответ на первый вопрос - -Ответ: 0, пустая коллекция. - -```html - - - -``` - -Это потому, что все элементы из `BODY` удаляются, а коллекция - *живая*. - -# Ответ на второй вопрос - -Ответ на второй вопрос зависит от браузера. В большинстве браузеров будет 3, коллекция не изменилась, так как она теперь привязана не к `BODY`, а к элементу, на котором идёт поиск, т.е. к `menu`. - -Но элемент `menu` находится в переменной, и поэтому должен быть жив, а значит и его дети тоже. Но некоторые браузеры (IE10) используют агрессивный подход при работе с памятью и очищают все элементы, кроме тех, которые непосредственно хранятся в переменных. - -Поэтому результат кода ниже в большинстве браузеров: `3`, а в IE10: `0`. - -```html - - - -``` - diff --git a/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/task.md b/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/task.md deleted file mode 100644 index 79d31016..00000000 --- a/2-ui/1-document/6-searching-elements-internals/1-collection-length-after-remove/task.md +++ /dev/null @@ -1,35 +0,0 @@ -# Длина коллекции после удаления элементов - -[importance 5] - -Вот небольшой документ: - -```html - -``` - -1. Что выведет следующий код (простой вопрос)? - -```js -var lis = document.body.getElementsByTagName('li'); - -document.body.innerHTML = ""; - -alert( lis.length ); -``` - -2. А такой код (вопрос посложнее)? - -```js -var menu = document.getElementById('menu'); -var lis = menu.getElementsByTagName('li'); - -document.body.innerHTML = ""; - -alert( lis.length ); -``` - diff --git a/2-ui/1-document/6-searching-elements-internals/2-compare-elements-count/solution.md b/2-ui/1-document/6-searching-elements-internals/2-compare-elements-count/solution.md deleted file mode 100644 index 8a39f575..00000000 --- a/2-ui/1-document/6-searching-elements-internals/2-compare-elements-count/solution.md +++ /dev/null @@ -1,5 +0,0 @@ -Значение `aList1` изменится, потому что `getElementsByTagName` - *живая* коллекция. Она автоматически дополнится новым элементом `a` и ее длина увеличится на 1. - -А вот `querySelector`, наоборот, возвращает статичный список узлов. Он ссылается на те же самые элементы, что бы не происходило с документом. Поэтому длина `aList2.length` останется неизменной. - - diff --git a/2-ui/1-document/6-searching-elements-internals/2-compare-elements-count/task.md b/2-ui/1-document/6-searching-elements-internals/2-compare-elements-count/task.md deleted file mode 100644 index 8076c8f9..00000000 --- a/2-ui/1-document/6-searching-elements-internals/2-compare-elements-count/task.md +++ /dev/null @@ -1,13 +0,0 @@ -# Сравнение количества элементов - -[importance 5] - -Для любого документа сделаем следующее: - -```js -var aList1 = document.getElementsByTagName('a'); -var aList2 = document.querySelectorAll('a'); -``` - -Что произойдёт со значениями `aList1.length`, `aList2.length`, если в документе вдруг появится ещё одна ссылка `...`? - diff --git a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/solution.md b/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/solution.md deleted file mode 100644 index c4ca86c7..00000000 --- a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/solution.md +++ /dev/null @@ -1,31 +0,0 @@ -Для бенчмаркинга будем использовать функцию `bench(f, times)`, которая запускает функцию `f` `times` раз и возвращает разницу во времени: - -```js -function bench(f, times) { - var d = new Date(); - for (var i = 0; i < times; i++) f(); - return new Date() - d; -} -``` - -Первый вариант (неверный) -- замерять разницу между функциями `runGet/runQuery`, вот так: - -```js -function runGet() { - var results = document.getElementsByTagName('p'); -} - -function runQuery() { - var results = document.querySelectorAll('p'); -} - -alert( bench(runGet, 10000) ); // вывести время 1000*runGet -``` - -Он даст неверные результаты, т.к. `getElementsByTagName` является "живым поисковым запросом". Если не обратиться к его результатам, то поиска не произойдет вообще, т.е. `runGet` ничего по сути не ищет. - -...А `querySelectorAll` всегда производит поиск и формирует список элементов. - -Более правильный тест -- это не только запустить поиск, но и получить все элементы, как это делается в реальной жизни. - - diff --git a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/solution.view/index.html b/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/solution.view/index.html deleted file mode 100644 index ca1cfb81..00000000 --- a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/solution.view/index.html +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - - - - - -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/source.view/index.html b/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/source.view/index.html deleted file mode 100644 index 4275fa0b..00000000 --- a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/source.view/index.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            -

            1

            -

            2

            -

            3

            -

            4

            -

            5

            -

            6

            -

            7

            -

            8

            -

            9

            - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/task.md b/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/task.md deleted file mode 100644 index 8b368b49..00000000 --- a/2-ui/1-document/6-searching-elements-internals/3-benchmark-search-dom/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Бенчмаркинг методов поиска в DOM - -[importance 2] - -Какой метод поиска элементов работает быстрее: `getElementsByTagName(tag)` или `querySelectorAll(tag)`? - -Напишите код, который измеряет разницу между ними. - - - -*P.S. В задаче есть подвох, все не так просто. Если разница больше 10 раз -- вы решили ее неверно. Тогда подумайте, почему такое может быть.* diff --git a/2-ui/1-document/6-searching-elements-internals/4-get-second-li/solution.md b/2-ui/1-document/6-searching-elements-internals/4-get-second-li/solution.md deleted file mode 100644 index 930f4120..00000000 --- a/2-ui/1-document/6-searching-elements-internals/4-get-second-li/solution.md +++ /dev/null @@ -1,21 +0,0 @@ -Можно так: - -```js -var li = ul.getElementsByTagName('li')[1]; -``` - -Или так: - -```js -var li = ul.querySelector('li:nth-child(2)'); -``` - -Оба этих вызова будут перебирать детей `UL` и остановят перебор на найденном элементе. - -А вот так -- браузер найдет все элементы, а затем выберет второй. Это дольше: - -```js -var li = ul.querySelectorAll('li')[1]; -``` - -На практике разница в производительности будет видна только для действительно больших списков, либо при частом выполнении запроса. Браузер перебирает элементы весьма шустро. diff --git a/2-ui/1-document/6-searching-elements-internals/4-get-second-li/task.md b/2-ui/1-document/6-searching-elements-internals/4-get-second-li/task.md deleted file mode 100644 index 6dbecfe4..00000000 --- a/2-ui/1-document/6-searching-elements-internals/4-get-second-li/task.md +++ /dev/null @@ -1,16 +0,0 @@ -# Получить второй LI - -[importance 5] - -Есть длинный список `ul`: - -```html -
              -
            • ...
            • -
            • ...
            • -
            • ...
            • - ... -
            -``` - -Как наиболее эффективно получить второй `LI`? diff --git a/2-ui/1-document/6-searching-elements-internals/article.md b/2-ui/1-document/6-searching-elements-internals/article.md deleted file mode 100644 index d2935b7c..00000000 --- a/2-ui/1-document/6-searching-elements-internals/article.md +++ /dev/null @@ -1,172 +0,0 @@ -# Внутреннее устройство поисковых методов - -Эта глава не обязательна при первом чтении учебника. - -Если вы хотите действительно глубоко понимать, что происходит при поиске, то посмотрите эту главу. Если нет -- её можно пропустить. - -[cut] - -Несмотря на схожесть в синтаксисе, поисковые методы `get*` и `querySelector*` внутри устроены очень по-разному. - -## document.getElementById(id) - -Браузер поддерживает у себя внутреннее соответствие `id -> элемент`. Поэтому нужный элемент возвращается сразу. Это очень быстро. - -## elem.querySelector(query), elem.querySelectorAll(query) - -Чтобы найти элементы, удовлетворяющие поисковому запросу, браузер не использует никаких сложных структур данных. - -Он просто перебирает все подэлементы внутри элемента `elem`(или по всему документу, если вызов в контексте документа) и проверяет каждый элемент на соответствие запросу `query`. - -Вызов `querySelector` прекращает перебор после первого же найденного элемента, а `querySelectorAll` собирает найденные элементы в "псевдомассив": внутреннюю структуру данных, по сути аналогичную массиву JavaScript. - -Этот перебор происходит очень быстро, так как осуществляется непосредственно движком браузера, а не JavaScript-кодом. - -Оптимизации: -
              -
            • В случае поиска по ID: `elem.querySelector('#id')`, большинство браузеров оптимизируют поиск, используя вызов `getElementById`.
            • -
            • Последние результаты поиска сохраняются в кеше. Но это до тех пор, пока документ как-нибудь не изменится.
            • -
            - - -## elem.getElementsBy*(...) - -Результаты поиска `getElementsBy*` -- живые! При изменении документа -- изменяется и результат запроса. - -Например, найдём все `div` при помощи `querySelectorAll` и `getElementsByTagName`, а потом изменим документ: - -```html - -
            - -``` - -Как видно, длина коллекции, найденной через `querySelectorAll`, осталась прежней. А длина коллекции, возвращённой `getElementsByTagName`, изменилась. - -Дело в том, что результат запросов `getElementsBy*` -- это не массив, а специальный объект, имеющий тип NodeList или HTMLCollection. Он похож на массив, так как имеет нумерованные элементы и длину, но внутри это не готовая коллекция, а "живой поисковой запрос". - -Собственно поиск выполняется только при обращении к элементам коллекции или к её длине. - -## Алгоритмы getElementsBy* - -Поиск `getElementsBy*` наиболее сложно сделать эффективно, так как его результат -- "живая" коллекция, она должна быть всегда актуальной для текущего состояния документа. - -```js -var elems = document.getElementsByTagName('div'); -alert( elems[0] ); -*!* -// изменили документ -*/!* -alert( elems[0] ); // результат может быть уже другой -``` - -Можно искать заново при каждой попытке получить элемент из `elems`. Тогда результат будет всегда актуален, но поиск будет работать уж слишком медленно. Да и зачем? Ведь, скорее всего, документ не поменялся. - -**Чтобы производительность `getElementsBy*` была достаточно хорошей, активно используется кеширование результатов поиска.** - -Для этого есть два основных способа: назовём их условно "Способ Firefox" (Firefox, IE) и "Способ WebKit" (Chrome, Safari, Opera). - -Для примера, рассмотрим поиск в произвольном документе, в котором есть 1000 элементов `div`. - -Посмотрим, как будут работать браузеры, если нужно выполнить следующий код: - -```js -// вместо document может быть любой элемент -var elems = document.getElementsByTagName('div'); -alert( elems[0] ); -alert( elems[995] ); -alert( elems[500] ); -alert( elems.length ); -``` - -
            -
            Способ Firefox
            -
            Перебрать подэлементы `document.body` в порядке их появления в поддереве. Запоминать *все найденные элементы* во внутренней структуре данных, чтобы при повторном обращении обойтись без поиска. - -Разбор действий браузера при выполнении кода выше: -
            1. Браузер создаёт пустую "живую коллекцию" `elems`. Пока ничего не ищет.
            2. -
            3. Перебирает элементы, пока не найдёт первый `div`. Запоминает его и возвращает.
            4. -
            5. Перебирает элементы дальше, пока не найдёт элемент с индексом `995`. Запоминает все найденные.
            6. -
            7. Возвращает ранее запомненный элемент с индексом `500`, без дополнительного поиска!
            8. -
            9. Продолжает обход поддерева с элемента, на котором остановился (`995`) и до конца. Запоминает найденные элементы и возвращает их количество.
            10. -
            -
            -
            Способ WebKit
            -
            Перебирать подэлементы `document.body`. Запоминать только один, *последний найденный*, элемент, а также, по окончании перебора -- длину коллекции. - -Здесь кеширование используется меньше. - -Разбор действий браузера по строкам: -
            1. Браузер создаёт пустую "живую коллекцию" `elems`. Пока ничего не ищет.
            2. -
            3. Перебирает элементы, пока не найдёт первый `div`. Запоминает его и возвращает.
            4. -
            5. Перебирает элементы дальше, пока не найдёт элемент с индексом `995`. Запоминает его и возвращает.
            6. -
            7. Браузер запоминает только последний найденный, поэтому не помнит об элементе `500`. Нужно найти его перебором поддерева. Этот перебор можно начать либо с начала -- вперед по поддереву, 500й по счету) либо с элемента `995` -- назад по поддереву, 495й по счету. Так как назад разница в индексах меньше, то браузер выбирает второй путь и идёт от 995го назад 495 раз. Запоминает теперь уже 500й элемент и возвращает его.
            8. -
            9. Продолжает обход поддерева с 500го (не 995го!) элемента и до конца. Запоминает число найденных элементов и возвращает его.
            10. -
            -
            -
            - -Основное различие -- в том, что Firefox запоминает все найденные, а Webkit -- только последний. Таким образом, "метод Firefox" требует больше памяти, но гораздо эффективнее при повторном доступе к предыдущим элементам. - -А "метод Webkit" ест меньше памяти и при этом работает не хуже в самом важном и частом случае -- последовательном переборе коллекции, без возврата к ранее выбранным. - -**Запомненные элементы сбрасываются при изменениях DOM.** - -Документ может меняться. При этом, если изменение может повлиять на результаты поиска, то запомненные элементы необходимо сбросить. Например, добавление нового узла `div` сбросит запомненные элементы коллекции `elem.getElementsByTagName('div')`. - -Сбрасывание запомненных элементов при изменении документа выполняется интеллектуально. -
              -
            1. Во-первых, при добавлении элемента будут сброшены только те коллекции, которые могли быть затронуты обновлением. Например, если в документе есть два независимых раздела `
              `, и поисковая коллекция привязана к первому из них, то при добавлении во второй -- она сброшена не будет. - -Если точнее -- будут сброшены все коллекции, привязанные к элементам вверх по иерархии от непосредственного родителя нового `div` и выше, то есть такие, которые потенциально могли измениться. И только они. -
            2. -
            3. Во-вторых, если добавлен только `div`, то не будут сброшены запомненные элементы для поиска по другим тегам, например `elem.getElementsByTagName('a')`.
            4. -
            5. ...И, конечно же, не любые изменения DOM приводят к сбросу кешей, а только те, которые могут повлиять на коллекцию. Если где-то добавлен новый атрибут элементу -- с кешем для `getElementsByTagName` ничего не произойдёт, так как атрибут никак не может повлиять на результат поиска по тегу.
            6. -
            - -Прочие поисковые методы, такие как `getElementsByClassName` тоже сбрасывают кеш при изменениях интеллектуально. - -Разницу в алгоритмах поиска легко "пощупать". Посмотрите сами: - -```html - - -``` - -В примере выше первый цикл проходит элементы последовательно. А второй -- идет по шагам: один с начала, потом один с конца, потом ещё один с начала, ещё один -- с конца, и так далее. - -Количество обращений к элементам одинаково. - -
              -
            • В браузерах, которые запоминают все найденные (Firefox, IE) -- скорость будет одинаковой.
            • -
            • В браузерах, которые запоминают только последний (Webkit) -- разница будет порядка 100 и более раз, так как браузер вынужден бегать по дереву при каждом запросе заново.
            • -
            - diff --git a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/console-innerhtml.png b/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/console-innerhtml.png deleted file mode 100644 index 715f8ed3..00000000 Binary files a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/console-innerhtml.png and /dev/null differ diff --git a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/console-innerhtml@2x.png b/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/console-innerhtml@2x.png deleted file mode 100644 index 3256dea5..00000000 Binary files a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/console-innerhtml@2x.png and /dev/null differ diff --git a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/solution.md b/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/solution.md deleted file mode 100644 index 0b7e7714..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -**Однозначно правильный ответ невозможен.** - -В консоли не выводятся пробельные узлы. Если перед `

            ` находится пробельный узел, то будет `undefined`, а если нет -- то текст внутри `

            `. - -Пример с `undefined`: - -```html - - -

            Привет, мир!

            - - - -``` - -Если убрать из него перевод строки перед `

            `, то было бы `"Привет, мир!"`. \ No newline at end of file diff --git a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/task.md b/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/task.md deleted file mode 100644 index e5bab48c..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/1-console-firstchild-innerhtml/task.md +++ /dev/null @@ -1,12 +0,0 @@ -# Что выведет код в консоли? - -[importance 5] - -В браузере Chrome открыт HTML-документ. - -Вы зашли во вкладку Elements и видите такую картинку: - - -В настоящий момент выбран элемент ``. - -Что выведет код `$0.firstChild.innerHTML` в консоли? \ No newline at end of file diff --git a/2-ui/1-document/7-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md b/2-ui/1-document/7-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md deleted file mode 100644 index 0c8dc882..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/2-lastchild-nodetype-inline/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Небольшой подвох -- в том, что во время выполнения скрипта последним тегом является `SCRIPT`. Браузер не может обработать страницу дальше, пока не выполнит скрипт. - -Так что результат будет `1` (узел-элемент). - -```html - - - - - - - - - -``` - diff --git a/2-ui/1-document/7-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md b/2-ui/1-document/7-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md deleted file mode 100644 index 279b0784..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/2-lastchild-nodetype-inline/task.md +++ /dev/null @@ -1,19 +0,0 @@ -# В инлайн скрипте lastChild.nodeType - -[importance 5] - -Что выведет скрипт на этой странице? - -```html - - - - - - - - -``` - diff --git a/2-ui/1-document/7-basic-dom-node-properties/3-tag-in-comment/solution.md b/2-ui/1-document/7-basic-dom-node-properties/3-tag-in-comment/solution.md deleted file mode 100644 index 6301505b..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/3-tag-in-comment/solution.md +++ /dev/null @@ -1,19 +0,0 @@ -Ответ: **`BODY`**. - -```html - - -``` - -Происходящее по шагам: -
              -
            1. Заменяем содержимое `` на комментарий. Он будет иметь вид <!--BODY-->, так как `body.tagName == "BODY"`. Как мы помним, свойство `tagName` в HTML всегда находится в верхнем регистре.
            2. -
            3. Этот комментарий теперь является первым и единственным потомком `body.firstChild`.
            4. -
            5. Получим значение `data` для комментария `body.firstChild`. Оно равно содержимому узла для всех узлов, кроме элементов. Содержимое комментария: `"BODY"`.
            6. -
            \ No newline at end of file diff --git a/2-ui/1-document/7-basic-dom-node-properties/3-tag-in-comment/task.md b/2-ui/1-document/7-basic-dom-node-properties/3-tag-in-comment/task.md deleted file mode 100644 index e2cc5b02..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/3-tag-in-comment/task.md +++ /dev/null @@ -1,16 +0,0 @@ -# Тег в комментарии - -[importance 3] - -Что выведет этот код? - -```html - -``` - diff --git a/2-ui/1-document/7-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md b/2-ui/1-document/7-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md deleted file mode 100644 index d27accb7..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md +++ /dev/null @@ -1,72 +0,0 @@ - -Объектом какого класса является `document`, можно выяснить так: -```js -//+ run -alert(document); // [object HTMLDocument] -``` - -Или так: -```js -//+ run -alert(document.constructor); // function HTMLDocument() { ... } -``` - -Итак, `document` -- объект класса `HTMLDocument`. - -Какое место `HTMLDocument` занимает в иерархии? - -Можно поискать в документации. Но попробуем выяснить это самостоятельно. - -Вопрос не такой простой и требует хорошего понимания [прототипного наследования](/class-inheritance). - -Вспомним, как оно устроено: -
              -
            • Методы объекта `document` находятся в `prototype` конструктора, в данном случае -- `HTMLDocument.prototype`.
            • -
            • У `HTMLDocument.prototype` есть ссылка `__proto__` на прототип-родитель.
            • -
            • У прототипа-родителя может быть ссылка `__proto__` на его родитель, и так далее.
            • -
            - -При поиске свойства в `document`, если его там нет, оно ищется в `document.__proto__`, затем в `document.__proto__.__proto__` и так далее, пока не найдём, или пока цепочка `__proto__` не закончится. Это обычное устройство класса, без наследования. - -Нам нужно лишь узнать, что находится в этих самых `__proto__`. - -Строго говоря, там могут быть любые объекты. Вовсе не обязательно, чтобы объектам из цепочки прототипов соответствовали какие-то конструкторы. - -Вполне может быть цепочка, где родители -- просто обычные JS-объекты: -```js -document -> HTMLDocument.prototype -> obj1 -> obj2 -> ... -``` - -Однако, здесь мы знаем, что наследование -- "на классах", то есть, эти объекты `obj1`, `obj2` являются `prototype` неких функций-конструкторов: - -```js -document -> HTMLDocument.prototype -> F1.prototype -> F2.prototype -> ... -``` - -Что стоит на месте `F1`, `F2`? - -Опять же, если говорить про некие абстрактные объекты, то откуда нам знать, какие функции на них ссылаются через `prototype`? Ниоткуда. Один объект может быть в `prototype` хоть у десятка функций. - -Но в стандартном прототипном наследовании один объект является `prototype` ровно у одной функции. Причём при создании функции в её `prototype` уже есть объект со свойством `constructor`, которое ссылается обратно на функцию: -```js -F.prototype = { constructor: F } -``` - -Это свойство `constructor`, если конечно его не удалить или не перезаписать нечаянно (чего делать не следует), и позволяет из прототипа узнать соответствующий ему конструктор. - -```js -//+ run -// цепочка наследования: -alert(HTMLDocument.prototype.constructor); // function HTMLDocument -alert(HTMLDocument.prototype.__proto__.constructor); // function Document -alert(HTMLDocument.prototype.__proto__.__proto__.constructor); // function Node -``` - -При выводе объекта через `console.dir(document)` в Google Chrome, мы тоже можем, раскрывая `__proto__`, увидеть эти названия (`HTMLDocument`, `Document`, `Node`). - -Браузерная консоль их берёт как раз из свойства `constructor`. - - - - - diff --git a/2-ui/1-document/7-basic-dom-node-properties/4-where-document-in-hierarchy/task.md b/2-ui/1-document/7-basic-dom-node-properties/4-where-document-in-hierarchy/task.md deleted file mode 100644 index 517eeb3a..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/4-where-document-in-hierarchy/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# Где в DOM-иерархии document? - -[importance 4] -Объектом какого класса является `document`? - -Какое место он занимает в DOM-иерархии? - -Наследует ли он `Node` или `Element`? - -Воспользуйтесь для решения тем фактом, что DOM-узлы образуют стандартную прототипную иерархию классов. - diff --git a/2-ui/1-document/7-basic-dom-node-properties/article.md b/2-ui/1-document/7-basic-dom-node-properties/article.md deleted file mode 100644 index 0bb288f2..00000000 --- a/2-ui/1-document/7-basic-dom-node-properties/article.md +++ /dev/null @@ -1,555 +0,0 @@ -# Свойства узлов: тип, тег и содержимое - -В этой главе мы познакомимся с основными, самыми важными свойствами, которые отвечают за тип DOM-узла, тег и содержимое. - -[cut] - -## Классы, иерархия DOM - -Самое главное различие между DOM-узлами -- разные узлы являются объектами различных классов. - -Поэтому, к примеру, у узла, соответствующего тегу `` -- одни свойства, у `
            ` -- другие, у `` -- третьи. - -Есть и кое-что общее, за счёт наследования. - -Классы DOM образуют иерархию. - -Основной объект в ней: [Node](http://dom.spec.whatwg.org/#interface-node), от которого наследуют остальные: - - - -На рисунке выше изображены основные классы: - - -Узнать класс узла очень просто -- достаточно привести его к строке, к примеру, вывести: - -```js -//+ run -alert( document.body ); // [object HTMLBodyElement] -``` - -Можно и проверить при помощи `instanceof`: - -```js -//+ run -alert( document.body instanceof HTMLBodyElement ); // true -alert( document.body instanceof HTMLElement ); // true -alert( document.body instanceof Element ); // true -alert( document.body instanceof Node ); // true -``` - -Как видно, DOM-узлы -- обычные JavaScript-объекты. Их классы заданы в прототипном стиле. В этом легко убедиться, если вывести в консоли любой элемент через `console.dir(elem)`. Или даже можно напрямую обратиться к методам, которые хранятся в `Node.prototype`, `Element.prototype` и так далее. - -[smart header="`console.dir(elem)` против `console.log(elem)`"] -Вывод `console.log(elem)` и `console.dir(elem)` различен. - -
              -
            • `console.log` выводит элемент в виде, удобном для исследования HTML-структуры.
            • -
            • `console.dir` выводит элемент в виде JavaScript-объекта, удобно для анализа его свойств.
            • -
            -Попробуйте сами на `document.body`. -[/smart] - -Детальное описание свойств и методов каждого DOM-класса дано в [спецификации](https://html.spec.whatwg.org/multipage/). - -Например, [The input element](https://html.spec.whatwg.org/multipage/forms.html#the-input-element) описывает класс, соответствующий ``, включая [interface HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement), который нас как раз и интересует. - -При описании свойств и методов используется не JavaScript, а специальный язык [IDL](https://ru.wikipedia.org/wiki/%D0%AF%D0%B7%D1%8B%D0%BA_%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D1%8F_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81%D0%BE%D0%B2) (Interface Definition Language), который достаточно легко понять "с ходу". - -Вот из него выдержка, с комментариями: - -```js -// Объявлен HTMLInputElement -// двоеточие означает, что он наследует от HTMLElement -interface HTMLInputElement: HTMLElement { - - // у всех таких элементов есть строковые свойства - // accept, alt, autocomplete, value - attribute DOMString accept; - attribute DOMString alt; - attribute DOMString autocomplete; - attribute DOMString value; - - // и логическое свойство autofocus - attribute boolean autofocus; - ... - // а также метод select, который значение не возвращает (void) - void select(); - ... -} -``` - -Далее в этом разделе мы поговорим о самых главных свойствах узлов DOM, которые используются наиболее часто. - -## Тип: nodeType - -Тип узла содержится в его свойстве `nodeType`. - -Как правило, мы работаем всего с двумя типами узлов: -
              -
            • Элемент.
            • -
            • Текстовый узел.
            • -
            - -На самом деле типов узлов гораздо больше. Строго говоря, их 12, и они описаны в спецификации с древнейших времён, см.
            DOM Уровень 1: - -```js -interface Node { - // Всевозможные значения nodeType - const unsigned short ELEMENT_NODE = 1; - const unsigned short ATTRIBUTE_NODE = 2; - const unsigned short TEXT_NODE = 3; - const unsigned short CDATA_SECTION_NODE = 4; - const unsigned short ENTITY_REFERENCE_NODE = 5; - const unsigned short ENTITY_NODE = 6; - const unsigned short PROCESSING_INSTRUCTION_NODE = 7; - const unsigned short COMMENT_NODE = 8; - const unsigned short DOCUMENT_NODE = 9; - const unsigned short DOCUMENT_TYPE_NODE = 10; - const unsigned short DOCUMENT_FRAGMENT_NODE = 11; - const unsigned short NOTATION_NODE = 12; - ... -} -``` - -В частности, тип "Элемент" `ELEMENT_NODE` имеет номер 1, а "Текст" `TEXT_NODE` -- номер 3. - -Например, выведем все узлы-потомки `document.body`, *являющиеся элементами*: - -```html - - -
            Читатели:
            -
              -
            • Вася
            • -
            • Петя
            • -
            - - - - - -``` - -Тип узла можно только читать, изменить его невозможно. - - -## Тег: nodeName и tagName - -Существует целых два свойства: `nodeName` и `tagName`, которые содержат название(тег) элемента узла. - -**Название HTML-тега всегда находится в верхнем регистре.** - -Например, для `document.body`: - -```js -//+ run -alert( document.body.nodeName ); // BODY -alert( document.body.tagName ); // BODY -``` - -[smart header="В XHTML `nodeName` может быть не в верхнем регистре"] -У браузера есть два режима обработки документа: HTML и XML-режим. Обычно используется режим HTML. - -XML-режим включается, когда браузер получает XML-документ через `XMLHttpRequest`(технология AJAX) или при наличии заголовка `Content-Type: application/xml+xhtml`. - -В XML-режиме сохраняется регистр и `nodeName` может выдать "body" или даже "bOdY" -- в точности как указано в документе. XML-режим используют очень редко. -[/smart] - -### Какая разница между tagName и nodeName ? - -Разница отражена в названиях свойств, но неочевидна. - -
              -
            • Свойство `tagName` есть только у элементов `Element` (в IE8- также у комментариев, но это ошибка в браузере).
            • -
            • Свойство `nodeName` определено для любых узлов `Node`, для элементов оно равно `tagName`, а для не-элементов обычно содержит строку с типом узла.
            • -
            - -Таким образом, при помощи `tagName` мы можем работать только с элементами, а `nodeName` может что-то сказать и о других типах узлов. - -Например, сравним `tagName` и `nodeName` на примере узла-комментария и объекта `document`: - -```html - - - - - - -``` - -При работе с элементами, как это обычно бывает, имеет смысл использовать свойство `tagName` -- оно короче. - -## innerHTML: содержимое элемента - -Свойство `innerHTML` описано в спецификации HTML 5 -- embedded content. - -Оно позволяет получить HTML-содержимое элемента в виде строки. В `innerHTML` можно и читать и писать. - -Пример выведет на экран все содержимое `document.body`, а затем заменит его на другое: - -```html - - -

            Параграф

            -
            Div
            - - - - -``` - -Значение, возвращаемое `innerHTML` -- всегда валидный HTML-код. При записи можно попробовать записать что угодно, но браузер исправит ошибки: - -```html - - - - - - -``` - -Свойство `innerHTML` -- одно из самых часто используемых. - -### Тонкости innerHTML - -`innerHTML` не так прост, как может показаться, и таит в себе некоторые тонкости, которые могут сбить с толку новичка, а иногда и опытного программиста. - -Ознакомьтесь с ними. Даже если этих сложностей у вас *пока* нет, эта информация отложится где-то в голове и поможет, когда проблема появится. - -[warn header="Для таблиц в IE9- -- `innerHTML` только для чтения"] -В Internet Explorer версии 9 и ранее, innerHTML доступно только для чтения для элементов `COL`, `COLGROUP`, `FRAMESET`, `HEAD`, `HTML`, `STYLE`, `TABLE`, `TBODY`, `TFOOT`, `THEAD`, `TITLE`, `TR`. - -В частности, в IE9- запрещена запись в `innerHTML` для любых табличных элементов, кроме ячеек (`TD/TH`). -[/warn] - -[warn header="Добавление `innerHTML+=` осуществляет перезапись"] -Синтаксически, можно добавить текст к `innerHTML` через `+=`: - -```js -chatDiv.innerHTML += "
            Привет !
            "; -chatDiv.innerHTML += "Как дела?"; -``` - -На практике этим следует пользоваться с большой осторожностью, так как фактически происходит не добавление, а перезапись: -
              -
            1. Удаляется старое содержание
            2. -
            3. На его место становится новое значение `innerHTML`.
            4. -
            - -Так как новое значение записывается с нуля, то **все изображения и другие ресурсы будут перезагружены**. В примере выше вторая строчка перезагрузит `smile.gif`, который был до неё. Если в `chatDiv` много текста, то эта перезагрузка будет очень заметна. - -Есть и другие побочные эффекты, например если существующий текст был выделен мышкой, то в большинстве браузеров это выделение пропадёт. Если в HTML был ``, в который посетитель что-то ввёл, то введённое значение пропадёт. И тому подобное. - -К счастью, есть и другие способы добавить содержимое, не использующие `innerHTML`. -[/warn] - -[warn header="Скрипты не выполняются"] -Если в `innerHTML` есть тег `script` -- он не будет выполнен. - -К примеру: - -```html - -
            - - -``` - -В примере закрывающий тег `` разбит на две строки, т.к. иначе браузер подумает, что это конец скрипта. Вставленный скрипт не выполнится. - -Исключение -- IE9-, в нем вставляемый скрипт выполняются, если у него есть атрибут `defer`. Но это нестандартная возможность, которой не следует пользоваться. - -[/warn] - -[warn header="IE8- обрезает `style` и `script` в начале `innerHTML`"] -Если в начале `innerHTML` находятся стили ` - - - - - - список - - - - - - - - \ No newline at end of file diff --git a/2-ui/1-document/9-attributes-and-custom-properties/2-set-class-links/task.md b/2-ui/1-document/9-attributes-and-custom-properties/2-set-class-links/task.md deleted file mode 100644 index 30a6af59..00000000 --- a/2-ui/1-document/9-attributes-and-custom-properties/2-set-class-links/task.md +++ /dev/null @@ -1,31 +0,0 @@ -# Поставьте класс ссылкам - -[importance 3] - -Сделайте желтыми внешние ссылки, добавив им класс `external`. - -Все ссылки без `href`, без протокола и начинающиеся с `http://internal.com` считаются внутренними. - -```html - - - -список - -``` - - -Результат: -[iframe border=1 height=180 src="solution"] - diff --git a/2-ui/1-document/9-attributes-and-custom-properties/article.md b/2-ui/1-document/9-attributes-and-custom-properties/article.md deleted file mode 100644 index e695b11b..00000000 --- a/2-ui/1-document/9-attributes-and-custom-properties/article.md +++ /dev/null @@ -1,555 +0,0 @@ -# Атрибуты и DOM-свойства - -При чтении HTML браузер генерирует DOM-модель. При этом большинство стандартных HTML-атрибутов становятся свойствами соответствующих объектов. - -Например, если тег выглядит как ``, то у объекта будет свойство `body.id = "page"`. - -Но это преобразование -- не один-в-один. Бывают ситуации, когда атрибут имеет одно значение, а свойство -- другое. Бывает и так, что атрибут есть, а свойства с таким названием не создаётся. - -Если коротко -- HTML-атрибуты и DOM-свойства обычно, но не всегда соответствуют друг другу, нужно понимать, что такое свойство и что такое атрибут, чтобы работать с ними правильно. - -[cut] -## Свои DOM-свойства - -Ранее мы видели некоторые встроенные свойства DOM-узлов. Но, технически, никто нас ими не ограничивает. - -**Узел DOM -- это объект, поэтому, как и любой объект в JavaScript, он может содержать пользовательские свойства и методы.** - -Например, создадим в `document.body` новое свойство и запишем в него объект: - -```js -//+ run -document.body.myData = { - name: 'Петр', - familyName: 'Петрович' -}; - -alert( document.body.myData.name ); // Петр -``` - -Можно добавить и новую функцию: - -```js -//+ run -document.body.sayHi = function() { - alert( this.nodeName ); -} - -document.body.sayHi(); // BODY, выполнилась с правильным this -``` - -Нестандартные свойства и методы видны только в JavaScript и никак не влияют на отображение соответствующего тега. - -Обратим внимание, пользовательские DOM-свойства: - -
              -
            • Могут иметь любое значение.
            • -
            • Названия свойств *чувствительны* к регистру.
            • -
            • Работают за счет того, что DOM-узлы являются объектами JavaScript.
            • -
            - -## Атрибуты - -Элементам DOM, с другой стороны, соответствуют HTML-теги, у которых есть текстовые атрибуты. - -Конечно, здесь речь именно об узлах-элементах, не о текстовых узлах или комментариях. - -Доступ к атрибутам осуществляется при помощи стандартных методов: -
              -
            • `elem.hasAttribute(name)` - проверяет наличие атрибута
            • -
            • `elem.getAttribute(name)` - получает значение атрибута
            • -
            • `elem.setAttribute(name, value)` - устанавливает атрибут
            • -
            • `elem.removeAttribute(name)` - удаляет атрибут
            • -
            - -Эти методы возвращают именно то значение, которое находится в HTML. - -Также все атрибуты элемента можно получить с помощью свойства `elem.attributes`, которое содержит псевдо-массив объектов типа [Attr](http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-637646024). - -В отличие от свойств, атрибуты: - -
              -
            • Всегда являются строками.
            • -
            • Их имя *нечувствительно* к регистру (ведь это HTML)
            • -
            • Видны в `innerHTML` (за исключением старых IE)
            • -
            - -Рассмотрим отличия между DOM-свойствами и атрибутами на примере HTML-кода: - -```html - -
            - -``` - -Пример ниже устанавливает атрибуты и демонстрирует их особенности. - -```html - - -
            - - - -``` - -При запуске кода выше обратите внимание: -
              -
            1. `getAttribute('About')` -- первая буква имени атрибута `About` написана в верхнем регистре, а в HTML -- в нижнем, но это не имеет значения, так как имена нечувствительны к регистру.
            2. -
            3. Мы можем записать в атрибут любое значение, но оно будет превращено в строку. Объекты также будут автоматически преобразованы.
            4. -
            5. После добавления атрибута его можно увидеть в `innerHTML` элемента.
            6. -
            7. Коллекция `attributes` содержит все атрибуты в виде объектов со свойствами `name` и `value`.
            8. -
            - -## Когда полезен доступ к атрибутам? - -Когда браузер читает HTML и создаёт DOM-модель, то он создаёт свойства для всех *стандартных* атрибутов. - -Например, свойства тега `'A'` описаны в спецификации DOM: HTMLAnchorElement. - -Например, у него есть свойство `"href"`. Кроме того, он имеет `"id"` и другие свойства, общие для всех элементов, которые описаны в спецификации в HTMLElement. - -Все стандартные свойства DOM синхронизируются с атрибутами, однако не всегда такая синхронизация происходит 1-в-1, поэтому иногда нам нужно значение именно из HTML, то есть атрибут. - -Рассмотрим несколько примеров. - -### Ссылка "как есть" из атрибута href - -Синхронизация не гарантирует одинакового значения в атрибуте и свойстве. - -Для примера, посмотрим, что произойдет с атрибутом `"href"` при изменении свойства: - -```html - - - -``` - -Это происходит потому, что атрибут может быть любым, а свойство `href`, в соответствии со спецификацией W3C, должно быть полной ссылкой. - -Стало быть, если мы хотим именно то, что в HTML, то нужно обращаться через атрибут. - -[smart header="Есть и другие подобные атрибуты"] - -Кстати, есть и другие атрибуты, которые не копируются в точности. Например, DOM-свойство `input.checked` имеет логическое значение `true/false`, а HTML-атрибут `checked` -- любое строковое, важно лишь его наличие. - -Работа с `checked` через атрибут и свойство: - -```html - - - - -``` -[/smart] - -### Исходное значение value - -Изменение некоторых свойств обновляет атрибут. Но это скорее исключение, чем правило. - -**Чаще синхронизация -- односторонняя: свойство зависит от атрибута, но не наоборот.** - -Например, при изменении свойства `input.value` атрибут `input.getAttribute('value')` не меняется: - -```html - - - - - -``` - -То есть, изменение DOM-свойства `value` на атрибут не влияет, он остаётся таким же. - -А вот изменение атрибута обновляет свойство: - -```html - - - - - -``` - -Эту особенность можно красиво использовать. - -Получается, что атрибут `input.getAttribute('value')` хранит оригинальное (исходное) значение даже после того, как пользователь заполнил поле и свойство изменилось. - -Например, можно взять изначальное значение из атрибута и сравнить со свойством, чтобы узнать, изменилось ли значение. А при необходимости и перезаписать свойство атрибутом, отменив изменения. - -## Классы в виде строки: className - -Атрибуту `"class"` соответствует свойство `className`. - -Так как слово `"class"` является зарезервированным словом в Javascript, то при проектировании DOM решили, что соответствующее свойство будет называться `className`. - -Например: - -```html - - - - -``` - -Кстати, есть и другие атрибуты, которые называются иначе, чем свойство. Например, атрибуту `for` (`