Typeof, [[class]] and duck typing

Lecture



Typeof, [[class]] and duck typing

  1. typeof operator
  2. [[Class]] for embedded objects
  3. Duck typing
  4. Type checking for user objects
  5. Polymorphism
  6. Total

In this chapter, we will look at how to create polymorphic functions, that is, those that handle arguments differently, depending on their type. For example, the output function can format numbers and dates differently.

To implement this, you need a way to determine the type of a variable. And here in JavaScript there is a whole "zoo" of ways, but we'll figure it out now.


As we know, there are several primitive types :

null
Special type, contains only null .
undefined
Special type, contains only the value undefined .
number
Numbers: 0 , 3.14 , as well as NaN and Infinity values
boolean
true , false .
string
Strings, such as "Мяу" or the empty string "" .

All other values ​​are objects , including functions and arrays.

typeof operator

The typeof operator returns the type of the argument. It has two syntaxes:

  1. Operator syntax: typeof x .
  2. Function syntax: typeof(x) .

They work the same, but the first syntax is shorter.

The result of typeof is a string containing the type:

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"

The last two lines are marked, because typeof behaves in them in a special way.

  1. The result of typeof null == "object" is an officially recognized error in the language, which is stored for compatibility.

    Actually, null is not an object, but a primitive. This is immediately apparent if you try to assign a property to it:

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

  2. For function f value of typeof f is "function" . Of course, the function is an object. On the other hand, such a selection of functions in practice is rather a plus, since makes it easy to define a function.

Do not use typeof to check the variable.

In the old code, you can sometimes see a code like this:

if ( typeof jQuery !== 'undefined' ) {
   ...
}
Its author apparently wants to check if the jQuery variable exists. Moreover, he is referring to the global jQuery variable, which is created in the external script, because he knows everything about his local ones.

A shorter if (jQuery) code will generate an error if the variable is not defined, and typeof jQuery does not generate an error in such cases, but returns undefined .

But just here typeof not needed! There is another way:

1 if (window.jQuery !== undefined ) { ... }
2
3 //а если мы знаем, что это объект, то проверку можно упростить:
4 if (window.jQuery) { ... }
When accessing a global variable through a window there will be no error, because by syntax this is a call to the property of the object, and such calls if the property is missing simply return undefined .

The typeof operator works reliably with primitive types other than null , as well as with functions. But ordinary objects, arrays and dates for typeof all look the same, they are of type 'object' :

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

Therefore, it is impossible to distinguish them using typeof .

[[Class]] for embedded objects

The main typeof problem is the inability to distinguish objects other than functions. But there is another way to get the type.

All embedded objects have a hidden [[Class]] property. It is equal to "Array" for arrays, "Date" for dates, etc.

This property cannot be obtained directly, there is a trick to read it.

The fact is that toString from a standard object prints [[Class]] in a small wrapper. For example:

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

Here, inside [object ...] indicated just the value of [[Class]] , which for an ordinary object is exactly "Object" . For dates it will be Date , for arrays - Array , etc.

Most built-in objects in JavaScript have their own toString method. Therefore, we will use a technique called “method borrowing” ( “method borrowing” ).

We will take the toString function from the standard object and run it in the context of those values ​​for which we need to get the type:

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

Let us analyze what is happening in more detail.

  1. You can rewrite this line in two:
    var obj = {};
    var toClass = obj.toString;

    In other words, we create an empty object {} and copy the reference to its toString method into the variable toClass .

    We do this because the internal toString implementation of the standard Object returns [[Class]] . Other objects ( Date , Array , etc.) have their own toString for this purpose as well.

  2. Call the copied method in the context of the required object obj .

    We could do better:

    1 var arr = [1,2];
    2 arr.toClass = {}.toString;
    3
    4 alert( arr.toClass() ); // [object Array]
    ... But why copy an extra property into an object? The syntax toClass.call(arr) does the same, so we use it.

  3. All class received. If desired, you can remove the [object ...] wrapper by taking a substring by calling slice(8,-1) .

The method also works with primitives:

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

... But without use strict calling the call with arguments null or undefined passes this = window . This is the behavior of the old JavaScript standard.

