JavaScript. Подробное руководство, 6-е издание, стр. 114

<b>  var t, c, n; // type, class, name</b>

<b>  // Специальный случай для значения null: </b>

<b>  if (о === null) return &quot;null&quot;:</b>

<b>  // Другой специальный случай: NaN - единственное значение, не равное самому себе: </b>

<b>  if (о !== о) return &quot;nan&quot;;</b>

<b>  // Применять typeof для любых значений, отличных от &quot;object&quot;. </b>

<b>  // Так идентифицируются простые значения и функции, </b>

<b>  if ((t = typeof о) !== &quot;object&quot;) return t;</b>

<b>  // Вернуть класс объекта, если это не &quot;Object&quot;.</b>

<b>  // Так идентифицируется большинство встроенных объектов, </b>

<b>  if ((с = classof(o)) !== &quot;Object&quot;) return с;</b>

<b>  // Вернуть имя конструктора объекта, если имеется</b>

<b>  if (о.constructor &amp;&amp; typeof о.constructor === &quot;function&quot; &amp;&amp;</b>

<b>    (n = о.constructor.getName())) return n;</b>

<b>  // He удалось определить конкретный тип, поэтому остается лишь </b>

<b>  // просто вернуть &quot;Object&quot; </b>

<b>  return &quot;Object&quot;;</b>

<b>}</b>

<b>// Возвращает класс объекта, </b>

<b>function classof(o) {</b>

<b>  return Object.prototype.toString.call(о).slice(8,-1);</b>

<b>};</b>

<b>// Возвращает имя функции (может быть &quot;&quot;) или null - для объектов,</b>

<b>// не являющихся функциями </b>

<b>Function.prototype.getName = function() {</b>

<b>  if (&quot;name&quot; 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>&lt;=</b>
, но не делает никаких предположений о типах значений границ диапазона. Благодаря тому что класс не ограничивается определенным типом значений, его метод
<b>includes()</b>
способен обрабатывать значения границ любых типов, которые могут сравниваться с помощью операторов отношения:

<b>var lowercase = new Range(&quot;a&quot;, '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>
уменьшается, массив автоматически усекается. Можно было бы сказать, что эти две особенности описывают, как массивы «плавают» и «крякают». Если вы пишете программу, где требуется, чтобы объект «плавал» и «крякал» как массив, вы не сможете использовать в ней объект, который только «ходит» как массив.