Seleccionar página

Como ya sabemos, Javascript es un lenguaje con tipado débil dinámico, es decir, las variables no tienen un tipo específico y es posible comparar datos de diferentes tipos produciéndose la conversión implícita de los valores al realizar la comparación. Esto es una ventaja en ocasiones y un inconveniente en otras. En gran medida el amor u odio que se tenga a esta característica dependerá de la trayectoria con otros lenguajes de programación que tenga cada uno y con cuales se sienta más cómodo.

Una de las principales herramientas en lenguajes con tipado dinámico es comprobar el tipo de datos con el que nos estamos enfrentando. Para ello existen diferentes aproximaciones o estrategias para comprobación de tipos. Todas ellas tienen ventajas e inconvenientes.A continuación, vamos a hacer un repaso general a estas estrategias y como cada uno de los elementos de Javascript (en su versión ES6) se comporta con cada una de ellas.

Con todas estas estrategias hemos creado una función genérica de comprobación de tipos que, creemos, cubre la prácticamente totalidad de los casos que nos podemos encontrar. Esta función, que hemos denominado simplemente type, tiene este aspecto:

function type(value) {
    var r;
    return (
        (typeof value === 'object') ?
            (value === null) ?
                'null' :
                (typeof value.constructor === 'function' &&
                (r = value.constructor.name) !== 'Object') ?
                    (r === '' || r === undefined) ?
                        Function.prototype.toString.call(value.constructor)
                            .match(/^\n?(function|class)(\w?)/)[2] || 'anonymous' :
                        r
                    :
                    Object.prototype.toString.call(value).match(/\s(.*)\]/)[1]
            :
            (typeof value === 'number') ?
                (isNaN(value)) ?
                    'NaN' :
                    'number'
                :
                typeof value
    );
}

Estrategias para la identificación de tipos de datos

Nota: En los códigos siguientes vamos a utilizar console.assert(). Este método está disponible en la gran mayoría de los navegadores modernos y en Node. Ejecuta una expresión y si es verdadera continua sin hacer nada y si es falsa lanza una excepción. La utilizaremos para comprobar los tipos de datos de forma sencilla, rápida y sin necesidad de librerías externas.

typeof

La primera aproximación que podemos hacer es utilizar typeof, una instrucción pensada de forma específica para conocer el tipo del valor de una variable. Funciona perfectamente con los tipos primitivos y con las funciones, pero en el caso de los objetos ofrece muy poca información, ya que para todos ellos devuelve object sin mayor detalle.

console.assert( typeof true === 'boolean' );
console.assert( typeof [] === 'object' );
console.assert( typeof new Date() === 'object' );

instanceof

Para los objetos existe una instrucción bastante práctica que permite conocer si un objeto determinado es una instancia de una clase superior. De esta forma podemos comprobar si un determinado dato es de tipo Date, Array, etc.

console.assert( new Date() instanceof Date === true );
console.assert( [] instanceof Array === true );

Como todos los objetos heredan de Object, cualquier objeto es una instancia de Object y de esta forma podemos diferenciar un objeto de los tipos de datos primitivos:

console.assert( new Date() instanceof Object === true );
console.assert( 10 instanceof Object === false );

Object.prototype.toString

Una forma indirecta, pero bastante interesante, de conocer el tipo de un objeto es utilizar el método toString() del prototipo de Object pasando como parámetro el valor que queremos comprobar. Este método devuelve una cadena de texto que indicará que es un objeto y de qué tipo es enmarcado entre corchetes.

console.assert( Object.prototype.toString.call([]) === '[object Array]' );
console.assert( Object.prototype.toString.call(new Date()) === '[object Date]' );

myObject.constructor.name

Todas las instancias de los objetos tienen un método myVar.constructor y este método, como todas las funciones en la mayoría de las implementaciones, tiene una propiedad name que contiene el nombre de la función. Lamentablemente no todos los navegadores facilitan esta propiedad name y en esas ocasiones deberemos utilizar un método alternativo para obtener el nombre de la función (o de la clase) que ha servido como constructor del objeto por medio del método toString() del prototipo de Function que nos devuelve el código fuente del constructor y del cual podemos extraer el nombre.

console.assert( [].constructor.name === 'Array' );
console.assert( new Date().constructor.name === 'Date' );
console.assert( Function.prototype.toString.call(new Date().constructor)
        .match(/^\n?(function|class)\s*(\w*)/)[2] === 'Date' );