This method can only give type for embedded objects. For custom constructors, always [[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]

Calling {}.toString in the console may fail

When testing the code in the console, you may find that if you enter {}.toString.call(...) on the command line, there will be an error. On the other hand, the alert( {}.toString... ) call alert( {}.toString... ) - works.

This error occurs because the curly braces { } in the main code stream are interpreted as a block. The interpreter reads {}.toString.call(...) like this:

{ } // пустой блок кода
.toString.call(...) // а что это за точка в начале? не понимаю, ошибка! // а что это за точка в начале? не понимаю, ошибка!
Curly brackets are considered an object only if they are in the context of an expression. In particular, wrapping parentheses ( {}.toString... ) will also work fine.

Duck typing

Duck typing is based on one well-known proverb: "If it looks like a duck, then it really is a duck . "

Translated: “If it looks like a duck, swims like a duck and quacks like a duck, then it’s probably a duck (what a difference it really is) .

The meaning of duck typing is to test methods and properties, regardless of the type of the object.

We can check the array by specifying the presence of the splice method:

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

Note that in if(x.splice) we do not call the x.splice() method, but try to get the x.splice property x.splice . For arrays, it always exists and is a function, i.e. will give a true logical context.

You can check for the date by checking the availability of the getTime method:

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

It looks fragile, it can be broken by passing a similar object with the same method. But in practice, duck typing works well and stably, especially when it is not the type itself that is important, but the support of the methods.

Type checking for user objects

To check who created the object, there is an instanceof operator.

Syntax: obj instanceof Func .

For example:

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

The instanceof operator also works for embedded objects:

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

The instanceof operator can only perform a check, it does not allow to get the type as a string, but in most cases this is not required.

Why is [[Class]] more robust instanceof ?

As we have seen, instanceof successfully works with embedded objects:

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

... However, there is a case when this method will let us down. Namely, if the object is created in another window or iframe , and from there it is transferred to the current window.

In this case, the arr instanceof Array returns false , since Each window and frame has its own set of embedded objects. The arr array is Array in the context of that window .

The [[Class]] method is free from this flaw.

Polymorphism

We use type checking to create the polymorphic function sayHi .

It will work in three modes:

  1. Without arguments: displays Привет .
  2. With an argument that is not an array: displays “hello” and this argument.
  3. With an argument that is an array, it says hello to everyone.

An example of such a function:

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( [ "Саша" , "Петя" , [ "Маша" , "Юля" ] ] ); // Привет Саша..Петя..Маша..Юля

Pay attention, even support of nested arrays was obtained

Total

There are two ways to get a type :

typeof
Good for primitives and functions, lying about null .
[[Class]] property
You can get it using {}.toString.call(obj) . This property contains the type for embedded objects and primitives, except for null and undefined .

There are two more ways to check types :

Duck typing
You can check the method support.
instanceof operator
Works with any objects that have been built in and created by the visitor using constructors: if (obj instanceof User) { ... } .

Type checking is used, as a rule, to create polymorphic functions, that is, those that work differently depending on the type of argument.

Importance: 5

Write the function outputDate(date) , which displays the date in the format dd.mm.yy

Her first argument should contain a date in one of the types:

  1. Like a Date object.
  2. As a string in the format yyyy-mm-dd .
  3. As the number of seconds since 01.01.1970 .
  4. As an array [гггг, мм, дд] , the month starts from zero

To do this, you need to determine the data type of the argument and, if necessary, convert the input data to the desired format.

Work example:

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

Decision

For the definition of the primitive type string / number suitable operator typeof.

Examples of his work:

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

The typeof operator does not know how to distinguish between different types of objects, they all look the same to him: "object" . Therefore, it cannot distinguish Date from Array .

Use to distinguish between them, the [[Class]] property.

Function:

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

[Open task in new window]

Importance: 2

There is a function to check whether the value is a built-in Object :

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

How will this function work if isObject(undefined) is called?

  1. Will she return the answer immediately in the line: (1) ?
  2. If so, what will happen if you remove the line (1) ? That is, what will return {}.toString.call(undefined) and why?
Decision
  1. Yes. Since undefined == null , string (1) will return to return false .
  2. If you remove the line (1) , then browsers behave more interesting ...

    In theory, if the lax mode, then the f.call(null/undefined) call should pass to f global object as the context of this . So the browser should start the {}.toString method in the context of the window . And further - the result depends on what its [[Class]] property is.

    In reality, most browsers use a modern standard here and return [object Undefined] . Try it yourself ..

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

Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Scripting client side JavaScript, jqvery, BackBone

Terms: Scripting client side JavaScript, jqvery, BackBone