final cleanup regexps
|
@ -210,13 +210,13 @@ Adobe Flash -- кросс-браузерная платформа для мул
|
||||||
<ul>
|
<ul>
|
||||||
<li>Язык [CoffeeScript](http://coffeescript.org/) -- это "синтаксический сахар" поверх JavaScript, он сосредоточен на большей ясности и краткости кода. Как правило, его особенно любят программисты на Ruby.</li>
|
<li>Язык [CoffeeScript](http://coffeescript.org/) -- это "синтаксический сахар" поверх JavaScript, он сосредоточен на большей ясности и краткости кода. Как правило, его особенно любят программисты на Ruby.</li>
|
||||||
<li>Язык [TypeScript](http://www.typescriptlang.org/) сосредоточен на добавлении строгой типизации данных, он предназначен для упрощения разработки и поддержки больших систем. Его разрабатывает MicroSoft.</li>
|
<li>Язык [TypeScript](http://www.typescriptlang.org/) сосредоточен на добавлении строгой типизации данных, он предназначен для упрощения разработки и поддержки больших систем. Его разрабатывает MicroSoft.</li>
|
||||||
<li>Язык [Dart](https://www.dartlang.org/) предложен компанией Google как замена JavaScript, но другие ведущие интернет-компании объявили о своей незаинтересованности в Dart. Возможно, в будущем он может составить конкуренцию JS.</li>
|
<li>Язык [Dart](https://www.dartlang.org/) интересен тем, что он не только транслируется в JavaScript, как и другие языки, но и имеет свою независимую среду выполнения, которая даёт ему ряд возможностей и доступна для встраивания в приложения (вне браузера). Он разрабатывается компанией Google.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
[smart header="ES6 и ES7 прямо сейчас"]
|
[smart header="ES6 и ES7 прямо сейчас"]
|
||||||
Существуют также трансляторы, которые берут код, использующий возможности будущих стандартов JavaScript, и преобразуют его в более старый вариант, который понимают все браузеры.
|
Существуют также трансляторы, которые берут код, использующий возможности будущих стандартов JavaScript, и преобразуют его в более старый вариант, который понимают все браузеры.
|
||||||
|
|
||||||
Например, [6to5](https://6to5.org/).
|
Например, [babeljs](https://babeljs.io/).
|
||||||
|
|
||||||
Благодаря этому, мы можем использовать многие возможности будущего уже сегодня.
|
Благодаря этому, мы можем использовать многие возможности будущего уже сегодня.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
|
@ -268,72 +268,6 @@ function showWarning(width, height, title, contents) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### "Именованные аргументы"
|
|
||||||
|
|
||||||
*Именованные аргументы* -- альтернативная техника работы с аргументами, которая вообще не использует `arguments`.
|
|
||||||
|
|
||||||
Некоторые языки программирования позволяют передать параметры как-то так: `f(width=100, height=200)`, то есть по именам, а что не передано, тех аргументов нет. Это очень удобно в тех случаях, когда аргументов много, сложно запомнить их порядок и большинство вообще не надо передавать, по умолчанию подойдёт.
|
|
||||||
|
|
||||||
Такая ситуация часто встречается в компонентах интерфейса. Например, у "меню" может быть масса настроек отображения, которые можно "подкрутить" но обычно нужно передать всего один-два главных параметра, а остальные возьмутся по умолчанию.
|
|
||||||
|
|
||||||
В JavaScript для этих целей используется передача аргументов в виде объекта, а в его свойствах мы передаём параметры.
|
|
||||||
|
|
||||||
Получается так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function showWarning(options) {
|
|
||||||
var width = options.width || 200; // по умолчанию
|
|
||||||
var height = options.height || 100;
|
|
||||||
|
|
||||||
var title = options.title || "Предупреждение";
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
showWarning({
|
|
||||||
```
|
|
||||||
|
|
||||||
Вызвать такую функцию очень легко. Достаточно передать объект аргументов, указав в нем только нужные:
|
|
||||||
|
|
||||||
```js
|
|
||||||
showWarning({
|
|
||||||
contents: "Вы вызвали функцию" // и всё понятно!
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Сравним это с передачей аргументов через список:
|
|
||||||
|
|
||||||
```js
|
|
||||||
showWarning(null, null, "Предупреждение!");
|
|
||||||
// мысль программиста "а что это за null, null в начале? ох, надо глядеть описание функции"
|
|
||||||
```
|
|
||||||
|
|
||||||
Не правда ли, объект -- гораздо проще и понятнее?
|
|
||||||
|
|
||||||
Еще один бонус кроме красивой записи -- возможность повторного использования объекта аргументов:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var opts = {
|
|
||||||
width: 400,
|
|
||||||
height: 200,
|
|
||||||
contents: "Текст"
|
|
||||||
};
|
|
||||||
|
|
||||||
showWarning(opts);
|
|
||||||
|
|
||||||
opts.contents = "Другой текст";
|
|
||||||
|
|
||||||
*!*
|
|
||||||
showWarning(opts); // вызвать с новым текстом, без копирования других аргументов
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Именованные аргументы применяются во многих JavaScript-фреймворках.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Устаревшее свойство arguments.callee [#arguments-callee]
|
## Устаревшее свойство arguments.callee [#arguments-callee]
|
||||||
|
|
||||||
[warn header="Используйте NFE вместо `arguments.callee`"]
|
[warn header="Используйте NFE вместо `arguments.callee`"]
|
||||||
|
@ -407,6 +341,72 @@ function f3() {
|
||||||
|
|
||||||
В учебнике мы это свойство также не будем использовать.
|
В учебнике мы это свойство также не будем использовать.
|
||||||
|
|
||||||
|
|
||||||
|
## "Именованные аргументы"
|
||||||
|
|
||||||
|
*Именованные аргументы* -- альтернативная техника работы с аргументами, которая вообще не использует `arguments`.
|
||||||
|
|
||||||
|
Некоторые языки программирования позволяют передать параметры как-то так: `f(width=100, height=200)`, то есть по именам, а что не передано, тех аргументов нет. Это очень удобно в тех случаях, когда аргументов много, сложно запомнить их порядок и большинство вообще не надо передавать, по умолчанию подойдёт.
|
||||||
|
|
||||||
|
Такая ситуация часто встречается в компонентах интерфейса. Например, у "меню" может быть масса настроек отображения, которые можно "подкрутить" но обычно нужно передать всего один-два главных параметра, а остальные возьмутся по умолчанию.
|
||||||
|
|
||||||
|
В JavaScript для этих целей используется передача аргументов в виде объекта, а в его свойствах мы передаём параметры.
|
||||||
|
|
||||||
|
Получается так:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function showWarning(options) {
|
||||||
|
var width = options.width || 200; // по умолчанию
|
||||||
|
var height = options.height || 100;
|
||||||
|
|
||||||
|
var title = options.title || "Предупреждение";
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
showWarning({
|
||||||
|
```
|
||||||
|
|
||||||
|
Вызвать такую функцию очень легко. Достаточно передать объект аргументов, указав в нем только нужные:
|
||||||
|
|
||||||
|
```js
|
||||||
|
showWarning({
|
||||||
|
contents: "Вы вызвали функцию" // и всё понятно!
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Сравним это с передачей аргументов через список:
|
||||||
|
|
||||||
|
```js
|
||||||
|
showWarning(null, null, "Предупреждение!");
|
||||||
|
// мысль программиста "а что это за null, null в начале? ох, надо глядеть описание функции"
|
||||||
|
```
|
||||||
|
|
||||||
|
Не правда ли, объект -- гораздо проще и понятнее?
|
||||||
|
|
||||||
|
Еще один бонус кроме красивой записи -- возможность повторного использования объекта аргументов:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var opts = {
|
||||||
|
width: 400,
|
||||||
|
height: 200,
|
||||||
|
contents: "Текст"
|
||||||
|
};
|
||||||
|
|
||||||
|
showWarning(opts);
|
||||||
|
|
||||||
|
opts.contents = "Другой текст";
|
||||||
|
|
||||||
|
*!*
|
||||||
|
showWarning(opts); // вызвать с новым текстом, без копирования других аргументов
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Именованные аргументы применяются во многих JavaScript-фреймворках.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Итого
|
## Итого
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -354,5 +354,5 @@ function Rabbit() {
|
||||||
|
|
||||||
...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе.
|
...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе.
|
||||||
|
|
||||||
Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса -- она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор [6to5](http://6to5.org/).
|
Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса -- она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор [babeljs](https://babeljs.io/).
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<text id="jump:-function" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal">
|
<text id="jump:-function" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal">
|
||||||
<tspan x="24" y="175" fill="#8A704D">jump: function</tspan>
|
<tspan x="24" y="175" fill="#8A704D">jump: function</tspan>
|
||||||
<tspan x="131.761719" y="175" fill="#999647"></tspan>
|
<tspan x="131.761719" y="175" fill="#999647"></tspan>
|
||||||
<tspan x="24" y="191" fill="#7ED321">run: function</tspan>
|
<tspan x="24" y="191" fill="#417505">run: function</tspan>
|
||||||
</text>
|
</text>
|
||||||
<text id="Rabbit.prototype" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-style="italic" font-weight="normal" fill="#8A704D">
|
<text id="Rabbit.prototype" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-style="italic" font-weight="normal" fill="#8A704D">
|
||||||
<tspan x="13" y="149">Rabbit.prototype</tspan>
|
<tspan x="13" y="149">Rabbit.prototype</tspan>
|
||||||
|
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
Но могут быть и другие, например `PropertyError` -- эта ошибка будет возникать, если в прочитанном объекте нет свойства `name` или `age`.
|
Но могут быть и другие, например `PropertyError` -- эта ошибка будет возникать, если в прочитанном объекте нет свойства `name` или `age`.
|
||||||
|
|
||||||
Реализуем её:
|
Реализуем класс `PropertyError`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function PropertyError(property) {
|
function PropertyError(property) {
|
||||||
|
@ -34,17 +34,17 @@ function PropertyError(property) {
|
||||||
PropertyError.prototype = Object.create(Error.prototype);
|
PropertyError.prototype = Object.create(Error.prototype);
|
||||||
```
|
```
|
||||||
|
|
||||||
Посмотрим внимательнее на код -- в нём важные детали того, как можно позаботиться о стандартных свойствах объекта ошибки:
|
В этом коде вы можете видеть ряд важных деталей, важных именно для ошибок:
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt>`name` -- имя ошибки.</dt>
|
<dt>`name` -- имя ошибки.</dt>
|
||||||
<dd>Должно совпадать с именем функции, просто записали строку в него.</dd>
|
<dd>Должно совпадать с именем функции.</dd>
|
||||||
<dt>`message` -- сообщение об ошибке.</dt>
|
<dt>`message` -- сообщение об ошибке.</dt>
|
||||||
<dd>Несмотря на то, что `PropertyError` наследует от `Error` (последняя строка), конструктор у неё немного другой. Он принимает не сообщение об ошибке, а название свойства `property`, ну а сообщение генерируется из него.
|
<dd>Несмотря на то, что `PropertyError` наследует от `Error` (последняя строка), конструктор у неё немного другой. Он принимает не сообщение об ошибке, а название свойства `property`, ну а сообщение генерируется из него.
|
||||||
|
|
||||||
В результате в ошибке есть как стандартное свойство `message`, так и более точное `property`.
|
В результате в объекте ошибки есть как стандартное свойство `message`, так и более точное `property`.
|
||||||
|
|
||||||
Это полезная практика -- добавлять в объект ошибки свойства, более подробно описывающие ситуацию, которых нет в базовых объектах `Error`.</dd>
|
Это частая практика -- добавлять в объект ошибки свойства, которых нет в базовых объектах `Error`, более подробно описывающие ситуацию для данного класса ошибок.</dd>
|
||||||
<dt>`stack` -- стек вызовов, которые в итоге привели к ошибке.</dt>
|
<dt>`stack` -- стек вызовов, которые в итоге привели к ошибке.</dt>
|
||||||
<dd>У встроенных объектов `Error` это свойство есть автоматически, вот к примеру:
|
<dd>У встроенных объектов `Error` это свойство есть автоматически, вот к примеру:
|
||||||
```js
|
```js
|
||||||
|
@ -53,30 +53,28 @@ function f() {
|
||||||
alert( new Error().stack );
|
alert( new Error().stack );
|
||||||
}
|
}
|
||||||
|
|
||||||
f();
|
f(); // выведет список вложенных вызовов, с номерами строк, где они были сделаны
|
||||||
```
|
```
|
||||||
|
|
||||||
Если же объект делаем мы, то "по умолчанию" такого свойства у него не будет. Нам нужно как-то самим узнавать последовательность вложенных вызовов на текущий момент. Однако удобного способа сделать это в JavaScript нет, поэтому мы поступаем хитро и копируем его из нового объекта `new Error`, который генерируем тут же.
|
Если же объект ошибки делаем мы, то "по умолчанию" у него такого свойства у него не будет. Нам нужно как-то самим узнавать последовательность вложенных вызовов на текущий момент. Однако удобного способа сделать это в JavaScript нет, поэтому мы поступаем хитро и копируем его из нового объекта `new Error`, который генерируем тут же.
|
||||||
|
|
||||||
В V8 (Chrome, Opera, Node.JS) есть нестандартное расширение [Error.captureStackTrace](https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi), которое позволяет стек получать.
|
В V8 (Chrome, Opera, Node.JS) есть нестандартное расширение [Error.captureStackTrace](https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi), которое позволяет стек получать.
|
||||||
|
|
||||||
Строка из кода выше:
|
Это делает строка из кода выше:
|
||||||
```js
|
```js
|
||||||
Error.captureStackTrace(this, PropertyError);
|
Error.captureStackTrace(this, PropertyError);
|
||||||
```
|
```
|
||||||
|
|
||||||
Вызов записывает в объект `this` (текущий объект ошибки) стек вызовов, а второй аргумент -- это текущий конструктор, он не обязателен, но если есть, то говорит, что при генерации стека нужно на этой функции остановиться. В результате в стеке не будет информации о том, что делалось внутри конструктора `PropertyError`.
|
Такой вызов записывает в объект `this` (текущий объект ошибки) стек вызовов, ну а второй аргумент -- вообще не обязателен, но если есть, то говорит, что при генерации стека нужно на этой функции остановиться. В результате в стеке будет информация о цепочке вложенных вызовов вплоть до вызова `PropertyError`.
|
||||||
|
|
||||||
То есть, будет последовательность вызовов до генерации ошибки, но не включая код самого конструктора ошибки, который, как правило, не интересен. Такое поведение максимально соответствует встроенным ошибкам JavaScript.
|
То есть, будет последовательность вызовов до генерации ошибки, но не включая код самого конструктора ошибки, который, как правило, не интересен. Такое поведение максимально соответствует встроенным ошибкам JavaScript.
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
[smart header="Конструктор родителя здесь не нужен"]
|
[smart header="Конструктор родителя здесь не нужен"]
|
||||||
В коде выше не вызывается конструктор родителя. Обычно, когда мы наследуем, то мы вызываем его.
|
Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядел бы как `Error.call(this, message)`.
|
||||||
|
|
||||||
В данном случае вызов выглядел бы как `Error.call(this, message)`.
|
Однако, встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Поэтому и вызывать его здесь нет необходимости.
|
||||||
|
|
||||||
Однако, встроенный конструктор `Error` на редкость прост, он ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Поэтому и вызывать его здесь нет необходимости.
|
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,7 +91,7 @@ function PropertyError(property) {
|
||||||
this.name = "PropertyError";
|
this.name = "PropertyError";
|
||||||
|
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.message = "Отсутствует свойство " + property;
|
this.message = "Ошибка в свойстве " + property;
|
||||||
|
|
||||||
if (Error.captureStackTrace) {
|
if (Error.captureStackTrace) {
|
||||||
Error.captureStackTrace(this, PropertyError);
|
Error.captureStackTrace(this, PropertyError);
|
||||||
|
@ -137,49 +135,50 @@ try {
|
||||||
alert( "Здравствуйте, Аноним!" );
|
alert( "Здравствуйте, Аноним!" );
|
||||||
*/!*
|
*/!*
|
||||||
} else {
|
} else {
|
||||||
alert( err.message ); // Отсутствует свойство ...
|
alert( err.message ); // Ошибка в свойстве ...
|
||||||
}
|
}
|
||||||
} else if (err instanceof SyntaxError) {
|
} else if (err instanceof SyntaxError) {
|
||||||
alert( "Ошибка в данных: " + err.message );
|
alert( "Ошибка в синтаксисе данных: " + err.message );
|
||||||
} else {
|
} else {
|
||||||
throw err; // неизвестная ошибка, не знаю что с ней делать
|
throw err; // неизвестная ошибка, не знаю что с ней делать
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Обратим внимание на проверку типа ошибки в `try..catch`.
|
Всё работает -- и наша ошибка `PropertyError` и встроенная `SyntaxError` корректно генерируются, перехватываются, обрабатываются.
|
||||||
|
|
||||||
Оператор `instanceof` поддерживает иерархию. Это значит, что если мы в дальнейшем решим как-то ещё уточнить тип ошибки `PropertyError`, то для объекта, наследующего от него, `e instanceof PropertyError` по-прежнему будет работать.
|
Обратим внимание на проверку типа ошибки в `try..catch`. Оператор `instanceof` проверяет класс с учётом наследования. Это значит, что если мы в дальнейшем решим создать новый тип ошибки, наследующий от `PropertyError`, то проверка `err instanceof PropertyError` для класса-наследника тоже будет работать. Код получился расширяемым, это очень важно.
|
||||||
|
|
||||||
## Дальнейшее наследование
|
## Дальнейшее наследование
|
||||||
|
|
||||||
Чтобы создать иерархию, нужно наследовать от `PropertyError`.
|
|
||||||
|
|
||||||
`PropertyError` -- это просто общего вида ошибка в свойстве. Создадим ошибку `PropertyRequiredError`, которая означает, что свойства нет.
|
`PropertyError` -- это просто общего вида ошибка в свойстве. Создадим ошибку `PropertyRequiredError`, которая означает, что свойства нет.
|
||||||
|
|
||||||
Типичный вид конструктора-наследника -- такой:
|
Эт подвид `PropertyError`, так что унаследуем он неё. Общий вид конструктора-наследника -- стандартный:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function PropertyRequiredError(property) {
|
function PropertyRequiredError(property) {
|
||||||
|
// вызываем конструктор родителя и передаём текущие аргументы
|
||||||
PropertyError.apply(this, arguments);
|
PropertyError.apply(this, arguments);
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Можем ли мы просто вызвать конструктор родителя и ничего не делать в дополнение? Увы, нет.
|
Достаточно ли в наследнике просто вызвать конструктор родителя? Увы, нет.
|
||||||
|
|
||||||
Если так поступить, то свойство `this.name` будет некорректным, да и `Error.captureStackTrace` тоже получит неправильную функцию вторым параметром.
|
Если так поступить, то свойство `this.name` будет некорректным, да и `Error.captureStackTrace` тоже получит неправильную функцию вторым параметром.
|
||||||
|
|
||||||
Можно ли как-то поправить конструктор родителя? Убрать из него все упоминания о конкретном классе `PropertyError` и сделать код универсальным?
|
Можно ли как-то поправить конструктор родителя, чтобы от него было проще наследовать?
|
||||||
|
|
||||||
Частично -- да. Как мы помним, существует свойство `constructor`, которое есть в `prototype` по умолчанию, и которое мы можем намеренно сохранить при наследовании:
|
Для этого нужно убрать из него упоминания о конкретном классе `PropertyError`, чтобы сделать код универсальным. Частично -- это возможно. Как мы помним, существует свойство `constructor`, которое есть в `prototype` по умолчанию, и которое мы можем намеренно сохранить при наследовании.
|
||||||
|
|
||||||
|
Исправим родителя `PropertyError` для более удобного наследования от него:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function PropertyError(property) {
|
function PropertyError(property) {
|
||||||
this.name = "PropertyError";
|
this.name = "PropertyError";
|
||||||
|
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.message = "Отсутствует свойство " + property;
|
this.message = "Ошибка в свойстве " + property;
|
||||||
|
|
||||||
if (Error.captureStackTrace) {
|
if (Error.captureStackTrace) {
|
||||||
Error.captureStackTrace(this, *!*this.constructor*/!*); // (*)
|
Error.captureStackTrace(this, *!*this.constructor*/!*); // (*)
|
||||||
|
@ -195,9 +194,9 @@ PropertyError.prototype.constructor = PropertyError;
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
В строке `(*)` это свойство было использовано, чтобы получить конструктор уже не в виде жёсткой ссылки `PropertyError`, а тот, который использован для текущего объекта. В наследнике там будет `PropertyRequiredError`, как и задумано.
|
В строке `(*)` вместо ссылки на `PropertyError` используем `constructor` чтобы получить именно конструктор для текущего объекта. В наследнике там будет `PropertyRequiredError`, как и задумано.
|
||||||
|
|
||||||
Мы убрали одно упоминание, но с `this.name`, увы, сложности. Сейчас при наследовании оно будет всегда `"PropertyError"`. Все браузеры, кроме IE11-, поддерживают имя у Function Declaration, то есть имя функции можно было бы получить из `this.constructor.name`, но в IE11- это работать не будет.
|
Мы убрали одну жёсткую привязку к `PropertyError`, но со второй (`this.name`), увы, сложности. Оно должно содержать имя ошибки, то есть, имя её функции-конструктора. Его можно получить через `this.name = this.constructor.name`, но в IE11- это работать не будет.
|
||||||
|
|
||||||
Если подерживать IE11-, то тут уж придётся в наследнике его записывать вручную.
|
Если подерживать IE11-, то тут уж придётся в наследнике его записывать вручную.
|
||||||
|
|
||||||
|
@ -218,13 +217,68 @@ var err = new PropertyRequiredError("age");
|
||||||
alert( err instanceof PropertyError ); // true
|
alert( err instanceof PropertyError ); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
Здесь заодно и `message` было перезаписано на более точное. Если хочется избежать записи и перезаписи, то можно оформить его в виде геттера через `Object.defineProperty`.
|
Здесь заодно и `message` в наследнике было перезаписано на более точное. Если хочется избежать записи и перезаписи, то можно оформить его в виде геттера через `Object.defineProperty`.
|
||||||
|
|
||||||
## Итого
|
## Итого
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Чтобы наследовать встроенному классу ошибок `Error`, нужно самостоятельно позаботиться о `name`, `message` и `stack`.</li>
|
<li>Чтобы наследовать от ошибок `Error`, нужно самостоятельно позаботиться о `name`, `message` и `stack`.</li>
|
||||||
<li>Благодаря `instanceof` мы получили удобную поддержку иерархии ошибок, с возможностью в любой момент добавить новые классы, понятным кодом и предсказуемым поведением.</li>
|
<li>Благодаря тому, что `instanceof` поддерживает наследование, удобно организуются проверки на нужный тип. В иерархию ошибок можно в любой момент добавить новые классы, с понятным кодом и предсказуемым поведением.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Чтобы создавать наследники от `Error` было проще, можно создать класс `CustomError`, записать в него универсальный код, наподобие `PropertyError` и далее наследовать уже от него.
|
Чтобы создавать наследники от `Error` было проще, можно создать класс `CustomError`, записать в него универсальный код, наподобие `PropertyError` и далее наследовать уже от него:
|
||||||
|
|
||||||
|
```js
|
||||||
|
*!*
|
||||||
|
// общего вида "наша" ошибка
|
||||||
|
*/!*
|
||||||
|
function CustomError(message) {
|
||||||
|
this.name = "CustomError";
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
} else {
|
||||||
|
this.stack = (new Error()).stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomError.prototype = Object.create(Error.prototype);
|
||||||
|
CustomError.prototype.constructor = CustomError;
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// наследник
|
||||||
|
*/!*
|
||||||
|
function PropertyError(property) {
|
||||||
|
CustomError.call(this, "Отсутствует свойство " + property)
|
||||||
|
this.name = "PropertyError";
|
||||||
|
|
||||||
|
this.property = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyError.prototype = Object.create(CustomError.prototype);
|
||||||
|
PropertyError.prototype.constructor = PropertyError;
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// и ещё уровень
|
||||||
|
*/!*
|
||||||
|
function PropertyRequiredError(property) {
|
||||||
|
PropertyError.call(this, property);
|
||||||
|
this.name = 'PropertyRequiredError';
|
||||||
|
this.message = 'Отсутствует свойство ' + property;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyRequiredError.prototype = Object.create(PropertyError.prototype);
|
||||||
|
PropertyRequiredError.prototype.constructor = PropertyRequiredError;
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// использование
|
||||||
|
*/!*
|
||||||
|
var err = new PropertyRequiredError("age");
|
||||||
|
// пройдёт проверку
|
||||||
|
alert( err instanceof PropertyRequiredError ); // true
|
||||||
|
alert( err instanceof PropertyError ); // true
|
||||||
|
alert( err isntanceof CustomError ); // true
|
||||||
|
alert( err isntanceof Error ); // true
|
||||||
|
```
|
||||||
|
|
174
1-js/9-prototypes/8-mixins/article.md
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
# Примеси
|
||||||
|
|
||||||
|
В JavaScript невозможно унаследовать от двух и более объектов. Ссылка `__proto__` -- только одна.
|
||||||
|
|
||||||
|
Но потребность такая существует -- к примеру, мы написали код, релизующий методы работы с шаблонизатором или методы по обмену событиями, и хочется легко и непринуждённо добавлять эти возможности к любому классу.
|
||||||
|
|
||||||
|
Обычно это делают через примеси.
|
||||||
|
|
||||||
|
Примесь (англ. mixin) -- класс или объект, реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для самостоятельного использования.
|
||||||
|
|
||||||
|
<!--break-->
|
||||||
|
|
||||||
|
## Пример примеси
|
||||||
|
|
||||||
|
Самый простой вариант примеси -- это объект с полезными методами, которые мы просто копируем в нужный прототип.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```js
|
||||||
|
//+ run
|
||||||
|
*!*
|
||||||
|
// примесь
|
||||||
|
*/!*
|
||||||
|
var sayHiMixin = {
|
||||||
|
sayHi: function() {
|
||||||
|
alert("Привет " + this.name);
|
||||||
|
},
|
||||||
|
sayBye: function() {
|
||||||
|
alert("Пока " + this.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
// использование:
|
||||||
|
*/!*
|
||||||
|
function User(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// передать методы примеси
|
||||||
|
for(var key in sayHiMixin) User.prototype[key] = sayHiMixin[key];
|
||||||
|
|
||||||
|
// User "умеет" sayHi
|
||||||
|
new User("Вася").sayHi(); // Привет Вася
|
||||||
|
```
|
||||||
|
|
||||||
|
Как видно из примера, методы примеси активно используют `this` и предназначены именно для запуска в контексте "объекта-носителя примеси".
|
||||||
|
|
||||||
|
Если какие-то из методов примеси не нужны -- их можно перезаписать своими после копирования.
|
||||||
|
|
||||||
|
|
||||||
|
## Примесь для событий
|
||||||
|
|
||||||
|
Теперь пример из реальной жизни.
|
||||||
|
|
||||||
|
Важный аспект, который может понадобиться объектам -- это умение работать с событиями.
|
||||||
|
|
||||||
|
То есть, чтобы объект мог специальным вызовом генерировать "уведомление о событии", а на эти уведомления другие объекты могли "подписываться", чтобы их получать.
|
||||||
|
|
||||||
|
Например, объект "Пользователь" при входе на сайт может генерировать событие `"login"`, а другие объекты, например "Календарь" может такие уведомления получать и подгружать информацию о пользователе.
|
||||||
|
|
||||||
|
Или объект "Меню" может при выборе пункта меню генерировать событие `"select"` с информацией о выбранном пункте меню, а другие объекты -- подписавшись на это событие, будут узнавать об этом.
|
||||||
|
|
||||||
|
События -- это средство "поделиться информацией" с неопределённым кругом заинтересованных лиц. А там уже кому надо -- тот среагирует.
|
||||||
|
|
||||||
|
Примесь `eventMixin`, реализующая события:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var eventMixin = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подписка на событие
|
||||||
|
* Использование:
|
||||||
|
* menu.on('select', function(item) { ... }
|
||||||
|
*/
|
||||||
|
on: function(eventName, handler) {
|
||||||
|
if (!this._eventHandlers) this._eventHandlers = {};
|
||||||
|
if (!this._eventHandlers[eventName]) {
|
||||||
|
this._eventHandlers[eventName] = [];
|
||||||
|
}
|
||||||
|
this._eventHandlers[eventName].push(handler);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прекращение подписки
|
||||||
|
* menu.off('select', handler)
|
||||||
|
*/
|
||||||
|
off: function(eventName, handler) {
|
||||||
|
var handlers = this._eventHandlers && this._eventHandlers[eventName];
|
||||||
|
if (!handlers) return;
|
||||||
|
for(var i=0; i<handlers.length; i++) {
|
||||||
|
if (handlers[i] == handler) {
|
||||||
|
handlers.splice(i--, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация события с передачей данных
|
||||||
|
* this.trigger('select', item);
|
||||||
|
*/
|
||||||
|
trigger: function(eventName /*, ... */) {
|
||||||
|
|
||||||
|
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
|
||||||
|
return; // обработчиков для события нет
|
||||||
|
}
|
||||||
|
|
||||||
|
// вызвать обработчики
|
||||||
|
var handlers = this._eventHandlers[eventName];
|
||||||
|
for (var i = 0; i < handlers.length; i++) {
|
||||||
|
handlers[i].apply(this, [].slice.call(arguments, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Здесь есть три метода:
|
||||||
|
<ol>
|
||||||
|
<li>`.on(имя события, функция)` -- назначает функцию к выполнению при наступлении события с данным именем. Такие функции хранятся в защищённом свойстве объекта `_eventHandlers`.</li>
|
||||||
|
<li>`.off(имя события, функция)` -- удаляет функцию из списка предназначенных к выполнению.</li>
|
||||||
|
<li>`.trigger(имя события, аргументы)` -- генерирует событие, при этом вызываются все назначенные на него функции, и им передаются аргументы.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
|
||||||
|
```js
|
||||||
|
//+ run
|
||||||
|
// Класс Menu с примесью eventMixin
|
||||||
|
function Menu() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var key in eventMixin) Menu.prototype[key] = eventMixin[key];
|
||||||
|
|
||||||
|
// Генерирует событие select при выборе значения
|
||||||
|
Menu.prototype.choose = function(value) {
|
||||||
|
*!*
|
||||||
|
this.trigger("select", value);
|
||||||
|
*/!*
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создадим меню
|
||||||
|
var menu = new Menu();
|
||||||
|
|
||||||
|
// При наступлении события select вызвать эту функцию
|
||||||
|
*!*
|
||||||
|
menu.on("select", function(value) {
|
||||||
|
alert("Выбрано значение " + value);
|
||||||
|
});
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
// Запускаем выбор (сработает событие)
|
||||||
|
menu.choose("123");
|
||||||
|
```
|
||||||
|
|
||||||
|
...То есть, смысл событий -- обычно в том, что объект, в процессе своей деятельности, внутри себя (`this.trigger`) генерирует уведомления, на которые внешний код через `menu.on(...)` может быть подписан. И узнавать из них ценную информцию о происходящем, например -- что выбран некий пункт меню.
|
||||||
|
|
||||||
|
Один раз написав методы `on/off/trigger` в примеси, мы затем можем использовать их во множестве прототипов.
|
||||||
|
|
||||||
|
## Итого
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Примесь -- объект, содержащий методы и свойства для реализации конкретного функционала.
|
||||||
|
Возможны вариации этого приёма проектирования. Например, примесь может предусматривать конструктор, который должен запускаться в конструкторе объекта. Но как правило просто набора методов хватает.</li>
|
||||||
|
<li>Для добавления примеси в класс -- её просто "подмешивают" в прототип.</li>
|
||||||
|
<li>"Подмешать" можно сколько угодно примесей, но если имена методов в разных примесях совпадают, то возможны конфликты. Их уже разрешать -- разработчику. Например, можно заменить конфликтующий метод на свой, который будет решать несколько задач сразу. Конфликты при грамотно оформленных примесях возникают редко.</li></ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
Двузначное шестнадцатиричное число -- это ``pattern`[0-9a-f]{2}` (с учётом флага ``pattern`/i`).
|
||||||
|
|
||||||
|
Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: ``pattern`[0-9a-f]{2}(:[0-9a-f]{2}){5}`
|
||||||
|
|
||||||
|
И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в ``pattern`^...$`.
|
||||||
|
|
||||||
|
Итого, в действии:
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
//+ run
|
||||||
|
var re = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i;
|
||||||
|
|
||||||
|
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||||
|
|
||||||
|
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||||
|
|
||||||
|
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||||
|
|
||||||
|
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||||
|
```
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Проверьте MAC-адрес
|
||||||
|
|
||||||
|
MAC-адрес сетевого интерфейса состоит из шести двузначиных шестандцатиричных чисел, разделённых двоеточием.
|
||||||
|
|
||||||
|
Например: ``subject`'01:32:54:67:89:AB'`.
|
||||||
|
|
||||||
|
Напишите регулярное выражение, которое по строке проверяет, является ли она корректным MAC-адресом.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
```js
|
||||||
|
var re = ваш регэксп
|
||||||
|
|
||||||
|
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||||
|
|
||||||
|
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||||
|
|
||||||
|
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||||
|
|
||||||
|
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||||
|
```
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Предпросмотр (неготово)
|
||||||
|
|
||||||
|
Требуется добавить главу про предпросмотр lookahead.
|
||||||
|
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -1,4 +1,4 @@
|
||||||
# Найдите время
|
# Найдите время в одном из форматов
|
||||||
|
|
||||||
Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`.
|
Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`.
|
||||||
|
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
Начало шаблона очевидно: ``pattern`<style`.
|
||||||
|
|
||||||
|
А вот дальше... Мы не можем написать просто ``pattern`<style.*?>`, так как ``match`<styler>` удовлетворяет этому регэкспу.
|
||||||
|
|
||||||
|
Нужно уточнить его. После ``match`<style` должен быть либо пробел, после которого может быть что-то ещё, либо закрытие тега.
|
||||||
|
|
||||||
|
На языке регэкспов: ``pattern`<style(>|\s.*?>)`.
|
||||||
|
|
||||||
|
В действии:
|
||||||
|
|
||||||
|
```js
|
||||||
|
//+ run
|
||||||
|
var re = /<style(>|\s.*?>)/g;
|
||||||
|
|
||||||
|
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Найдите тег style
|
||||||
|
|
||||||
|
Напишите регулярное выражение, которое будет искать в тексте тег `<style>`. Подходят как обычный тег `<style>`, так и вариант с атрибутами `<style type="...">`.
|
||||||
|
|
||||||
|
Но регулярное выражение не должно находить `<styler>`!
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var re = ваш регэксп
|
||||||
|
|
||||||
|
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||||
|
```
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Регулярные выражения [незавершён]
|
# Регулярные выражения
|
||||||
|
|
||||||
Регулярные выражения -- мощный способ поиска и замены для строк.
|
Регулярные выражения -- мощный способ поиска и замены для строк.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |