renovations

This commit is contained in:
Ilya Kantor 2015-03-22 00:36:11 +03:00
parent c108f03596
commit 9122b131d0
12 changed files with 580 additions and 52 deletions

View file

@ -0,0 +1,28 @@
function formatDate(date) {
if (typeof date == 'number') {
// перевести секунды в миллисекунды и преобразовать к Date
date = new Date(date * 1000);
} else if (typeof date == 'string') {
// разобрать строку и преобразовать к Date
date = date.split('-');
date = new Date(date[0], date[1] - 1, date[2]);
} else if (Array.isArray(date)) {
date = new Date(date[0], date[1], date[2]);
}
// преобразования для поддержки полиморфизма завершены,
// теперь мы работаем с датой (форматируем её)
var day = date.getDate();
if (day < 10) day = '0' + day;
var month = date.getMonth() + 1;
if (month < 10) month = '0' + month;
// взять 2 последние цифры года
var year = date.getFullYear() % 100;
if (year < 10) year = '0' + year;
var formattedDate = day + '.' + month + '.' + year;
return formattedDate;
}

View file

@ -0,0 +1,18 @@
describe("formatDate", function() {
it("читает дату вида гггг-мм-дд из строки", function() {
assert.equal(formatDate('2011-10-02'), "02.10.11");
});
it("читает дату из числа 1234567890 (миллисекунды)", function() {
assert.equal(formatDate(1234567890), "14.02.09");
});
it("читает дату из массива вида [гггг, м, д]", function() {
assert.equal(formatDate([2014, 0, 1]), "01.01.14");
});
it("читает дату из объекта Date", function() {
assert.equal(formatDate(new Date(2014, 0, 1)), "01.01.14");
});
});

View file

@ -0,0 +1,15 @@
Для определения примитивного типа строка/число подойдет оператор [typeof](#type-typeof).
Примеры его работы:
```js
//+ run
alert( typeof 123 ); // "number"
alert( typeof "строка" ); // "string"
alert( typeof new Date() ); // "object"
alert( typeof [] ); // "object"
```
Оператор `typeof` не умеет различать разные типы объектов, они для него все на одно лицо: `"object"`. Поэтому он не сможет отличить `Date` от `Array`.
Для отличия `Array` используем вызов `Array.isArray`. Если он неверен, значит у нас дата.

View file

@ -0,0 +1,26 @@
# Полиморфная функция formatDate
[importance 5]
Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`.
Ее первый аргумент должен содержать дату в одном из видов:
<ol>
<li>Как объект `Date`.</li>
<li>Как строку в формате `yyyy-mm-dd`.</li>
<li>Как число *секунд* с `01.01.1970`.</li>
<li>Как массив `[гггг, мм, дд]`, месяц начинается с нуля</li>
</ol>
Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат.
Пример работы:
```js
function formatDate(date) { /* ваш код */ }
alert( formatDate('2011-10-02') ); // 02.10.11
alert( formatDate(1234567890) ); // 14.02.09
alert( formatDate([2014, 0, 1]) ); // 01.01.14
alert( formatDate(new Date(2014, 0, 1)) ); // 01.01.14
```

View file

@ -0,0 +1,252 @@
# Типы данных: [[Class]], instanceof и утки
Время от времени бывает удобно создавать так называемые "полиморфные" функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.
Для реализации такой возможности нужен способ определить тип переменной.
## Оператор typeof
Мы уже знакомы с простейшим способом -- оператором [typeof](#type-typeof).
Оператор `typeof` надежно работает с примитивными типами, кроме `null`, а также с функциями. Он возвращает для них тип в виде строки:
```js
//+ run no-beautify
alert( typeof 1 ); // 'number'
alert( typeof true ); // 'boolean'
alert( typeof "Текст" ); // 'string'
alert( typeof undefined ); // 'undefined'
alert( typeof null ); // 'object' (ошибка в языке)
alert( typeof alert ); // 'function'
```
...Но все объекты, включая массивы и даты для `typeof` -- на одно лицо, они имеют один тип `'object'`:
```js
//+ run
alert( typeof {} ); // 'object'
alert( typeof [] ); // 'object'
alert( typeof new Date ); // 'object'
```
Поэтому различить их при помощи `typeof` нельзя, и в этом его основной недостаток.
## Секретное свойство [[Class]]
Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`.
Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе.
Оно взято в квадратные скобки, так как это свойство -- внутреннее. Явно получить его нельзя, но можно прочитать его "в обход", воспользовавшись методом `toString` стандартного объекта `Object`.
Его внутренняя реализация выводит `[[Class]]` в небольшом обрамлении, как `"[object значение]"`.
Например:
```js
//+ run
var toString = {}.toString;
var arr = [1, 2];
alert( toString.call(arr) ); // [object Array]
var date = new Date;
alert( toString.call(date) ); // [object Date]
var obj = { name: "Вася" };
alert( toString.call(date) ); // [object Object]
```
В первой строке мы взяли метод `toString`, принадлежащий именно стандартному объекту `{}`. Нам пришлось это сделать, так как у `Date` и `Array` -- свои собственные методы `toString`, которые работают иначе.
Затем мы вызываем этот `toString` в контексте нужного объекта `obj`, и он возвращает его внутреннее, невидимое другими способами, свойство `[[Class]]`.
**Для получения `[[Class]]` нужна именно внутренняя реализация `toString` стандартного объекта `Object`, другая не подойдёт.**
К счастью, методы в JavaScript -- это всего лишь функции-свойства объекта, которые можно скопировать в переменную и применить на другом объекте через `call/apply`. Что мы и делаем для `{}.toString`.
Метод также можно использовать с примитивами:
```js
//+ run
alert( {}.toString.call(123) ); // [object Number]
alert( {}.toString.call("строка") ); // [object String]
```
[warn header="Вызов `{}.toString` в консоли может выдать ошибку"]
При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку `{}.toString.call(...)` -- будет ошибка. С другой стороны, вызов `alert( {}.toString... )` -- работает.
Эта ошибка возникает потому, что фигурные скобки `{ }` в основном потоке кода интерпретируются как блок. Интерпретатор читает `{}.toString.call(...)` так:
```js
//+ no-beautify
{ } // пустой блок кода
.toString.call(...) // а что это за точка в начале? не понимаю, ошибка!
```
Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки `( {}.toString... )` тоже сработает нормально.
[/warn]
Для большего удобства можно сделать функцию `getClass`, которая будет возвращать только сам `[[Class]]`:
```js
//+ run
function getClass(obj) {
return {}.toString.call(obj).slice(8, -1);
}
alert( getClass(new Date) ); // Date
alert( getClass([1, 2, 3]) ); // Array
```
Заметим, что свойство `[[Class]]` есть и доступно для чтения указанным способом -- у всех *встроенных* объектов. Но его нет у объектов, которые создают *наши функции*. Точнее, оно есть, но равно всегда `"Object"`.
Например:
```js
//+ run
function User() {}
var user = new User();
alert( {}.toString.call(user) ); // [object Object], не [object User]
```
Поэтому узнать тип таким образом можно только для встроенных объектов.
## Метод Array.isArray()
Для проверки на массивов есть специальный метод: `Array.isArray(arr)`. Он возвращает `true` только если `arr` -- массив:
```js
//+ run
alert( Array.isArray([1,2,3]) ); // true
alert( Array.isArray("not array")); // false
```
Но этот метод -- единственный в своём роде.
Других аналогичных, типа `Object.isObject`, `Date.isDate` -- нет.
## Оператор instanceof
Оператор `instanceof` позволяет проверить, создан ли объект данной функцией, причём работает для любых функций -- как встроенных, так и наших.
```js
//+ run
function User() {}
var user = new User();
alert( user instanceof User ); // true
```
Таким образом, `instanceof`, в отличие от `[[Class]]` и `typeof` может помочь выяснить тип для новых объектов, созданных нашими конструкторами.
Заметим, что оператор `instanceof` -- сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим, и затем вернёмся к `instanceof` в главе [](/instanceof).
## Утиная типизация
Альтернативный подход к типу -- "утиная типизация", которая основана на одной известной пословице: *"If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck (who cares what it really is)"*.
В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*.
Смысл утиной типизации -- в проверке необходимых методов и свойств.
Например, мы можем проверить, что объект -- массив, не вызывая `Array.isArray`, а просто уточнив наличие важного для нас метода, например `splice`:
```js
//+ run
var something = [1, 2, 3];
if (something.splice) {
alert( 'Это утка! То есть, массив!' );
}
```
Обратите внимание -- в `if` мы не вызываем метод `something.splice()`, а пробуем получить само свойство `something.splice`. Для массивов оно всегда есть и является функцией, т.е. даст в логическом контексте `true`.
Проверить на дату можно, определив наличие метода `getTime`:
```js
//+ run
var x = new Date();
if (x.getTime) {
alert( 'Дата!' );
alert( x.getTime() ); // работаем с датой
}
```
С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом.
Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле).
То есть, мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным.
[smart header="Проверка интерфейса"]
Если говорить словами "классического программирования", то "duck typing" -- это проверка реализации объектом требуемого интерфейса. Если реализует -- ок, используем его. Если нет -- значит это что-то другое.
[/smart]
## Пример полиморфной функции
Пример полиморфной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому:
```js
//+ run
function sayHi(who) {
if (Array.isArray(who)) {
who.forEach(sayHi);
} else {
alert( 'Привет, ' + who );
}
}
// Вызов с примитивным аргументом
sayHi("Вася"); // Привет, Вася
// Вызов с массивом
sayHi(["Саша", "Петя"]); // Привет, Саша... Петя
// Вызов с вложенными массивами - тоже работает!
sayHi(["Саша", "Петя", ["Маша", "Юля"]]); // Привет Саша..Петя..Маша..Юля
```
Проверку на массив в этом примере можно заменить на "утиную" -- нам ведь нужен только метод `forEach`:
```js
//+ run
function sayHi(who) {
if (who.forEach) { // если есть forEach
who.forEach(sayHi); // предполагаем, что он ведёт себя "как надо"
} else {
alert( 'Привет, ' + who );
}
}
```
## Итого
Для написания полиморфных (это удобно!) функций нам нужна проверка типов.
<ul>
<li>Для примитивов с ней отлично справляется оператор `typeof`.
У него две особенности:
<ol>
<li>Он считает `null` объектом, это внутренняя ошибка в языке.</li>
<li>Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.</li>
</ol>
</li>
<li>Для встроенных объектов мы можем получить тип из скрытого свойства `[[Class]]`, при помощи вызова `{}.toString.call(obj).slice(8, -1)`. Не работает для конструкторов, которые объявлены нами.
</li>
<li>Оператор `obj instanceof Func` проверяет, создан ли объект `obj` функцией `Func`, работает для любых конструкторов. Более подробно мы разберём его в главе [](/instanceof).</li>
<li>И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется "утиная типизация".</li>
</ul>

