diff --git a/1-js/4-data-structures/10-arguments-pseudoarray/article.md b/1-js/4-data-structures/10-arguments-pseudoarray/article.md index 47d13f8e..485ae3d8 100644 --- a/1-js/4-data-structures/10-arguments-pseudoarray/article.md +++ b/1-js/4-data-structures/10-arguments-pseudoarray/article.md @@ -37,7 +37,9 @@ log(a,b,c); // вызовется вторая функция Это называется "полиморфизмом функций" или "перегрузкой функций". В JavaScript ничего подобного нет. -**Может быть только одна функция с именем `log`, которая вызывается с любыми аргументами.** А уже внутри она может посмотреть, с чем вызвана и по-разному отработать. +**Может быть только одна функция с именем `log`, которая вызывается с любыми аргументами.** + +А уже внутри она может посмотреть, с чем вызвана и по-разному отработать. В примере выше второе объявление `log` просто переопределит первое. [/smart] @@ -142,9 +144,7 @@ for(var i=0; i
`new Date(datestring)`
-
Если единственный аргумент - строка, используется вызов `Date.parse` для ее разбора.
+
Если единственный аргумент - строка, используется вызов `Date.parse` (см. далее) для чтения даты из неё.
`new Date(year, month, date, hours, minutes, seconds, ms)`
Дату можно создать, используя компоненты в местной временной зоне. Для этого формата обязательны только первые два аргумента. Отсутствующие параметры, начиная с `hours` считаются равными нулю, а `date` -- единице. -**Заметим, что год `year` должен быть из 4 цифр, а отсчет месяцев `month` начинается с нуля 0.** Например: +Заметим: + + +Например: ```js -new Date(2011, 0, 1) // 1 января 2011, 00:00:00 в местной временной зоне -new Date(2011, 0) // то же самое, date по умолчанию равно 1 -new Date(2011, 0, 1, 0, 0, 0, 0); // то же самое +new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 января 2011, 00:00:00 +new Date(2011, 0, 1); // то же самое, часы/секунды по умолчанию равны 0 ``` Дата задана с точностью до миллисекунд: ```js //+ run -var d = new Date(2011, 0, 1, 2, 3, 4, 567); -alert(d); // 1.01.2011, 02:03:04.567 +var date = new Date(2011, 0, 1, 2, 3, 4, 567); +alert(date); // 1.01.2011, 02:03:04.567 ```
@@ -67,7 +72,7 @@ alert(d); // 1.01.2011, 02:03:04.567
Получить соответствующие компоненты.
-[warn header="Устаревший `getYear()`"] +[warn header="Не `getYear()`, а `getFullYear()`"] Некоторые браузеры реализуют нестандартный метод `getYear()`. Где-то он возвращает только две цифры из года, где-то четыре. Так или иначе, этот метод отсутствует в стандарте JavaScript. Не используйте его. Для получения года есть `getFullYear()`. [/warn] @@ -85,17 +90,22 @@ alert(d); // 1.01.2011, 02:03:04.567 ```js //+ run +// текущая дата var date = new Date(); -alert( date.getHours() ); // час в вашей зоне для даты date -alert( date.getUTCHours() ); // час в зоне GMT+0 для даты date +// час в текущей временной зоне +alert( date.getHours() ); + +// сколько сейчас времени в Лондоне? +// час в зоне GMT+0 +alert( date.getUTCHours() ); ``` Кроме описанных выше, существуют два специальных метода без UTC-варианта:
`getTime()`
-
Возвращает число миллисекунд, прошедших с 01.01.1970 00:00:00 UTC. Это то же число, которое используется в конструкторе `new Date(milliseconds)`.
+
Возвращает число миллисекунд, прошедших с 1 января 1970 года GMT+0, то есть того же вида, который используется в конструкторе `new Date(milliseconds)`.
`getTimezoneOffset()`
Возвращает разницу между местным и UTC-временем, в минутах. @@ -296,7 +306,9 @@ alert('Время walkLength: ' +timeLength + 'мс'); ``` [smart header="Более точное время с `performance.now()`"] -В современных браузерах (кроме IE9-) вызов [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/performance.now) возвращает количество миллисекунд, прошедшее с начала загрузки страницы, а если точнее -- с момента выгрузки предыдущей страницы из памяти. +В современных браузерах (кроме IE9-) вызов [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/performance.now) возвращает количество миллисекунд, прошедшее с начала загрузки страницы. Причём именно с самого начала, до того, как загрузился HTML-файл, если точнее -- с момента выгрузки предыдущей страницы из памяти. + +Так что это время включает в себя всё, включая начальное обращение к серверу. Его можно посмотреть в любом месте страницы, даже в ``, чтобы узнать, сколько времени потребовалось браузеру, чтобы до него добраться, включая загрузку HTML. @@ -348,17 +360,17 @@ console.timeEnd("All Benchmarks");
  • Автоматически выносят инвариант, то есть постоянное в цикле значение типа `arr.length`, за пределы цикла.
  • Стараются понять, значения какого типа хранит данная переменная или массив, какую структуру имеет объект и, исходя из этого, оптимизировать внутренние алгоритмы.
  • Выполняют простейшие операции, например сложение явно заданных чисел и строк, на этапе компиляции.
  • -
  • В теории, могут выкинуть код, который ни на что не влияет, например присваивание к неиспользуемой локальной переменной, хотя делают это редко.
  • +
  • Могут обнаружить, что некий код, например присваивание к неиспользуемой локальной переменной, ни на что не влияет и вообще исключить его из выполнения, хотя делают это редко.
  • -Они могут влиять на результаты тестов. +Эти оптимизации могут влиять на результаты тестов, поэтому измерять скорость базовых операций JavaScript ("проводить миробенчмаркинг") до того, как вы изучите внутренности JavaScript-интерпретаторов и поймёте, что они реально делают на таком коде, не рекомендуется. [/warn] -## Форматирование +## Форматирование и вывод дат Во всех браузерах, кроме IE10-, поддерживается новый стандарт [Ecma 402](http://www.ecma-international.org/publications/standards/Ecma-402.htm), который добавляет специальные методы для форматирования дат. -Это делается взыовом `date.toLocaleString(локаль, опции)`, у которого много настроек. Он позволяет указать, какие параметры даты нужно вывести, и ряд настроек вывода, после чего интерпретатор сам сформирует строку. +Это делается вызовом `date.toLocaleString(локаль, опции)`, в котором можно задать много настроек. Он позволяет указать, какие параметры даты нужно вывести, и ряд настроек вывода, после чего интерпретатор сам сформирует строку. Пример с почти всеми параметрами даты и русским, затем английским (США) форматированием: @@ -389,7 +401,7 @@ alert( date.toLocaleString("en-US", options) ); // Wednesday, December 31, 2014
    `toString()`, `toDateString()`, `toTimeString()`
    -
    Возвращают стандартное строчное представление, не указанное в стандарте, а зависящее от браузера. Единственное требование - читаемость человеком. Метод `toString` возвращает дату целиком, `toDateString()` и `toTimeString()` - только дату и время соответственно. +
    Возвращают стандартное строчное представление, не заданное жёстко в стандарте, а зависящее от браузера. Единственное требование к нему -- читаемость человеком. Метод `toString` возвращает дату целиком, `toDateString()` и `toTimeString()` -- только дату и время соответственно. ```js //+ run @@ -413,7 +425,7 @@ alert( d.toISOString() ); // вывод, похожий на '2011-01-26T13:51:5
    -**Если хочется иметь большую гибкость и кросс-браузерность, то также можно воспользоваться специальной библиотекой, например [Moment.JS](http://momentjs.com/) или написать свою функцию.** +Если хочется иметь большую гибкость и кросс-браузерность, то также можно воспользоваться специальной библиотекой, например [Moment.JS](http://momentjs.com/) или написать свою функцию форматирования. @@ -421,31 +433,29 @@ alert( d.toISOString() ); // вывод, похожий на '2011-01-26T13:51:5 Все современные браузеры, включая IE9+, понимают даты в упрощённом формате ISO 8601 Extended. -Этот формат выглядит так: `YYYY-MM-DDTHH:mm:ss.sssZ`. Для разделения даты и времени в нем используется символ `'T'`. Часть `'Z'` обозначает (необязательную) временную зону -- она может отсутствовать, тогда зона UTC, либо может быть символ `z` -- тоже UTC, или зона в формате `+-hh:mm`. +Этот формат выглядит так: `YYYY-MM-DDTHH:mm:ss.sssZ`, где: -Также возможны упрощенные варианты, к примеру: +
      +
    • `YYYY-MM-DD` -- дата в формате год-месяц-день.
    • +
    • Обычный символ `T` используется как разделитель.
    • +
    • `HH:mm:ss.sss` -- время: часы-минуты-секунды-миллисекунды.
    • +
    • Часть `'Z'` обозначает временную зону -- в формате `+-hh:mm`, либо символ `Z`, обозначающий UTC. По стандарту её можно не указывать, тогда UTC, но в Safari с этим ошибка, так что лучше указывать всегда.
    • +
    -```js -YYYY -YYYY-MM -YYYY-MM-DD -``` +Также возможны укороченные варианты, например `YYYY-MM-DD` или `YYYY-MM` или даже только `YYYY`. Метод `Date.parse(str)` разбирает строку `str` в таком формате и возвращает соответствующее ей количество миллисекунд. Если это невозможно, `Date.parse` возвращает `NaN`. -На момент написания некоторые браузеры (Safari) воспринимали формат без `'Z'` как дату в локальной таймзоне (по стандарту UTC), поэтому пример ниже в них работает некорректно: +Например: ```js //+ run -var msNoZone = Date.parse('2012-01-26T13:51:50.417'); // без зоны, значит UTC +var msUTC = Date.parse('2012-01-26T13:51:50.417Z'); // зона UTC -alert(msNoZone); // 1327571510417 (число миллисекунд) - -var msZ = Date.parse('2012-01-26T13:51:50.417z'); // зона z означает UTC -alert(msZ == msNoZone); // true, если браузер правильный +alert(msUTC); // 1327571510417 (число миллисекунд) ``` -С таймзоной `-07:00 GMT` в конце все современные браузеры работают правильно: +С таймзоной `-07:00 GMT`: ```js //+ run @@ -454,6 +464,7 @@ var ms = Date.parse('2012-01-26T13:51:50.417-07:00'); alert(ms); // 1327611110417 (число миллисекунд) ``` + [smart header="Формат дат для IE8-"] До появления спецификации EcmaScript 5 формат не был стандартизован, и браузеры, включая IE8-, имели свои собственные форматы дат. Частично, эти форматы пересекаются. diff --git a/1-js/4-data-structures/12-typeof-duck-typing/article.md b/1-js/4-data-structures/12-typeof-duck-typing/article.md index cfcfe2d4..85b633a3 100644 --- a/1-js/4-data-structures/12-typeof-duck-typing/article.md +++ b/1-js/4-data-structures/12-typeof-duck-typing/article.md @@ -19,11 +19,11 @@
    Строки, такие как `"Мяу"` или пустая строка `""`.
    -Все остальные значения являются **объектами**, включая функции и массивы. +Все остальные значения, включая даты и массивы, являются объектами. ## Оператор typeof [#type-typeof] -Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса: +Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса: со скобками и без:
    1. Синтаксис оператора: `typeof x`.
    2. Синтаксис функции: `typeof(x)`.
    3. @@ -91,14 +91,14 @@ alert( typeof new Date ); // 'object' Смысл утиной типизации -- в проверке необходимых методов и свойств. -Например, у нас функция работает с массивами. Мы можем проверить, что объект -- массив, уточнив наличие метода `splice`: +Например, у нас функция работает с массивами. Мы можем проверить, что объект -- массив, уточнив наличие метода `splice`, который, как известно, есть у всех массивов: ```js //+ run -var something = [1,2,3]; +var something = [1, 2, 3]; if (something.splice) { - alert('Массив!'); + alert('Это утка! То есть, массив!'); } ``` @@ -121,16 +121,14 @@ if (x.getTime) { ## Полиморфизм -Используем проверку типов для того, чтобы создать полиморфную функцию `sayHi(who)`, которая говорит "Привет" своему аргументу. - -При этом, если передали массив, она должна вызвать себя для каждого подэлемента. +Пример полимофрной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому: ```js //+ run function sayHi(who) { - if (who.splice) { // проверка на массив (или что-то похожее) - for(var i=0; iДля функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.
    -Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся исползовать, но это не обязательно. +Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся использовать. diff --git a/1-js/4-data-structures/8-array-methods/11-array-unique/solution.md b/1-js/4-data-structures/8-array-methods/11-array-unique/solution.md index 7240bc6c..d367656a 100644 --- a/1-js/4-data-structures/8-array-methods/11-array-unique/solution.md +++ b/1-js/4-data-structures/8-array-methods/11-array-unique/solution.md @@ -41,7 +41,7 @@ alert( unique(strings) ); // кришна, харе, 8-()
  • Для второго элемента -- это обойдётся в `1` операцию доступа к элементам `result`.
  • Для третьего элемента -- это обойдётся в `2` операции доступа к элементам `result`.
  • ...Для n-го элемента -- это обойдётся в `n-1` операций доступа к элементам `result`.
  • - + Всего 0 + 1 + 2 + ... + n-1 = (n-1)*n/2 = n2/2 - n/2 (как сумма арифметической прогрессии), то есть количество операций растёт примерно как квадрат от `n`. diff --git a/1-js/4-data-structures/9-array-iteration/article.md b/1-js/4-data-structures/9-array-iteration/article.md index 90fe2ce4..bf02ed2b 100644 --- a/1-js/4-data-structures/9-array-iteration/article.md +++ b/1-js/4-data-structures/9-array-iteration/article.md @@ -8,9 +8,9 @@ Метод ["arr.forEach(callback[, thisArg])"](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach) используется для перебора массива. -Он позволяет перебрать массив при помощи функции `callback`, что зачастую гораздо элегантнее, нежели цикл `for`. +Он позволяет для каждого элемента массива вызывает функцию `callback`. -Функция `callback` вызывается для каждого элемента с тремя параметрами `callback(item, i, arr)`: +Этой функции он передаёт три параметра `callback(item, i, arr)`: -Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- прекратит выполнение функции и возвратит `this`. +Иными словами, вызов `return` с объектом вернёт объект, а с чем угодно, кроме объекта -- возвратит, как обычно, `this`. Например, возврат объекта: @@ -97,9 +100,9 @@ function BigAnimal() { alert( new BigAnimal().name ); // Мышь, получили this (а Годзилла пропал) ``` -Эта особенность работы `new` прописана в стандарте, знать о ней полезно, но используется она весьма редко. +Эта особенность работы `new` прописана в стандарте, но используется она весьма редко. -[smart] +[smart header="Можно без скобок"] Кстати, при вызове `new` без аргументов скобки можно не ставить: ```js @@ -108,11 +111,14 @@ var animal = new BigAnimal; // <-- без скобок var animal = new BigAnimal(); ``` +Не сказать, что выбрасывание скобок -- "хороший стиль", но такой синтаксис допустим стандартом. [/smart] ## Создание методов в конструкторе -Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать. +Использование функций для создания объекта дает большую гибкость. Можно передавать конструктору параметры, определяющие как его создавать, и он будет "клепать" объекты заданным образом. + +Добавим в создаваемый объект ещё и метод. Например, `new User(name)` создает объект с заданным значением свойства `name` и методом `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 new file mode 100644 index 00000000..1719a50b --- /dev/null +++ b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/solution.md @@ -0,0 +1,49 @@ + +```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 new file mode 100644 index 00000000..7f4950aa --- /dev/null +++ b/1-js/6-objects-more/4-descriptors-getters-setters/1-replace-property-getter/task.md @@ -0,0 +1,32 @@ +# Добавить 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/8-oop/4-descriptors-getters-setters/article.md b/1-js/6-objects-more/4-descriptors-getters-setters/article.md similarity index 71% rename from 1-js/8-oop/4-descriptors-getters-setters/article.md rename to 1-js/6-objects-more/4-descriptors-getters-setters/article.md index bc7f5eb2..5cd7c99f 100644 --- a/1-js/8-oop/4-descriptors-getters-setters/article.md +++ b/1-js/6-objects-more/4-descriptors-getters-setters/article.md @@ -2,9 +2,7 @@ В этой главе мы рассмотрим возможности, которые позволяют очень гибко и мощно управлять всеми свойствами объекта, включая их аспекты -- изменяемость, видимость в цикле `for..in` и даже "невидимые" геттеры-сеттеры. -Они поддерживаются всеми современными браузерами, но не IE8-. Точнее говоря, они поддерживаются даже в IE8, но не для всех объектов, а только для DOM-объектов (они используются при работе со страницей, это сейчас вне нашего рассмотрения). - -Большая часть этих методов, в частности, работа с дескрипторами, не задействуется в других главах учебника для обеспечения совместимости с IE8-, но во вспомогательных скриптах -- библиотеках для тестирования, сборки, а также для сервера Node.JS они используются достаточно активно. +Они поддерживаются всеми современными браузерами, но не IE8-. Точнее говоря, они поддерживаются даже в IE8, но не для всех объектов, а только для DOM-объектов (используются при работе со страницей, это сейчас вне нашего рассмотрения). [cut] ## Дескрипторы в примерах @@ -28,28 +26,20 @@ Object.defineProperty(obj, prop, descriptor)
    `descriptor`
    Дескриптор -- объект, который описывает поведение свойства. В нём могут быть следующие поля: -
    -
    `value`
    -
    Значение свойства, по умолчанию `undefined`
    -
    `writable`
    -
    Значение свойства можно менять, если `true`. По умолчанию `false`.
    -
    `configurable`
    -
    Если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи `defineProperty`. По умолчанию `false`.
    -
    `enumerable`
    -
    Если `true`, то свойство будет участвовать в переборе `for..in`. По умолчанию `false`.
    -
    `get`
    -
    Функция, которая возвращает значение свойства. По умолчанию `undefined`.
    -
    `set`
    -
    Функция, которая записывает значение свойства. По умолчанию `undefined`.
    -
    -
    - + Чтобы избежать конфликта, запрещено одновременно указывать значение `value` и функции `get/set`. Либо значение, либо функции для его чтения-записи, одно из двух. Также запрещено и не имеет смысла указывать `writable` при наличии `get/set`-функций. Далее мы подробно разберём эти свойства на примерах. -### Пример: обычное свойство +## Обычное свойство Обычное свойство добавить очень просто. @@ -65,7 +55,7 @@ user.name = "Вася"; Object.defineProperty(user, "name", { value: "Вася" }); ``` -### Пример: свойство-константа +## Свойство-константа Для того, чтобы сделать свойство неизменяемым, добавим ему флаги `writable` и `configurable`: @@ -91,11 +81,9 @@ user.name = "Петя"; */!* ``` -**Заметим, что ошибки при попытке изменения такого свойства произойдут только при `use strict`.** +Заметим, что без `use strict` операция записи "молча" не сработает, а при `use strict` дополнительно генерируется ошибка. -Без `use strict` операция записи "молча" не сработает. - -### Пример: свойство, скрытое для for..in +## Свойство, скрытое для for..in Встроенный метод `toString`, как и большинство встроенных методов, не участвует в цикле `for..in`. Это удобно, так как обычно такое свойство является "служебным". @@ -113,7 +101,9 @@ for(var key in user) alert(key); // name, toString */!* ``` -`Object.defineProperty` может помочь исключить `toString` из списка итерации. Достаточно поставить ему флаг `enumerable: false`: +Мы бы хотели, чтобы поведение нашего метода `toString` было таким же, как и стандартного. + +`Object.defineProperty` может исключить `toString` из списка итерации, поставив ему флаг `enumerable: false`. По стандарту, у встроенного `toString` этот флаг уже стоит. ```js //+ run @@ -123,6 +113,7 @@ var user = { }; *!* +// помечаем toString как не подлежащий перебору в for..in Object.defineProperty(user, "toString", {enumerable: false}); for(var key in user) alert(key); // name @@ -131,7 +122,7 @@ for(var key in user) alert(key); // name Обратим внимание, вызов `defineProperty` не перезаписал свойство, а просто модифицировал настройки у существующего `toString`. -### Пример: свойство как функция-геттер +## Свойство-функция Дескриптор позволяет задать свойство, которое на самом деле работает как функция. Для этого в нём нужно указать эту функцию в `get`. @@ -157,11 +148,7 @@ alert(user.fullName); // Вася Петров */!* ``` -**Обратим внимание, снаружи это обычное свойство `user.fullName`.** - -Лишь в описании указывается, что на самом деле его значение возвращается функцией. - -### Пример: свойство геттер-сеттер +Обратим внимание, снаружи `fullName` -- это обычное свойство `user.fullName`. Но дескриптор указывает, что на самом деле его значение возвращается функцией. Также можно указать функцию, которая используется для записи значения, при помощи дескриптора `set`. @@ -196,9 +183,9 @@ alert(user.firstName); // Петя alert(user.surname); // Иванов ``` -## Геттеры и сеттеры в литералах +## Указание get/set в литералах -Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать геттеры/сеттеры можно прямо в его определении. +Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать свойства-функции можно прямо в его определении. Для этого используется особый синтаксис: `get свойство` или `set свойство`. @@ -234,13 +221,13 @@ alert(user.surname); // Иванов (поставил сеттер) */!* ``` -## Да здравствуют геттеры и сеттеры! +## Да здравствуют get/set! -Казалось бы, зачем нам назначать геттеры и сеттеры через всякие хитрые вызовы? Можно же сделать функции `getFullName`, `setFullName`... +Казалось бы, зачем нам назначать get/set для свойства через всякие хитрые вызовы, когда можно сделать просто функции с самого начала? Например, `getFullName`, `setFullName`... -**Основной бонус -- возможность получить контроль над свойством в любой момент!** +Конечно, в ряде случаев свойства выглядят короче, такое решение просто может быть красивым. Но основной бонус -- это гибкость, возможность получить контроль над свойством в любой момент! -В начале разработки мы можем использовать обычные свойства, например у `User` будет имя `name` и возраст `age`: +Например, в начале разработки мы используем обычные свойства, например у `User` будет имя `name` и возраст `age`: ```js function User(name, age) { @@ -253,11 +240,11 @@ var pete = new User("Петя", 25); alert(pete.age); // 25 ``` -**С обычными свойствами в коде меньше букв, они удобны.** +С обычными свойствами в коде меньше букв, они удобны, причины использовать функции пока нет. -...Но рано или поздно может наступить расплата! +...Но рано или поздно может произойти что-то, что потребует более сложной логики. -Например, когда написано много кода, который использует эти свойства, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`: +Например, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`: ```js function User(name, birthday) { @@ -272,9 +259,7 @@ var pete = new User("Петя", new Date(1987, 6, 1)); Можно, конечно, найти все места и поправить их, но это долго, а иногда и невозможно, скажем, если вы взаимодействуете со сторонней библиотекой, код в которой -- чужой и влезать в него нежелательно. -Геттеры позволяют обойти проблему легко и непринуждённо. - -Просто добавляем геттер `age`: +Добавление `get`-функции `age` позволяет обойти проблему легко и непринуждённо: ```js //+ run @@ -297,7 +282,7 @@ var pete = new User("Петя", new Date(1987, 6, 1)); alert(pete.age); // получает возраст из даты рождения ``` -**Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя совместимость внешнего интерфейса.** +Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя полную совместимость. ## Другие методы работы со свойствами @@ -338,7 +323,9 @@ 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` -- все: +`Object.keys` возвращает только `enumerable`-свойства. + +`Object.getOwnPropertyNames` -- возвращает все: ```js //+ run diff --git a/1-js/6-objects-more/3-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 similarity index 100% rename from 1-js/6-objects-more/3-static-properties-and-methods/1-objects-counter/_js.view/solution.js rename to 1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/solution.js diff --git a/1-js/6-objects-more/3-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 similarity index 100% rename from 1-js/6-objects-more/3-static-properties-and-methods/1-objects-counter/_js.view/test.js rename to 1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/_js.view/test.js diff --git a/1-js/6-objects-more/3-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 similarity index 100% rename from 1-js/6-objects-more/3-static-properties-and-methods/1-objects-counter/solution.md rename to 1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/solution.md diff --git a/1-js/6-objects-more/3-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 similarity index 100% rename from 1-js/6-objects-more/3-static-properties-and-methods/1-objects-counter/task.md rename to 1-js/6-objects-more/5-static-properties-and-methods/1-objects-counter/task.md diff --git a/1-js/6-objects-more/3-static-properties-and-methods/article.md b/1-js/6-objects-more/5-static-properties-and-methods/article.md similarity index 97% rename from 1-js/6-objects-more/3-static-properties-and-methods/article.md rename to 1-js/6-objects-more/5-static-properties-and-methods/article.md index decd48c6..2ee1e348 100644 --- a/1-js/6-objects-more/3-static-properties-and-methods/article.md +++ b/1-js/6-objects-more/5-static-properties-and-methods/article.md @@ -50,7 +50,7 @@ Article.showCount(); // (2) Здесь `Article.count` -- статическое свойство, а `Article.showCount` -- статический метод. -**Обратите внимание на контекст `this`. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`!** +Обратим внимание на использование `this` в примере выше. Несмотря на то, что переменная и метод -- статические, он всё ещё полезен. В строке `(1)` он равен `Article`. ## Пример: сравнение объектов diff --git a/1-js/6-objects-more/4-call-apply/1-rewrite-sum-arguments/solution.md b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md similarity index 100% rename from 1-js/6-objects-more/4-call-apply/1-rewrite-sum-arguments/solution.md rename to 1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/solution.md diff --git a/1-js/6-objects-more/4-call-apply/1-rewrite-sum-arguments/task.md b/1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md similarity index 100% rename from 1-js/6-objects-more/4-call-apply/1-rewrite-sum-arguments/task.md rename to 1-js/6-objects-more/6-call-apply/1-rewrite-sum-arguments/task.md diff --git a/1-js/6-objects-more/4-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 similarity index 100% rename from 1-js/6-objects-more/4-call-apply/2-apply-function-skip-first-argument/_js.view/solution.js rename to 1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/solution.js diff --git a/1-js/6-objects-more/4-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 similarity index 100% rename from 1-js/6-objects-more/4-call-apply/2-apply-function-skip-first-argument/_js.view/test.js rename to 1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/_js.view/test.js diff --git a/1-js/6-objects-more/4-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 similarity index 100% rename from 1-js/6-objects-more/4-call-apply/2-apply-function-skip-first-argument/solution.md rename to 1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/solution.md diff --git a/1-js/6-objects-more/4-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 similarity index 100% rename from 1-js/6-objects-more/4-call-apply/2-apply-function-skip-first-argument/task.md rename to 1-js/6-objects-more/6-call-apply/2-apply-function-skip-first-argument/task.md diff --git a/1-js/6-objects-more/4-call-apply/article.md b/1-js/6-objects-more/6-call-apply/article.md similarity index 82% rename from 1-js/6-objects-more/4-call-apply/article.md rename to 1-js/6-objects-more/6-call-apply/article.md index 8308ec8d..2af573cb 100644 --- a/1-js/6-objects-more/4-call-apply/article.md +++ b/1-js/6-objects-more/6-call-apply/article.md @@ -1,6 +1,6 @@ # Явное указание this: "call", "apply" -Итак, мы знаем, что в `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`. +Итак, мы знаем, что `this` -- это текущий объект при вызове "через точку" и новый объект при конструировании через `new`. В этой главе наша цель получить окончательное и полное понимание `this` в JavaScript. Для этого не хватает всего одного элемента: способа явно указать `this` при помощи методов `call` и `apply`. @@ -26,7 +26,7 @@ function showFullName() { } ``` -**Обратите внимание, JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска.** +Пока объекта нет, но это нормально, ведь JavaScript позволяет использовать `this` везде. Любая функция может в своём коде упомянуть `this`, каким будет это значение -- выяснится в момент запуска. Вызов `showFullName.call(user)` запустит функцию, установив `this = user`, вот так: @@ -138,13 +138,13 @@ alert( obj.join(';') ); // "A;Б;В" [/smart] -**...Однако, копирование метода из одного объекта в другой не всегда приемлемо!** +...Однако, копирование метода из одного объекта в другой не всегда приемлемо! Представим на минуту, что вместо `arguments` у нас -- произвольный объект. У него тоже есть числовые индексы, `length` и мы хотим вызвать в его контексте метод `[].join`. То есть, ситуация похожа на `arguments`, но (!) вполне возможно, что у объекта есть *свой* метод `join`. Поэтому копировать `[].join`, как сделано выше, нельзя: если он перезапишет собственный `join` объекта, то будет страшный бардак и путаница. -**Безопасно вызвать метод нам поможет `call`:** +Безопасно вызвать метод нам поможет `call`: ```js //+ run @@ -208,7 +208,16 @@ showFullName.call(user, 'firstName', 'surname'); showFullName.apply(user, ['firstName', 'surname']); ``` -Преимущество `apply` перед `call` отчётливо видно в следующем примере, когда мы формируем массив аргументов динамически: +Преимущество `apply` перед `call` отчётливо видно, когда мы формируем массив аргументов динамически. + +Например, в JavaScript есть встроенная функция `Math.max(a, b, c...)`, которая возвращает максимальное значение из аргументов: + +```js +//+ run +alert( Math.max(1, 5, 2) ); // 5 +``` + +При помощи `apply` мы могли бы найти максимум в произвольном массиве, вот так: ```js //+ run @@ -221,57 +230,46 @@ arr.push(2); alert( Math.max.apply(null, arr) ); // 5 ``` -Обратим внимание, в примере выше вызывается метод `Math.max`. Его стандартное применение -- это выбор максимального аргумента из переданных, которых может быть сколько угодно: - -```js -//+ run -alert( Math.max(1, 5, 2) ); // 5 -alert( Math.max(1, 5, 2, 8) ); // 8 -``` - В примере выше мы передали аргументы через массив -- второй параметр `apply`... Но вы, наверное, заметили небольшую странность? В качестве контекста `this` был передан `null`. Строго говоря, полным эквивалентом вызову `Math.max(1,2,3)` был бы вызов `Math.max.apply(Math, [1,2,3])`. В обоих этих вызовах контекстом будет объект `Math`. -Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max`не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? - -**Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве!** +Но в данном случае в качестве контекста можно передавать что угодно, поскольку в своей внутренней реализации метод `Math.max` не использует `this`. Действительно, зачем `this`, если нужно всего лишь выбрать максимальный из аргументов? Вот так, при помощи `apply` мы получили короткий и элегантный способ вычислить максимальное значение в массиве! [smart header="Вызов `call/apply` с `null` или `undefined`"] -В старом стандарте при указании первого аргумента `null` или `undefined` в `call/apply`, функция получала `this = window`, например: - -```js -//+ run -function f() { - alert(this); -} - -f.call(null); // window -``` - -Это поведение исправлено в современном стандарте ([15.3](http://es5.github.com/x15.3.html#x15.3.4.3)). - -Если функция работает в строгом режиме, то `this` передаётся "как есть": +В современном стандарте `call/apply` передают `this` "как есть". А в старом, без `use strict`, при указании первого аргумента `null` или `undefined` в `call/apply`, функция получает `this = window`, например: +Современный стандарт: ```js //+ run function f() { "use strict"; *!* - alert(this); // null, а не window + alert(this); // null */!* } f.call(null); ``` +Без `use strict`: + +```js +//+ run +function f() { + alert(this); // window +} + +f.call(null); +``` + [/smart] ## Итого про this - Значение `this` устанавливается в зависимости от того, как вызвана функция: +
    При вызове функции как метода
    diff --git a/1-js/6-objects-more/5-bind/1-cross-browser-bind/solution.md b/1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md similarity index 100% rename from 1-js/6-objects-more/5-bind/1-cross-browser-bind/solution.md rename to 1-js/6-objects-more/7-bind/1-cross-browser-bind/solution.md diff --git a/1-js/6-objects-more/5-bind/1-cross-browser-bind/task.md b/1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md similarity index 100% rename from 1-js/6-objects-more/5-bind/1-cross-browser-bind/task.md rename to 1-js/6-objects-more/7-bind/1-cross-browser-bind/task.md diff --git a/1-js/6-objects-more/5-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 similarity index 100% rename from 1-js/6-objects-more/5-bind/2-write-to-object-after-bind/solution.md rename to 1-js/6-objects-more/7-bind/2-write-to-object-after-bind/solution.md diff --git a/1-js/6-objects-more/5-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 similarity index 100% rename from 1-js/6-objects-more/5-bind/2-write-to-object-after-bind/task.md rename to 1-js/6-objects-more/7-bind/2-write-to-object-after-bind/task.md diff --git a/1-js/6-objects-more/5-bind/3-second-bind/solution.md b/1-js/6-objects-more/7-bind/3-second-bind/solution.md similarity index 100% rename from 1-js/6-objects-more/5-bind/3-second-bind/solution.md rename to 1-js/6-objects-more/7-bind/3-second-bind/solution.md diff --git a/1-js/6-objects-more/5-bind/3-second-bind/task.md b/1-js/6-objects-more/7-bind/3-second-bind/task.md similarity index 100% rename from 1-js/6-objects-more/5-bind/3-second-bind/task.md rename to 1-js/6-objects-more/7-bind/3-second-bind/task.md diff --git a/1-js/6-objects-more/5-bind/4-function-property-after-bind/solution.md b/1-js/6-objects-more/7-bind/4-function-property-after-bind/solution.md similarity index 100% rename from 1-js/6-objects-more/5-bind/4-function-property-after-bind/solution.md rename to 1-js/6-objects-more/7-bind/4-function-property-after-bind/solution.md diff --git a/1-js/6-objects-more/5-bind/4-function-property-after-bind/task.md b/1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md similarity index 100% rename from 1-js/6-objects-more/5-bind/4-function-property-after-bind/task.md rename to 1-js/6-objects-more/7-bind/4-function-property-after-bind/task.md diff --git a/1-js/6-objects-more/5-bind/5-question-use-bind/solution.md b/1-js/6-objects-more/7-bind/5-question-use-bind/solution.md similarity index 100% rename from 1-js/6-objects-more/5-bind/5-question-use-bind/solution.md rename to 1-js/6-objects-more/7-bind/5-question-use-bind/solution.md diff --git a/1-js/6-objects-more/5-bind/5-question-use-bind/task.md b/1-js/6-objects-more/7-bind/5-question-use-bind/task.md similarity index 100% rename from 1-js/6-objects-more/5-bind/5-question-use-bind/task.md rename to 1-js/6-objects-more/7-bind/5-question-use-bind/task.md diff --git a/1-js/6-objects-more/5-bind/6-ask-currying/solution.md b/1-js/6-objects-more/7-bind/6-ask-currying/solution.md similarity index 100% rename from 1-js/6-objects-more/5-bind/6-ask-currying/solution.md rename to 1-js/6-objects-more/7-bind/6-ask-currying/solution.md diff --git a/1-js/6-objects-more/5-bind/6-ask-currying/task.md b/1-js/6-objects-more/7-bind/6-ask-currying/task.md similarity index 100% rename from 1-js/6-objects-more/5-bind/6-ask-currying/task.md rename to 1-js/6-objects-more/7-bind/6-ask-currying/task.md diff --git a/1-js/6-objects-more/5-bind/article.md b/1-js/6-objects-more/7-bind/article.md similarity index 77% rename from 1-js/6-objects-more/5-bind/article.md rename to 1-js/6-objects-more/7-bind/article.md index 6864e875..c21511fa 100644 --- a/1-js/6-objects-more/5-bind/article.md +++ b/1-js/6-objects-more/7-bind/article.md @@ -39,7 +39,7 @@ setTimeout( user.sayHi, 1000); // undefined (не Вася!) */!* ``` -**При запуске кода выше через секунду выводится вовсе не `"Вася"`, а `undefined`!** +При запуске кода выше через секунду выводится вовсе не `"Вася"`, а `undefined`! Это произошло потому, что в примере выше `setTimeout` получил функцию `user.sayHi`, но не её контекст. То есть, последняя строчка аналогична двум таким: @@ -48,7 +48,8 @@ var f = user.sayHi; setTimeout(f, 1000); // контекст user потеряли ``` -**Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно?** + +Ситуация довольно типична -- мы хотим передать метод объекта куда-то в другое место кода, откуда он потом может быть вызван. Как бы прикрепить к нему контекст, желательно, с минимумом плясок с бубном и при этом надёжно? Есть несколько способов решения, среди которых мы, в зависимости от ситуации, можем выбирать. @@ -86,24 +87,23 @@ setTimeout(function() { ```js function bind(func, context) { - return function() { + return function() { // (*) return func.apply(context, arguments); }; } ``` -Параметры: -
    -
    `func`
    -
    Произвольная функция
    -
    `context`
    -
    Произвольный объект
    +Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно: -Результатом вызова `bind(func, context)` будет, как видно из кода, функция-обёртка, которая передаёт все вызовы `func`, указывая при этом правильный контекст `context`. +```js +function() { // (*) + return func.apply(context, arguments); +}; +``` -Чтобы понять, как она работает, нужно вспомнить тему "замыкания" -- здесь `bind` возвращает анонимную функцию, которая при вызове получает контекст `context` из внешней области видимости и передаёт вызов в `func` вместе с этим контекстом `context` и аргументами `arguments`. +Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`). -**В результате вызова `bind` мы получаем как бы "ту же функцию, но с фиксированным контекстом".** +Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`. Пример с `bind`: @@ -151,7 +151,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
    Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.
    -Результат вызова: `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, но это делается редко, мы поговорим о них позже. +Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже. Пример со встроенным методом `bind`: @@ -234,13 +234,12 @@ alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15 ``` -**При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё.** +При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё. -## Задачи +## Функция дла задач - -Рассмотрим для дальнейших задач "функцию для вопросов" `ask`: +В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`: ```js function ask(question, answer, ok, fail) { @@ -250,9 +249,9 @@ function ask(question, answer, ok, fail) { } ``` -Пока в этой функции ничего особого нет. Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`. +Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`. -Однако, тем не менее, эта функция взята из реального проекта. Просто обычно она сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно. +В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно. Пример использования: diff --git a/1-js/6-objects-more/6-decorators/1-logging-decorator/_js.view/solution.js b/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/solution.js similarity index 100% rename from 1-js/6-objects-more/6-decorators/1-logging-decorator/_js.view/solution.js rename to 1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/solution.js diff --git a/1-js/6-objects-more/6-decorators/1-logging-decorator/_js.view/test.js b/1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/test.js similarity index 100% rename from 1-js/6-objects-more/6-decorators/1-logging-decorator/_js.view/test.js rename to 1-js/6-objects-more/8-decorators/1-logging-decorator/_js.view/test.js diff --git a/1-js/6-objects-more/6-decorators/1-logging-decorator/solution.md b/1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md similarity index 100% rename from 1-js/6-objects-more/6-decorators/1-logging-decorator/solution.md rename to 1-js/6-objects-more/8-decorators/1-logging-decorator/solution.md diff --git a/1-js/6-objects-more/6-decorators/1-logging-decorator/task.md b/1-js/6-objects-more/8-decorators/1-logging-decorator/task.md similarity index 100% rename from 1-js/6-objects-more/6-decorators/1-logging-decorator/task.md rename to 1-js/6-objects-more/8-decorators/1-logging-decorator/task.md diff --git a/1-js/6-objects-more/6-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 similarity index 100% rename from 1-js/6-objects-more/6-decorators/2-logging-decorator-arguments/_js.view/solution.js rename to 1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/solution.js diff --git a/1-js/6-objects-more/6-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 similarity index 100% rename from 1-js/6-objects-more/6-decorators/2-logging-decorator-arguments/_js.view/test.js rename to 1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/_js.view/test.js diff --git a/1-js/6-objects-more/6-decorators/2-logging-decorator-arguments/solution.md b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md similarity index 100% rename from 1-js/6-objects-more/6-decorators/2-logging-decorator-arguments/solution.md rename to 1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/solution.md diff --git a/1-js/6-objects-more/6-decorators/2-logging-decorator-arguments/task.md b/1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md similarity index 100% rename from 1-js/6-objects-more/6-decorators/2-logging-decorator-arguments/task.md rename to 1-js/6-objects-more/8-decorators/2-logging-decorator-arguments/task.md diff --git a/1-js/6-objects-more/6-decorators/3-caching-decorator/_js.view/solution.js b/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/solution.js similarity index 100% rename from 1-js/6-objects-more/6-decorators/3-caching-decorator/_js.view/solution.js rename to 1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/solution.js diff --git a/1-js/6-objects-more/6-decorators/3-caching-decorator/_js.view/test.js b/1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/test.js similarity index 100% rename from 1-js/6-objects-more/6-decorators/3-caching-decorator/_js.view/test.js rename to 1-js/6-objects-more/8-decorators/3-caching-decorator/_js.view/test.js diff --git a/1-js/6-objects-more/6-decorators/3-caching-decorator/solution.md b/1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md similarity index 100% rename from 1-js/6-objects-more/6-decorators/3-caching-decorator/solution.md rename to 1-js/6-objects-more/8-decorators/3-caching-decorator/solution.md diff --git a/1-js/6-objects-more/6-decorators/3-caching-decorator/task.md b/1-js/6-objects-more/8-decorators/3-caching-decorator/task.md similarity index 100% rename from 1-js/6-objects-more/6-decorators/3-caching-decorator/task.md rename to 1-js/6-objects-more/8-decorators/3-caching-decorator/task.md diff --git a/1-js/6-objects-more/6-decorators/article.md b/1-js/6-objects-more/8-decorators/article.md similarity index 76% rename from 1-js/6-objects-more/6-decorators/article.md rename to 1-js/6-objects-more/8-decorators/article.md index 746ac446..7b038f03 100644 --- a/1-js/6-objects-more/6-decorators/article.md +++ b/1-js/6-objects-more/8-decorators/article.md @@ -3,15 +3,14 @@ JavaScript предоставляет удивительно гибкие возможности по работе с функциями: их можно передавать, в них можно записывать данные как в объекты, у них есть свои встроенные методы... Конечно, этим нужно уметь пользоваться. В этой главе, чтобы более глубоко понимать работу с функциями, мы рассмотрим создание функций-обёрток или, иначе говоря, "декораторов". + [cut] -## Примеры декораторов +[Декоратор](http://en.wikipedia.org/wiki/Decorator_pattern) -- приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение. -Декоратор -- приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение. +*Декоратор* получает функцию и возвращает обертку, которая делает что-то своё "вокруг" вызова основной функции. -***Декоратор* получает функцию и возвращает обертку, которая делает что-то своё "вокруг" вызова основной функции.** - -### bind -- привязка контекста +## bind -- привязка контекста Один простой декоратор вы уже видели ранее -- это функция [bind](/bind): @@ -25,11 +24,32 @@ function bind(func, context) { Вызов `bind(func, context)` возвращает обёртку, которая ставит `this` и передаёт основную работу функции `func`. -### Декоратор -- измеритель времени +## Декоратор-таймер -Посмотрим немного более сложный декоратор, замеряющий время выполнения функции: +Создадим более сложный декоратор, замеряющий время выполнения функции. -**При помощи декоратора `timingDecorator` мы можем взять произвольную функцию и одним движением руки прикрутить к ней измеритель времени:** +Он будет называться `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 @@ -40,7 +60,7 @@ function timingDecorator(f, timer) { return function() { var start = performance.now(); - var result = f.apply(this, arguments); + var result = f.apply(this, arguments); // (*) if (!timers[timer]) timers[timer] = 0; timers[timer] += performance.now() - start; @@ -70,11 +90,15 @@ alert( timers.fibo + 'мс' ); */!* ``` -Обратим внимание на ключевую строку декоратора`var result = f.apply(this, arguments)`. +Обратим внимание на строку `(*)` внутри декоратора, которая и осуществляет передачу вызова: -**Этот приём называется "форвардинг вызова" (от англ. forwarding): текущий контекст и аргументы через `apply` передаются в функцию, так что изнутри `f` всё выглядит так, как будто это была вызвана она, а не декоратор.** +```js +var result = f.apply(this, arguments); // (*) +``` -### Декоратор для проверки типа +Этот приём называется "форвардинг вызова" (от англ. forwarding): текущий контекст и аргументы через `apply` передаются в функцию `f`, так что изнутри `f` всё выглядит так, как была вызвана она напрямую, а не декоратор. + +## Декоратор для проверки типа В JavaScript, как правило, пренебрегают проверками типа. В функцию, которая должна получать число, может быть передана строка, булево значение или даже объект. @@ -85,6 +109,7 @@ function sum(a, b) { return a + b; } +// передадим в функцию для сложения чисел нечисловые значения alert( sum(true, { name: "Вася", age: 35 }) ); // true[Object object] ``` @@ -142,7 +167,7 @@ sum(1, ["array", "in", "sum?!?"]); // некорректный аргумент **Один раз пишем декоратор и дальше просто применяем этот функционал везде, где нужно.** -### Декоратор проверки доступа +## Декоратор проверки доступа И наконец посмотрим ещё один, последний пример. diff --git a/1-js/6-objects-more/index.md b/1-js/6-objects-more/index.md index e021da19..94f1be86 100644 --- a/1-js/6-objects-more/index.md +++ b/1-js/6-objects-more/index.md @@ -1,3 +1,3 @@ # Методы объектов и контекст вызова -Начинаем изучать объектно-ориентированную разработку -- как работают объекты и функции, что такое контекст вызова и почему его значение нельзя предсказать. Как, всё же, гарантировать правильный контекст и многие другие, не самые простые, темы. \ No newline at end of file +Начинаем изучать объектно-ориентированную разработку -- как работают объекты и функции, что такое контекст вызова и способы его передачи. \ No newline at end of file diff --git a/1-js/7-js-misc/2-class-property/article.md b/1-js/7-js-misc/1-class-property/article.md similarity index 91% rename from 1-js/7-js-misc/2-class-property/article.md rename to 1-js/7-js-misc/1-class-property/article.md index fca95e60..53a4b3b5 100644 --- a/1-js/7-js-misc/2-class-property/article.md +++ b/1-js/7-js-misc/1-class-property/article.md @@ -2,7 +2,7 @@ Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`. -**Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе.** +Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе. Оно взято в квадратные скобки, так как это свойство -- внутреннее. Явно получить его нельзя, но можно прочитать его "в обход", воспользовавшись методом `toString` из `Object`. @@ -100,4 +100,4 @@ alert( {}.toString.call("строка") ); // [object String]
  • Для доступа к `[[Class]]` используется `{}.toString.call(obj).slice(8, -1)`.
  • -Обычно в JavaScript используется утиная типизация. Свойство `[[Class]]` -- самое надёжное средство проверки типа встроенных объектов, но обычно утиной типизации вполне хватает. \ No newline at end of file +Обычно в JavaScript используется "утиная" типизация. Свойство `[[Class]]` -- самое надёжное средство проверки типа встроенных объектов, но обычно утиной типизации вполне хватает. \ No newline at end of file diff --git a/1-js/7-js-misc/3-json/1-serialize-object/solution.md b/1-js/7-js-misc/2-json/1-serialize-object/solution.md similarity index 100% rename from 1-js/7-js-misc/3-json/1-serialize-object/solution.md rename to 1-js/7-js-misc/2-json/1-serialize-object/solution.md diff --git a/1-js/7-js-misc/3-json/1-serialize-object/task.md b/1-js/7-js-misc/2-json/1-serialize-object/task.md similarity index 100% rename from 1-js/7-js-misc/3-json/1-serialize-object/task.md rename to 1-js/7-js-misc/2-json/1-serialize-object/task.md diff --git a/1-js/7-js-misc/3-json/2-serialize-object-circular/solution.md b/1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md similarity index 100% rename from 1-js/7-js-misc/3-json/2-serialize-object-circular/solution.md rename to 1-js/7-js-misc/2-json/2-serialize-object-circular/solution.md diff --git a/1-js/7-js-misc/3-json/2-serialize-object-circular/task.md b/1-js/7-js-misc/2-json/2-serialize-object-circular/task.md similarity index 100% rename from 1-js/7-js-misc/3-json/2-serialize-object-circular/task.md rename to 1-js/7-js-misc/2-json/2-serialize-object-circular/task.md diff --git a/1-js/7-js-misc/3-json/article.md b/1-js/7-js-misc/2-json/article.md similarity index 93% rename from 1-js/7-js-misc/3-json/article.md rename to 1-js/7-js-misc/2-json/article.md index 6fe91086..d52a8f1e 100644 --- a/1-js/7-js-misc/3-json/article.md +++ b/1-js/7-js-misc/2-json/article.md @@ -32,8 +32,6 @@
  • `JSON.stringify` -- превращает объекты в строку в формате JSON, используется, когда нужно из JavaScript передать данные по сети.
  • -Далее мы разберём их подробнее. - ## Метод JSON.parse Вызов `JSON.parse(str)` превратит строку с данными в формате JSON в JavaScript-объект/массив/значение. @@ -63,7 +61,7 @@ alert( user.friends[1] ); // 1 Данные могут быть сколь угодно сложными, объекты и массивы могут включать в себя другие объекты и массивы. Главное чтобы они соответствовали формату. [warn header="JSON-объекты ≠ JavaScript-объекты"] -**Объекты в формате JSON похожи на обычные JavaScript-объекты, но отличаются от них более строгими требованиями к строкам -- они должны быть именно в двойных кавычках.** +Объекты в формате JSON похожи на обычные JavaScript-объекты, но отличаются от них более строгими требованиями к строкам -- они должны быть именно в двойных кавычках. В частности, первые два свойства объекта ниже -- некорректны: @@ -77,6 +75,8 @@ alert( user.friends[1] ); // 1 ``` Кроме того, в формате JSON не поддерживаются комментарии. Он предназначен только для передачи данных. + +Есть нестандартное расширение формата JSON, которое называется [JSON5](http://json5.org/) и как раз разрешает ключи без кавычек, комментарии и т.п, как в обычном JavaScript. На данном этапе, это отдельная библиотека. [/warn] ## Умный разбор: JSON.parse(str, reviver) @@ -111,7 +111,7 @@ alert( event.date.getDate() ); // ошибка! Дело в том, что значением `event.date` является строка, а отнюдь не объект `Date`. Откуда методу `JSON.parse` знать, что нужно превратить строку именно в дату? -**Для интеллектуального восстановления из строки у `JSON.parse(str, reviver)` есть второй параметр `reviver`, который является функцией `function (key, value)`.** +**Для интеллектуального восстановления из строки у `JSON.parse(str, reviver)` есть второй параметр `reviver`, который является функцией `function(key, value)`.** Если она указана, то в процессе чтения объекта из строки `JSON.parse` передаёт ей по очереди все создаваемые пары ключ-значение и может возвратить либо преобразованное значение, либо `undefined`, если его нужно пропустить. @@ -155,9 +155,7 @@ alert( schedule.events[1].date.getDate() ); // сработает! ## Сериализация, метод JSON.stringify -Метод `JSON.stringify(value, replacer, space)` преобразует (*"сериализует"*) значение в JSON-строку. - -Он поддерживается во всех браузерах, включая IE8+. Для более IE7- рекомендуется библиотека [JSON-js](https://github.com/douglascrockford/JSON-js), которая добавляет аналогичную функциональность. +Метод `JSON.stringify(value, replacer, space)` преобразует ("сериализует") значение в JSON-строку. Пример использования: @@ -292,9 +290,9 @@ alert(str); // {"name":"Вася","age":25} В примере выше функция пропустит свойство с названием `window`. Для остальных она просто возвращает значение, передавая его стандартному алгоритму. А могла бы и как-то обработать. -**Функция `replacer` работает рекурсивно.** - +[smart header="Функция `replacer` работает рекурсивно"] То есть, если объект содержит вложенные объекты, массивы и т.п., то все они пройдут через `replacer`. +[/smart] ### Красивое форматирование diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/1-output-numbers-100ms/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/1-output-numbers-100ms/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/1-output-numbers-100ms/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/1-output-numbers-100ms/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/1-output-numbers-100ms/task.md diff --git a/1-js/7-js-misc/4-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 similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/2-output-numbers-100ms-settimeout/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/2-output-numbers-100ms-settimeout/solution.md diff --git a/1-js/7-js-misc/4-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 similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/2-output-numbers-100ms-settimeout/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/2-output-numbers-100ms-settimeout/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/3-highlight-tactics/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/3-highlight-tactics/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/3-highlight-tactics/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/3-highlight-tactics/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/3-highlight-tactics/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/3-highlight-tactics/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/3-highlight-tactics/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/3-highlight-tactics/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/4-settimeout-result/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/4-settimeout-result/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/4-settimeout-result/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/4-settimeout-result/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/4-settimeout-result/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/5-setinterval-result/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/5-setinterval-result/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/5-setinterval-result/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/5-setinterval-result/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/5-setinterval-result/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/5-setinterval-result/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/5-setinterval-result/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/5-setinterval-result/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/6-who-runs-faster/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/6-who-runs-faster/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/6-who-runs-faster/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/6-who-runs-faster/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/6-who-runs-faster/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/6-who-runs-faster/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/6-who-runs-faster/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/6-who-runs-faster/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/7-delay/_js.view/solution.js b/1-js/7-js-misc/3-setTimeout-setInterval/7-delay/_js.view/solution.js similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/7-delay/_js.view/solution.js rename to 1-js/7-js-misc/3-setTimeout-setInterval/7-delay/_js.view/solution.js diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/7-delay/_js.view/test.js b/1-js/7-js-misc/3-setTimeout-setInterval/7-delay/_js.view/test.js similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/7-delay/_js.view/test.js rename to 1-js/7-js-misc/3-setTimeout-setInterval/7-delay/_js.view/test.js diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/7-delay/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/7-delay/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/7-delay/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/7-delay/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/7-delay/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/7-delay/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/7-delay/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/7-delay/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/_js.view/solution.js b/1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/_js.view/solution.js similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/_js.view/solution.js rename to 1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/_js.view/solution.js diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/_js.view/test.js b/1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/_js.view/test.js similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/_js.view/test.js rename to 1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/_js.view/test.js diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/8-debounce/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/8-debounce/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/_js.view/solution.js b/1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/_js.view/solution.js similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/_js.view/solution.js rename to 1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/_js.view/solution.js diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/_js.view/test.js b/1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/_js.view/test.js similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/_js.view/test.js rename to 1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/_js.view/test.js diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/solution.md b/1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/solution.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/solution.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/solution.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/task.md b/1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/task.md similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/9-throttle/task.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/9-throttle/task.md diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/article.md b/1-js/7-js-misc/3-setTimeout-setInterval/article.md similarity index 94% rename from 1-js/7-js-misc/4-setTimeout-setInterval/article.md rename to 1-js/7-js-misc/3-setTimeout-setInterval/article.md index bec1b1a0..5ac95ffe 100644 --- a/1-js/7-js-misc/4-setTimeout-setInterval/article.md +++ b/1-js/7-js-misc/3-setTimeout-setInterval/article.md @@ -53,7 +53,7 @@ setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася */!* ``` -**Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.** +Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки. То есть такая запись тоже сработает: @@ -62,7 +62,7 @@ setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася setTimeout("alert('Привет')", 1000); ``` -**Однако, использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.** +Однако, использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости. Вместо них используйте анонимные функции, вот так: @@ -184,7 +184,7 @@ setTimeout(function run() { При `setInterval` внутренний таймер будет срабатывать чётко каждые `100` мс и вызывать `func(i)`: - + Вы обратили внимание?... @@ -202,7 +202,7 @@ setTimeout(function run() { А так будет выглядить картинка с рекурсивным `setTimeout`: - + **При рекурсивном `setTimeout` задержка всегда фиксирована и равна 100мс.** @@ -223,7 +223,7 @@ setTimeout(function() {}, 100);
  • Для `setInterval` -- ссылка исчезнет при очистке таймера.
  • -**Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые `setInterval` могут приводить к излишним тратам памяти.** +Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые `setInterval` могут приводить к излишним тратам памяти. [/smart] @@ -259,8 +259,10 @@ setTimeout(function() {}, 100); **Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.** +[online] Посмотрим снижение частоты в действии на небольшом примере. + При клике на кнопку ниже запускается `setInterval(..., 90)`, который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь.
    @@ -287,7 +289,7 @@ function timerIntervalLog() { Если ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные красным. Кроме того, вы заметите, что таймер не является идеально точным ;) - +[/online] ## Разбивка долгих скриптов @@ -314,9 +316,3 @@ function timerIntervalLog() { -## Полезные декораторы - -Эти декораторы часто востребованы в реальных проектах. Постарайтесь сделать их без подсказок. - - - diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/setInterval-anim.view/index.html b/1-js/7-js-misc/3-setTimeout-setInterval/setInterval-anim.view/index.html similarity index 100% rename from 1-js/7-js-misc/4-setTimeout-setInterval/setInterval-anim.view/index.html rename to 1-js/7-js-misc/3-setTimeout-setInterval/setInterval-anim.view/index.html diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval.svg b/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval.svg new file mode 100644 index 00000000..5c53444b --- /dev/null +++ b/1-js/7-js-misc/3-setTimeout-setInterval/setinterval-interval.svg @@ -0,0 +1,51 @@ + + + + setinterval-interval + Created with Sketch. + + + + + + + func(1) + + + + + + + + + + func(2) + + + + + + + + + + func(3) + + + + + + + + 100 + + + 200 + + + 300 + + + + + \ No newline at end of file diff --git a/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval.svg b/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval.svg new file mode 100644 index 00000000..1c9d78cc --- /dev/null +++ b/1-js/7-js-misc/3-setTimeout-setInterval/settimeout-interval.svg @@ -0,0 +1,62 @@ + + + + settimeout-interval + Created with Sketch. + + + + + + + func(1) + + + + + + + + + + func(2) + + + + + + + + + + func(3) + + + + + + + + 100 + + + 100 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/1-js/7-js-misc/5-eval/1-eval-calculator/solution.md b/1-js/7-js-misc/4-eval/1-eval-calculator/solution.md similarity index 100% rename from 1-js/7-js-misc/5-eval/1-eval-calculator/solution.md rename to 1-js/7-js-misc/4-eval/1-eval-calculator/solution.md diff --git a/1-js/7-js-misc/5-eval/1-eval-calculator/task.md b/1-js/7-js-misc/4-eval/1-eval-calculator/task.md similarity index 100% rename from 1-js/7-js-misc/5-eval/1-eval-calculator/task.md rename to 1-js/7-js-misc/4-eval/1-eval-calculator/task.md diff --git a/1-js/7-js-misc/5-eval/article.md b/1-js/7-js-misc/4-eval/article.md similarity index 81% rename from 1-js/7-js-misc/5-eval/article.md rename to 1-js/7-js-misc/4-eval/article.md index d2c961fa..26f6ee40 100644 --- a/1-js/7-js-misc/5-eval/article.md +++ b/1-js/7-js-misc/4-eval/article.md @@ -65,15 +65,15 @@ alert(a); // ошибка, переменная не определена Иными словами, в новом стандарте `eval` имеет свою область видимости, а к внешним переменным обращается через замыкание, аналогично тому, как работают обычные функции. [/smart] -## Грамотное использование eval +## Неграмотное использование eval Начнём с того, что `eval` применяется очень редко. Действительно редко. Есть даже такое выражение "eval is evil" (eval -- зло). -Причина проста: когда-то JavaScript был гораздо более слабым языком, чем сейчас и некоторые вещи без `eval` было сделать невозможно. Но те времена давно прошли. И теперь найти тот случай, когда действительно надо выполнить код из строки -- это надо постараться. +Причина проста: когда-то JavaScript был гораздо более слабым языком, чем сейчас, и некоторые вещи без `eval` было сделать невозможно. Но те времена давно прошли. И теперь найти тот случай, когда действительно надо выполнить код из строки -- это надо постараться. Но если вы действительно знаете, что это именно тот случай и вам необходим `eval` -- есть ряд вещей, которые нужно иметь в виду. -**Доступ к локальным переменным -- самое страшное зло `eval`.** +Доступ к локальным переменным -- худшее, что можно сделать при `eval`. Дело в том, что локальные переменные могут быть легко переименованы: @@ -86,9 +86,7 @@ function sayHi() { Переменная `phrase` может быть переименована в `hello`, и если строка `str` обращается к ней -- будет ошибка. -**Современные средства сжатия JavaScript переименовывают локальные переменные автоматически.** - -Это считается безопасным, так как локальная переменная видна лишь внутри функции и если в ней везде поменять `phrase` на `p`, то никто этого не заметит. +Современные средства сжатия JavaScript переименовывают локальные переменные автоматически. Это считается безопасным, так как локальная переменная видна лишь внутри функции и если в ней везде поменять `phrase` на `p`, то никто этого не заметит. До сжатия: @@ -116,17 +114,19 @@ function sayHi() { } ``` -**Теперь очевидно, что если где-то в функции есть `eval`, то любое его взаимодействие с локальными переменными будет нарушено с непредсказуемыми побочными эффектами.** +Итак, если где-то в функции есть `eval`, то его взаимодействие с локальными переменными будет нарушено с непредсказуемыми побочными эффектами. Некоторые инструменты сжатия предупреждают, когда видят `eval` или стараются вообще не сжимать такой код вместе с его внешними функциями, но всё это борьба с последствиями кривого кода. -### Запуск скрипта в глобальной области +Как правило, `eval` не нужен, именно поэтому говорят, "eval is evil". + +## Запуск скрипта в глобальной области Ок, взаимодействовать с локальными переменными нельзя. -Но допустим мы загрузили с сервера или вручную сгенерировали скрипт, который нужно выполнить в глобальной области, вне любых функций. +Но допустим мы загрузили с сервера или вручную сгенерировали скрипт, который нужно выполнить. Желательно, в глобальной области, вне любых функций, чтобы он уж точно к локальным переменным отношения не имел. -**Есть два трюка для выполнения кода в глобальной области.** +Здесь `eval` может пригодиться. Есть два трюка для выполнения кода в глобальной области:
    1. Везде, кроме IE8-, достаточно вызвать `eval` не напрямую, а через `window.eval`. @@ -171,11 +171,11 @@ var a = 1; })(); ``` -### Взаимодействие с внешним кодом, new Function +## Внешние данные через new Function -Бывает, что в код, выполняемый при помощи `eval`, всё же нужно передать какие-то значения. +Итак, у нас есть код, который, всё же, нужно выполнить динамически, через `eval`, но не просто скрипт -- а ему нужно передать какие-то значения. - Считать их из локальных переменных нельзя: как мы видели, это подвержено ошибкам при переименовании переменных и сразу ломается при сжатии JavaScript. +Как мы говорили ранее, считать их из локальных переменных нельзя: это подвержено ошибкам при переименовании переменных и сразу ломается при сжатии JavaScript. Да и вообще, неочевидно и криво. **К счастью, существует отличная альтернатива `eval`, которая позволяет корректно взаимодействовать c внешним кодом: `new Function`.** diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/interval1.png b/1-js/7-js-misc/4-setTimeout-setInterval/interval1.png deleted file mode 100755 index 2c60a630..00000000 Binary files a/1-js/7-js-misc/4-setTimeout-setInterval/interval1.png and /dev/null differ diff --git a/1-js/7-js-misc/4-setTimeout-setInterval/timeout.png b/1-js/7-js-misc/4-setTimeout-setInterval/timeout.png deleted file mode 100755 index 2ab19038..00000000 Binary files a/1-js/7-js-misc/4-setTimeout-setInterval/timeout.png and /dev/null differ diff --git a/1-js/7-js-misc/6-exception/1-finally-ili-prosto-kod/solution.md b/1-js/7-js-misc/5-exception/1-finally-ili-prosto-kod/solution.md similarity index 100% rename from 1-js/7-js-misc/6-exception/1-finally-ili-prosto-kod/solution.md rename to 1-js/7-js-misc/5-exception/1-finally-ili-prosto-kod/solution.md diff --git a/1-js/7-js-misc/6-exception/1-finally-ili-prosto-kod/task.md b/1-js/7-js-misc/5-exception/1-finally-ili-prosto-kod/task.md similarity index 100% rename from 1-js/7-js-misc/6-exception/1-finally-ili-prosto-kod/task.md rename to 1-js/7-js-misc/5-exception/1-finally-ili-prosto-kod/task.md diff --git a/1-js/7-js-misc/6-exception/2-eval-calculator-errors/solution.md b/1-js/7-js-misc/5-exception/2-eval-calculator-errors/solution.md similarity index 100% rename from 1-js/7-js-misc/6-exception/2-eval-calculator-errors/solution.md rename to 1-js/7-js-misc/5-exception/2-eval-calculator-errors/solution.md diff --git a/1-js/7-js-misc/6-exception/2-eval-calculator-errors/task.md b/1-js/7-js-misc/5-exception/2-eval-calculator-errors/task.md similarity index 100% rename from 1-js/7-js-misc/6-exception/2-eval-calculator-errors/task.md rename to 1-js/7-js-misc/5-exception/2-eval-calculator-errors/task.md diff --git a/1-js/7-js-misc/6-exception/article.md b/1-js/7-js-misc/5-exception/article.md similarity index 71% rename from 1-js/7-js-misc/6-exception/article.md rename to 1-js/7-js-misc/5-exception/article.md index 0b44a210..6736167a 100644 --- a/1-js/7-js-misc/6-exception/article.md +++ b/1-js/7-js-misc/5-exception/article.md @@ -1,6 +1,8 @@ # Перехват ошибок, "try..catch" -Как бы мы хорошо ни программировали, в коде бывают ошибки. Обычно скрипт при ошибке, как говорят, "падает", с выводом ошибки в консоль. +Как бы мы хорошо ни программировали, в коде бывают ошибки. Или, как их иначе называют, "исключительные ситуации" (исключения). + +Обычно скрипт при ошибке, как говорят, "падает", с выводом ошибки в консоль. Но бывают случаи, когда нам хотелось бы как-то контролировать ситуацию, чтобы скрипт не просто "упал", а сделал что-то разумное. @@ -33,7 +35,7 @@ try { При этом переменная `err` (можно выбрать и другое название) будет содержать объект ошибки с подробной информацией о произошедшем.
    -**Таким образом, при ошибке в `try` скрипт не падает, а продолжает выполнение, и мы получаем возможность обработать ошибку внутри `catch`.** +**Таким образом, при ошибке в `try` скрипт не "падает", и мы получаем возможность обработать ошибку внутри `catch`.** Посмотрим это на примерах. @@ -87,14 +89,33 @@ alert("Потом код продолжит выполнение..."); [warn header="`try..catch` подразумевает, что код синтаксически верен"] -Если грубо нарушена структура кода, например не закрыта фигурная скобка или где-то стоит лишняя запятая, то никакой `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] + + +## Объект ошибки В примере выше мы видим объект ошибки. У него есть три основных свойства:
    @@ -195,7 +216,9 @@ try { **В качестве конструктора ошибок можно использовать встроенный конструктор: `new Error(message)` или любой другой.** -В данном случае мы используем конструктор `new SyntaxError(message)`, он создаст ошибку того же типа, что и `JSON.parse`. +В JavaScript встроен ряд конструкторов для стандартных ошибок: `SyntaxError`, `ReferenceError`, `RangeError` и некоторые другие. Можно использовать и их, но только чтобы не было путаницы. + +В данном случае мы используем конструктор `new SyntaxError(message)`. Он создаёт ошибку того же типа, что и `JSON.parse`. ```js //+ run @@ -226,15 +249,15 @@ try { Конечно, может! Код -- это вообще мешок с ошибками, бывает даже так что библиотеку выкладывают в открытый доступ, она там 10 лет лежит, её смотрят миллионы людей и на 11й год находятся опаснейшие ошибки. Такова жизнь, таковы люди. -**Блок `catch` в нашем примере предназначен для обработки ошибок, возникающих при некорректных данных.** +Блок `catch` в нашем примере предназначен для обработки ошибок, возникающих при некорректных данных. Если же в него попала какая-то другая ошибка, то вывод сообщения о "некорректных данных" будет дезинформацией посетителя. -Если же в него попала какая-то другая ошибка, то вывод сообщения о "некорректных данных" будет дезинформацией посетителя. Ошибку, о которой `catch` не знает, он должен пропустить. +**Ошибку, о которой `catch` не знает, он не должен обрабатывать.** -**Такая техника называется *"проброс исключения"*: в `catch(e)` мы анализируем объект ошибки, и если он нам не подходит, то делаем `throw e`.** +Такая техника называется *"проброс исключения"*: в `catch(e)` мы анализируем объект ошибки, и если он нам не подходит, то делаем `throw e`. При этом ошибка "выпадает" из `try..catch` наружу. Далее она может быть поймана либо внешним блоком `try..catch` (если есть), либо "повалит" скрипт. -Например: +В примере ниже `catch` обрабатывает только ошибки `SyntaxError`, а остальные -- выбрасывает дальше: ```js //+ run @@ -267,11 +290,9 @@ try { } ``` -**Ошибка, которая возникла внутри `catch`, "выпадает" наружу.** +Заметим, что ошибка, которая возникла внутри блока `catch`, "выпадает" наружу, как если бы была в обычном коде. -Возможно, что этот `try..catch` вызывается внутри другого, более общего `try.catch`, и он как раз умеет обрабатывать выпавшую ошибку. - -Тогда получится так: +В следующем примере такие ошибки обрабатываются ещё одним, "более внешним" `try..catch`: ```js //+ run @@ -305,44 +326,60 @@ try { В примере выше `try..catch` внутри `readData` умеет обрабатывать только `SyntaxError`, а внешний -- все ошибки. -Если же внешнего `try..catch` нет, то ошибка "вываливается" в консоль, и скрипт умирает. +Без внешнего проброшенная ошибка "вывалилась" бы в консоль, с остановкой скрипта. -### Последняя надежда: window.onerror +## Оборачивание исключений -Допустим, ошибка произошла вне блока `try..catch` или выпала из `try..catch` наружу, во внешний код. Скрипт упал. +И, для полноты картины -- последняя, самая продвинутая техника по работе с ошибками. Она, впрочем, является стандартной практикой во многих объектно-ориентированных языках. -Можно ли как-то узнать о том, что произошло? Да, конечно. +Цель функции `readData` в примере выше -- прочитать данные. При чтении могут возникать разные ошибки, не только `SyntaxError`, но и, возможно, к примеру, `URIError` (неправильное применение функций работы с URI), да и другие. -**В браузере существует специальное свойство `window.onerror`, если в него записать функцию, то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда "выпала" ошибка.** +Код, который вызвал `readData`, хотел бы иметь информацию об ошибке, но проверять и перехватывать все типы ошибок ему недосуг. Тогда мы делаем одну ошибку, которая относится именно к общей операции, например `ReadError` и будем генерировать её. А "исходную" ошибку -- присваивать в свойство, на случай, если она, всё же, понадобится. -Необходимо лишь позаботиться, чтобы функция была назначена заранее. - -Например: - -```html - - +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; + } +} ``` -Как правило, роль `window.onerror` заключается в том, чтобы не оживить скрипт -- скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают. - -Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: [](https://errorception.com/) или [](http://www.muscula.com/). - - - ## Секция finally Конструкция `try..catch` может содержать ещё один блок: `finally`. @@ -464,10 +501,49 @@ alert( func() ); // сначала finally, потом 1 [/smart] +## Последняя надежда: window.onerror + +Допустим, ошибка произошла вне блока `try..catch` или выпала из `try..catch` наружу, во внешний код. Скрипт упал. + +Можно ли как-то узнать о том, что произошло? Да, конечно. + +В браузере существует специальное свойство `window.onerror`, если в него записать функцию, то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда "выпала" ошибка. + +Необходимо лишь позаботиться, чтобы функция была назначена заранее. + +Например: + +```html + + +``` + +Как правило, роль `window.onerror` заключается в том, чтобы не оживить скрипт -- скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают. + +Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: [](https://errorception.com/) или [](http://www.muscula.com/). + ## Итого -Конструкция `try..catch` позволяет обработать произвольные ошибки в блоке кода. +Обработка ошибок -- большая и важная тема. + +В JavaScript для этого предусмотрены: + +
      +
    • Конструкция `try..catch..finally` -- она позволяет обработать произвольные ошибки в блоке кода. Это удобно в тех случаях, когда проще сделать действие и потом разбираться с результатом, чем долго и нудно проверять, не упадёт ли чего. @@ -485,28 +561,18 @@ alert( func() ); // сначала finally, потом 1 } ``` -Возможны также варианты `try..catch` или `try..finally`. - -Вместе с `try..catch` используется оператор `throw err`, который генерирует ошибку `err`, причём в качестве `err` рекомендуется генерировать объекты встроенного типа, например [Error](http://javascript.ru/Error) или совместимые с ним. - -[warn header="`try..catch` работает только в синхронном коде"] -Ошибку, которая произойдёт в будущем, например, в `setTimeout`, `try..catch` не поймает: - -```js -//+ run -try { - setTimeout(function() { - throw new Error(); // вылетит в консоль - }, 1000); -} catch(e) { - alert("не сработает"); -} -// на момент срабатывания setTimeout этот код уже завершится -``` - -[/warn] +Возможны также варианты `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/8-oop/1-about-oop/article.md b/1-js/8-oop/1-about-oop/article.md index c6842bbc..e03121f4 100644 --- a/1-js/8-oop/1-about-oop/article.md +++ b/1-js/8-oop/1-about-oop/article.md @@ -4,16 +4,17 @@ Гораздо позже появилось [объектно-ориентированное программирование](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` из себя не представляет, это просто коллекция независимых функций с общим префиксом `Math`. [/warn] @@ -35,9 +36,9 @@ vasya.sayHi(); // пользователь умеет говор Здесь мы видим ярко выраженную сущность -- `User` (посетитель). -**При объектно-ориентированной разработке мы описываем происходящее на уровне объектов, которые создаются, меняют свои свойства, взаимодействуют друг с другом и (в случае браузера) со страницей, в общем, живут.** +ООП -- это наука о том, как делать правильную архитектуру. У неё есть свои принципы, например [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/article.md b/1-js/8-oop/2-internal-external-interface/article.md index 533545c2..291e205d 100644 --- a/1-js/8-oop/2-internal-external-interface/article.md +++ b/1-js/8-oop/2-internal-external-interface/article.md @@ -32,7 +32,7 @@ ## Внутренний и внешний интерфейс -В программировании есть чёткое разграничение методов и свойств объекта на две группы: +В программировании мы будем разделять методы и свойства объекта на две группы:
    • *Внутренний интерфейс* -- это свойства и методы, доступ к которым может быть осуществлен только из других методов объекта, их также называют "приватными" (есть и другие термины, встретим их далее).
    • @@ -46,27 +46,13 @@ Но снаружи кофеварка закрыта специальным кожухом, чтобы никто к ним не подобрался. Детали скрыты и недоступны. Виден лишь внешний интерфейс. -**Все, что нужно для пользования объектом -- это внешний интерфейс.** - -О внутреннем пользователю вообще знать не обязательно. - -[smart header="Между приватным и публичным"] -Приватные свойства полностью закрыты для доступа снаружи, а публичные -- наоборот, полностью открыты. Это крайности, между которыми бывают промежуточные варианты. - -
        -
      • В языке С++ можно открыть доступ к приватным переменным одного класса -- другому, объявив его "дружественным".
      • -
      • В языке Java можно объявлять переменные, которые доступны всем классам внутри "пакета".
      • -
      • Между объектами можно организовать "наследование" и сделать свойства открытыми только для "наследников", такой вариант доступа называют "защищённым".
      • -
      - -В этом учебнике будем изучать наследование и защищённые свойства, но позже, а пока сосредоточимся на приватном и публичном доступе... И, конечно, использовать мы будем JavaScript :) -[/smart] +Получив объект, всё, что нужно для пользования им -- это знать внешний интерфейс. О внутреннем же знать вообще не обязательно. Это были общие слова по теории программирования. Далее мы реализуем кофеварку на JavaScript с приватными и публичными свойствами. В кофеварке много деталей, мы конечно, не будем моделировать каждый винтик, а сосредоточимся на основных приёмах разработки. -### Шаг 1: публичное и приватное свойство +## Шаг 1: публичное и приватное свойство Конструктор кофеварок будет называться `CoffeeMachine`. @@ -85,28 +71,28 @@ var coffeeMachine = new CoffeeMachine(100); coffeeMachine.waterAmount = 200; ``` -**Локальные переменные, включая параметры конструктора, являются приватными свойствами.** +**Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.** В примере выше это `power` -- мощность кофеварки, которая указывается при создании и далее будет использована для расчёта времени кипячения. К локальным переменным конструктора нельзя обратиться снаружи, но они доступны внутри самого конструктора. -**Свойства, записанные в `this`, являются публичными.** +**Свойства, записанные в `this`, можно считать публичными.** Здесь свойство `waterAmount` записано в объект, а значит -- доступно для модификации снаружи. Можно доливать и выливать воду в любом количестве. [smart header="Вопрос терминологии"] -Может возникнуть вопрос -- почему я назвал `power` "приватным свойством", ведь это локальная *переменная*, а никакое не *свойство* объекта? +Далее мы будем называть `power` как "локальной переменной", так и "приватным свойством" объекта. -Здесь небольшой конфликт терминологий. +Это, смотря, с какой стороны посмотреть. Термины "приватное свойство/метод", "публичное свойство/метод" относятся к общей теории ООП. А их конкретная реализация в языке программирования может быть различной. -Здесь ООП-принцип "приватного свойства" реализован через локальные переменные, поэтому и "локальная переменная" и "приватное свойство" -- правильные термины, в зависимости от того, с какой точки зрения посмотреть -- кода или архитектуры ООП. +Здесь ООП-принцип "приватного свойства" реализован через локальные переменные, поэтому и "локальная переменная" и "приватное свойство" -- правильные термины, в зависимости от того, с какой точки зрения взглянуть -- кода или архитектуры ООП. [/smart] -### Шаг 2: публичный и приватный методы +## Шаг 2: публичный и приватный методы Добавим публичный метод `run`, запускающий кофеварку, а также вспомогательные внутренние методы `getBoilTime` и `onReady`: @@ -141,11 +127,11 @@ coffeeMachine.waterAmount = 200; coffeeMachine.run(); ``` -**Приватные методы, такие как `onReady`, `getBoilTime` объявляются как вложенные функции.** +Приватные методы, такие как `onReady`, `getBoilTime` могут быть объявлены как вложенные функции. В результате естественным образом получается, что доступ к ним (через замыкание) имеют только другие функции, объявленные в том же конструкторе. -### Шаг 3: константа +## Шаг 3: константа Для расчёта времени на кипячение воды используется формула `c*m*ΔT / power`, где:
        @@ -171,7 +157,7 @@ function CoffeeMachine(power) { // расчёт времени для кипячения function getBoilTime() { - return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; + return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; // ошибка! } */!* @@ -181,7 +167,7 @@ function CoffeeMachine(power) { } this.run = function() { - setTimeout(onReady, getBoilTime()); + setTimeout(onReady, getBoilTime()); }; } @@ -194,9 +180,9 @@ coffeeMachine.run(); Удельная теплоёмкость `WATER_HEAT_CAPACITY` выделена большими буквами, так как это константа. -**Внимание, при запуске кода выше в методе `getBoilTime` будет ошибка. Как вы думаете, почему?** +Внимание, при запуске кода выше в методе `getBoilTime` будет ошибка. Как вы думаете, почему? -### Шаг 4: доступ к объекту из внутреннего метода +## Шаг 4: доступ к объекту из внутреннего метода Внутренний метод вызывается так: `getBoilTime()`. А чему при этом равен `this`?... Как вы наверняка помните, в современном стандарте он будет `undefined` (в старом -- `window`), из-за этого при чтении `this.waterAmount` возникнет ошибка! @@ -311,11 +297,11 @@ coffeeMachine.run(); Теперь `getBoilTime` получает `self` из замыкания. -**Конечно, чтобы это работало, мы не должны изменять `self`, а все приватные методы, которые хотят привязаться к текущему объекту, должны использовать внутри себя `self` вместо `this`.** +**Конечно, чтобы это работало, мы не должны изменять `self`, а все приватные методы, которые хотят иметь доступ к текущему объекту, должны использовать внутри себя `self` вместо `this`.** Вместо `self` можно использовать любое другое имя переменной, например `var me = this`. -## Что нам даст разделение доступов? +## Итого Итак, мы сделали кофеварку с публичными и приватными методами и заставили их корректно работать. diff --git a/1-js/8-oop/3-getters-setters/article.md b/1-js/8-oop/3-getters-setters/article.md index a6888832..c961d184 100644 --- a/1-js/8-oop/3-getters-setters/article.md +++ b/1-js/8-oop/3-getters-setters/article.md @@ -153,5 +153,14 @@ alert( coffeeMachine.waterAmount() ); // 450 Единый геттер-сеттер используется реже, чем две отдельные функции, но в некоторых JavaScript-библиотеках, например [jQuery](http://jquery.com) и [D3](http://d3js.org) подобный подход принят на уровне концепта. -## Задачи +## Итого + +
          +
        • Для большего контроля над присвоением и чтением значения, вместо свойства делают "функцию-геттер" и "функцию-сеттер", геттер возвращает значение, сеттер -- устанавливает.
        • +
        • Если свойство предназначено только для чтения, то может быть только геттер, только для записи -- только сеттер.
        • +
        • В качестве альтернативы паре геттер/сеттер применяют единую функцию, которая без аргументов ведёт себя как геттер, а с аргументом -- как сеттер.
        • +
        + +Также можно организовать геттеры/сеттеры для свойства, не меняя структуры кода, через [дескрипторы свойств](/descriptors-getters-setters). + diff --git a/1-js/8-oop/4-descriptors-getters-setters/1-replace-property-getter/solution.md b/1-js/8-oop/4-descriptors-getters-setters/1-replace-property-getter/solution.md deleted file mode 100644 index efe1006b..00000000 --- a/1-js/8-oop/4-descriptors-getters-setters/1-replace-property-getter/solution.md +++ /dev/null @@ -1,27 +0,0 @@ - - -```js -//+ run -function CoffeeMachine(power, capacity) { - var waterAmount = 0; - - Object.defineProperty(this, "waterAmount", { - - get: function() { - return waterAmount; - }, - - set: function(amount) { - if (amount > capacity) { - throw new Error("Нельзя залить больше, чем " + capacity); - } - - waterAmount = amount; - } - }); -} - -var coffeeMachine = new CoffeeMachine(1000, 300); -coffeeMachine.waterAmount = 500; -``` - diff --git a/1-js/8-oop/4-descriptors-getters-setters/1-replace-property-getter/task.md b/1-js/8-oop/4-descriptors-getters-setters/1-replace-property-getter/task.md deleted file mode 100644 index 73d213ef..00000000 --- a/1-js/8-oop/4-descriptors-getters-setters/1-replace-property-getter/task.md +++ /dev/null @@ -1,22 +0,0 @@ -# Заменить свойство на встроенные геттеры/сеттеры - -[importance 5] - -Вам попал в руки код кофеварки, который использует свойство `this.waterAmount` для хранения количества воды: - -```js -function CoffeeMachine(power, capacity) { - // количество воды в кофеварке - this.waterAmount = 0; -} - -// создать кофеварку -var coffeeMachine = new CoffeeMachine(1000, 300); - -// залить воды -coffeeMachine.waterAmount = 500; -``` - -Задача -- сделать так, чтобы при присвоении `coffeeMachine.waterAmount = 500` выдавалась ошибка, если значение больше `capacity` (в примере выше `300`). - -Для этого реализуйте `waterAmount` через геттер и сеттер, который будет проверять корректность установки. Используйте для этого `Object.defineProperty`. \ No newline at end of file