diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js new file mode 100644 index 00000000..106b9ca8 --- /dev/null +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/solution.js @@ -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; +} \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/test.js b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/test.js new file mode 100644 index 00000000..a458b62f --- /dev/null +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/_js.view/test.js @@ -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"); + }); + +}); \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md new file mode 100644 index 00000000..878033e9 --- /dev/null +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/solution.md @@ -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`. Если он неверен, значит у нас дата. \ No newline at end of file diff --git a/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md new file mode 100644 index 00000000..6dcf7974 --- /dev/null +++ b/1-js/7-js-misc/1-class-instanceof/1-format-date-polymorphic/task.md @@ -0,0 +1,26 @@ +# Полиморфная функция formatDate + +[importance 5] + +Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`. + +Ее первый аргумент должен содержать дату в одном из видов: +
    +
  1. Как объект `Date`.
  2. +
  3. Как строку в формате `yyyy-mm-dd`.
  4. +
  5. Как число *секунд* с `01.01.1970`.
  6. +
  7. Как массив `[гггг, мм, дд]`, месяц начинается с нуля
  8. +
+Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат. + +Пример работы: + +```js +function formatDate(date) { /* ваш код */ } + +alert( formatDate('2011-10-02') ); // 02.10.11 +alert( formatDate(1234567890) ); // 14.02.09 +alert( formatDate([2014, 0, 1]) ); // 01.01.14 +alert( formatDate(new Date(2014, 0, 1)) ); // 01.01.14 +``` + diff --git a/1-js/7-js-misc/1-class-instanceof/article.md b/1-js/7-js-misc/1-class-instanceof/article.md new file mode 100644 index 00000000..f84e41f6 --- /dev/null +++ b/1-js/7-js-misc/1-class-instanceof/article.md @@ -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 ); + } +} +``` + +## Итого + +Для написания полиморфных (это удобно!) функций нам нужна проверка типов. + + + diff --git a/10-regular-expressions-javascript/10-regexp-backreferences/article.md b/10-regular-expressions-javascript/10-regexp-backreferences/article.md index bb8e3a82..2fd40485 100644 --- a/10-regular-expressions-javascript/10-regexp-backreferences/article.md +++ b/10-regular-expressions-javascript/10-regexp-backreferences/article.md @@ -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 ); // Пушкин, Александр ``` -К скобочной группе можно также обратиться в самом шаблоне. +## Группа в шаблоне -Рассмотрим это в реальном примере -- необходимо найти строку в кавычках. Эта строка может быть в одинарных кавычках '...' или в двойных "..." -- не важно, в каких именно, но открывающая и закрывающая кавычки должны быть одинаковыми. +Выше был пример использования содержимого групп в строке замены. Это удобно, когда нужно реорганизовать содержимое или создать новое с использованием старого. -Как такие строки искать? Регэксп `['"](.*?)['"]` позволяет использовать разные кавычки, но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке "She's the one": +Но к скобочной группе можно также обратиться в самом поисковом шаблоне, ссылкой вида `\номер`. + +Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными '...' или двойными "..." -- и то и другое должно искаться корректно. + +Как такие строки искать? + +Можно в регэкспе предусмотреть произвольные кавычки: `['"](.*?)['"]`. Такой регэксп найдёт строки вида "...", '...', но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке "She's the one": ```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' ``` Как видно, регэксп нашёл открывающую кавычку ", затем текст, вплоть до новой кавычки ', которая закрывает соответствие. @@ -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" Обратим внимание на два нюанса: diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md b/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md index a036a6c6..455ad673 100644 --- a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md +++ b/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md @@ -1,4 +1,4 @@ -# Чёрная дыра бэктрекинга +# Чёрная дыра бэктрекинга [todo] Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже подвешивать браузер. diff --git a/10-regular-expressions-javascript/9-regexp-groups/article.md b/10-regular-expressions-javascript/9-regexp-groups/article.md index 192401ae..77ecde32 100644 --- a/10-regular-expressions-javascript/9-regexp-groups/article.md +++ b/10-regular-expressions-javascript/9-regexp-groups/article.md @@ -1,4 +1,4 @@ -# Группы [todo] +# Скобочные группы Часть шаблона может быть заключена в скобки (...). Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами". @@ -9,6 +9,9 @@ [cut] + +## Пример + В примере ниже, шаблон (go)+ находит один или более повторяющихся 'go': ```js @@ -16,30 +19,57 @@ alert( 'Gogogo now!'.match(/(go)+/i ); // "Gogogo" ``` -Без скобок, шаблон /go+/ означал бы g, после которого идёт одна или более o, например: goooo. +Без скобок, шаблон /go+/ означал бы g, после которого идёт одна или более o, например: goooo. А скобки "группируют" (go) вместе. -**Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему, в том числе -- в шаблоне и строке замены.** +## Содержимое группы -Например, найти HTML-тег можно шаблоном <.*?>. Скорее всего, после поиска мы захотим что-то сделать с результатом, и нас будет интересовать содержимое `<...>`. +Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему -- в шаблоне и строке замены и, конечно же, в результатах. -Для удобства заключим его в скобки: <(.*?)>. Тогда содержимое скобок можно будет получить отдельно. +Например, найти HTML-тег можно шаблоном <.*?>. -Используем метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match). В результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы, в данном случае -- только одна: +После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: <(.*?)>. Тогда оно будет доступно отдельно. + +При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне <(.*?)> скобочная группа только одна: ```js //+ run -var str = '

Привет, мир!