View file

@ -1,9 +1,13 @@
# Обратные ссылки \\n и $n
# Обратные ссылки: \n и $n
Скобочные группы можно не только получать в результате.
На скобочные группы можно ссылаться как в самом паттерне, так и в строке замены.
[cut]
Ссылки в строке замены мы уже видели: они имеют вид `$n`, где `n` -- это номер скобочной группы. Вместо `$n` подставляется содержимое соответствующей скобки:
## Группа в замене
Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы. Вместо `$n` подставляется содержимое соответствующей скобки:
```js
//+ run
@ -13,19 +17,26 @@ name = name.replace(/([а-яё]+) ([а-яё]+)/i, "$2, $1");
alert( name ); // Пушкин, Александр
```
К скобочной группе можно также обратиться в самом шаблоне.
## Группа в шаблоне
Рассмотрим это в реальном примере -- необходимо найти строку в кавычках. Эта строка может быть в одинарных кавычках <code class="subject">'...'</code> или в двойных <code class="subject">"..."</code> -- не важно, в каких именно, но открывающая и закрывающая кавычки должны быть одинаковыми.
Выше был пример использования содержимого групп в строке замены. Это удобно, когда нужно реорганизовать содержимое или создать новое с использованием старого.
Как такие строки искать? Регэксп <code class="pattern">`['"](.*?)['"]`</code> позволяет использовать разные кавычки, но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке <code class="subject">"She's the one"</code>:
Но к скобочной группе можно также обратиться в самом поисковом шаблоне, ссылкой вида `\номер`.
Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными <code class="subject">'...'</code> или двойными <code class="subject">"..."</code> -- и то и другое должно искаться корректно.
Как такие строки искать?
Можно в регэкспе предусмотреть произвольные кавычки: <code class="pattern">`['"](.*?)['"]`</code>. Такой регэксп найдёт строки вида <code class="match">"..."</code>, <code class="match">'...'</code>, но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке <code class="subject">"She's the one"</code>:
```js
//+ run
str = "He said:\"She's the one\"."
str = "He said:\"She's the one\".";
reg = /['"](.*?)['"]/g
reg = /['"](.*?)['"]/g;
alert(str.match(reg)) // "She'
// Результат не соответствует замыслу
alert( str.match(reg) ); // "She'
```
Как видно, регэксп нашёл открывающую кавычку <code class="match">"</code>, затем текст, вплоть до новой кавычки <code class="match">'</code>, которая закрывает соответствие.
@ -34,11 +45,11 @@ alert(str.match(reg)) // "She'
```js
//+ run
str = "He said:\"She's the one\"."
str = "He said:\"She's the one\".";
reg = /(['"])(.*?)\1/g
reg = /(['"])(.*?)\1/g;
alert(str.match(reg)) // "She's the one"
alert( str.match(reg) ); // "She's the one"
```
Теперь работает верно!
@ -46,7 +57,7 @@ alert(str.match(reg)) // "She's the one"
Обратим внимание на два нюанса:
<ul>
<li>В строке замены ссылка на первую скобочную группу выглядит как `$1`, а в шаблоне нужно использовать `\1`.</li>
<li>Чтобы обращаться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`, то есть `(?:['"])` не подошло бы.</li>
<li>Чтобы использовать скобочную группу в строке замены -- нужно использовать ссылку вида `$1`, а в шаблоне -- обратный слэш: `\1`.</li>
<li>Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации.</li>
</ul>

View file

@ -1,4 +1,4 @@
# Чёрная дыра бэктрекинга
# Чёрная дыра бэктрекинга [todo]
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже подвешивать браузер.

View file

@ -1,4 +1,4 @@
# Группы [todo]
# Скобочные группы
Часть шаблона может быть заключена в скобки <code class="pattern">(...)</code>. Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами".
@ -9,6 +9,9 @@
</ol>
[cut]
## Пример
В примере ниже, шаблон <code class="pattern">(go)+</code> находит один или более повторяющихся <code class="pattern">'go'</code>:
```js
@ -16,30 +19,57 @@
alert( 'Gogogo now!'.match(/(go)+/i ); // "Gogogo"
```
Без скобок, шаблон <code class="pattern">/go+/</code> означал бы <code class="subject">g</code>, после которого идёт одна или более <code class="subject">o</code>, например: <code class="match">goooo</code>.
Без скобок, шаблон <code class="pattern">/go+/</code> означал бы <code class="subject">g</code>, после которого идёт одна или более <code class="subject">o</code>, например: <code class="match">goooo</code>. А скобки "группируют" <code class="pattern">(go)</code> вместе.
**Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему, в том числе -- в шаблоне и строке замены.**
## Содержимое группы
Например, найти HTML-тег можно шаблоном <code class="pattern">&lt;.*?&gt;</code>. Скорее всего, после поиска мы захотим что-то сделать с результатом, и нас будет интересовать содержимое `<...>`.
Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему -- в шаблоне и строке замены и, конечно же, в результатах.
Для удобства заключим его в скобки: <code class="pattern">&lt;(.*?)&gt;</code>. Тогда содержимое скобок можно будет получить отдельно.
Например, найти HTML-тег можно шаблоном <code class="pattern">&lt;.*?&gt;</code>.
Используем метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match). В результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы, в данном случае -- только одна:
После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: <code class="pattern">&lt;(.*?)&gt;</code>. Тогда оно будет доступно отдельно.
При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне <code class="pattern">&lt;(.*?)&gt;</code> скобочная группа только одна:
```js
//+ run
var str = '<h1>Привет, мир!</h1>'
var reg = /<(.*?)>/
var str = '<h1>Привет, мир!</h1>';
var reg = /<(.*?)>/;
alert(str.match(reg)) // массив: <h1>, h1
alert( str.match(reg) ); // массив: <h1>, h1
```
Для поиска всех совпадений, как мы обсуждали ранее, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).
Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение <code class="match">&lt;h1&gt;</code>, а закрывающий <code class="match">&lt;/h1&gt;</code> не нашёл, поскольку без флага `/.../g` ищется только первое совпадение.
**Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.**
Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec):
Например, в строке <code class="subject">&lt;span class="my"&gt;</code> нас может интересовать отдельно тег `span` и, для примера, его первая буква.
```js
//+ run
var str = '<h1>Привет, мир!</h1>';
var reg = /<(.*?)>/g;
var match;
while ((match = reg.exec(str)) !== null) {
// сначала выведет первое совпадение: <h1>,h1
// затем выведет второе совпадение: </h1>,/h1
alert(match);
}
```
Теперь найдено оба совпадения <code class="pattern">&lt;(.*?)&gt;</code>, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае).
## Вложенные группы
Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.
Например, при поиске тега в <code class="subject">&lt;span class="my"&gt;</code> нас может интересовать:
<ol>
<li>Содержимое тега целиком: `span class="my"`.</li>
<li>В отдельную переменную для удобства хотелось бы поместить тег: `span`.</li>
<li>Также может быть удобно отдельно выделить атрибуты `class="my"`.</li>
</ol>
Добавим скобки в регулярное выражение:
@ -47,15 +77,18 @@ alert(str.match(reg)) // массив: <h1>, h1
//+ run
var str = '<span class="my">';
reg = /<(([a-z])[a-z0-9]*).*?>/;
reg = /<(([a-z]+)\s*([^>]*))>/;
alert( str.match(reg) ); // <span class="my">, span, s
```
Вот так выглядят скобочные группы:
<img src="groups.png">
На нулевом месте -- всегда совпадение полностью, далее -- группы. Их вложенность означает всего лишь, что группа 1 содержит группу 2. Нумерация всегда идёт слева направо, по открывающей скобке.
<img src="regexp-nested-groups.svg">
На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке.
В данном случае получилось, что группа 1 включает в себя содержимое групп 2 и 3. Это совершенно нормальная ситуация, которая возникает, когда нужно выделить что-то отдельное внутри большей группы.
**Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `undefined`).**
@ -87,40 +120,32 @@ alert( match[1] ); // undefined, для (z)? ничего нет
alert( match[2] ); // c
```
Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы <code class="pattern">(z)?</code> в ней ничего нет.
Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы <code class="pattern">(z)?</code> в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`.
**Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало <code class="pattern">?:</code>**
## Исключение из запоминания через ?:
Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно. Тогда мы просто ставим сразу после открывающей скобки `?:`
Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно.
В примере ниже есть скобочная группа <code class="pattern">(go-?)</code>, которая сама по себе не интересна, но входит в результаты:
Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало <code class="pattern">?:</code>.
Например, мы хотим найти <code class="pattern">(go)+</code>, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим.
Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: <code class="pattern">(?:go)+</code>.
Например:
```js
//+ run
var str = "Go-go John!";
var str = "Gogo John!";
*!*
var reg = /(go-?)* (\w+)/i;
var reg = /(?:go)+ (\w+)/i;
*/!*
var result = str.match(reg);
alert( result[0] ); // Go-go John
alert( result[1] ); // go
alert( result[2] ); // John
```
Исключим её из запоминаемых:
```js
//+ run
var str = "Go-go John!";
*!*
var reg = /(?:go-?)* (\w+)/i;
*/!*
var result = str.match(reg);
alert( result[0] ); // Go-go John
alert( result.length ); // 2
alert( result[1] ); // John
```
В примере выше массив результатов имеет длину `2` и содержит только полное совпадение и результат <code class="pattern">(\w+)</code>. Это удобно в тех случаях, когда содержимое скобок нас не интересует.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="320px" height="130px" viewBox="0 0 320 130" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
<title>regexp-nested-groups.svg</title>
<desc>Created with bin/sketchtool.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="regexp-nested-groups.svg" sketch:type="MSArtboardGroup">
<text id="&lt;(([a-z]+)\s*([^&gt;]*)" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal">
<tspan x="20" y="75" fill="#8A704D">&lt;</tspan>
<tspan x="33.1953125" y="75" fill="#DC2022">((</tspan>
<tspan x="59.5859375" y="75" fill="#8A704D">[a-z]+</tspan>
<tspan x="138.757812" y="75" fill="#DC2022">)</tspan>
<tspan x="151.953125" y="75" fill="#8A704D">\s*</tspan>
<tspan x="191.539062" y="75" fill="#DC2022">(</tspan>
<tspan x="204.734375" y="75" fill="#8A704D">[^&gt;]*</tspan>
<tspan x="270.710938" y="75" fill="#D0011B">))</tspan>
<tspan x="297.101562" y="75" fill="#8A704D">&gt;</tspan>
</text>
<path d="M42.5,45.6458333 L42.5,29.3541667" id="Line" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M290.5,45.6458333 L290.5,29.3541667" id="Line-2" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M42.5,28.5 L290.5,28.5" id="Line" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M52.5,101.645833 L52.5,85.3541667" id="Line-5" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M145.5,101.645833 L145.5,85.3541667" id="Line-4" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M52.5,102.5 L145.5,102.5" id="Line-3" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<text id="1" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#D0011B">
<tspan x="29" y="44">1</tspan>
</text>
<text id="span-class=&quot;my&quot;" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#417505">
<tspan x="82" y="26">span class="my"</tspan>
</text>
<text id="2" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#D0011B">
<tspan x="40" y="101">2</tspan>
</text>
<text id="span" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#417505">
<tspan x="73" y="117">span</tspan>
</text>
<path d="M197.5,101.645833 L197.5,85.3541667" id="Line-8" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M277.5,101.645833 L277.5,85.3541667" id="Line-7" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M197.5,102.5 L277.5,102.5" id="Line-6" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<text id="3" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#D0011B">
<tspan x="185" y="101">3</tspan>
</text>
<text id="class=&quot;my&quot;" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#417505">
<tspan x="185" y="119">class="my"</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,104 @@
# Секретное свойство [[Class]]
Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`.
Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе.
Оно взято в квадратные скобки, так как это свойство -- внутреннее. Явно получить его нельзя, но можно прочитать его "в обход", воспользовавшись методом `toString` из `Object`.
[cut]
## Получение [[Class]]
Вернёмся к примеру, который видели раньше:
```js
//+ run
var obj = {};
alert( obj ); // [object Object]
```
**В выводе стандартного `toString` для объектов внутри `[object ...]` указано как раз значение `[[Class]]`.**
Для обычного объекта это как раз и есть `"Object"`, но если бы такой `toString` запустить для даты, то будет `[object Date]`, для массивов -- `[object Array]` и т.п.
К сожалению или к счастью, но большинство встроенных объектов в JavaScript имеют свой собственный метод `toString`: для массивов он выводит элементы через запятую, для дат -- строчное представление и так далее.
То есть, просто вызов `[1,2,3].toString()` вернёт нам `1,2,3` и никакой информации про `[[Class]]`.
Поэтому для получения `[[Class]]` мы одолжим функцию `toString` у стандартного объекта и запустим её в контексте тех значений, для которых нужно получить тип. В этом нам поможет метод `call`:
```js
//+ run
var toClass = {}.toString; // (1)
var arr = [1, 2];
alert( toClass.call(arr) ); // (2) [object Array]
var date = new Date;
alert( toClass.call(date) ); // [object Date]
var type = toClass.call(date).slice(8, -1); // (3)
alert( type ); // Date
```
Разберем происходящее более подробно.
<ol>
<li>Можно переписать эту строку в две:
```js
var obj = {};
var toClass = obj.toString;
```
Иначе говоря, мы создаём пустой объект `{}` и копируем ссылку на его метод `toString` в переменную `toClass`.
**Для получения `[[Class]]` нужна именно внутренняя реализация `toString` стандартного объекта `Object`, другая не подойдёт.**</li>
<li>Вызываем скопированный метод в контексте нужного объекта `obj`.
Мы могли бы поступить проще -- одолжить метод под другим названием:
```js
//+ run
var arr = [1, 2];
arr.toClass = {}.toString;
alert( arr.toClass() ); // [object Array]
```
...Но зачем копировать лишнее свойство в объект? Синтаксис `toClass.call(arr)` делает то же самое, поэтому используем его.
</li>
<li>Всё, класс получен. При желании можно убрать обёртку `[object ...]`, взяв подстроку вызовом `slice(8,-1)`.</li>
</ol>
Метод также можно использовать с примитивами:
```js
//+ run
alert( {}.toString.call(123) ); // [object Number]
alert( {}.toString.call("строка") ); // [object String]
```
[warn header="Вызов `{}.toString` в консоли может выдать ошибку"]
При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку `{}.toString.call(...)` -- будет ошибка. С другой стороны, вызов `alert( {}.toString... )` -- работает.
Эта ошибка возникает потому, что фигурные скобки `{ }` в основном потоке кода интерпретируются как блок. Интерпретатор читает `{}.toString.call(...)` так:
```js
//+ no-beautify
{ } // пустой блок кода
.toString.call(...) // а что это за точка в начале? не понимаю, ошибка!
```
Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки `( {}.toString... )` тоже сработает нормально.
[/warn]
## Итого
<ul>
<li>Свойство `[[Class]]` позволяет получить тип для встроенных объектов. Далее мы будем рассматривать создание своих объектов через функцию-конструктор, с ними `[[Class]]` не работает.</li>
<li>Для доступа к `[[Class]]` используется `{}.toString.call(obj).slice(8, -1)`.</li>
</ul>
Обычно в JavaScript используется "утиная" типизация. Свойство `[[Class]]` -- самое надёжное средство проверки типа встроенных объектов, но обычно утиной типизации вполне хватает.

Binary file not shown.