Tipos de datos y las estrategias de consulta

Vamos a analizar cada una de estas estrategias con los diferentes tipos de datos de los que disponemos en Javascript y como se comportan en cada caso a fin de determinar la mejor estrategia en cada caso.

Tipos primitivos

Javascript tiene seis tipos primitivos:

  • Sin definir (undefined)
  • Nulo (null)
  • Lógicos (boolean)
  • Numérico (number)
  • Cadena (string)
  • Símbolo (symbol)

Todos los demás tipos son objetos (Object): Array, Date, Promise y todos los demás tipos son objetos.

undefined

console.assert( typeof undefined === 'undefined' );
console.assert( undefined instanceof Object === false );
console.assert( Object.prototype.toString.call(undefined) === '[object Undefined]' );
// TypeError: Cannot read property "constructor" of undefined
// console.assert( undefined.constructor.name );

Cualquier variable no definida tiene como valor un tipo undefined. Este valor no es un objeto y la mejor estrategia para identificarlo es utilizar typeof o simplemente myVar === undefined ya que existe una variable global con el nombre undefined.

null

var testNull = null;
console.assert( typeof testNull === 'object' );
console.assert( testNull instanceof Object === false );
console.assert( Object.prototype.toString.call(testNull) === '[object Null]' );
// TypeError: Cannot read property "constructor" of null
console.assert( testNull.constructor.name );

Como se puede comprobar null es un objeto según typeof, pero por otra parte no es una instancia de Object. Esta es una circunstancia peculiar que nos obliga a un tratamiento específico de null, ya que la única forma de comprobar si un valor es nulo, es compararlo con el literal null:

var testNull = null;
console.assert( (testNull === null) );

symbol

var mySymbol = Symbol();
console.assert( typeof mySymbol === 'symbol' );
console.assert( mySymbol instanceof Object === false );
console.assert( Object.prototype.toString.call(mySymbol) === '[object Symbol]' );
console.assert( mySymbol.constructor.name === 'Symbol' );

En ES6 disponemos de un nuevo tipo primitivo, los símbolos (symbol). Estos tipos de datos son utilizados como claves en objetos y mapas de datos sin que puedan ser convertidos a otros tipos, por lo que para poder acceder a estos elementos tendremos que tener una referencia al símbolo con el que se creó.

Cada vez que creamos un símbolo obtenemos un valor único, por lo que son de mucha utilidad a la hora de crear constantes con valores únicos y asegurarnos que no se va a producir confusiones por valores duplicados.

Existen algunos símbolos conocidos y que se utilizan para acceder a algunas características avanzadas del lenguaje como Symbol.iterator que nos permite acceder al método interno que devuelve un objeto iterador de un objeto Array, String, Map o Set.

En todos los casos, la mejor forma de saber si un valor es de tipo symbol es utilizar typeof.

Lógico (boolean), Numérico (number) y cadena (string)

En estos tres casos hay que diferenciar los tipos primitivos (normalmente descritos en minúsculas) que se crean por medio de las expresiones literales para estos tipos, y los objetos de igual nombre (normalmente escritos con la primera letra en mayúscula) que se crean llamando a los constructores con new seguido del constructor. Estos objetos son wrappers de los tipos primitivos y ofrece propiedades y métodos para el manejo de este tipo de valores.

Es importante comprender que Javascript realiza una conversión implícita cuando utilizamos un valor primitivo con propiedades o métodos, por ejemplo, cuando consultamos el tamaño de una cadena "text".length. En estos casos el objeto wrapper es creado internamente para realizar la invocación al método o propiedad y posteriormente eliminado.

Como podemos ver las diferentes estrategias funcionan de forma diferente en el caso de los tipos primitivos y en el caso de los objetos wrapper.

// Primitive boolean
var testBoolean = true;
console.assert( typeof testBoolean === 'boolean' );
console.assert( testBoolean instanceof Object === false );
console.assert( testBoolean instanceof Boolean === false );
console.assert( Object.prototype.toString.call(testBoolean) === '[object Boolean]' );
console.assert( testBoolean.constructor.name === 'Boolean' );

// Object Boolean
var testBooleanObject = new Boolean(true);
console.assert( typeof testBooleanObject === 'object' );
console.assert( testBooleanObject instanceof Object === true );
console.assert( testBooleanObject instanceof Boolean === true );
console.assert( Object.prototype.toString.call(testBooleanObject) === '[object Boolean]' );
console.assert( testBooleanObject.constructor.name === 'Boolean' );

