Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Оператор typeof, [[Class]] и утиная типизация

Лекция



Привет, сегодня поговорим про оператор typeof [[class]], обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое оператор typeof [[class]], утиная типизация , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).

Оператор typeof, [[Class]] и утиная типизация

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

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

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


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

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

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

Оператор typeof

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

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

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

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

 

01 typeof undefined // "undefined"
02  
03 typeof 0    // "number"
04   
05 typeof true // "boolean"
06  
07 typeof "foo" // "string"
08  
09 typeof {} // "object"
10  
11 typeof null  // "object"
12 typeof function(){} // "function"

 

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

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

    На самом деле null — это не объект, а примитив. Это сразу видно, если попытаться присвоить ему свойство:

     

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

     

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

 

Не используйте typeof для проверки переменной

В старом коде можно иногда увидеть код вроде такого:

if (typeof jQuery !== 'undefined') {
  ...
}
Его автор, видимо, хочет проверить, существует ли переменная jQuery. Причем, он имеет в виду именно глобальную переменную jQuery, которая создается во внешнем скрипте, так как про свои локальные он и так все знает.

 

Более короткий код if (jQuery) выдаст ошибку, если переменная не определена, аtypeof jQuery в таких случаях ошибку не выдает, а возвращает undefined.

Но как раз здесь typeof не нужен! Есть другой способ:

1 if (window.jQuery !== undefined) { ... }
2  
3 //а если мы знаем, что это объект, то проверку можно упростить:
4 if (window.jQuery) { ... }
При доступе к глобальной переменной через window не будет ошибки, ведь по синтаксису это — обращение к свойству объекта, а такие обращения при отсутствующем свойстве просто возвращают undefined.

 

 

Оператор typeof надежно работает с примитивными типами, кроме null, а также с функциями. Но обычные объекты, массивы и даты для typeof все на одно лицо, они имеют тип 'object':

 

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

 

Поэтому различить их при помощи typeof нельзя.

 

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

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

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

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

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

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

 

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

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

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

 

   
01 var toClass = {}.toString; // (1)
02  
03 var arr = [1,2];
04 alert( toClass.call(arr) ); // (2) [object Array]
05  
06 var date = new Date;
07 alert( toClass.call(date) ); // [object Date]
08  
09 var type = toClass.call(date).slice(8, -1); // (3)
10 alert(type); // Date

 

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

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

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

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

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

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

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

     

  3. Все, класс получен. Об этом говорит сайт https://intellect.icu . При желании можно убрать обертку [object ...], взяв подстроку вызовомslice(8,-1).

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

   
1 alert( {}.toString.call(123) ); // [object Number]
2 alert( {}.toString.call("строка") ); // [object String]

 

…Но без use strict вызов call с аргументами null или undefined передает this = window. Таково поведение старого стандарта JavaScript.

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

 

   
1 function Animal(name) {
2   this.name = name;
3 }
4 var animal = new Animal("Винни-пух");
5  
6 var type = {}.toString.call( animal );
7  
8 alert(type); // [object Object]

 

 

Вызов {}.toString в консоли может выдать ошибку

При тестировании кода в консоли вы можете обнаружить, что если ввести в командную строку {}.toString.call(...) — будет ошибка. С другой стороны, вызовalert( {}.toString... ) — работает.

Эта ошибка возникает потому, что фигурные скобки { } в основном потоке кода интерпретируются как блок. Интерпретатор читает {}.toString.call(...) так:

{ } // пустой блок кода
.toString.call(...) // а что это за точка в начале? не понимаю, ошибка!
Фигурные скобки считаются объектом, только если они находятся в контексте выражения. В частности, оборачивание в скобки ( {}.toString... ) тоже сработает нормально.

 

 

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

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

   
1 var x = [1,2,3];
2  
3 if (x.splice) {
4   alert('Массив!');
5 }

 

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

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

 

   
1 var x = new Date();
2  
3 if (x.getTime) {
4   alert('Дата!');
5 }

 

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

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

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

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

