Skip to content

Commit 9122b13

Browse files
committed
renovations
1 parent c108f03 commit 9122b13

File tree

12 files changed

+576
-48
lines changed

12 files changed

+576
-48
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
function formatDate(date) {
2+
if (typeof date == 'number') {
3+
// перевести секунды в миллисекунды и преобразовать к Date
4+
date = new Date(date * 1000);
5+
} else if (typeof date == 'string') {
6+
// разобрать строку и преобразовать к Date
7+
date = date.split('-');
8+
date = new Date(date[0], date[1] - 1, date[2]);
9+
} else if (Array.isArray(date)) {
10+
date = new Date(date[0], date[1], date[2]);
11+
}
12+
// преобразования для поддержки полиморфизма завершены,
13+
// теперь мы работаем с датой (форматируем её)
14+
15+
var day = date.getDate();
16+
if (day < 10) day = '0' + day;
17+
18+
var month = date.getMonth() + 1;
19+
if (month < 10) month = '0' + month;
20+
21+
// взять 2 последние цифры года
22+
var year = date.getFullYear() % 100;
23+
if (year < 10) year = '0' + year;
24+
25+
var formattedDate = day + '.' + month + '.' + year;
26+
27+
return formattedDate;
28+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
describe("formatDate", function() {
2+
it("читает дату вида гггг-мм-дд из строки", function() {
3+
assert.equal(formatDate('2011-10-02'), "02.10.11");
4+
});
5+
6+
it("читает дату из числа 1234567890 (миллисекунды)", function() {
7+
assert.equal(formatDate(1234567890), "14.02.09");
8+
});
9+
10+
it("читает дату из массива вида [гггг, м, д]", function() {
11+
assert.equal(formatDate([2014, 0, 1]), "01.01.14");
12+
});
13+
14+
it("читает дату из объекта Date", function() {
15+
assert.equal(formatDate(new Date(2014, 0, 1)), "01.01.14");
16+
});
17+
18+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Для определения примитивного типа строка/число подойдет оператор [typeof](#type-typeof).
2+
3+
Примеры его работы:
4+
5+
```js
6+
//+ run
7+
alert( typeof 123 ); // "number"
8+
alert( typeof "строка" ); // "string"
9+
alert( typeof new Date() ); // "object"
10+
alert( typeof [] ); // "object"
11+
```
12+
13+
Оператор `typeof` не умеет различать разные типы объектов, они для него все на одно лицо: `"object"`. Поэтому он не сможет отличить `Date` от `Array`.
14+
15+
Для отличия `Array` используем вызов `Array.isArray`. Если он неверен, значит у нас дата.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Полиморфная функция formatDate
2+
3+
[importance 5]
4+
5+
Напишите функцию `formatDate(date)`, которая возвращает дату в формате `dd.mm.yy`.
6+
7+
Ее первый аргумент должен содержать дату в одном из видов:
8+
<ol>
9+
<li>Как объект `Date`.</li>
10+
<li>Как строку в формате `yyyy-mm-dd`.</li>
11+
<li>Как число *секунд* с `01.01.1970`.</li>
12+
<li>Как массив `[гггг, мм, дд]`, месяц начинается с нуля</li>
13+
</ol>
14+
Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат.
15+
16+
Пример работы:
17+
18+
```js
19+
function formatDate(date) { /* ваш код */ }
20+
21+
alert( formatDate('2011-10-02') ); // 02.10.11
22+
alert( formatDate(1234567890) ); // 14.02.09
23+
alert( formatDate([2014, 0, 1]) ); // 01.01.14
24+
alert( formatDate(new Date(2014, 0, 1)) ); // 01.01.14
25+
```
26+
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# Типы данных: [[Class]], instanceof и утки
2+
3+
Время от времени бывает удобно создавать так называемые "полиморфные" функции, то есть такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.
4+
5+
Для реализации такой возможности нужен способ определить тип переменной.
6+
7+
## Оператор typeof
8+
9+
Мы уже знакомы с простейшим способом -- оператором [typeof](#type-typeof).
10+
11+
Оператор `typeof` надежно работает с примитивными типами, кроме `null`, а также с функциями. Он возвращает для них тип в виде строки:
12+
13+
```js
14+
//+ run no-beautify
15+
alert( typeof 1 ); // 'number'
16+
alert( typeof true ); // 'boolean'
17+
alert( typeof "Текст" ); // 'string'
18+
alert( typeof undefined ); // 'undefined'
19+
alert( typeof null ); // 'object' (ошибка в языке)
20+
alert( typeof alert ); // 'function'
21+
```
22+
23+
...Но все объекты, включая массивы и даты для `typeof` -- на одно лицо, они имеют один тип `'object'`:
24+
25+
```js
26+
//+ run
27+
alert( typeof {} ); // 'object'
28+
alert( typeof [] ); // 'object'
29+
alert( typeof new Date ); // 'object'
30+
```
31+
32+
Поэтому различить их при помощи `typeof` нельзя, и в этом его основной недостаток.
33+
34+
## Секретное свойство [[Class]]
35+
36+
Для встроенных объектов есть одна "секретная" возможность узнать их тип, которая связана с методом `toString`.
37+
38+
Во всех встроенных объектах есть специальное свойство `[[Class]]`, в котором хранится информация о его типе или конструкторе.
39+
40+
Оно взято в квадратные скобки, так как это свойство -- внутреннее. Явно получить его нельзя, но можно прочитать его "в обход", воспользовавшись методом `toString` стандартного объекта `Object`.
41+
42+
Его внутренняя реализация выводит `[[Class]]` в небольшом обрамлении, как `"[object значение]"`.
43+
44+
Например:
45+
46+
```js
47+
//+ run
48+
var toString = {}.toString;
49+
50+
var arr = [1, 2];
51+
alert( toString.call(arr) ); // [object Array]
52+
53+
var date = new Date;
54+
alert( toString.call(date) ); // [object Date]
55+
56+
var obj = { name: "Вася" };
57+
alert( toString.call(date) ); // [object Object]
58+
```
59+
60+
В первой строке мы взяли метод `toString`, принадлежащий именно стандартному объекту `{}`. Нам пришлось это сделать, так как у `Date` и `Array` -- свои собственные методы `toString`, которые работают иначе.
61+
62+
Затем мы вызываем этот `toString` в контексте нужного объекта `obj`, и он возвращает его внутреннее, невидимое другими способами, свойство `[[Class]]`.
63+
64+
**Для получения `[[Class]]` нужна именно внутренняя реализация `toString` стандартного объекта `Object`, другая не подойдёт.**
65+
66+
К счастью, методы в JavaScript -- это всего лишь функции-свойства объекта, которые можно скопировать в переменную и применить на другом объекте через `call/apply`. Что мы и делаем для `{}.toString`.
67+
68+
Метод также можно использовать с примитивами:
69+
70+
```js
71+
//+ run
72+
alert( {}.toString.call(123) ); // [object Number]
73+
alert( {}.toString.call("строка") ); // [object String]
74+
```
75+
76+
[warn header="Вызов `{}.toString` в консоли может выдать ошибку"]
77+
При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку `{}.toString.call(...)` -- будет ошибка. С другой стороны, вызов `alert( {}.toString... )` -- работает.
78+
79+
Эта ошибка возникает потому, что фигурные скобки `{ }` в основном потоке кода интерпретируются как блок. Интерпретатор читает `{}.toString.call(...)` так:
80+
81+
```js
82+
//+ no-beautify
83+
{ } // пустой блок кода
84+
.toString.call(...) // а что это за точка в начале? не понимаю, ошибка!
85+
```
86+
87+
Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки `( {}.toString... )` тоже сработает нормально.
88+
[/warn]
89+
90+
91+
Для большего удобства можно сделать функцию `getClass`, которая будет возвращать только сам `[[Class]]`:
92+
93+
```js
94+
//+ run
95+
function getClass(obj) {
96+
return {}.toString.call(obj).slice(8, -1);
97+
}
98+
99+
alert( getClass(new Date) ); // Date
100+
alert( getClass([1, 2, 3]) ); // Array
101+
```
102+
103+
Заметим, что свойство `[[Class]]` есть и доступно для чтения указанным способом -- у всех *встроенных* объектов. Но его нет у объектов, которые создают *наши функции*. Точнее, оно есть, но равно всегда `"Object"`.
104+
105+
Например:
106+
107+
```js
108+
//+ run
109+
function User() {}
110+
111+
var user = new User();
112+
113+
alert( {}.toString.call(user) ); // [object Object], не [object User]
114+
```
115+
116+
Поэтому узнать тип таким образом можно только для встроенных объектов.
117+
118+
## Метод Array.isArray()
119+
120+
Для проверки на массивов есть специальный метод: `Array.isArray(arr)`. Он возвращает `true` только если `arr` -- массив:
121+
122+
```js
123+
//+ run
124+
alert( Array.isArray([1,2,3]) ); // true
125+
alert( Array.isArray("not array")); // false
126+
```
127+
128+
Но этот метод -- единственный в своём роде.
129+
130+
Других аналогичных, типа `Object.isObject`, `Date.isDate` -- нет.
131+
132+
133+
## Оператор instanceof
134+
135+
Оператор `instanceof` позволяет проверить, создан ли объект данной функцией, причём работает для любых функций -- как встроенных, так и наших.
136+
137+
```js
138+
//+ run
139+
function User() {}
140+
141+
var user = new User();
142+
143+
alert( user instanceof User ); // true
144+
```
145+
146+
Таким образом, `instanceof`, в отличие от `[[Class]]` и `typeof` может помочь выяснить тип для новых объектов, созданных нашими конструкторами.
147+
148+
Заметим, что оператор `instanceof` -- сложнее, чем кажется. Он учитывает наследование, которое мы пока не проходили, но скоро изучим, и затем вернёмся к `instanceof` в главе [](/instanceof).
149+
150+
151+
## Утиная типизация
152+
153+
Альтернативный подход к типу -- "утиная типизация", которая основана на одной известной пословице: *"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)"*.
154+
155+
В переводе: *"Если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка (какая разница, что это на самом деле)"*.
156+
157+
Смысл утиной типизации -- в проверке необходимых методов и свойств.
158+
159+
Например, мы можем проверить, что объект -- массив, не вызывая `Array.isArray`, а просто уточнив наличие важного для нас метода, например `splice`:
160+
161+
```js
162+
//+ run
163+
var something = [1, 2, 3];
164+
165+
if (something.splice) {
166+
alert( 'Это утка! То есть, массив!' );
167+
}
168+
```
169+
170+
Обратите внимание -- в `if` мы не вызываем метод `something.splice()`, а пробуем получить само свойство `something.splice`. Для массивов оно всегда есть и является функцией, т.е. даст в логическом контексте `true`.
171+
172+
Проверить на дату можно, определив наличие метода `getTime`:
173+
174+
```js
175+
//+ run
176+
var x = new Date();
177+
178+
if (x.getTime) {
179+
alert( 'Дата!' );
180+
alert( x.getTime() ); // работаем с датой
181+
}
182+
```
183+
184+
С виду такая проверка хрупка, ее можно "сломать", передав похожий объект с тем же методом.
185+
186+
Но как раз в этом и есть смысл утиной типизации: если объект похож на дату, у него есть методы даты, то будем работать с ним как с датой (какая разница, что это на самом деле).
187+
188+
То есть, мы намеренно позволяем передать в код нечто менее конкретное, чем определённый тип, чтобы сделать его более универсальным.
189+
190+
[smart header="Проверка интерфейса"]
191+
Если говорить словами "классического программирования", то "duck typing" -- это проверка реализации объектом требуемого интерфейса. Если реализует -- ок, используем его. Если нет -- значит это что-то другое.
192+
[/smart]
193+
194+
195+
## Пример полиморфной функции
196+
197+
Пример полиморфной функции -- `sayHi(who)`, которая будет говорить "Привет" своему аргументу, причём если передан массив -- то "Привет" каждому:
198+
199+
```js
200+
//+ run
201+
function sayHi(who) {
202+
203+
if (Array.isArray(who)) {
204+
who.forEach(sayHi);
205+
} else {
206+
alert( 'Привет, ' + who );
207+
}
208+
}
209+
210+
// Вызов с примитивным аргументом
211+
sayHi("Вася"); // Привет, Вася
212+
213+
// Вызов с массивом
214+
sayHi(["Саша", "Петя"]); // Привет, Саша... Петя
215+
216+
// Вызов с вложенными массивами - тоже работает!
217+
sayHi(["Саша", "Петя", ["Маша", "Юля"]]); // Привет Саша..Петя..Маша..Юля
218+
```
219+
220+
Проверку на массив в этом примере можно заменить на "утиную" -- нам ведь нужен только метод `forEach`:
221+
222+
```js
223+
//+ run
224+
function sayHi(who) {
225+
226+
if (who.forEach) { // если есть forEach
227+
who.forEach(sayHi); // предполагаем, что он ведёт себя "как надо"
228+
} else {
229+
alert( 'Привет, ' + who );
230+
}
231+
}
232+
```
233+
234+
## Итого
235+
236+
Для написания полиморфных (это удобно!) функций нам нужна проверка типов.
237+
238+
<ul>
239+
<li>Для примитивов с ней отлично справляется оператор `typeof`.
240+
241+
У него две особенности:
242+
<ol>
243+
<li>Он считает `null` объектом, это внутренняя ошибка в языке.</li>
244+
<li>Для функций он возвращает `function`, по стандарту функция не считается базовым типом, но на практике это удобно и полезно.</li>
245+
</ol>
246+
</li>
247+
<li>Для встроенных объектов мы можем получить тип из скрытого свойства `[[Class]]`, при помощи вызова `{}.toString.call(obj).slice(8, -1)`. Не работает для конструкторов, которые объявлены нами.
248+
</li>
249+
<li>Оператор `obj instanceof Func` проверяет, создан ли объект `obj` функцией `Func`, работает для любых конструкторов. Более подробно мы разберём его в главе [](/instanceof).</li>
250+
<li>И, наконец, зачастую достаточно проверить не сам тип, а просто наличие нужных свойств или методов. Это называется "утиная типизация".</li>
251+
</ul>
252+

0 commit comments

Comments
 (0)