Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Типы данных: получение и проверка

  1. Оператор typeof
    1. typeof и объекты
  2. [[Class]] для встроенных объектов
  3. «Утиная» типизация
  4. Проверка типа для пользовательских объектов
  5. Итого

Полиморфные функции - это такие, которые по-разному обрабатывают аргументы, в зависимости от их типа. Например, функция вывода может по-разному форматировать числа и даты.

Для реализации такой возможности нужен способ определить тип переменной. И здесь в JavaScript есть некоторый «зоопарк» способов, но мы в нем сейчас разберемся.

Как мы знаем, существует несколько примитивных типов:

null
Специальный тип, содержит только значение null.
undefined
Специальный тип, содержит только значение undefined.
number
Числа: 0, 3.14, а также значения NaN и Infinity
boolean
true, false.
string
Строки, такие как "Мяу" или пустая строка "".

Все остальные значения являются объектами, включая функции и массивы.

Оператор typeof

Оператор typeof возвращает тип аргумента. У него есть два синтаксиса:

  1. Синтаксис оператора: typeof x.
  2. Синтаксис функции: typeof(x).

Работают они одинаково, но первый синтаксис короче.

Результатом typeof является строка, содержащая тип:

typeof undefined // "undefined" 

typeof 0    // "number" 
 
typeof true // "boolean" 

typeof "foo" // "string" 

typeof {} // "object" 

*!*
typeof null  // "object" 
typeof function(){} // "function" 
*/!*

Последние две строки помечены, потому что typeof ведет себя в них по-особому.

  1. Результат typeof null == "object" - это официальная ошибка в языке, которая сохраняется для совместимости.

    На самом деле null - это не объект, а примитив. Мы можем это даже проверить, попытавшись присвоить свойство:

    var x = null;
    x.prop = 1;  // ошибка, т.к. нельзя присвоить свойство примитиву
    

  2. Для функции f значением typeof f является "function". Это не совсем корректно, т.к. функция является объектом, и непонятна причина, по которой их следует выделять. С другой — на практике это плюс, т.к. позволяет легко определить функцию.

typeof и объекты

Оператор typeof надежно работает с примитивными типами (кроме null). Но он ничего не сообщит об объектах, кроме функций.

Обычные объекты, массивы и даты для typeof все на одно лицо, они имеют тип 'object':

alert( typeof {} ); // 'object'
alert( typeof [] ); // 'object'
alert( typeof new Date ); // 'object'

[[Class]] для встроенных объектов

Основная проблема typeof - неумение различать объекты, кроме функций. Но есть и другой способ получения типа.

У всех встроенных объектов есть скрытое свойство [[Class]]. Оно равно "Array" для массивов, "Date" для дат и т.п.

Это свойство нельзя получить напрямую, есть трюк для того, чтобы прочитать его.

Дело в том, что toString от стандартного объекта выводит [[Class]] в небольшой обертке. Например:

var obj = {};
alert( obj ); // [object Object]

Здесь внутри [object ...] указано как раз значение [[Class]], которое для обычного объекта как раз и есть "Object". Для дат оно будет Date, для массивов — Array и т.п.

К сожалению, большинство объектов переопределяют toString на свой собственный. Поэтому мы будем использовать технику, которая называется «одалживание метода» («method borrowing»).

Мы возьмем функцию toString от стандартного объекта и будем запускать его в контексте тех значений, для которых нужно получить тип:

var toStandardString = {}.toString; // (1)

var arr = [1,2];
alert( toStandardString.call(arr) ); // (2) [object Array]

var date = new Date;
alert( toStandardString.call(date) ); // [object Date]

var cls = toStandardString.call(date).slice(8, -1); // (3)
alert(cls); // Date

Разберем происходящее более подробно.

  1. Можно переписать эту строку в две:
    var obj = {};
    var toStandardString = obj.toString;
    

    Иначе говоря, мы создаём пустой объект {} и копируем ссылку на его метод toString в переменную toStandardString .

    Мы делаем это, потому что внутренняя реализация toString стандартного объекта Object возвращает [[Class]]. У других объектов (Date, Array и т.п.) toString свой и для этой цели не подойдёт.

  2. Вызываем скопированный метод в контексте нужного объекта obj.

    Мы могли бы поступить проще:

    var arr = [1,2];
    arr.toStandardString = {}.toString;
    
    alert( arr.toStandardString() ); // [object Array]
    
    .. Но зачем копировать лишнее свойство в объект? Синтаксис toStandardString.call(arr) делает то же самое, поэтому используем его.

  3. Все, класс получен. При желании можно убрать обертку [object ...], взяв подстроку slice(8,-1). Использована переменная cls, т.к. class — зарезервированное слово в JavaScript.

Метод также работает с примитивами:

alert( toStandardString.call(123) ) // [object Number]
alert( toStandardString.call("строка")) ) // [object String]

… Кроме специальных значений null и undefined, поскольку вызов call с аргументами null или undefined, по спецификации, передает this = window.

Метод используется только для встроенных объектов. Для пользовательских конструкторов [[Class]] = "Object":

function Animal(name) { 
  this.name = name;
}
var animal = new Animal("Винни-пух");

