refactor types
This commit is contained in:
parent
0712ddc698
commit
c108f03596
10 changed files with 69 additions and 438 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# Полиморфная функция 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
|
||||
```
|
||||
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
# Полиморфизм, typeof и утиная типизация
|
||||
|
||||
В этой главе мы рассмотрим, как создавать *полиморфные* функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.
|
||||
|
||||
Для реализации такой возможности нужен способ определить тип переменной.
|
||||
|
||||
[cut]
|
||||
Как мы знаем, существует несколько *примитивных типов*:
|
||||
<dl>
|
||||
<dt>`null`</dt>
|
||||
<dd>Специальный тип, содержит только значение `null`.</dd>
|
||||
<dt>`undefined`</dt>
|
||||
<dd>Специальный тип, содержит только значение `undefined`.</dd>
|
||||
<dt>`number`</dt>
|
||||
<dd>Числа: `0`, `3.14`, а также значения `NaN` и `Infinity`</dd>
|
||||
<dt>`boolean`</dt>
|
||||
<dd>`true`, `false`.</dd>
|
||||
<dt>`string`</dt>
|
||||
<dd>Строки, такие как `"Мяу"` или пустая строка `""`.</dd>
|
||||
</dl>
|
||||
|
||||
Все остальные значения, включая даты и массивы, являются объектами.
|
||||
|
||||
## Оператор typeof [#type-typeof]
|
||||
|
||||
Оператор `typeof` возвращает тип аргумента. У него есть два синтаксиса: со скобками и без:
|
||||
<ol>
|
||||
<li>Синтаксис оператора: `typeof x`.</li>
|
||||
<li>Синтаксис функции: `typeof(x)`.</li>
|
||||
</ol>
|
||||
|
||||
Работают они одинаково, но первый синтаксис короче.
|
||||
|
||||
**Результатом `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` ведет себя в них по-особому.
|
||||
|
||||
<ol>
|
||||
<li>Результат `typeof null == "object"` -- это официально признанная ошибка в языке, которая сохраняется для совместимости.
|
||||
|
||||
На самом деле `null` -- это не объект, а примитив. Это сразу видно, если попытаться присвоить ему свойство:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var x = null;
|
||||
x.prop = 1; // ошибка, т.к. нельзя присвоить свойство примитиву
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>Для функции `f` значением `typeof f` является `"function"`. На самом деле функция не является отдельным базовым типом в JavaScript, все функции являются объектами, но такое выделение функций на практике удобно, так как позволяет легко определить функцию.</li>
|
||||
</ol>
|
||||
|
||||
**Оператор `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`.
|
||||
|
||||
У него две особенности:
|
||||
<ol>
|
||||
<li>Он считает `null` объектом, это внутренняя ошибка в языке.</li>
|
||||
<li>Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.</li>
|
||||
</ol>
|
||||
|
||||
Там, где нужно различать объекты, обычно используется утиная типизация, то есть мы смотрим, есть ли в объекте нужный метод, желательно -- тот, который мы собираемся использовать.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue