From c108f03596bf9ddd368ecd8c46d748da8c94dd4d Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Sat, 21 Mar 2015 16:48:12 +0300 Subject: [PATCH] refactor types --- 1-js/2-first-steps/7-types-intro/article.md | 63 +++++- .../_js.view/solution.js | 28 --- .../_js.view/test.js | 18 -- .../1-format-date-polymorphic/solution.md | 55 ------ .../1-format-date-polymorphic/task.md | 26 --- .../12-typeof-duck-typing/article.md | 184 ------------------ .../5-closures-module/article.md | 5 +- .../4-descriptors-getters-setters/article.md | 12 +- 1-js/6-objects-more/7-bind/article.md | 12 +- 1-js/7-js-misc/1-class-property/article.md | 104 ---------- 10 files changed, 69 insertions(+), 438 deletions(-) delete mode 100644 1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/solution.js delete mode 100644 1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/test.js delete mode 100644 1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/solution.md delete mode 100644 1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/task.md delete mode 100644 1-js/4-data-structures/12-typeof-duck-typing/article.md delete mode 100644 1-js/7-js-misc/1-class-property/article.md diff --git a/1-js/2-first-steps/7-types-intro/article.md b/1-js/2-first-steps/7-types-intro/article.md index 6f7a242c..5ca4feff 100644 --- a/1-js/2-first-steps/7-types-intro/article.md +++ b/1-js/2-first-steps/7-types-intro/article.md @@ -1,4 +1,4 @@ -# Шесть типов данных +# Шесть типов данных, typeof В JavaScript существует несколько основных типов данных. @@ -104,12 +104,65 @@ alert( x ); // "undefined" Первые 5 типов называют *"примитивными"*. -Особняком стоит шестой тип: *"объекты"*. К нему относятся, например, даты, функции, он используется для коллекций данных и для объявления более сложных сущностей. +Особняком стоит шестой тип: *"объекты"*. -Позже, в главе [про объекты](/object) мы вернёмся к этому типу и рассмотрим его принципиальные отличия от примитивов. +Он используется для коллекций данных и для объявления более сложных сущностей. + +Объявляются объекты при помощи фигурных скобок `{...}`, например: + +```js +var user = { name: "Вася" }; +``` + +Мы подробно разберём способы объявления объектов и, вообще, работу с объектами, позже, в главе [](/object). + +## Оператор typeof [#type-typeof] + +Оператор `typeof` возвращает тип аргумента. + +У него есть два синтаксиса: со скобками и без: +
    +
  1. Синтаксис оператора: `typeof x`.
  2. +
  3. Синтаксис функции: `typeof(x)`.
  4. +
+ +Работают они одинаково, но первый синтаксис короче. + +**Результатом `typeof` является строка, содержащая тип:** + +```js +typeof undefined // "undefined" + +typeof 0 // "number" + +typeof true // "boolean" + +typeof "foo" // "string" + +typeof {} // "object" + +*!* +typeof null // "object" (1) +*/!* + +*!* +typeof function(){} // "function" (2) +*/!* +``` + +Последние две строки помечены, потому что `typeof` ведет себя в них по-особому. + +
    +
  1. Результат `typeof null == "object"` -- это официально признанная ошибка в языке, которая сохраняется для совместимости. На самом деле `null` -- это не объект, а отдельный тип данных.
  2. +
  3. Функции мы пройдём чуть позже. Пока лишь заметим, что функции не являются отдельным базовым типом в JavaScript, а подвидом объектов. Но `typeof` выделяет функции отдельно, возвращая для них `"function"`. На практике это весьма удобно, так как позволяет легко определить функцию.
  4. +