// Primitive type number
var testNumber = 10;
console.assert( typeof testNumber === 'number' );
console.assert( testNumber instanceof Object === false );
console.assert( testNumber instanceof Number === false );
console.assert( Object.prototype.toString.call(testNumber) === '[object Number]' );
console.assert( testNumber.constructor.name === 'Number' );

// Object Number
var testNumberObject = new Number(10);
console.assert( typeof testNumberObject === 'object' );
console.assert( testNumberObject instanceof Object === true );
console.assert( testNumberObject instanceof Number === true );
console.assert( Object.prototype.toString.call(testNumberObject) === '[object Number]' );
console.assert( testNumberObject.constructor.name === 'Number' );

// Primitive type string
var testString = 'text';
console.assert( typeof testString === 'string' );
console.assert( testString instanceof Object === false );
console.assert( testString instanceof String === false );
console.assert( Object.prototype.toString.call(testString) === '[object String]' );
console.assert( testString.constructor.name === 'String' );

// Object String
var testStringObject = new String('text');
console.assert( typeof testStringObject === 'object' );
console.assert( testStringObject instanceof Object === true );
console.assert( testStringObject instanceof String === true );
console.assert( Object.prototype.toString.call(testStringObject) === '[object String]' );
console.assert( testStringObject.constructor.name === 'String' );

Nos encontramos con un reto, ya que como se puede observar, las llamadas de Object.prototype.toString.call() y .constructor.name sobre los tipos primitivos convierte estos en objetos (aparecen con la primera letra en mayúscula) y por lo tanto nos pueden confundir. En general los tipos de datos primitivos funcionan bien con typeof.

Funciones

var testFunction = function () {};
console.assert( typeof testFunction === 'function' );
console.assert( testFunction instanceof Object === true );
console.assert( testFunction instanceof Function === true );
console.assert( Object.prototype.toString.call(testFunction) === '[object Function]' );
console.assert( testFunction.constructor.name === 'Function' );

Las funciones en Javascript son objetos, pero en este caso typeof sí nos devolverá 'function' como tipo de este tipo de objetos, por lo que es una buena estrategia. Esta es una excepción, ya que es el único caso donde typeof devuelve un tipo concreto de objeto, ya que las funciones son un elemento clave del lenguaje.

Esta característica se mantiene para las funciones creadas con el constructor Function(), ya que new Function() no crea un wrapper por encima del tipo function, si no que funciona de forma parecida a eval(), creando una nueva función interpretando el texto que se ha pasado como cuerpo de la función.

var testNewFunction = new Function('return true;');
console.assert( typeof testNewFunction === 'function' );
console.assert( testNewFunction instanceof Object === true );
console.assert( testNewFunction instanceof Function === true );
console.assert( Object.prototype.toString.call(testNewFunction) === '[object Function]' );
console.assert( testNewFunction.constructor.name === 'Function' );

Igualmente, la respuesta de typeof es aplicable a las funciones flecha (arrow function):

var testArrowFunction = (x, y) => x + y;
console.assert( typeof testArrowFunction === 'function' );
console.assert( testArrowFunction instanceof Object === true );
console.assert( testArrowFunction instanceof Function === true );
console.assert( Object.prototype.toString.call(testArrowFunction) === '[object Function]' );
console.assert( testArrowFunction.constructor.name === 'Function' );

Si queremos diferenciar una función flecha de una función normal tendremos que usar métodos alternativos, ya que por los métodos que estamos analizando en estos momentos no somos capaces de diferenciar si es una función normal o fecha. Consultar Cómo diferenciar arrow function de function para más información sobre este tema.

Para las funciones generadoras typeof responde de forma similar al resto de funciones y nos indicará que el valor devuelto es una función:

var testGeneratorFunction = function *() {yield true;};
console.assert( typeof testGeneratorFunction === 'function' );
console.assert( testGeneratorFunction instanceof Object === true );
console.assert( testGeneratorFunction  instanceof Function === true );
console.assert( Object.prototype.toString.call(testGeneratorFunction) === '[object GeneratorFunction]' );
console.assert( testGeneratorFunction.constructor.name === 'GeneratorFunction' );

Las funciones generadoras no disponen de un constructor similar a Function(), pero es muy sencillo obtener un constructor de funciones generadoras, sólo es necesario crear una función generadora y obtener su constructor. A partir de aquí podemos crear nuevas funciones generadoras partiendo de una cadena de texto. De nuevo, typeof nos indicará que el valor devuelto es una función:

let GF = Object.getPrototypeOf(function*(){}).constructor;
typeof new GF('yield true;') === 'function'
new GF('yield true;') instanceof Object === true
new GF('yield true;') instanceof Function === true
Object.prototype.toString.call(new GF('yield true;')) === '[object GeneratorFunction]'
new GF('yield true;').constructor.name === 'GeneratorFunction'

Como se puede comprobar, crear una función generadora directamente o por medio de su constructor no hace variar sustancialmente el resultado. Internamente Javascript utiliza el nombre GeneratorFunction para las funciones generadoras y es el valor devuelto por las dos últimas estrategias, pero no podemos usar ese nombre directamente para comprobar con instanceof si es un objeto de ese tipo. Por lo tanto, si usamos typeof para las funciones generadoras veremos que son funciones. Si usamos toString() o myVar.constructor.name podremos saber que son funciones generadoras.

Object

Hay tres formas de crear un objeto, por medio de la expresión literal { }, por medio de new Object() y por Object.create(). Aunque puedan parecer equivalentes, lo cierto es que Object.create(null) permite crear un objeto sin ningún tipo de constructor, lo cual hace que nuestra estrategia con myObject.constructor.name se encuentra en este caso con un problema.

// Literal
var testObjectLiteral = {};
console.assert( typeof testObjectLiteral === 'object' );
console.assert( testObjectLiteral instanceof Object === true );
console.assert( Object.prototype.toString.call(testObjectLiteral) === '[object Object]' );
console.assert( testObjectLiteral.constructor.name === 'Object' );

// new Object
var testObjectConstructor = new Object();
console.assert( typeof testObjectConstructor === 'object' );
console.assert( testObjectConstructor instanceof Object === true );
console.assert( Object.prototype.toString.call(testObjectConstructor) === '[object Object]' );
console.assert( testObjectConstructor.constructor.name === 'Object' );

// new Object
var testObjectCreateNull = Object.create(null);
console.assert( typeof testObjectCreateNull === 'object' );
console.assert( testObjectCreateNull instanceof Object === false ); // !!! It's false !!!!
console.assert( Object.prototype.toString.call(testObjectCreateNull) === '[object Object]' );
// TypeError: Cannot read property 'name' of undefined
// console.assert( testObjectCreateNull.constructor.name );

Aunque podemos por medio de typeof podemos saber que es un objeto, si queremos saber que tipo de objeto tendremos que esforzarnos un poco más. Seguramente la opción de consultar myVar.constructor.name es una buena aproximación, pero debemos tener en cuenta el caso que acabamos de ver con Objet.create(null) para conseguir una solución consistente para todos los casos.

A continuación vamos a analizar la situación con varios tipos de objetos.

Otros objetos nativos instanciables con new

Array

Las matrices o Array se construyen por medio de la expresión literal [ ] o por medio del constructor new Array() o el método Array.of(). En todos los casos los Array son objetos y por lo tanto typeof no nos indica de que tipo se trata.

var testArray = [1,2,3,4];
console.assert( typeof testArray === 'object' );
console.assert( testArray instanceof Object === true );
console.assert( testArray instanceof Array === true );
console.assert( Object.prototype.toString.call(testArray) === '[object Array]' );
console.assert( testArray.constructor.name === 'Array' );
console.assert( Array.isArray(testArray) === true );

Desde ES 5.1 disponemos del método Array.isArray() para comprobar si un valor corresponde al tipo Array. Este tipo de método no lo tenemos disponibles para otros tipos de objetos y sólo podemos aprovecharlo de forma específica para los Array. El modelo de identificación por toString() o myVar.constructor.name son buenas alternativas. El método basado en instanceof es un poco más pesado, ya que además de comprobar que es un objeto tenemos que comprobar si es un Array.

Date y otros objetos

Además de Array, que tiene su método específico Array.isArray(), en la práctica todos de objetos de Javascript tienen el mismo comportamiento a la hora de comprobar su tipo. Hemos utilizado Date al ser un tipo muy utilizado, pero el comportamiento es similar en todos los objetos:

var testDate = new Date();
console.assert( typeof testDate === 'object' );
console.assert( testDate instanceof Object === true );
console.assert( testDate instanceof Date === true );
console.assert( Object.prototype.toString.call(testDate) === '[object Date]' );
console.assert( testDate.constructor.name === 'Date' );

Aunque no es el objetivo de este artículo, sólo como referencia general incluimos una lista de todos lo objetos que podemos encontrarnos y que todavía no hemos utilizado en ningún ejemplo.

new ArrayBuffer(10);               // ArrayBuffer
new DataView(new ArrayBuffer(10)); // DataView
new Float32Array(10);              // Float32Array
new Float64Array(10);              // Float64Array
new Uint16Array(19);               // Uint16Array
new Uint32Array(19);               // Uint32Array
new Uint8Array(19);                // Uint8Array
new Uint8ClampedArray(19);         // Uint8ClampedArray
new Promise(function (a, b) {});   // Promise
new Map([[0,1], [1,2], [2,3]]);    // Map
new Set([1, 2, 3]);                // Set
new WeakMap();                     // WeakMap
new WeakSet();                     // WeakSet

Tipos derivados de Error

Hay un caso especial que podemos tratar de forma particular: los tipos derivados de Error(). En Javascript, además del objeto Error hay otros seis tipos que heredan de Error pero que tienen su propio constructor: EvalError(), RangeError(), ReferenceError(), SyntaxError(), TypeError() y URIError(). Veamos un ejemplo:

var testURIError = new URIError();
console.assert( typeof testURIError === 'object' );
console.assert( testURIError instanceof Object === true );
console.assert( testURIError instanceof Error === true );
console.assert( testURIError instanceof URIError === true );
console.assert( Object.prototype.toString.call(testURIError) === '[object Error]' );
console.assert( testURIError.constructor.name === 'URIError' )

En estos casos podemos comprobar si son de tipo Error o si son de un tipo concreto por medio de instanceof (lo cual es un poco tedioso), Object.prototype.toString.call() siempre devolverá '[object Error]' y la única opción que nos queda es utilizar myVar.constructor.name.

Objetos globales: JSON y Math

Javascript dispone de dos objetos globales de los cuales no es posible crear objetos derivados, ya que carecen como tal de un constructor que permitan crear otro objetos de este tipo. Veamos cómo se comporta cada estrategia en este caso especial.

// JSON
// JSON
console.assert( typeof JSON === 'object' );
console.assert( JSON instanceof Object === true );
console.assert( Object.prototype.toString.call(JSON) === '[object JSON]' );
console.assert( JSON.constructor.name === 'Object' );

// Math
console.assert( typeof Math === 'object' );
console.assert( Math instanceof Object === true );
console.assert( Object.prototype.toString.call(Math) === '[object Math]' );
console.assert( Math.constructor.name === 'Object' );

En este caso Object.prototype.toString.call() es quien da un resultado más preciso, ya que indica con que objeto global nos estamos encontrando.

Proxy y Reflect

Los Proxy son un interesante recurso de ES6 que permite capturar las diferentes acciones sobre un objeto y de esta forma establecer un comportamiento dinámico que hasta el momento no era posible. En este caso es imposible diferenciar un objeto con Proxy de otro que no tenga un handler asociado.

var myObj = new Proxy({},  {set: (obj, prop, value) => obj[prop] = value * 10});
console.assert( typeof myObj === 'object' );
console.assert( myObj instanceof Object === true );
console.assert( myObj instanceof Proxy === false );
console.assert( Object.prototype.toString.call(myObj) === '[object Object]' );
console.assert( myObj.constructor.name === 'Object' );

Reflect un objeto global (como Math y JSON) que tiene un conjunto de métodos similares a Object, aunque no es posible crear objetos de este tipo, ya que carece de constructor. Curiosamente no es posible identificar este objeto con ninguna de las estrategias utilizadas hasta ahora.

console.assert( typeof Reflect === 'object' );
console.assert( Reflect instanceof Object === true );
console.assert( Object.prototype.toString.call(Reflect) === '[object Object]' );
console.assert( Reflect.constructor.name === 'Object' );

Symbol.iterator

Una característica muy interesante de ES6 son los iteradores e iterables. Son un elemento clave para el uso de for...of, el operador de propagación (spread operator) y las funciones generadoras. Una forma de obtener un objeto iterable de Array, String, Map, Set y los Array con tipo (Typed Array) es utilizar Symbol.iterable para obtener le método que se invoca internamente para obtener un objeto de este tipo. Vamos a ver cómo podemos identificar el objeto iterador que devuelve el método iterable.