Например:

   
1 function Animal(name) {
2   this.name = name;
3 }
4 var animal = new Animal("Винни-пух");
5  
6 alert( animal instanceof Animal ); // true

 

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

 

   
1 var d = new Date();
2 alert(d instanceof Date); // true
3  
4 function f() { }
5 alert(f instanceof Function); // true

 

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

 

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

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

 

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

 

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

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

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

 

Полиморфизм

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

Она будет работать в трех режимах:

  1. Без аргументов: выводит Привет.
  2. С аргументом, который не является массивом: выводит «привет» и этот аргумент.
  3. С аргументом, который является массивом — говорит всем «привет».

Пример такой функции:

 

   
01 function sayHi(who) {
02   if (!arguments.length) {
03     alert('Привет');
04     return;
05   }
06  
07   if ( {}.toString.call(who) == '[object Array]' ) {
08     for(var i=0; i<who.length; i++) sayHi(who[i]);
09     return;
10   }
11  
12   alert('Привет, ' + who);
13 }
14  
15 // Использование:
16 sayHi(); // Привет
17 sayHi("Вася"); // Привет, Вася
18  
19 sayHi( ["Саша""Петя", ["Маша""Юля"] ] ); // Привет Саша..Петя..Маша..Юля

 

Обратите внимание, получилась даже поддержка вложенных массивов  

Итого

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

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

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

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

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

 

Важность: 5

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

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

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

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

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

1 function outputDate(date) { /* ваш код */ }
2  
3 outputDate( '2011-10-02' );  // 02.10.11
4 outputDate( 1234567890 );  // 14.02.09
5 outputDate( [2000,0,1] ); // 01.01.00
6 outputDate( new Date(2000,0,1) ); // 01.01.00

 

Решение

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

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

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

 

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

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

Функция:

   
01 function outputDate(date) {
02   if (typeof date == 'number') {
03     // перевести секунды в миллисекунды и преобразовать к Date
04     date = new Date(date*1000);
05   else if(typeof date == 'string') {
06     // разобрать строку и преобразовать к Date
07     date = date.split('-');
08     date = new Date(date[0], date[1]-1, date[2]);
09   else if ( {}.toString.call(date) == '[object Array]' ) {
10     date = new Date(date[0], date[1], date[2]);
11   }
12  
13   var day = date.getDate();
14   if (day < 10) day = '0' + day;
15  
16   var month = date.getMonth()+1;
17   if (month < 10) month = '0' + month;
18  
19   // взять 2 последние цифры года
20   var year = date.getFullYear() % 100;
21   if (year < 10) year = '0' + year;
22    
23   var formattedDate = day + '.' + month + '.' + year;
24  
25   alert(formattedDate);
26 }
27  
28 outputDate( '2011-10-02' );  // 02.10.11
29 outputDate( 1234567890 );  // 14.02.09
30 outputDate( [2000,0,1] ); // 01.01.00
31 outputDate( new Date(2000,0,1) ); // 01.01.00

 

[Открыть задачу в новом окне]

 

 

Важность: 2

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

1 function isObject(x) {
2   if (x == nullreturn false// (1)
3  
4   return {}.toString.call(x) == "[object Object]"// (2)
5 }

 

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

  1. Вернет ли она ответ сразу в строке: (1)?
  2. Если да, то что будет, если убрать строку (1)? То есть, что вернет{}.toString.call(undefined) и почему?
Решение
  1. Да. Так как undefined == null, то строка (1) завершит выполнение возвратомfalse.
  2. Если убрать строку (1), то браузеры ведут себя более интересно…

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

    В реальности же — большинство браузеров применяют здесь современный стандарт и возвращают [object Undefined]. Попробуйте сами..

     

       
    1 // возможны варианты (в зависимости от браузера)
    2 // [object Undefined]
    3 // [object Object] 
    4 // [object DOMWindow]
    5 alert( {}.toString.call(undefined) );

К сожалению, в одной статье не просто дать все знания про оператор typeof [[class]]. Но я - старался. Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое оператор typeof [[class]], утиная типизация и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

создано: 2014-10-07
обновлено: 2021-03-13
132542



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)