' -var reg = /<(.*?)>/ +var str = '

Привет, мир!

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

, h1 +alert( str.match(reg) ); // массив:

, 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`. В примере выше он нашёл только первое совпадение <h1>, а закрывающий </h1> не нашёл, поскольку без флага `/.../g` ищется только первое совпадение. -**Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.** +Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec): -Например, в строке <span class="my"> нас может интересовать отдельно тег `span` и, для примера, его первая буква. +```js +//+ run +var str = '

Привет, мир!

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

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

,/h1 + alert(match); +} +``` + +Теперь найдено оба совпадения <(.*?)>, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае). + +## Вложенные группы +Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо. + +Например, при поиске тега в <span class="my"> нас может интересовать: + +
    +
  1. Содержимое тега целиком: `span class="my"`.
  2. +
  3. В отдельную переменную для удобства хотелось бы поместить тег: `span`.
  4. +
  5. Также может быть удобно отдельно выделить атрибуты `class="my"`.
  6. +
Добавим скобки в регулярное выражение: @@ -47,15 +77,18 @@ alert(str.match(reg)) // массив:

, h1 //+ run var str = ''; -reg = /<(([a-z])[a-z0-9]*).*?>/; +reg = /<(([a-z]+)\s*([^>]*))>/; alert( str.match(reg) ); // , span, s ``` Вот так выглядят скобочные группы: - -На нулевом месте -- всегда совпадение полностью, далее -- группы. Их вложенность означает всего лишь, что группа 1 содержит группу 2. Нумерация всегда идёт слева направо, по открывающей скобке. + + +На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке. + +В данном случае получилось, что группа 1 включает в себя содержимое групп 2 и 3. Это совершенно нормальная ситуация, которая возникает, когда нужно выделить что-то отдельное внутри большей группы. **Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `undefined`).** @@ -87,40 +120,32 @@ alert( match[1] ); // undefined, для (z)? ничего нет alert( match[2] ); // c ``` -Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы (z)? в ней ничего нет. +Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы (z)? в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`. -**Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало ?:** +## Исключение из запоминания через ?: -Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно. Тогда мы просто ставим сразу после открывающей скобки `?:` +Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно. -В примере ниже есть скобочная группа (go-?), которая сама по себе не интересна, но входит в результаты: +Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало ?:. + + +Например, мы хотим найти (go)+, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим. + +Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: (?:go)+. + +Например: ```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` и содержит только полное совпадение и результат (\w+). Это удобно в тех случаях, когда содержимое скобок нас не интересует. diff --git a/10-regular-expressions-javascript/9-regexp-groups/groups.png b/10-regular-expressions-javascript/9-regexp-groups/groups.png deleted file mode 100644 index f0cd8c8e..00000000 Binary files a/10-regular-expressions-javascript/9-regexp-groups/groups.png and /dev/null differ diff --git a/10-regular-expressions-javascript/9-regexp-groups/regexp-nested-groups.svg b/10-regular-expressions-javascript/9-regexp-groups/regexp-nested-groups.svg new file mode 100644 index 00000000..37961bf4 --- /dev/null +++ b/10-regular-expressions-javascript/9-regexp-groups/regexp-nested-groups.svg @@ -0,0 +1,49 @@ + + + + regexp-nested-groups.svg + Created with bin/sketchtool. + + + + + < + (( + [a-z]+ + ) + \s* + ( + [^>]* + )) + > + + + + + + + + + 1 + + + span class="my" + + + 2 + + + span + + + + + + 3 + + + class="my" + + + + \ No newline at end of file diff --git a/archive/1-class-property/article.md b/archive/1-class-property/article.md new file mode 100644 index 00000000..34572c52 --- /dev/null +++ b/archive/1-class-property/article.md @@ -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 +``` + +Разберем происходящее более подробно. + +
    +
  1. Можно переписать эту строку в две: + +```js +var obj = {}; +var toClass = obj.toString; +``` + +Иначе говоря, мы создаём пустой объект `{}` и копируем ссылку на его метод `toString` в переменную `toClass`. + +**Для получения `[[Class]]` нужна именно внутренняя реализация `toString` стандартного объекта `Object`, другая не подойдёт.**
  2. +
  3. Вызываем скопированный метод в контексте нужного объекта `obj`. + +Мы могли бы поступить проще -- одолжить метод под другим названием: + +```js +//+ run +var arr = [1, 2]; +arr.toClass = {}.toString; + +alert( arr.toClass() ); // [object Array] +``` + +...Но зачем копировать лишнее свойство в объект? Синтаксис `toClass.call(arr)` делает то же самое, поэтому используем его. +
  4. +
  5. Всё, класс получен. При желании можно убрать обёртку `[object ...]`, взяв подстроку вызовом `slice(8,-1)`.
  6. +
+ +Метод также можно использовать с примитивами: + +```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] + +## Итого + +
    +
  • Свойство `[[Class]]` позволяет получить тип для встроенных объектов. Далее мы будем рассматривать создание своих объектов через функцию-конструктор, с ними `[[Class]]` не работает.
  • +
  • Для доступа к `[[Class]]` используется `{}.toString.call(obj).slice(8, -1)`.
  • +
+ +Обычно в JavaScript используется "утиная" типизация. Свойство `[[Class]]` -- самое надёжное средство проверки типа встроенных объектов, но обычно утиной типизации вполне хватает. \ No newline at end of file diff --git a/figures.sketch b/figures.sketch index 264784b2..efa5014c 100644 Binary files a/figures.sketch and b/figures.sketch differ