+ +К работе с типами мы также вернёмся более подробно в будущем, после изучения основных структур данных. ## Итого -Есть 5 "примитивных" типов: `number`, `string`, `boolean`, `null`, `undefined` и 6-й тип -- объекты `object`. +Есть 5 "примитивных" типов: `number`, `string`, `boolean`, `null`, `undefined` и 6-й тип -- объекты `object`. -Очень скоро мы изучим их во всех деталях. \ No newline at end of file +Очень скоро мы изучим их во всех деталях. + +Оператор `typeof x` позволяет выяснить, какой тип находится в `x`, возвращая его в виде строки. \ No newline at end of file diff --git a/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/solution.js b/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/solution.js deleted file mode 100644 index 5e5198e7..00000000 --- a/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/solution.js +++ /dev/null @@ -1,28 +0,0 @@ -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 (date.length) { // есть длина, но не строка - значит массив - 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/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/test.js b/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/test.js deleted file mode 100644 index a458b62f..00000000 --- a/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/_js.view/test.js +++ /dev/null @@ -1,18 +0,0 @@ -describe("formatDate", function() { - it("читает дату вида гггг-мм-дд из строки", function() { - assert.equal(formatDate('2011-10-02'), "02.10.11"); - }); - - it("читает дату из числа 1234567890 (миллисекунды)", function() { - assert.equal(formatDate(1234567890), "14.02.09"); - }); - - it("читает дату из массива вида [гггг, м, д]", function() { - assert.equal(formatDate([2014, 0, 1]), "01.01.14"); - }); - - it("читает дату из объекта Date", function() { - assert.equal(formatDate(new Date(2014, 0, 1)), "01.01.14"); - }); - -}); \ No newline at end of file diff --git a/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/solution.md b/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/solution.md deleted file mode 100644 index ddf7c0aa..00000000 --- a/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/solution.md +++ /dev/null @@ -1,55 +0,0 @@ -Для определения примитивного типа строка/число подойдет оператор [typeof](#type-typeof). - -Примеры его работы: - -```js -//+ run -alert( typeof 123 ); // "number" -alert( typeof "строка" ); // "string" -alert( typeof new Date() ); // "object" -alert( typeof [] ); // "object" -``` - -Оператор `typeof` не умеет различать разные типы объектов, они для него все на одно лицо: `"object"`. Поэтому он не сможет отличить `Date` от `Array`. - -Используем для них утиную типизацию: - -Функция: - -```js -//+ run -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 (date.length) { // есть длина, но не строка - значит массив - 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; -} - -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/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/task.md b/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/task.md deleted file mode 100644 index 6dcf7974..00000000 --- a/1-js/4-data-structures/12-typeof-duck-typing/1-format-date-polymorphic/task.md +++ /dev/null @@ -1,26 +0,0 @@ -# Полиморфная функция formatDate - -[importance 5] - -Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`. - -Ее первый аргумент должен содержать дату в одном из видов: -
    -
  1. Как объект `Date`.
  2. -
  3. Как строку в формате `yyyy-mm-dd`.
  4. -
  5. Как число *секунд* с `01.01.1970`.
  6. -
  7. Как массив `[гггг, мм, дд]`, месяц начинается с нуля
  8. -
-Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат. - -Пример работы: - -```js -function formatDate(date) { /* ваш код */ } - -alert( formatDate('2011-10-02') ); // 02.10.11 -alert( formatDate(1234567890) ); // 14.02.09 -alert( formatDate([2014, 0, 1]) ); // 01.01.14 -alert( formatDate(new Date(2014, 0, 1)) ); // 01.01.14 -``` - diff --git a/1-js/4-data-structures/12-typeof-duck-typing/article.md b/1-js/4-data-structures/12-typeof-duck-typing/article.md deleted file mode 100644 index 7815b69c..00000000 --- a/1-js/4-data-structures/12-typeof-duck-typing/article.md +++ /dev/null @@ -1,184 +0,0 @@ -# Полиморфизм, typeof и утиная типизация - -В этой главе мы рассмотрим, как создавать *полиморфные* функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты. - -Для реализации такой возможности нужен способ определить тип переменной. - -[cut] -Как мы знаем, существует несколько *примитивных типов*: -
-
`null`
-
Специальный тип, содержит только значение `null`.
-
`undefined`
-
Специальный тип, содержит только значение `undefined`.
-
`number`
-
Числа: `0`, `3.14`, а также значения `NaN` и `Infinity`
-
`boolean`
-
`true`, `false`.
-
`string`
-
Строки, такие как `"Мяу"` или пустая строка `""`.
-
- -Все остальные значения, включая даты и массивы, являются объектами. - -## Оператор typeof [#type-typeof] - -Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса: со скобками и без: -
    -
  1. Синтаксис оператора: `typeof x`.
  2. -
  3. Синтаксис функции: `typeof(x)`.
  4. -