var cls = {}.toString.call( animal );

alert(cls); // [object Object]

«Утиная» типизация

Утиная типизация основана на одной известной пословице: «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:

var x = [1,2,3];

if (x.splice) {
  alert('Массив!');
}

Обратите внимание — в if(x.splice) мы не вызываем метод x.splice(), а пробуем получить само свойство x.splice. Для массивов оно всегда есть и является функцией, т.е. даст в логическом контексте true.

Проверить на дату можно, проверив наличие метода getTime:

var x = new Date();

if (x.getTime) {
  alert('Дата!');
}

С виду такая проверка хрупка, ее можно сломать, передав похожий объект с тем же методом. Но на практике утиная типизация хорошо и стабильно работает, особенно когда важен не сам тип, а поддержка методов.

Проверка типа для пользовательских объектов

Для проверки, кем был создан объект, есть оператор instanceof.

Синтаксис: obj instanceof Func.

Например:

function Animal(name) { 
  this.name = name;
}
var animal = new Animal("Винни-пух");

*!*
alert( animal instanceof Animal ); // true
*/!*

Оператор instanceof также работает для встроенных объектов:

var d = new Date(); 
alert(d instanceof Date); // true

function f() { }
alert(f instanceof Function); // true

Оператор instanceof может лишь осуществить проверку, он не позволяет получить тип в виде строки, но в большинстве случаев этого и не требуется.

Почему [[Class]] надежнее instanceof?

Как мы видели, instanceof успешно работает со встроенными объектами:

var arr = [1,2,3];
alert(arr instanceof Array); // true

…Однако, есть случай, когда такой способ нас подведет. А именно, если объект создан в другом окне или iframe, а оттуда передан в текущее окно.

При этом arr instanceof Array вернет false, т.к. в каждом окне и фрейме — свой собственный набор встроенных объектов. Массив arr является Array в контексте того window.

Метод [[Class]] свободен от этого недостатка.

Итого

Для получения типа есть два способа:

typeof
Хорош для примитивов и функций, врёт про null.
Свойство [[Class]]
Можно получить, используя {}.toString.call(obj). Это свойство содержит тип для встроенных объектов и примитивов, кроме null и undefined.

Для проверки типов есть еще два способа:

Утиная типизация
Можно проверить поддержку метода.
Оператор instanceof
Работает с любыми объектами, встроенными и созданными посетителем при помощи конструкторов: if (obj instanceof User) { ... }.

Напишите функцию outputDate(date), которая выводит дату в формате dd.mm.yy.

Ее первый аргумент должен содержать дату в одном из видов:

  1. Как объект Date.
  2. Как строку в формате yyyy-mm-dd.
  3. Как число секунд с 01.01.1970.
  4. Как массив [гггг, мм, дд], месяц начинается с нуля

Для этого вам понадобится определить тип данных аргумента и, при необходимости, преобразовать входные данные в нужный формат.

Пример работы:

function outputDate(date) { /* ваш код */ 

outputDate( '2011-10-02' );  // 02.10.11
outputDate( 1234567890 );  // 14.02.09
outputDate( [2000,0,1] ); // 01.01.00
outputDate( new Date(2000,0,1) ); // 01.01.00

Решение
Решение

Для определения примитивного типа строка/число подойдет оператор typeof.

Примеры его работы:

alert(typeof 123); // "number"
alert(typeof "строка"); // "string"
alert(typeof new Date()); // "object"
alert(typeof []); // "object"

Оператор typeof не умеет различать разные типы объектов, они для него все на одно лицо: "object". Поэтому он не сможет отличить Date от Array.

Используем для того, чтобы их различать, свойство [[Class]].

Функция:

function outputDate(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 ( {}.toString.call(date) == '[object Array]' ) {
    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;

  alert(formattedDate);
}

outputDate( '2011-10-02' );  // 02.10.11
outputDate( 1234567890 );  // 14.02.09
outputDate( [2000,0,1] ); // 01.01.00
outputDate( new Date(2000,0,1) ); // 01.01.00

Есть код для проверки, является ли значение объектом Object:

function isObject(x) {
  if (x == null) return false; // (1)
  return {}.toString.call(x) == "[object Object]"; // (2)
}

Как будет работать этот код в случае вызова isObject(undefined)?

  1. Какая строка isObject вернёт ответ: (1) или (2)?
  2. Что будет, если убрать строку (1)? Т.е. что вернёт {}.toString.call(undefined)?
Решение
Решение
  1. Так как undefined == null, то строка (1) вернёт false.
  2. Если же убрать строку (1), то наблюдается кросс-браузерный «зоопарк» с {}.toString.call(undefined).

    По стандарту, если нестрогий режим, то вызов f.call(null/undefined) передаёт в f глобальный объект в качестве контекста this. Так что браузер должен метод {}.toString запустить в контексте window. А далее — результат зависит от того, какое у него свойство [[Class]].

    В реальности же — попробуйте сами.. Некоторые браузеры используют контекст undefined, некоторые window — нет…

    // возможны варианты:
    // [object Undefined]
    // [object global]
    // [object DOMWindow]
    // [object Object]
    alert( {}.toString.call(undefined) );
    


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!