<b> var t, c, n; // type, class, name</b>
<b> // Специальный случай для значения null: </b>
<b> if (о === null) return "null":</b>
<b> // Другой специальный случай: NaN - единственное значение, не равное самому себе: </b>
<b> if (о !== о) return "nan";</b>
<b> // Применять typeof для любых значений, отличных от "object". </b>
<b> // Так идентифицируются простые значения и функции, </b>
<b> if ((t = typeof о) !== "object") return t;</b>
<b> // Вернуть класс объекта, если это не "Object".</b>
<b> // Так идентифицируется большинство встроенных объектов, </b>
<b> if ((с = classof(o)) !== "Object") return с;</b>
<b> // Вернуть имя конструктора объекта, если имеется</b>
<b> if (о.constructor && typeof о.constructor === "function" &&</b>
<b> (n = о.constructor.getName())) return n;</b>
<b> // He удалось определить конкретный тип, поэтому остается лишь </b>
<b> // просто вернуть "Object" </b>
<b> return "Object";</b>
<b>}</b>
<b>// Возвращает класс объекта, </b>
<b>function classof(o) {</b>
<b> return Object.prototype.toString.call(о).slice(8,-1);</b>
<b>};</b>
<b>// Возвращает имя функции (может быть "") или null - для объектов,</b>
<b>// не являющихся функциями </b>
<b>Function.prototype.getName = function() {</b>
<b> if ("name" in this) return this.name;</b>
<b> return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];</b>
<b>):</b>
Этот прием, основанный на использовании имени конструктора для идентификации класса объекта, имеет ту же проблему, что и прием на основе использования свойства
<b>constructor</b>
: не все объекты имеют свойство
<b>constructor</b>
. Кроме того, не все функции имеют имена. Если определить конструктор, используя выражение определения неименованной функции, метод
<b>getName()</b>
будет возвращать пустую строку:
<b>// Этот конструктор не имеет имени</b>
<b>var Complex = function(x,у) { this.r = х; this.і = у; }</b>
<b>// Этот конструктор имеет имя</b>
<b>var Range = function Range(f.t) { this.from = f; this.to = t; }</b>
9.5.4. Грубое определение типа
Ни один из приемов определения класса объекта, описанных выше, не свободен от проблем, по крайней мере, в клиентском JavaScript. Альтернативный подход состоит в том, чтобы вместо вопроса «какому классу принадлежит объект?» задать вопрос «что может делать этот объект?». Этот подход является типичным в таких языках программирования, как Python и Ruby, и носит название грубое определение типа (<duck-typing, или «утиная типизация») в честь высказывания (часто приписываемого поэту Джеймсу Уиткомбу Райли (James Whitcomb Riley)):
Когда я вижу птицу, которая ходит, как утка, плавает, как утка и крякает, как утка, я называю ее уткой.
Для программистов на языке JavaScript этот афоризм можно интерпретировать так: «Если объект может ходить, плавать и крякать как объект класса Duck, его можно считать объектом класса Duck, даже если он не наследует объект-прототип класса Duck».
Примером может служить класс
<b>Range</b>
из примера 9.2. Этот класс предназначен для представления диапазонов чисел. Однако обратите внимание, что конструктор
<b>Range()</b>
не проверяет типы аргументов, чтобы убедиться, что они являются числами. Аналогично метод
<b>includes()</b>
использует оператор
<b><=</b>
, но не делает никаких предположений о типах значений границ диапазона. Благодаря тому что класс не ограничивается определенным типом значений, его метод
<b>includes()</b>
способен обрабатывать значения границ любых типов, которые могут сравниваться с помощью операторов отношения:
<b>var lowercase = new Range("a", 'z');</b>
<b>var thisYear = new Range(new Date(2009, 0, 1), new Date(2010, 0, 1));</b>
Метод
<b>foreach()</b>
класса
<b>Range</b>
также не проверяет типы значений границ, но он использует функцию
<b>Math.ceil()</b>
и оператор
++
, вследствие чего может применяться только к числовым значениям границ диапазона.
В качестве еще одного примера вспомним объекты, подобных массивам, обсуждавшиеся в разделе 7.11. Во многих случаях нам не требуется знать, действительно ли объект является экземпляром класса
<b>Array</b>
: вполне достаточно знать, что он имеет свойство
<b>length</b>
с неотрицательным целочисленным значением. Если посчитать, что целочисленное свойство
<b>length</b>
- это способ массивов «ходить», то мы могли бы сказать, что любой объект, который умеет «ходить» так же, можно (во многих случаях) отнести к массивам.
Однако имейте в виду, что свойство
<b>length</b>
настоящих массивов обладает особым поведением: свойство
<b>length</b>
автоматически обновляется при добавлении нового элемента, а когда значение свойства
<b>length</b>
уменьшается, массив автоматически усекается. Можно было бы сказать, что эти две особенности описывают, как массивы «плавают» и «крякают». Если вы пишете программу, где требуется, чтобы объект «плавал» и «крякал» как массив, вы не сможете использовать в ней объект, который только «ходит» как массив.