- -Работают они одинаково, но первый синтаксис короче. - -**Результатом `typeof` является строка, содержащая тип:** - -```js -typeof undefined // "undefined" - -typeof 0 // "number" - -typeof true // "boolean" - -typeof "foo" // "string" - -typeof {} // "object" - -*!* -typeof null // "object" -*/!* - -function f() { /* ... */ } -typeof f // "function" -*/!* -``` - -Последние две строки помечены, потому что `typeof` ведет себя в них по-особому. - -
    -
  1. Результат `typeof null == "object"` -- это официально признанная ошибка в языке, которая сохраняется для совместимости. - -На самом деле `null` -- это не объект, а примитив. Это сразу видно, если попытаться присвоить ему свойство: - -```js -//+ run -var x = null; -x.prop = 1; // ошибка, т.к. нельзя присвоить свойство примитиву -``` - -
  2. -
  3. Для функции `f` значением `typeof f` является `"function"`. На самом деле функция не является отдельным базовым типом в JavaScript, все функции являются объектами, но такое выделение функций на практике удобно, так как позволяет легко определить функцию.
  4. -
- -**Оператор `typeof` надежно работает с примитивными типами, кроме `null`, а также с функциями. Но обычные объекты, массивы и даты для `typeof` все на одно лицо, они имеют тип `'object'`:** - -```js -//+ run -alert( typeof {} ); // 'object' -alert( typeof [] ); // 'object' -alert( typeof new Date ); // 'object' -``` - -Поэтому различить их при помощи `typeof` нельзя. - -## Утиная типизация - -Основная проблема `typeof` -- неумение различать объекты, кроме функций. Но есть и другой способ проверки типа. - -Так называемая "утиная типизация" основана на одной известной пословице: *"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)"*. - -В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*. - -Смысл утиной типизации -- в проверке необходимых методов и свойств. - -Например, у нас функция работает с массивами. Мы можем проверить, что объект -- массив, уточнив наличие метода `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( 'Дата!' ); -} -``` - -С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом. - -Но как раз в этом и есть смысл утиной типизации: если объект похож на массив, у него есть методы массива, то будем работать с ним как с массивом (какая разница, что это на самом деле). - -[smart header="Метод `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`) -- нет. - -Если нужно удостовериться, что мы получили именно массив, а не нечто похожее на него -- можно использовать `Array.isArray`. Но при этом нужно отдавать себе отчёт, что этим мы одновременно ограничиваем применимость кода: "похожие на массив" данные теперь обрабатываться не будут. Решение зависит от конкретной ситуации. -[/smart] - - - - - -## Полиморфизм - -Пример полиморфной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому: - -```js -//+ run -function sayHi(who) { - - if (Array.isArray(who)) { - who.forEach(sayHi); - } else { - alert( 'Привет, ' + who ); - } -} - -// Вызов с примитивным аргументом -sayHi("Вася"); // Привет, Вася - -// Вызов с массивом -sayHi(["Саша", "Петя"]); // Привет, Саша... Петя - -// Вызов с вложенными массивами - тоже работает! -sayHi(["Саша", "Петя", ["Маша", "Юля"]]); // Привет Саша..Петя..Маша..Юля -``` - -Здесь используется не "duck typing", а "жёсткая" проверка на массив. Можно было бы и поступить мягче -- проверить только наличие метода `forEach`: -```js -if (who.forEach) { - ... -} -``` - -## Итого - -Для написания полиморфных (это удобно!) функций нам нужна проверка типов. - -Для примитивов с ней отлично справляется оператор `typeof`. - -У него две особенности: -
    -
  1. Он считает `null` объектом, это внутренняя ошибка в языке.
  2. -
  3. Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.
  4. -