// Array Iterator
var testArrayIterator = new Array(1,2,3)[Symbol.iterator]();
console.assert( typeof testArrayIterator === 'object' );
console.assert( testArrayIterator instanceof Object === true );
console.assert( Object.prototype.toString.call(testArrayIterator) === '[object Array Iterator]' );
console.assert( testArrayIterator.constructor.name === 'Object' );

// String Iterator
var testStringIterator = new String('text')[Symbol.iterator]();
console.assert( typeof testStringIterator === 'object' );
console.assert( testStringIterator instanceof Object === true );
console.assert( Object.prototype.toString.call(testStringIterator) === '[object String Iterator]' );
console.assert( testStringIterator.constructor.name === 'Object' );

// Map Iterator
var testMapIterator = new Map()[Symbol.iterator]();
console.assert( typeof testMapIterator === 'object' );
console.assert( testMapIterator instanceof Object === true );
console.assert( Object.prototype.toString.call(testMapIterator) === '[object Map Iterator]' );
console.assert( testMapIterator.constructor.name === 'Object' );

// Set Iterator
var testSetIterator = new Set()[Symbol.iterator]();
console.assert( typeof testSetIterator === 'object' );
console.assert( testSetIterator instanceof Object === true );
console.assert( Object.prototype.toString.call(testSetIterator) === '[object Set Iterator]' );
console.assert( testSetIterator.constructor.name === 'Object' );

// TypeArray (Array Iterator)
var testFloat64ArrayIterator = new Float64Array()[Symbol.iterator]();
console.assert( typeof testFloat64ArrayIterator === 'object' );
console.assert( testFloat64ArrayIterator instanceof Object === true );
console.assert( Object.prototype.toString.call(testFloat64ArrayIterator) === '[object Array Iterator]' );
console.assert( testFloat64ArrayIterator.constructor.name === 'Object' );

Como se puede observar, Javascript tiene unos tipos Array Iterator, String Iterator, Map Iterator y Set Iterator. En el caso de los Array tipados el iterador es de tipo Array Iterator. En todos los casos la única estrategia que ofrece información detallada es Object.prototype.toString.call().

Nuestros constructores y clases

Cualquier objeto creado a partir de un constructor o clase que creemos en nuestros programas también es susceptible de comprobar su tipo. Normalmente nos bastará con saber que son objetos, pero en ocasiones nos interesará saber exactamente qué tipo de objetos son, es decir, sobre que constructor o clase se han creado. Vamos a ver algunos ejemplos de como se comportan las diferentes estrategias:

// Clase
class MyClass {}
var testMyObjectClass = new MyClass();
console.assert( typeof testMyObjectClass === 'object' );
console.assert( testMyObjectClass instanceof Object === true );
console.assert( testMyObjectClass instanceof MyClass === true );
console.assert( Object.prototype.toString.call(testMyObjectClass) === '[object Object]' );
console.assert( testMyObjectClass.constructor.name === 'MyClass' );

// Class extend Date
class MyDate extends Date {}
var testMyObjectClassExtends = new MyDate();
console.assert( typeof testMyObjectClassExtends === 'object' );
console.assert( testMyObjectClassExtends instanceof Object === true );
console.assert( testMyObjectClassExtends instanceof Date === true );
console.assert( testMyObjectClassExtends instanceof MyDate === true );
console.assert( Object.prototype.toString.call(testMyObjectClassExtends) === '[object Date]' );
console.assert( testMyObjectClassExtends.constructor.name === 'MyDate' );

// Constructor
function MyConstructor() {}
var testMyObjectConstructor = new MyConstructor();
console.assert( typeof testMyObjectConstructor === 'object' );
console.assert( testMyObjectConstructor instanceof Object === true );
console.assert( testMyObjectConstructor instanceof MyConstructor === true );
console.assert( Object.prototype.toString.call(testMyObjectConstructor) === '[object Object]' );
console.assert( testMyObjectConstructor.constructor.name === 'MyConstructor' );

Como se puede observar, la estrategia que mejor se comporta es myVar.constructor.name, ya que nos indica el nombre de la clase o función sobre la que hemos creado el objeto. Por su parte instanceof es una solución si conocemos de antemano el nombre de la clase o función sobre la que queremos preguntar, pero si no lo tenemos claro, no es un método válido.

Función genérica para determinar el tipo

Después de esta revisión general de todas las posibilidades puede parecer que no hemos llegado a ninguna conclusión, ya que las estrategias que son válidas para unos casos, tienen problemas en otros. Por ello hemos creado una función genérica que va analizando las diferentes situaciones y va seleccionando la alternativa más adecuada en cada caso y devuelve el tipo de cualquier tipo de dato.

Para evitar el problema que hemos señalado anteriormente con algunos navegadores que no implementan la propiedad name en el método constructor hemos utilizado una versión un poco más compleja que analiza el código del constructor y extrae el nombre de la función o de la clase:

class OtherClass {}
var testExtractSource = new OtherClass();
console.assert( testExtractSource.constructor.name === 'OtherClass');
console.assert( Function.prototype.toString.call(testExtractSource.constructor)
        .match(/^\n?(function|class)\s*(\w*)/)[2] === 'OtherClass');

Al inicio del texto hemos puesto una versión funcional de la función genérica para la comprobación del tipo. Es posible que alguno se sienta un poco perdido a la hora de interpretar su funcionamiento, por lo que aquí incluimos una versión estructurada de la misma función.

function typeDebug(value) {
    var r;
    if (typeof value === 'object') {
        if (value === null) {
            return 'null';
        }
        if (typeof value.constructor === 'function' &&
            (r = value.constructor.name) !== 'Object') {
            if (r === '' || r === undefined) {
                return Function.prototype.toString.call(value.constructor)
                    .match(/^\n?(function|class)(\w?)/)[2] || 'anonymous';
            }
            return r;
        }
        return Object.prototype.toString.call(value).match(/\s(.*)\]/)[1];
    } else if (typeof value === 'number') {
        return isNaN(value) ? 'NaN' : 'number';
    }
    return typeof value;
}

Hemos publicado type() como paquete en npm con el nombre estype (cada vez es más difícil encontrar nombres disponibles medianamente comprensibles). Se puede consultar el paquete en https://www.npmjs.com/package/estype y el código completo en https://github.com/todojs/estype.

Esperamos que esta larga explicación haya sido de vuestro interés y que esta función -o una similar que vosotros mismos os construyáis- sea de utilidad en vuestros desarrollos.

Novedades

JSDayES – Vídeos

Si te lo has perdido o si quieres volver a ver las charlas y talleres del impresionante JSDayES 2017, aquí tienes todos los vídeos. Ponentes de primer orden, tanto nacionales como internacionales, muestran todos los aspectos de Javascript.

The CITGM Diaries by Myles Borins [video]

Myles Borins de Google, miembro del CTC de Node.js (Node.js Core Technical Committee), nos cuenta (en inglés, por supuesto) como funciona CITGM (Canary In The Gold Mine), una herramienta que permite obtener un módulo de NPM y probarlo usando una versión específica de NodeJS.

Debate: Tecnologías de Front Web [vídeo]

Desde las principales comunidades de desarrollo de tecnologías de front (Madrid JS, Polymer Madrid, Angular Madrid y VueJS Madrid) se ha organizado este debate que pretende ser un ejercicio de sentido común en relación a las tecnologías de front actuales centradas en componentes.

breves

Descrubir algunas características de console

En el día a día nos encontramos muy a menudo utilizando console. Es una navaja multiusos que nos facilita la vida a la hora de depurar nuestro código. La mayoría de nosotros ha utilizado console.log(), pero tiene otras muchas funcionalidades.

Matrices dispersas o sparse arrays en JS

Una característica que puede producir algunos problemas, si no lo tenemos en cuenta, es la posibilidad de tener matrices con huecos, es decir, con algunos de sus elementos sin definir. Es lo que se suele denominar una matriz dispersa o sparse array. Veamos cómo trabajar con esta características de las matrices.

Algunos operadores de bits usados con asiduidad

Cada día más a menudo podemos encontrar operadores binarios utilizados como formas abreviadas de algunas operaciones que de otra forma sería algo menos compactas y, quizás, más comprensibles. Veamos algunos casos en detalle.

Cómo diferenciar arrow function de function

En un reciente artículo Javier Vélez Reyes hace patente las principales diferencias entre las funciones tradicionales y las funciones flecha, ya que ambos modelos no son equivalentes e intercambiables. Veamos cómo es posible saber si una función ha sido construida por medio de la instrucción function o como una arrow function.