- -Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся использовать. - diff --git a/1-js/5-functions-closures/5-closures-module/article.md b/1-js/5-functions-closures/5-closures-module/article.md index 95958f90..134fa7b8 100644 --- a/1-js/5-functions-closures/5-closures-module/article.md +++ b/1-js/5-functions-closures/5-closures-module/article.md @@ -198,7 +198,7 @@ function work() { Здесь нам не важно, какие, нас интересует именно как описана эта библиотека, как в ней применяется приём "модуль". -Вот выдержка из исходного файла: +Вот примерная выдержка из исходного файла: ```js //+ run no-beautify @@ -221,8 +221,7 @@ function work() { // код функции size, пока что доступен только внутри */!* function size(collection) { - var length = collection ? collection.length : 0; - return typeof length == 'number' ? length : Object.keys(collection).length; + return Object.keys(collection).length; } *!* diff --git a/1-js/6-objects-more/4-descriptors-getters-setters/article.md b/1-js/6-objects-more/4-descriptors-getters-setters/article.md index eb052300..f1453e79 100644 --- a/1-js/6-objects-more/4-descriptors-getters-setters/article.md +++ b/1-js/6-objects-more/4-descriptors-getters-setters/article.md @@ -243,9 +243,7 @@ alert( pete.age ); // 25 С обычными свойствами в коде меньше букв, они удобны, причины использовать функции пока нет. -...Но рано или поздно может произойти что-то, что потребует более сложной логики. - -Например, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`: +...Но рано или поздно могут произойти изменения. Например, в `User` может стать более целесообразно вместо возраста `age` хранить дату рождения `birthday`: ```js function User(name, birthday) { @@ -263,12 +261,13 @@ var pete = new User("Петя", new Date(1987, 6, 1)); Добавление `get`-функции `age` позволяет обойти проблему легко и непринуждённо: ```js -//+ run +//+ run no-beautify function User(name, birthday) { this.name = name; this.birthday = birthday; *!* + // age будет высчитывать возраст по birthday Object.defineProperty(this, "age", { get: function() { var todayYear = new Date().getFullYear(); @@ -280,10 +279,11 @@ function User(name, birthday) { var pete = new User("Петя", new Date(1987, 6, 1)); -alert( pete.age ); // получает возраст из даты рождения +alert( pete.birthday ); // и дата рождения доступна +alert( pete.age ); // и возраст ``` -Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя полную совместимость. +Таким образом, `defineProperty` позволяет нам начать с обычных свойств, а в будущем, при необходимости, можно в любой момент заменить их на функции, реализующие более сложную логику. ## Другие методы работы со свойствами diff --git a/1-js/6-objects-more/7-bind/article.md b/1-js/6-objects-more/7-bind/article.md index 844d5c8d..4af65892 100644 --- a/1-js/6-objects-more/7-bind/article.md +++ b/1-js/6-objects-more/7-bind/article.md @@ -415,15 +415,9 @@ function mul(a, b) { return a * b; }; -function ask(question, correctAnswer, ok, fail) { - var result; - if (typeof correctAnswer == 'boolean') { - result = confirm(question); - } else { - result = prompt(question, ''); - } - - if (result == correctAnswer) ok() +function ask(question, answer, ok, fail) { + var result = prompt(question, ''); + if (result.toLowerCase() == answer.toLowerCase()) ok(); else fail(); } diff --git a/1-js/7-js-misc/1-class-property/article.md b/1-js/7-js-misc/1-class-property/article.md deleted file mode 100644 index 34572c52..00000000 --- a/1-js/7-js-misc/1-class-property/article.md +++ /dev/null @@ -1,104 +0,0 @@ -# Секретное свойство [[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] - -## Итого - - - -Обычно в JavaScript используется "утиная" типизация. Свойство `[[Class]]` -- самое надёжное средство проверки типа встроенных объектов, но обычно утиной типизации вполне хватает. \ No newline at end of file